ami.js
Version:
<p align="center"> <img src="https://cloud.githubusercontent.com/assets/214063/23213764/78ade038-f90c-11e6-8208-4fcade5f3832.png" width="60%"> </p>
434 lines (374 loc) • 11.7 kB
JavaScript
/* globals Stats, dat, AMI*/
// standard global variables
var controls;
var renderer;
var camera;
var statsyay;
var threeD;
//
var sceneLayer0TextureTarget;
var sceneLayer1TextureTarget;
//
var sceneLayer0;
//
var lutLayer0;
var sceneLayer1;
var meshLayer1;
var uniformsLayer1;
var materialLayer1;
var lutLayer1;
var sceneLayerMix;
var meshLayerMix;
var uniformsLayerMix;
var materialLayerMix;
var layerMix = {
opacity1: 1.0,
};
/**
* Init the scene
*/
function init() {
/**
* Animation loop
*/
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('container');
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 AMI.OrthographicCamera(
threeD.clientWidth / -2,
threeD.clientWidth / 2,
threeD.clientHeight / 2,
threeD.clientHeight / -2,
0.1,
10000
);
// controls
controls = new AMI.TrackballOrthoControl(camera, threeD);
controls.staticMoving = true;
controls.noRotate = true;
camera.controls = controls;
animate();
}
// init threeJS...
init();
var data = [
'000183.dcm',
'000219.dcm',
'000117.dcm',
'000240.dcm',
'000033.dcm',
'000060.dcm',
'000211.dcm',
'000081.dcm',
'000054.dcm',
'000090.dcm',
'000042.dcm',
'000029.dcm',
'000239.dcm',
'000226.dcm',
'000008.dcm',
'000128.dcm',
'000089.dcm',
'000254.dcm',
'000208.dcm',
'000047.dcm',
'000067.dcm'
];
var rawgit = 'https://cdn.rawgit.com/FNNDSC/data/master/dicom/andrei_abdomen/';
var dataFullPath = data.map(function(v) {
return rawgit + 'data/' + v;
});
var labelmap = ['000000.dcm'];
var labelmapFullPath = labelmap.map(function(v) {
return rawgit + 'segmentation/' + v;
});
var files = dataFullPath.concat(labelmapFullPath);
// load sequence for each file
// instantiate the loader
// it loads and parses the dicom image
var loader = new AMI.VolumeLoader(threeD);
/**
* Build GUI
*/
function buildGUI(stackHelper) {
/**
* Update Layer 1
*/
function updateLayer1() {
// update layer1 geometry...
if (meshLayer1) {
// dispose geometry first
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);
}
}
var stack = stackHelper.stack;
var gui = new dat.GUI({
autoPlace: false,
});
var customContainer = document.getElementById('my-gui-container');
customContainer.appendChild(gui.domElement);
var layer0Folder = gui.addFolder('CT');
layer0Folder.add(stackHelper.slice, 'invert');
var lutUpdate = layer0Folder.add(stackHelper.slice, 'lut', lutLayer0.lutsAvailable());
lutUpdate.onChange(function(value) {
lutLayer0.lut = value;
stackHelper.slice.lutTexture = lutLayer0.texture;
});
var 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
var layerMixFolder = gui.addFolder('Segmentation');
var 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();
/**
* Handle window resize
*/
function onWindowResize() {
var threeD = document.getElementById('container');
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
var mergedSeries = loader.data[0].mergeSeries(loader.data);
var stack = mergedSeries[0].stack[0];
var stack2 = mergedSeries[1].stack[0];
loader.free();
loader = null;
if (stack.modality === 'SEG') {
stack = mergedSeries[0].stack[0];
stack2 = mergedSeries[1].stack[0];
}
var stackHelper = new AMI.StackHelper(stack);
stackHelper.bbox.visible = false;
stackHelper.border.visible = false;
stackHelper.index = 10;
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();
var textures2 = [];
for (var m = 0; m < stack2._rawData.length; m++) {
var 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 = AMI.DataUniformShader.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!
var fs = new AMI.DataFragmentShader(uniformsLayer1);
var vs = new AMI.DataVertexShader();
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 = AMI.LayerUniformShader.uniforms();
uniformsLayerMix.uTextureBackTest0.value = sceneLayer0TextureTarget.texture;
uniformsLayerMix.uTextureBackTest1.value = sceneLayer1TextureTarget.texture;
let fls = new AMI.LayerFragmentShader(uniformsLayerMix);
let vls = new AMI.LayerVertexShader();
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, materialLayer1);
// go the LPS space
meshLayerMix.applyMatrix(stack._ijk2LPS);
sceneLayerMix.add(meshLayerMix);
//
// set camera
var worldbb = stack.worldBoundingBox();
var lpsDims = new THREE.Vector3(worldbb[1] - worldbb[0], worldbb[3] - worldbb[2], worldbb[5] - worldbb[4]);
// box: {halfDimensions, center}
var box = {
center: stack.worldCenter().clone(),
halfDimensions: new THREE.Vector3(lpsDims.x + 10, lpsDims.y + 10, lpsDims.z + 10)
};
// init and zoom
var 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 AMI.LutHelper(
'my-lut-canvases-l0',
'default',
'linear',
[[0, 0, 0, 0], [1, 1, 1, 1]],
[[0, 1], [1, 1]]
);
lutLayer0.luts = AMI.LutHelper.presetLuts();
lutLayer1 = new AMI.LutHelper(
'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);
});