import 'vtk.js/Sources/favicon';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'; import vtkAnnotatedCubeActor from 'vtk.js/Sources/Rendering/Core/AnnotatedCubeActor'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkHttpDataSetReader from 'vtk.js/Sources/IO/Core/HttpDataSetReader'; import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData'; import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper'; import vtkImageReslice from 'vtk.js/Sources/Imaging/Core/ImageReslice'; import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice'; import vtkInteractorStyleImage from 'vtk.js/Sources/Interaction/Style/InteractorStyleImage'; import vtkInteractorStyleTrackballCamera from 'vtk.js/Sources/Interaction/Style/InteractorStyleTrackballCamera'; import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper'; import vtkOutlineFilter from 'vtk.js/Sources/Filters/General/OutlineFilter'; import vtkOpenGLRenderWindow from 'vtk.js/Sources/Rendering/OpenGL/RenderWindow'; import vtkOrientationMarkerWidget from 'vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget'; import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer'; import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow'; import vtkRenderWindowInteractor from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor'; import vtkResliceCursorWidget from 'vtk.js/Sources/Widgets/Widgets3D/ResliceCursorWidget'; import vtkWidgetManager from 'vtk.js/Sources/Widgets/Core/WidgetManager';
import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource'; import { CaptureOn } from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants';
import { vec3 } from 'gl-matrix'; import { SlabMode } from 'vtk.js/Sources/Imaging/Core/ImageReslice/Constants';
import { xyzToViewType } from 'vtk.js/Sources/Widgets/Widgets3D/ResliceCursorWidget/Constants'; import controlPanel from './controlPanel.html';
const viewColors = [ [1, 0, 0], [0, 1, 0], [0, 0, 1], [0.5, 0.5, 0.5], ];
const viewAttributes = []; const widget = vtkResliceCursorWidget.newInstance(); const widgetState = widget.getWidgetState(); widgetState.setKeepOrthogonality(true); widgetState.setOpacity(0.6);
const showDebugActors = true;
const container = document.querySelector('body'); const table = document.createElement('table'); table.setAttribute('id', 'table'); container.appendChild(table);
const trLine0 = document.createElement('tr'); trLine0.setAttribute('id', 'line0'); table.appendChild(trLine0); const controlContainer = document.createElement('div'); trLine0.appendChild(controlContainer); controlContainer.innerHTML = controlPanel;
const trLine1 = document.createElement('tr'); trLine1.setAttribute('id', 'line1'); table.appendChild(trLine1);
const trLine2 = document.createElement('tr'); trLine2.setAttribute('id', 'line2'); table.appendChild(trLine2);
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()})`; }
widgetState.setOpacity(0.6);
const initialPlanesState = { ...widgetState.getPlanes() };
let view3D = null;
for (let i = 0; i < 4; i++) { const element = document.createElement('td');
if (i % 2 === 0) { trLine2.appendChild(element); } else { trLine1.appendChild(element); }
const obj = { renderWindow: vtkRenderWindow.newInstance(), renderer: vtkRenderer.newInstance(), GLWindow: vtkOpenGLRenderWindow.newInstance(), interactor: vtkRenderWindowInteractor.newInstance(), widgetManager: vtkWidgetManager.newInstance(), };
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.GLWindow.setContainer(element); 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.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, });
const orientationWidget = vtkOrientationMarkerWidget.newInstance({ actor: axes, interactor: obj.renderWindow.getInteractor(), }); orientationWidget.setEnabled(true); orientationWidget.setViewportCorner( vtkOrientationMarkerWidget.Corners.BOTTOM_RIGHT ); orientationWidget.setViewportSize(0.15); orientationWidget.setMinPixelSize(100); orientationWidget.setMaxPixelSize(300); }
function updateReslice( interactionContext = { viewType: '', reslice: null, actor: null, renderer: null, resetFocalPoint: false, // Reset the focal point to the center of the display image keepFocalPointPosition: false, // Defines if the focal point position is kepts (same display distance from reslice cursor center) 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, } ) { const obj = widget.updateReslicePlane( interactionContext.reslice, interactionContext.viewType ); if (obj.modified) { interactionContext.actor.setUserMatrix( interactionContext.reslice.getResliceAxes() ); interactionContext.sphereSources[0].setCenter(...obj.origin); interactionContext.sphereSources[1].setCenter(...obj.point1); interactionContext.sphereSources[2].setCenter(...obj.point2); } widget.updateCameraPoints( interactionContext.renderer, interactionContext.viewType, interactionContext.resetFocalPoint, interactionContext.keepFocalPointPosition, interactionContext.computeFocalPointOffset ); view3D.renderWindow.render(); return obj.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.onInteractionEvent( ({ computeFocalPointOffset, canUpdateFocalPoint }) => { const activeViewType = widget .getWidgetState() .getActiveViewType(); const keepFocalPointPosition = activeViewType !== viewType && canUpdateFocalPoint; updateReslice({ viewType, reslice, actor: obj.resliceActor, renderer: obj.renderer, resetFocalPoint: false, keepFocalPointPosition, computeFocalPointOffset, sphereSources: obj.sphereSources, }); } ); });
updateReslice({ viewType, reslice, actor: obj.resliceActor, renderer: obj.renderer, resetFocalPoint: true, keepFocalPointPosition: false, computeFocalPointOffset: true, sphereSources: obj.sphereSources, }); obj.renderWindow.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, keepFocalPointPosition: false, computeFocalPointOffset: true, sphereSources: obj.sphereSources, resetViewUp: true, }); obj.renderWindow.render(); }); view3D.renderer.resetCamera(); view3D.renderer.resetCameraClippingRange(); }
const checkboxOrthogonality = document.getElementById('checkboxOrthogality'); checkboxOrthogonality.addEventListener('change', (ev) => { widgetState.setKeepOrthogonality(checkboxOrthogonality.checked); });
const checkboxRotation = document.getElementById('checkboxRotation'); checkboxRotation.addEventListener('change', (ev) => { widgetState.setEnableRotation(checkboxRotation.checked); });
const checkboxTranslation = document.getElementById('checkboxTranslation'); checkboxTranslation.addEventListener('change', (ev) => { widgetState.setEnableTranslation(checkboxTranslation.checked); });
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(); });
|