import '@kitware/vtk.js/favicon';
import '@kitware/vtk.js/Rendering/Profiles/Volume'; import '@kitware/vtk.js/Rendering/Profiles/Geometry';
import { ColorMixPreset } from '@kitware/vtk.js/Rendering/Core/VolumeProperty/Constants'; import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader'; import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume'; import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper'; import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'; import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants'; import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData'; import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';
const { BlendMode } = Constants;
const controlPanel = ` <table> <tr> <td> <label for="modeselect">Blend Mode:</label> <select id="modeselect"> <option value="regular">Regular MIP</option> <option value="edge">Labelmap Edge MIP</option> </select> </td> </tr> <tr> <td> <label for="thickness1">Segment 1 Thickness:</label> <input type="number" id="thickness1" value="3" min="1" max="10" step="1"> </td> </tr> <tr> <td> <label for="thickness2">Segment 2 Thickness:</label> <input type="number" id="thickness2" value="3" min="1" max="10" step="1"> </td> </tr>
</table> `;
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ background: [0, 0, 0], }); const renderer = fullScreenRenderer.getRenderer(); const renderWindow = fullScreenRenderer.getRenderWindow();
const actor = vtkVolume.newInstance(); const mapper = vtkVolumeMapper.newInstance(); mapper.setBlendModeToMaximumIntensity(); actor.setMapper(mapper);
function createSphericalLabel( data, dims, center, radius, label, numberOfComponents ) { for (let k = 0; k < dims[2]; k++) { for (let j = 0; j < dims[1]; j++) { for (let i = 0; i < dims[0]; i++) { const dx = i - center[0]; const dy = j - center[1]; const dz = k - center[2]; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (distance <= radius) { const index = (k * dims[1] * dims[0] + j * dims[0] + i) * numberOfComponents + 1; data[index] = label; } } } } }
function setupTransferFunctions() { const ctfun = vtkColorTransferFunction.newInstance(); ctfun.addRGBPoint(0, 0, 0, 0); ctfun.addRGBPoint(255, 1.0, 1.0, 1.0);
const ofun = vtkPiecewiseFunction.newInstance(); ofun.addPoint(0.0, 0.1); ofun.addPoint(255.0, 1.0);
actor.getProperty().setRGBTransferFunction(0, ctfun); actor.getProperty().setScalarOpacity(0, ofun); }
function createRegularLabelmap(imageData, dims, center, radius1, radius2) { const values = new Uint8Array(imageData.getNumberOfPoints()); const labelMapData = vtkImageData.newInstance( imageData.get('spacing', 'origin', 'direction') );
const dataArray = vtkDataArray.newInstance({ numberOfComponents: 1, values, }); labelMapData.getPointData().setScalars(dataArray);
labelMapData.setDimensions(...imageData.getDimensions()); labelMapData.setSpacing(...imageData.getSpacing()); labelMapData.setOrigin(...imageData.getOrigin()); labelMapData.setDirection(...imageData.getDirection());
const labelmapArray = labelMapData.getPointData().getScalars().getData(); const numberOfComponents = 1;
createSphericalLabel( labelmapArray, dims, center, radius1, 1, numberOfComponents ); createSphericalLabel( labelmapArray, dims, [center[0] + 2 * radius1, center[1] + 2 * radius1, center[2] + 2 * radius1], radius2, 2, numberOfComponents );
const labelmapActor = vtkVolume.newInstance(); const labelmapMapper = vtkVolumeMapper.newInstance();
labelmapMapper.setInputData(labelMapData); labelmapActor.setMapper(labelmapMapper);
const labelMapColorFunction = vtkColorTransferFunction.newInstance(); labelMapColorFunction.addRGBPoint(0, 0, 0, 0); labelMapColorFunction.addRGBPoint(1, 0, 0, 1); labelMapColorFunction.addRGBPoint(2, 1, 0, 0);
const labelMapOpacityFunction = vtkPiecewiseFunction.newInstance(); labelMapOpacityFunction.addPoint(0, 0); labelMapOpacityFunction.addPoint(1, 0.5); labelMapOpacityFunction.addPoint(2, 0.5);
labelmapActor.getProperty().setRGBTransferFunction(0, labelMapColorFunction); labelmapActor.getProperty().setScalarOpacity(0, labelMapOpacityFunction);
labelmapActor.getProperty().setInterpolationTypeToNearest(); renderer.addVolume(labelmapActor);
return labelmapActor; }
function createAdvancedMIPLabelmap(imageData, dims, center, radius1, radius2) { const array = imageData.getPointData().getArray(0); const baseData = array.getData();
const numberOfComponents = 2; const cubeData = new Float32Array(numberOfComponents * baseData.length);
for (let i = 0; i < baseData.length; i++) { cubeData[i * numberOfComponents] = baseData[i]; }
createSphericalLabel(cubeData, dims, center, radius1, 1, numberOfComponents); createSphericalLabel( cubeData, dims, [center[0] + 2 * radius1, center[1] + 2 * radius1, center[2] + 2 * radius1], radius2, 2, numberOfComponents );
actor.getProperty().setColorMixPreset(ColorMixPreset.ADDITIVE);
const maskCtfun = vtkColorTransferFunction.newInstance(); maskCtfun.addRGBPoint(0, 0, 0, 0); maskCtfun.addRGBPoint(1, 0, 0, 1); maskCtfun.addRGBPoint(2, 1, 0, 0);
const maskOfun = vtkPiecewiseFunction.newInstance(); maskOfun.addPoint(0, 0); maskOfun.addPoint(1, 1); maskOfun.addPoint(2, 1);
const arrayAgain = mapper.getInputData().getPointData().getArray(0); arrayAgain.setData(cubeData); arrayAgain.setNumberOfComponents(2);
actor.getProperty().setRGBTransferFunction(1, maskCtfun); actor.getProperty().setScalarOpacity(1, maskOfun); actor.getProperty().setForceNearestInterpolation(1, true);
actor.getProperty().setLabelOutlineThickness(3);
return actor; }
const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
function updateOutlineThickness() { const thickness1 = parseInt(document.querySelector('#thickness1').value, 10); const thickness2 = parseInt(document.querySelector('#thickness2').value, 10); actor.getProperty().setLabelOutlineThickness([thickness1, thickness2]); renderWindow.render(); }
reader.setUrl(`${__BASE_PATH__}/data/volume/LIDC2.vti`).then(() => { reader.loadData().then(() => { const imageData = reader.getOutputData();
function createBasePipeline() { renderer.removeVolume(actor); mapper.setInputData(imageData);
renderer.addVolume(actor); setupTransferFunctions();
actor.getProperty().setInterpolationTypeToLinear(); actor.getProperty().setForceNearestInterpolation(1, true);
renderer.resetCamera(); renderWindow.render(); }
createBasePipeline();
const dims = imageData.getDimensions(); const center = dims.map((d) => Math.floor(d / 2)); const minDim = Math.min(...dims); const radius1 = Math.floor(minDim / 6); const radius2 = Math.floor(minDim / 6);
let edgeLabelmapActor = null; let regularLabelmapActor = null;
function updateBlendMode(mode) { if (mode === 'edge') { if (regularLabelmapActor) { renderer.removeVolume(regularLabelmapActor); regularLabelmapActor = null; }
edgeLabelmapActor = createAdvancedMIPLabelmap( imageData, dims, center, radius1, radius2 ); mapper.setBlendMode(BlendMode.LABELMAP_EDGE_PROJECTION_BLEND); } else { if (edgeLabelmapActor) { renderer.removeVolume(edgeLabelmapActor); edgeLabelmapActor = null;
createBasePipeline(); }
regularLabelmapActor = createRegularLabelmap( imageData, dims, center, radius1, radius2 ); mapper.setBlendMode(BlendMode.MAXIMUM_INTENSITY_BLEND); regularLabelmapActor .getMapper() .setBlendMode(BlendMode.COMPOSITE_BLEND); } renderer.resetCamera(); renderWindow.render(); }
actor.getProperty().setInterpolationTypeToLinear(); actor.getProperty().setForceNearestInterpolation(1, true);
renderer.resetCamera(); renderWindow.render();
updateBlendMode('regular');
fullScreenRenderer.addController(controlPanel);
const modeSelect = document.querySelector('#modeselect'); modeSelect.addEventListener('change', (event) => { updateBlendMode(event.target.value); });
const thickness1Input = document.querySelector('#thickness1'); const thickness2Input = document.querySelector('#thickness2');
thickness1Input.addEventListener('change', updateOutlineThickness); thickness2Input.addEventListener('change', updateOutlineThickness);
updateOutlineThickness(); }); });
global.source = reader; global.mapper = mapper; global.actor = actor; global.renderer = renderer; global.renderWindow = renderWindow;
|