import '@kitware/vtk.js/favicon';
import '@kitware/vtk.js/Rendering/Profiles/All';
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'; import vtkCylinderSource from '@kitware/vtk.js/Filters/Sources/CylinderSource'; import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; import vtkImageProperty from '@kitware/vtk.js/Rendering/Core/ImageProperty'; import vtkImageResliceMapper from '@kitware/vtk.js/Rendering/Core/ImageResliceMapper'; import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice'; import vtkInteractorStyleImage from '@kitware/vtk.js/Interaction/Style/InteractorStyleImage'; import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane'; import vtkPlaneWidget from '@kitware/vtk.js/Widgets/Widgets3D/ImplicitPlaneWidget'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; import { InterpolationType } from '@kitware/vtk.js/Rendering/Core/ImageProperty/Constants'; import { SlabTypes } from '@kitware/vtk.js/Rendering/Core/ImageResliceMapper/Constants';
import vtkResourceLoader from '@kitware/vtk.js/IO/Core/ResourceLoader'; import vtkLiteHttpDataAccessHelper from '@kitware/vtk.js/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper'; import vtkITKHelper from '@kitware/vtk.js/Common/DataModel/ITKHelper';
import { unzipSync } from 'fflate';
import controlPanel from './controlPanel.html';
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ background: [0.3, 0.3, 0.34], }); const renderer = fullScreenRenderer.getRenderer(); const renderWindow = fullScreenRenderer.getRenderWindow();
const ppty = vtkImageProperty.newInstance(); const mapper = vtkImageResliceMapper.newInstance(); const slicePlane = vtkPlane.newInstance(); slicePlane.setNormal(0, 1, 0); mapper.setSlicePlane(slicePlane); mapper.setSlabType(SlabTypes.MAX);
const slicePolyDataSource = vtkCylinderSource.newInstance({ height: 100, radius: 100, resolution: 20, capping: 1, center: [100, 100, 100], });
const actor = vtkImageSlice.newInstance(); actor.setMapper(mapper); renderer.addActor(actor);
const iStyle = vtkInteractorStyleImage.newInstance(); iStyle.setInteractionMode('IMAGE3D'); renderWindow.getInteractor().setInteractorStyle(iStyle);
const widgetManager = vtkWidgetManager.newInstance(); widgetManager.setRenderer(renderer);
const repStyle = { active: { plane: { opacity: 0.05, color: [1, 1, 1], }, normal: { opacity: 0.6, color: [0, 1, 0], }, origin: { opacity: 0.6, color: [0, 1, 0], }, }, inactive: { plane: { opacity: 0.0, color: [1, 1, 1], }, normal: { opacity: 0.3, color: [0.5, 0, 0], }, origin: { opacity: 0.3, color: [0.5, 0, 0], }, }, };
const widget = vtkPlaneWidget.newInstance(); widget.getWidgetState().setNormal(0, 0, 1); widget.setPlaceFactor(1); const w = widgetManager.addWidget(widget); w.setRepresentationStyle(repStyle);
function setImage(im) { const bds = im.getBounds(); mapper.setInputData(im); const imc = im.getCenter(); slicePlane.setOrigin(imc); slicePolyDataSource.setCenter(imc); widget.placeWidget(bds);
renderer.getActiveCamera().elevation(90); renderer.resetCamera(); renderer.getActiveCamera().dolly(1.5); renderer.resetCameraClippingRange(); const planeState = widget.getWidgetState(); planeState.setOrigin(slicePlane.getOrigin()); planeState.setNormal(slicePlane.getNormal()); planeState.onModified(() => { slicePlane.setOrigin(planeState.getOrigin()); slicePlane.setNormal(planeState.getNormal()); slicePolyDataSource.setCenter(planeState.getOrigin()); slicePolyDataSource.setDirection(planeState.getNormal()); if (mapper.getSlicePolyData()) { slicePolyDataSource.update(); mapper.setSlicePolyData(slicePolyDataSource.getOutputData()); } }); renderWindow.render(); }
async function update() { console.log('Fetching/downloading the input file, please wait...'); const zipFileData = await vtkLiteHttpDataAccessHelper.fetchBinary( `https://data.kitware.com/api/v1/item/6462789f546d74240b93fab3/download` );
console.log('Fetching/downloading input file done!');
const zipFileDataArray = new Uint8Array(zipFileData); const decompressedFiles = unzipSync(zipFileDataArray); const arrayBuffers = []; Object.keys(decompressedFiles).forEach((relativePath) => { if (relativePath.endsWith('.dcm')) { arrayBuffers.push(decompressedFiles[relativePath].buffer); } });
const { image: itkImage } = await window.itk.readImageDICOMArrayBufferSeries( arrayBuffers );
const vtkImage = vtkITKHelper.convertItkToVtkImage(itkImage); setImage(vtkImage); }
const rgb = vtkColorTransferFunction.newInstance(); rgb.addRGBPoint(0, 0, 0, 0); rgb.addRGBPoint(3926, 1, 1, 1); ppty.setRGBTransferFunction(rgb);
const ofun = vtkPiecewiseFunction.newInstance(); ofun.addPoint(0, 1); ofun.addPoint(3926, 1); ppty.setPiecewiseFunction(ofun);
ppty.setColorWindow(1400); ppty.setColorLevel(-500); actor.setProperty(ppty);
fullScreenRenderer.addController(controlPanel); const slabThicknessSlider = document.querySelector('.slabThickness'); const enableSlab = document.querySelector('.enableSlab'); enableSlab.addEventListener('click', () => { const slabFieldSet = document.querySelector('.slab'); if (slabFieldSet.disabled) { slabFieldSet.disabled = false; enableSlab.innerHTML = 'Disable slab mode'; mapper.setSlabThickness(Number(slabThicknessSlider.value)); renderWindow.render(); } else { slabFieldSet.disabled = true; enableSlab.innerHTML = 'Enable slab mode'; mapper.setSlabThickness(0); renderWindow.render(); } });
const slabTypes = document.querySelectorAll('.slabType'); for (let idx = 0; idx < slabTypes.length; ++idx) { const st = slabTypes[idx]; st.onchange = (e) => { if (e.target.value === 'min') { mapper.setSlabType(SlabTypes.MIN); } else if (e.target.value === 'max') { mapper.setSlabType(SlabTypes.MAX); } else if (e.target.value === 'sum') { mapper.setSlabType(SlabTypes.SUM); } else { mapper.setSlabType(SlabTypes.MEAN); } renderWindow.render(); }; } const sliceFunction = document.querySelectorAll('.spd'); for (let idx = 0; idx < sliceFunction.length; ++idx) { const st = sliceFunction[idx]; st.onchange = (e) => { if (e.target.value === 'plane') { mapper.setSlicePolyData(null); } else { slicePolyDataSource.update(); mapper.setSlicePolyData(slicePolyDataSource.getOutputData()); } renderWindow.render(); }; } const interpTypes = document.querySelectorAll('.interp'); for (let idx = 0; idx < interpTypes.length; ++idx) { const st = interpTypes[idx]; st.onchange = (e) => { if (e.target.value === 'linear') { actor.getProperty().setInterpolationType(InterpolationType.LINEAR); } else { actor.getProperty().setInterpolationType(InterpolationType.NEAREST); } renderWindow.render(); }; } document.querySelector('.slabThickness').addEventListener('change', (e) => { const value = Number(e.target.value); mapper.setSlabThickness(value); renderWindow.render(); });
vtkResourceLoader .loadScript( 'https://cdn.jsdelivr.net/npm/itk-wasm@1.0.0-b.8/dist/umd/itk-wasm.js' ) .then(update) .then(enableSlab.click());
global.actor = actor; global.mapper = mapper; global.property = ppty; global.rgb = rgb; global.ofun = ofun; global.renderer = renderer; global.renderWindow = renderWindow; global.widget = w;
|