UNPKG

diglettk

Version:

A medical imaging toolkit, built on top of vtk.js

218 lines (181 loc) 7.96 kB
import vtkGenericRenderWindow from "vtk.js/Sources/Rendering/Misc/GenericRenderWindow"; import { vec3, quat, mat4 } from "gl-matrix"; import "vtk.js/Sources/favicon"; // Load the rendering pieces we want to use (for both WebGL and WebGPU) import "vtk.js/Sources/Rendering/Profiles/Geometry"; import "vtk.js/Sources/Rendering/Profiles/Volume"; import "vtk.js/Sources/Rendering/Profiles/Glyph"; import vtkFullScreenRenderWindow from "vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow"; import vtkWidgetManager from "vtk.js/Sources/Widgets/Core/WidgetManager"; import vtkHttpDataSetReader from "vtk.js/Sources/IO/Core/HttpDataSetReader"; import vtkImageCroppingWidget from "vtk.js/Sources/Widgets/Widgets3D/ImageCroppingWidget"; import vtkColorTransferFunction from "vtk.js/Sources/Rendering/Core/ColorTransferFunction"; import vtkPiecewiseFunction from "vtk.js/Sources/Common/DataModel/PiecewiseFunction"; import vtkVolume from "vtk.js/Sources/Rendering/Core/Volume"; import vtkVolumeMapper from "vtk.js/Sources/Rendering/Core/VolumeMapper"; import vtkPlane from "vtk.js/Sources/Common/DataModel/Plane"; // Force the loading of HttpDataAccessHelper to support gzip decompression import "vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper"; import { createVolumeActor, setCamera, setActorProperties, setupCropWidget } from "./utils/utils"; function getCroppingPlanes(imageData, ijkPlanes) { const rotation = quat.create(); mat4.getRotation(rotation, imageData.getIndexToWorld()); const rotateVec = vec => { const out = [0, 0, 0]; vec3.transformQuat(out, vec, rotation); return out; }; const [iMin, iMax, jMin, jMax, kMin, kMax] = ijkPlanes; const origin = imageData.indexToWorld([iMin, jMin, kMin]); // opposite corner from origin const corner = imageData.indexToWorld([iMax, jMax, kMax]); return [ // X min/max vtkPlane.newInstance({ normal: rotateVec([1, 0, 0]), origin }), vtkPlane.newInstance({ normal: rotateVec([-1, 0, 0]), origin: corner }), // Y min/max vtkPlane.newInstance({ normal: rotateVec([0, 1, 0]), origin }), vtkPlane.newInstance({ normal: rotateVec([0, -1, 0]), origin: corner }), // X min/max vtkPlane.newInstance({ normal: rotateVec([0, 0, 1]), origin }), vtkPlane.newInstance({ normal: rotateVec([0, 0, -1]), origin: corner }) ]; } export function debuggingScene(inputVolume, element) { console.log(inputVolume); inputVolume.setOrigin([0, 0, 0]); // console.log("input", inputVolume); // const genericRenderWindow = vtkGenericRenderWindow.newInstance(); // genericRenderWindow.setContainer(element); // genericRenderWindow.setBackground([0, 0, 0]); // genericRenderWindow.resize(); // const renderer = genericRenderWindow.getRenderer(); // const renderWindow = genericRenderWindow.getRenderWindow(); // renderer.removeAllVolumes(); // let actor = createVolumeActor(inputVolume); // renderer.addVolume(actor); // renderer.resetCamera(); // setCamera(renderer.getActiveCamera(), actor.getCenter()); // // setActorProperties(actor); // let { widget, widgetManager } = setupCropWidget(renderer, actor.getMapper()); // widget.setVisibility(true); // widgetManager.renderWidgets(); // genericRenderWindow.resize(); // renderWindow.render(); // ---------------------------------------------------------------------------- // Standard rendering code setup // ---------------------------------------------------------------------------- const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ background: [0, 0, 0] }); const renderer = fullScreenRenderer.getRenderer(); const renderWindow = fullScreenRenderer.getRenderWindow(); const apiRenderWindow = fullScreenRenderer.getApiSpecificRenderWindow(); global.renderer = renderer; global.renderWindow = renderWindow; // ---------------------------------------------------------------------------- // 2D overlay rendering // ---------------------------------------------------------------------------- const overlaySize = 15; const overlayBorder = 2; const overlay = document.createElement("div"); overlay.style.position = "absolute"; overlay.style.width = `${overlaySize}px`; overlay.style.height = `${overlaySize}px`; overlay.style.border = `solid ${overlayBorder}px red`; overlay.style.borderRadius = "50%"; overlay.style.left = "-100px"; overlay.style.pointerEvents = "none"; document.querySelector("body").appendChild(overlay); // ---------------------------------------------------------------------------- // Widget manager // ---------------------------------------------------------------------------- const widgetManager = vtkWidgetManager.newInstance(); widgetManager.setRenderer(renderer); const widget = vtkImageCroppingWidget.newInstance(); function widgetRegistration(e) { const action = e ? e.currentTarget.dataset.action : "addWidget"; const viewWidget = widgetManager[action](widget); if (viewWidget) { viewWidget.setDisplayCallback(coords => { overlay.style.left = "-100px"; if (coords) { const [w, h] = apiRenderWindow.getSize(); overlay.style.left = `${Math.round( (coords[0][0] / w) * window.innerWidth - overlaySize * 0.5 - overlayBorder )}px`; overlay.style.top = `${Math.round( ((h - coords[0][1]) / h) * window.innerHeight - overlaySize * 0.5 - overlayBorder )}px`; } }); renderer.resetCamera(); renderer.resetCameraClippingRange(); } widgetManager.enablePicking(); renderWindow.render(); } // Initial widget register widgetRegistration(); // const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true }); const actor = vtkVolume.newInstance(); const mapper = vtkVolumeMapper.newInstance(); mapper.setSampleDistance(1.1); actor.setMapper(mapper); // create color and opacity transfer functions const ctfun = vtkColorTransferFunction.newInstance(); ctfun.addRGBPoint(0, 85 / 255.0, 0, 0); ctfun.addRGBPoint(95, 1.0, 1.0, 1.0); ctfun.addRGBPoint(225, 0.66, 0.66, 0.5); ctfun.addRGBPoint(255, 0.3, 1.0, 0.5); const ofun = vtkPiecewiseFunction.newInstance(); ofun.addPoint(0.0, 0.0); ofun.addPoint(255.0, 1.0); actor.getProperty().setRGBTransferFunction(0, ctfun); actor.getProperty().setScalarOpacity(0, ofun); actor.getProperty().setScalarOpacityUnitDistance(0, 3.0); actor.getProperty().setInterpolationTypeToLinear(); actor.getProperty().setUseGradientOpacity(0, true); actor.getProperty().setGradientOpacityMinimumValue(0, 2); actor.getProperty().setGradientOpacityMinimumOpacity(0, 0.0); actor.getProperty().setGradientOpacityMaximumValue(0, 20); actor.getProperty().setGradientOpacityMaximumOpacity(0, 1.0); actor.getProperty().setShade(true); actor.getProperty().setAmbient(0.2); actor.getProperty().setDiffuse(0.7); actor.getProperty().setSpecular(0.3); actor.getProperty().setSpecularPower(8.0); // mapper.setInputConnection(reader.getOutputPort()); mapper.setInputData(inputVolume); // reader.setUrl(`${__BASE_PATH__}/data/volume/LIDC2.vti`).then(() => { // reader.loadData().then(() => { // const image = reader.getOutputData(); let image = inputVolume; // update crop widget widget.copyImageDataDescription(image); const cropState = widget.getWidgetState().getCroppingPlanes(); cropState.onModified(() => { const planes = getCroppingPlanes(image, cropState.getPlanes()); mapper.removeAllClippingPlanes(); planes.forEach(plane => { mapper.addClippingPlane(plane); }); mapper.modified(); }); // add volume to renderer renderer.addVolume(actor); renderer.resetCamera(); renderer.resetCameraClippingRange(); renderWindow.render(); // }); // }); }