ami.js
Version:
<p align="center"> <img src="https://cloud.githubusercontent.com/assets/214063/23213764/78ade038-f90c-11e6-8208-4fcade5f3832.png" width="60%"> </p>
516 lines (455 loc) • 16.6 kB
JavaScript
/* globals Stats, dat*/
import CamerasOrthographic from 'base/cameras/cameras.orthographic';
import ControlsOrthographic from 'base/controls/controls.trackballortho';
import HelpersLut from 'base/helpers/helpers.lut';
import HelpersStack from 'base/helpers/helpers.stack';
import LoadersVolume from 'base/loaders/loaders.volume';
import ShadersLayerUniform from 'base/shaders/shaders.layer.uniform';
import ShadersLayerVertex from 'base/shaders/shaders.layer.vertex';
import ShadersLayerFragment from 'base/shaders/shaders.layer.fragment';
import ShadersDataUniform from 'base/shaders/shaders.data.uniform';
import ShadersDataVertex from 'base/shaders/shaders.data.vertex';
import ShadersDataFragment from 'base/shaders/shaders.data.fragment';
// standard global letiables
let controls;
let renderer;
let camera;
let statsyay;
let threeD;
//
let sceneLayer0TextureTarget;
let sceneLayer1TextureTarget;
//
let sceneLayer0;
let lutLayer0;
let sceneLayer1;
let meshLayer1;
let uniformsLayer1;
let materialLayer1;
let lutLayer1;
let sceneLayerMix;
let meshLayerMix;
let uniformsLayerMix;
let materialLayerMix;
let layerMix = {
opacity1: 1.0,
lut: null,
};
/**
* Init the labelmap app
*/
function init() {
/**
* Function is executed on each animation frame
*/
function animate() {
// render
controls.update();
// render first layer offscreen
renderer.render(sceneLayer0, camera, sceneLayer0TextureTarget, true);
// render second layer offscreen
renderer.render(sceneLayer1, camera, sceneLayer1TextureTarget, true);
// mix the layers and render it ON screen!
renderer.render(sceneLayerMix, camera);
statsyay.update();
// request new frame
requestAnimationFrame(function() {
animate();
});
}
// renderer
threeD = document.getElementById('r3d');
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setSize(threeD.clientWidth, threeD.clientHeight);
renderer.setClearColor(0x607D8B, 1);
threeD.appendChild(renderer.domElement);
// stats
statsyay = new Stats();
threeD.appendChild(statsyay.domElement);
// scene
sceneLayer0 = new THREE.Scene();
sceneLayer1 = new THREE.Scene();
sceneLayerMix = new THREE.Scene();
// render to texture!!!!
sceneLayer0TextureTarget = new THREE.WebGLRenderTarget(
threeD.clientWidth,
threeD.clientHeight,
{minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
});
sceneLayer1TextureTarget = new THREE.WebGLRenderTarget(
threeD.clientWidth,
threeD.clientHeight,
{minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
});
// camera
camera = new CamerasOrthographic(
threeD.clientWidth / -2, threeD.clientWidth / 2,
threeD.clientHeight / 2, threeD.clientHeight / -2,
0.1, 10000);
// controls
controls = new ControlsOrthographic(camera, threeD);
controls.staticMoving = true;
controls.noRotate = true;
camera.controls = controls;
animate();
}
window.onload = function() {
// init threeJS...
init();
let filenames = [
'000000.dcm', '000001.dcm', '000002.dcm', '000003.dcm', '000004.dcm',
'000005.dcm', '000006.dcm', '000007.dcm', '000008.dcm', '000009.dcm',
'000010.dcm',
'000011.dcm', '000012.dcm', '000013.dcm', '000014.dcm', '000015.dcm',
'000016.dcm', '000017.dcm', '000018.dcm', '000019.dcm', '000020.dcm',
'000021.dcm',
'000022.dcm', '000023.dcm', '000024.dcm', '000025.dcm', '000026.dcm',
'000027.dcm', '000028.dcm', '000029.dcm', '000030.dcm', '000031.dcm',
'000032.dcm',
'000033.dcm', '000034.dcm', '000035.dcm', '000036.dcm', '000037.dcm',
'000038.dcm', '000039.dcm', '000040.dcm', '000041.dcm', '000042.dcm',
'000043.dcm',
'000044.dcm', '000045.dcm', '000046.dcm', '000047.dcm', '000048.dcm',
'000049.dcm', '000050.dcm', '000051.dcm', '000052.dcm', '000053.dcm',
'000054.dcm',
'000055.dcm', '000056.dcm', '000057.dcm', '000058.dcm', '000059.dcm',
'000060.dcm', '000061.dcm', '000062.dcm', '000063.dcm', '000064.dcm',
'000065.dcm',
'000066.dcm', '000067.dcm', '000068.dcm', '000069.dcm', '000070.dcm',
'000071.dcm', '000072.dcm', '000073.dcm', '000074.dcm', '000075.dcm',
'000076.dcm',
'000077.dcm', '000078.dcm', '000079.dcm', '000080.dcm', '000081.dcm',
'000082.dcm', '000083.dcm', '000084.dcm', '000085.dcm', '000086.dcm',
'000087.dcm',
'000088.dcm', '000089.dcm', '000090.dcm', '000091.dcm', '000092.dcm',
'000093.dcm', '000094.dcm', '000095.dcm', '000096.dcm', '000097.dcm',
'000098.dcm',
'000099.dcm', '000100.dcm', '000101.dcm', '000102.dcm', '000103.dcm',
'000104.dcm', '000105.dcm', '000106.dcm', '000107.dcm', '000108.dcm',
'000109.dcm',
'000110.dcm', '000111.dcm', '000112.dcm', '000113.dcm', '000114.dcm',
'000115.dcm', '000116.dcm', '000117.dcm', '000118.dcm', '000119.dcm',
'000120.dcm',
'000121.dcm', '000122.dcm', '000123.dcm', '000124.dcm', '000125.dcm',
'000126.dcm', '000127.dcm', '000128.dcm', '000129.dcm', '000130.dcm',
'000131.dcm',
'000132.dcm', '000133.dcm', '000134.dcm', '000135.dcm', '000136.dcm',
'000137.dcm', '000138.dcm', '000139.dcm', '000140.dcm', '000141.dcm',
'000142.dcm',
'000143.dcm', '000144.dcm', '000145.dcm', '000146.dcm', '000147.dcm',
'000148.dcm', '000149.dcm', '000150.dcm', '000151.dcm', '000152.dcm',
'000153.dcm',
'000154.dcm', '000155.dcm', '000156.dcm', '000157.dcm', '000158.dcm',
'000159.dcm', '000160.dcm', '000161.dcm', '000162.dcm', '000163.dcm',
'000164.dcm',
'000165.dcm', '000166.dcm', '000167.dcm', '000168.dcm', '000169.dcm',
'000170.dcm', '000171.dcm', '000172.dcm', '000173.dcm', '000174.dcm',
'000175.dcm',
'000176.dcm', '000177.dcm', '000178.dcm', '000179.dcm', '000180.dcm',
'000181.dcm', '000182.dcm', '000183.dcm', '000184.dcm', '000185.dcm',
'000186.dcm',
'000187.dcm', '000188.dcm', '000189.dcm', '000190.dcm', '000191.dcm',
'000192.dcm', '000193.dcm', '000194.dcm', '000195.dcm', '000196.dcm',
'000197.dcm',
'000198.dcm', '000199.dcm', '000200.dcm', '000201.dcm', '000202.dcm',
'000203.dcm', '000204.dcm', '000205.dcm', '000206.dcm', '000207.dcm',
'000208.dcm',
'000209.dcm', '000210.dcm', '000211.dcm', '000212.dcm', '000213.dcm',
'000214.dcm', '000215.dcm', '000216.dcm', '000217.dcm', '000218.dcm',
'000219.dcm',
'000220.dcm', '000221.dcm', '000222.dcm', '000223.dcm', '000224.dcm',
'000225.dcm', '000226.dcm', '000227.dcm', '000228.dcm', '000229.dcm',
'000230.dcm',
'000231.dcm', '000232.dcm', '000233.dcm', '000234.dcm', '000235.dcm',
'000236.dcm', '000237.dcm', '000238.dcm', '000239.dcm', '000240.dcm',
'000241.dcm',
'000242.dcm', '000243.dcm', '000244.dcm', '000245.dcm', '000246.dcm',
'000247.dcm', '000248.dcm', '000249.dcm', '000250.dcm', '000251.dcm',
'000252.dcm',
'000253.dcm', '000254.dcm', '000255.dcm', '000256.dcm', '000257.dcm',
'000258.dcm', '000259.dcm', '000260.dcm', '000261.dcm', '000262.dcm',
'000263.dcm',
'000264.dcm', '000265.dcm', '000266.dcm', '000267.dcm', '000268.dcm',
'000269.dcm', '000270.dcm', '000271.dcm', '000272.dcm', '000273.dcm',
'000274.dcm',
'000275.dcm', '000276.dcm', '000277.dcm', '000278.dcm', '000279.dcm',
'000280.dcm', '000281.dcm', '000282.dcm', '000283.dcm', '000284.dcm',
'000285.dcm',
'000286.dcm', '000287.dcm', '000288.dcm', '000289.dcm', '000290.dcm',
'000291.dcm', '000292.dcm', '000293.dcm', '000294.dcm', '000295.dcm',
'000296.dcm',
'000297.dcm', '000298.dcm',
];
let files = filenames.map(function(v) {
return 'https://cdn.rawgit.com/FNNDSC/data/master/dicom/rsna_2/PET/' + v;
});
files.push(
'https://cdn.rawgit.com/FNNDSC/data/master/dicom/rsna_2/SEG/3DSlicer/tumor_User1_Manual_Trial1.dcm');
// load sequence for each file
// it loads and parses the dicom image
let loader = new LoadersVolume(threeD);
/**
* Build the GUI
*/
function buildGUI(stackHelper) {
/**
* Update Layer 1
*/
function updateLayer1() {
// update layer1 geometry...
if (meshLayer1) {
meshLayer1.geometry.dispose();
meshLayer1.geometry = stackHelper.slice.geometry;
meshLayer1.geometry.verticesNeedUpdate = true;
}
}
/**
* Update Layer Mix
*/
function updateLayerMix() {
// update layer1 geometry...
if (meshLayerMix) {
sceneLayerMix.remove(meshLayerMix);
meshLayerMix.material.dispose();
meshLayerMix.material = null;
meshLayerMix.geometry.dispose();
meshLayerMix.geometry = null;
// add mesh in this scene with right shaders...
meshLayerMix = new THREE.Mesh(
stackHelper.slice.geometry, materialLayerMix);
// go the LPS space
meshLayerMix.applyMatrix(stackHelper.stack._ijk2LPS);
sceneLayerMix.add(meshLayerMix);
}
}
let stack = stackHelper.stack;
let gui = new dat.GUI({
autoPlace: false,
});
let customContainer = document.getElementById('my-gui-container');
customContainer.appendChild(gui.domElement);
let layer0Folder = gui.addFolder('PET');
layer0Folder.add(
stackHelper.slice, 'windowWidth', 1, stack.minMax[1]).step(1).listen();
layer0Folder.add(
stackHelper.slice, 'windowCenter',
stack.minMax[0], stack.minMax[1]).step(1).listen();
layer0Folder.add(stackHelper.slice, 'intensityAuto');
layer0Folder.add(stackHelper.slice, 'invert');
let lutUpdate = layer0Folder.add(
stackHelper.slice, 'lut', lutLayer0.lutsAvailable());
lutUpdate.onChange(function(value) {
lutLayer0.lut = value;
stackHelper.slice.lutTexture = lutLayer0.texture;
});
let indexUpdate = layer0Folder.add(
stackHelper, 'index', 0, stack.dimensionsIJK.z - 1).step(1).listen();
indexUpdate.onChange(function() {
updateLayer1();
updateLayerMix();
});
layer0Folder.add(
stackHelper.slice, 'interpolation', 0, 1).step(1).listen();
layer0Folder.open();
// layer mix folder
let layerMixFolder = gui.addFolder('Segmentation');
let opacityLayerMix1 = layerMixFolder.add(
layerMix, 'opacity1', 0, 1).step(0.01);
opacityLayerMix1.onChange(function(value) {
uniformsLayerMix.uOpacity1.value = value;
});
layerMixFolder.open();
// hook up callbacks
controls.addEventListener('OnScroll', function(e) {
if (e.delta > 0) {
if (stackHelper.index >= stack.dimensionsIJK.z - 1) {
return false;
}
stackHelper.index += 1;
} else {
if (stackHelper.index <= 0) {
return false;
}
stackHelper.index -= 1;
}
updateLayer1();
updateLayerMix();
});
updateLayer1();
updateLayerMix();
/**
* On window resize callback
*/
function onWindowResize() {
let threeD = document.getElementById('r3d');
camera.canvas = {
width: threeD.clientWidth,
height: threeD.clientHeight,
};
camera.fitBox(2);
renderer.setSize(threeD.clientWidth, threeD.clientHeight);
}
window.addEventListener('resize', onWindowResize, false);
onWindowResize();
}
/**
* Handle series
*/
function handleSeries() {
//
//
// first stack of first series
let mergedSeries = loader.data[0].mergeSeries(loader.data);
loader.free();
loader = null;
let stack = mergedSeries[0].stack[0];
let stack2 = mergedSeries[1].stack[0];
if (stack.modality === 'SEG') {
stack = mergedSeries[0].stack[0];
stack2 = mergedSeries[1].stack[0];
}
let stackHelper = new HelpersStack(stack);
stackHelper.bbox.visible = false;
stackHelper.border.visible = false;
stackHelper.index = 247;
sceneLayer0.add(stackHelper);
//
//
// create labelmap....
// we only care about the geometry....
// get first stack from series
// prepare it
// * ijk2LPS transforms
// * Z spacing
// * etc.
//
stack2.prepare();
// pixels packing for the fragment shaders now happens there
stack2.pack();
let textures2 = [];
for (let m = 0; m < stack2._rawData.length; m++) {
let tex = new THREE.DataTexture(
stack2.rawData[m],
stack2.textureSize,
stack2.textureSize,
stack2.textureType,
THREE.UnsignedByteType,
THREE.UVMapping,
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping,
THREE.NearestFilter,
THREE.NearestFilter);
tex.needsUpdate = true;
tex.flipY = true;
textures2.push(tex);
}
// create material && mesh then add it to sceneLayer1
uniformsLayer1 = ShadersDataUniform.uniforms();
uniformsLayer1.uTextureSize.value = stack2.textureSize;
uniformsLayer1.uTextureContainer.value = textures2;
uniformsLayer1.uWorldToData.value = stack2.lps2IJK;
uniformsLayer1.uNumberOfChannels.value = stack2.numberOfChannels;
uniformsLayer1.uPixelType.value = stack2.pixelType;
uniformsLayer1.uBitsAllocated.value = stack2.bitsAllocated;
uniformsLayer1.uWindowCenterWidth.value =
[stack2.windowCenter, stack2.windowWidth];
uniformsLayer1.uRescaleSlopeIntercept.value =
[stack2.rescaleSlope, stack2.rescaleIntercept];
uniformsLayer1.uDataDimensions.value = [stack2.dimensionsIJK.x,
stack2.dimensionsIJK.y,
stack2.dimensionsIJK.z];
uniformsLayer1.uInterpolation.value = 0;
// generate shaders on-demand!
let fs = new ShadersDataFragment(uniformsLayer1);
let vs = new ShadersDataVertex();
materialLayer1 = new THREE.ShaderMaterial(
{side: THREE.DoubleSide,
uniforms: uniformsLayer1,
vertexShader: vs.compute(),
fragmentShader: fs.compute(),
});
// add mesh in this scene with right shaders...
meshLayer1 = new THREE.Mesh(stackHelper.slice.geometry, materialLayer1);
// go the LPS space
meshLayer1.applyMatrix(stack._ijk2LPS);
sceneLayer1.add(meshLayer1);
// Create the Mix layer
uniformsLayerMix = ShadersLayerUniform.uniforms();
uniformsLayerMix.uTextureBackTest0.value = sceneLayer0TextureTarget.texture;
uniformsLayerMix.uTextureBackTest1.value = sceneLayer1TextureTarget.texture;
let fls = new ShadersLayerFragment(uniformsLayerMix);
let vls = new ShadersLayerVertex();
materialLayerMix = new THREE.ShaderMaterial({
side: THREE.DoubleSide,
uniforms: uniformsLayerMix,
vertexShader: vls.compute(),
fragmentShader: fls.compute(),
transparent: true,
});
// add mesh in this scene with right shaders...
meshLayerMix = new THREE.Mesh(stackHelper.slice.geometry, materialLayerMix);
// go the LPS space
meshLayerMix.applyMatrix(stack._ijk2LPS);
sceneLayerMix.add(meshLayerMix);
//
// set camera
let worldbb = stack.worldBoundingBox();
let lpsDims = new THREE.Vector3(
worldbb[1] - worldbb[0],
worldbb[3] - worldbb[2],
worldbb[5] - worldbb[4]
);
// box: {halfDimensions, center}
let box = {
center: stack.worldCenter().clone(),
halfDimensions:
new THREE.Vector3(lpsDims.x + 10, lpsDims.y + 10, lpsDims.z + 10),
};
// init and zoom
let canvas = {
width: threeD.clientWidth,
height: threeD.clientHeight,
};
camera.directions = [stack.xCosine, stack.yCosine, stack.zCosine];
camera.box = box;
camera.canvas = canvas;
camera.update();
camera.fitBox(2);
// CREATE LUT
lutLayer0 = new HelpersLut(
'my-lut-canvases-l0',
'default',
'linear',
[[0, 0, 0, 0], [1, 1, 1, 1]],
[[0, 1], [1, 1]]);
lutLayer0.luts = HelpersLut.presetLuts();
lutLayer0.lut = 'random';
stackHelper.slice.lut = 1;
stackHelper.slice.lutTexture = lutLayer0.texture;
lutLayer1 = new HelpersLut(
'my-lut-canvases-l1',
'default',
'linear',
stack2.segmentationLUT,
stack2.segmentationLUTO,
true);
uniformsLayer1.uLut.value = 1;
uniformsLayer1.uTextureLUT.value = lutLayer1.texture;
buildGUI(stackHelper);
}
loader.load(files)
.then(function() {
handleSeries();
})
.catch(function(error) {
window.console.log('oops... something went wrong...');
window.console.log(error);
});
};