import '@kitware/vtk.js/favicon';
import '@kitware/vtk.js/Rendering/Profiles/All';
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; import vtkAnnotatedCubeActor from '@kitware/vtk.js/Rendering/Core/AnnotatedCubeActor'; import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader'; import vtkGenericRenderWindow from '@kitware/vtk.js/Rendering/Misc/GenericRenderWindow'; import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData'; import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper'; import vtkImageReslice from '@kitware/vtk.js/Imaging/Core/ImageReslice'; import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice'; import vtkInteractorStyleImage from '@kitware/vtk.js/Interaction/Style/InteractorStyleImage'; import vtkInteractorStyleTrackballCamera from '@kitware/vtk.js/Interaction/Style/InteractorStyleTrackballCamera'; import vtkMath from '@kitware/vtk.js/Common/Core/Math'; import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; import vtkOutlineFilter from '@kitware/vtk.js/Filters/General/OutlineFilter'; import vtkOrientationMarkerWidget from '@kitware/vtk.js/Interaction/Widgets/OrientationMarkerWidget'; import vtkResliceCursorWidget from '@kitware/vtk.js/Widgets/Widgets3D/ResliceCursorWidget'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
import vtkSphereSource from '@kitware/vtk.js/Filters/Sources/SphereSource'; import { CaptureOn } from '@kitware/vtk.js/Widgets/Core/WidgetManager/Constants';
import { vec3 } from 'gl-matrix'; import { SlabMode } from '@kitware/vtk.js/Imaging/Core/ImageReslice/Constants';
import { xyzToViewType, InteractionMethodsName, } from '@kitware/vtk.js/Widgets/Widgets3D/ResliceCursorWidget/Constants'; import controlPanel from './controlPanel.html';
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';
const viewColors = [ [1, 0, 0], [0, 1, 0], [0, 0, 1], [0.5, 0.5, 0.5], ];
const viewAttributes = []; window.va = viewAttributes; const widget = vtkResliceCursorWidget.newInstance(); window.widget = widget; const widgetState = widget.getWidgetState();
widgetState .getStatesWithLabel('sphere') .forEach((handle) => handle.setScale1(20)); const showDebugActors = true;
const appCursorStyles = { translateCenter: 'move', rotateLine: 'alias', translateAxis: 'pointer', default: 'default', };
const container = document.querySelector('body'); const controlContainer = document.createElement('div'); controlContainer.innerHTML = controlPanel; container.appendChild(controlContainer); const checkboxTranslation = document.getElementById('checkboxTranslation'); const checkboxShowRotation = document.getElementById('checkboxShowRotation'); const checkboxRotation = document.getElementById('checkboxRotation'); const checkboxOrthogonality = document.getElementById('checkboxOrthogonality');
function createSyntheticImageData(dims) { const imageData = vtkImageData.newInstance(); const newArray = new Uint8Array(dims[0] * dims[1] * dims[2]); const s = 0.1; imageData.setSpacing(s, s, s); imageData.setExtent(0, 127, 0, 127, 0, 127); let i = 0; for (let z = 0; z < dims[2]; z++) { for (let y = 0; y < dims[1]; y++) { for (let x = 0; x < dims[0]; x++) { newArray[i++] = (256 * (i % (dims[0] * dims[1]))) / (dims[0] * dims[1]); } } }
const da = vtkDataArray.newInstance({ numberOfComponents: 1, values: newArray, }); da.setName('scalars');
imageData.getPointData().setScalars(da);
return imageData; }
function createRGBStringFromRGBValues(rgb) { if (rgb.length !== 3) { return 'rgb(0, 0, 0)'; } return `rgb(${(rgb[0] * 255).toString()}, ${(rgb[1] * 255).toString()}, ${( rgb[2] * 255 ).toString()})`; }
const initialPlanesState = { ...widgetState.getPlanes() };
let view3D = null;
for (let i = 0; i < 4; i++) { const elementParent = document.createElement('div'); elementParent.setAttribute('class', 'view'); elementParent.style.width = '50%'; elementParent.style.height = '300px'; elementParent.style.display = 'inline-block';
const element = document.createElement('div'); element.setAttribute('class', 'view'); element.style.width = '100%'; element.style.height = '100%'; elementParent.appendChild(element);
container.appendChild(elementParent);
const grw = vtkGenericRenderWindow.newInstance(); grw.setContainer(element); grw.resize(); const obj = { renderWindow: grw.getRenderWindow(), renderer: grw.getRenderer(), GLWindow: grw.getApiSpecificRenderWindow(), interactor: grw.getInteractor(), widgetManager: vtkWidgetManager.newInstance(), orientationWidget: null, };
obj.renderer.getActiveCamera().setParallelProjection(true); obj.renderer.setBackground(...viewColors[i]); obj.renderWindow.addRenderer(obj.renderer); obj.renderWindow.addView(obj.GLWindow); obj.renderWindow.setInteractor(obj.interactor); obj.interactor.setView(obj.GLWindow); obj.interactor.initialize(); obj.interactor.bindEvents(element); obj.widgetManager.setRenderer(obj.renderer); if (i < 3) { obj.interactor.setInteractorStyle(vtkInteractorStyleImage.newInstance()); obj.widgetInstance = obj.widgetManager.addWidget(widget, xyzToViewType[i]); obj.widgetInstance.setScaleInPixels(true); obj.widgetInstance.setHoleWidth(50); obj.widgetInstance.setInfiniteLine(false); widgetState .getStatesWithLabel('line') .forEach((state) => state.setScale3(4, 4, 300)); widgetState .getStatesWithLabel('center') .forEach((state) => state.setOpacity(128)); obj.widgetInstance.setKeepOrthogonality(checkboxOrthogonality.checked); obj.widgetInstance.setCursorStyles(appCursorStyles); obj.widgetManager.enablePicking(); obj.widgetManager.setCaptureOn(CaptureOn.MOUSE_MOVE); } else { obj.interactor.setInteractorStyle( vtkInteractorStyleTrackballCamera.newInstance() ); }
obj.reslice = vtkImageReslice.newInstance(); obj.reslice.setSlabMode(SlabMode.MEAN); obj.reslice.setSlabNumberOfSlices(1); obj.reslice.setTransformInputSampling(false); obj.reslice.setAutoCropOutput(true); obj.reslice.setOutputDimensionality(2); obj.resliceMapper = vtkImageMapper.newInstance(); obj.resliceMapper.setInputConnection(obj.reslice.getOutputPort()); obj.resliceActor = vtkImageSlice.newInstance(); obj.resliceActor.setMapper(obj.resliceMapper); obj.sphereActors = []; obj.sphereSources = [];
for (let j = 0; j < 3; j++) { const sphere = vtkSphereSource.newInstance(); sphere.setRadius(10); const mapper = vtkMapper.newInstance(); mapper.setInputConnection(sphere.getOutputPort()); const actor = vtkActor.newInstance(); actor.setMapper(mapper); actor.getProperty().setColor(...viewColors[i]); actor.setVisibility(showDebugActors); obj.sphereActors.push(actor); obj.sphereSources.push(sphere); }
if (i < 3) { viewAttributes.push(obj); } else { view3D = obj; }
const axes = vtkAnnotatedCubeActor.newInstance(); axes.setDefaultStyle({ text: '+X', fontStyle: 'bold', fontFamily: 'Arial', fontColor: 'black', fontSizeScale: (res) => res / 2, faceColor: createRGBStringFromRGBValues(viewColors[0]), faceRotation: 0, edgeThickness: 0.1, edgeColor: 'black', resolution: 400, }); axes.setXMinusFaceProperty({ text: '-X', faceColor: createRGBStringFromRGBValues(viewColors[0]), faceRotation: 90, fontStyle: 'italic', }); axes.setYPlusFaceProperty({ text: '+Y', faceColor: createRGBStringFromRGBValues(viewColors[1]), fontSizeScale: (res) => res / 4, }); axes.setYMinusFaceProperty({ text: '-Y', faceColor: createRGBStringFromRGBValues(viewColors[1]), fontColor: 'white', }); axes.setZPlusFaceProperty({ text: '+Z', faceColor: createRGBStringFromRGBValues(viewColors[2]), }); axes.setZMinusFaceProperty({ text: '-Z', faceColor: createRGBStringFromRGBValues(viewColors[2]), faceRotation: 45, });
obj.orientationWidget = vtkOrientationMarkerWidget.newInstance({ actor: axes, interactor: obj.renderWindow.getInteractor(), }); obj.orientationWidget.setEnabled(true); obj.orientationWidget.setViewportCorner( vtkOrientationMarkerWidget.Corners.BOTTOM_RIGHT ); obj.orientationWidget.setViewportSize(0.15); obj.orientationWidget.setMinPixelSize(100); obj.orientationWidget.setMaxPixelSize(300);
if (i < 3) { const slider = document.createElement('input'); slider.type = 'range'; slider.min = 0; slider.max = 200; slider.style.bottom = '0px'; slider.style.width = '100%'; elementParent.appendChild(slider); obj.slider = slider;
slider.addEventListener('change', (ev) => { const newDistanceToP1 = ev.target.value; const dirProj = widget.getWidgetState().getPlanes()[ xyzToViewType[i] ].normal; const planeExtremities = widget.getPlaneExtremities(xyzToViewType[i]); const newCenter = vtkMath.multiplyAccumulate( planeExtremities[0], dirProj, Number(newDistanceToP1), [] ); widget.setCenter(newCenter); obj.widgetInstance.invokeInteractionEvent( obj.widgetInstance.getActiveInteraction() ); viewAttributes.forEach((obj2) => { obj2.interactor.render(); }); }); } }
function updateReslice( interactionContext = { viewType: '', reslice: null, actor: null, renderer: null, resetFocalPoint: false, // Reset the focal point to the center of the display image computeFocalPointOffset: false, // Defines if the display offset between reslice center and focal point has to be // computed. If so, then this offset will be used to keep the focal point position during rotation. spheres: null, slider: null, } ) { const modified = widget.updateReslicePlane( interactionContext.reslice, interactionContext.viewType ); if (modified) { const resliceAxes = interactionContext.reslice.getResliceAxes(); interactionContext.actor.setUserMatrix(resliceAxes); const planeSource = widget.getPlaneSource(interactionContext.viewType); interactionContext.sphereSources[0].setCenter(planeSource.getOrigin()); interactionContext.sphereSources[1].setCenter(planeSource.getPoint1()); interactionContext.sphereSources[2].setCenter(planeSource.getPoint2());
if (interactionContext.slider) { const planeExtremities = widget.getPlaneExtremities( interactionContext.viewType ); const length = Math.sqrt( vtkMath.distance2BetweenPoints(planeExtremities[0], planeExtremities[1]) ); const dist = Math.sqrt( vtkMath.distance2BetweenPoints( planeExtremities[0], widgetState.getCenter() ) ); interactionContext.slider.min = 0; interactionContext.slider.max = length; interactionContext.slider.value = dist; } } widget.updateCameraPoints( interactionContext.renderer, interactionContext.viewType, interactionContext.resetFocalPoint, interactionContext.computeFocalPointOffset ); view3D.renderWindow.render(); return modified; }
const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true }); reader.setUrl(`${__BASE_PATH__}/data/volume/LIDC2.vti`).then(() => { reader.loadData().then(() => { const image = reader.getOutputData(); widget.setImage(image);
const outline = vtkOutlineFilter.newInstance(); outline.setInputData(image); const outlineMapper = vtkMapper.newInstance(); outlineMapper.setInputData(outline.getOutputData()); const outlineActor = vtkActor.newInstance(); outlineActor.setMapper(outlineMapper); view3D.renderer.addActor(outlineActor);
viewAttributes.forEach((obj, i) => { obj.reslice.setInputData(image); obj.renderer.addActor(obj.resliceActor); view3D.renderer.addActor(obj.resliceActor); obj.sphereActors.forEach((actor) => { obj.renderer.addActor(actor); view3D.renderer.addActor(actor); }); const reslice = obj.reslice; const viewType = xyzToViewType[i];
viewAttributes .forEach((v) => { v.widgetInstance.onStartInteractionEvent(() => { updateReslice({ viewType, reslice, actor: obj.resliceActor, renderer: obj.renderer, resetFocalPoint: false, computeFocalPointOffset: true, sphereSources: obj.sphereSources, slider: obj.slider, }); });
v.widgetInstance.onInteractionEvent( (interactionMethodName) => { const canUpdateFocalPoint = interactionMethodName === InteractionMethodsName.RotateLine; const activeViewType = widget .getWidgetState() .getActiveViewType(); const computeFocalPointOffset = activeViewType === viewType || !canUpdateFocalPoint; updateReslice({ viewType, reslice, actor: obj.resliceActor, renderer: obj.renderer, resetFocalPoint: false, computeFocalPointOffset, sphereSources: obj.sphereSources, slider: obj.slider, }); } ); });
updateReslice({ viewType, reslice, actor: obj.resliceActor, renderer: obj.renderer, resetFocalPoint: true, computeFocalPointOffset: true, sphereSources: obj.sphereSources, slider: obj.slider, }); obj.interactor.render(); });
view3D.renderer.resetCamera(); view3D.renderer.resetCameraClippingRange();
const maxNumberOfSlices = vec3.length(image.getDimensions()); document.getElementById('slabNumber').max = maxNumberOfSlices; }); });
function updateViews() { viewAttributes.forEach((obj, i) => { updateReslice({ viewType: xyzToViewType[i], reslice: obj.reslice, actor: obj.resliceActor, renderer: obj.renderer, resetFocalPoint: true, computeFocalPointOffset: true, sphereSources: obj.sphereSources, resetViewUp: true, }); obj.renderWindow.render(); }); view3D.renderer.resetCamera(); view3D.renderer.resetCameraClippingRange(); }
checkboxTranslation.addEventListener('change', (ev) => { viewAttributes.forEach((obj) => obj.widgetInstance.setEnableTranslation(checkboxTranslation.checked) ); });
checkboxShowRotation.addEventListener('change', (ev) => { widgetState .getStatesWithLabel('rotation') .forEach((handle) => handle.setVisible(checkboxShowRotation.checked)); viewAttributes.forEach((obj) => { obj.interactor.render(); }); checkboxRotation.checked = checkboxShowRotation.checked; checkboxRotation.disabled = !checkboxShowRotation.checked; checkboxRotation.dispatchEvent(new Event('change')); });
checkboxRotation.addEventListener('change', (ev) => { viewAttributes.forEach((obj) => obj.widgetInstance.setEnableRotation(checkboxRotation.checked) ); checkboxOrthogonality.disabled = !checkboxRotation.checked; checkboxOrthogonality.dispatchEvent(new Event('change')); });
checkboxOrthogonality.addEventListener('change', (ev) => { viewAttributes.forEach((obj) => obj.widgetInstance.setKeepOrthogonality(checkboxOrthogonality.checked) ); });
const checkboxScaleInPixels = document.getElementById('checkboxScaleInPixels'); checkboxScaleInPixels.addEventListener('change', (ev) => { widget.setScaleInPixels(checkboxScaleInPixels.checked); viewAttributes.forEach((obj) => { obj.interactor.render(); }); });
const opacity = document.getElementById('opacity'); opacity.addEventListener('input', (ev) => { const opacityValue = document.getElementById('opacityValue'); opacityValue.innerHTML = ev.target.value; widget .getWidgetState() .getStatesWithLabel('handles') .forEach((handle) => handle.setOpacity(ev.target.value)); viewAttributes.forEach((obj) => { obj.interactor.render(); }); });
const optionSlabModeMin = document.getElementById('slabModeMin'); optionSlabModeMin.value = SlabMode.MIN; const optionSlabModeMax = document.getElementById('slabModeMax'); optionSlabModeMax.value = SlabMode.MAX; const optionSlabModeMean = document.getElementById('slabModeMean'); optionSlabModeMean.value = SlabMode.MEAN; const optionSlabModeSum = document.getElementById('slabModeSum'); optionSlabModeSum.value = SlabMode.SUM; const selectSlabMode = document.getElementById('slabMode'); selectSlabMode.addEventListener('change', (ev) => { viewAttributes.forEach((obj) => { obj.reslice.setSlabMode(Number(ev.target.value)); }); updateViews(); });
const sliderSlabNumberofSlices = document.getElementById('slabNumber'); sliderSlabNumberofSlices.addEventListener('change', (ev) => { const trSlabNumberValue = document.getElementById('slabNumberValue'); trSlabNumberValue.innerHTML = ev.target.value; viewAttributes.forEach((obj) => { obj.reslice.setSlabNumberOfSlices(ev.target.value); }); updateViews(); });
const buttonReset = document.getElementById('buttonReset'); buttonReset.addEventListener('click', () => { widgetState.setPlanes({ ...initialPlanesState }); widget.setCenter(widget.getWidgetState().getImage().getCenter()); updateViews(); });
const selectInterpolationMode = document.getElementById('selectInterpolation'); selectInterpolationMode.addEventListener('change', (ev) => { viewAttributes.forEach((obj) => { obj.reslice.setInterpolationMode(Number(ev.target.selectedIndex)); }); updateViews(); });
const checkboxWindowLevel = document.getElementById('checkboxWindowLevel'); checkboxWindowLevel.addEventListener('change', (ev) => { viewAttributes.forEach((obj, index) => { if (index < 3) { obj.interactor.setInteractorStyle( checkboxWindowLevel.checked ? vtkInteractorStyleImage.newInstance() : vtkInteractorStyleTrackballCamera.newInstance() ); } }); });
|