UNPKG

@cornerstonejs/core

Version:
293 lines (292 loc) • 11.4 kB
import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper.js'; import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice.js'; import { buildPlanarActorEntry } from './buildPlanarActorEntry.js'; import uuidv4 from '../../../utilities/uuidv4.js'; import { InterpolationType, MetadataModules, ViewportType, } from '../../../enums/index.js'; import { loadAndCacheImage } from '../../../loaders/imageLoader.js'; import { updateVTKImageDataWithCornerstoneImage } from '../../../utilities/updateVTKImageDataWithCornerstoneImage.js'; import * as metaData from '../../../metaData.js'; import { ActorRenderMode } from '../../../types/index.js'; import { applyPlanarImagePresentation, createVTKImageDataFromImage, getDefaultImageVOIRange, updateVTKImageDataGeometryFromImage, } from '../../helpers/planarImageRendering.js'; import { canvasToWorldContextPool, worldToCanvasContextPool, } from './planarAdapterCoordinateTransforms.js'; import { buildPlanarImageData } from './CpuImageSliceRenderPath.js'; import { triggerPlanarNewImage } from './planarImageEvents.js'; import { applyPlanarICameraToActor, applyPlanarICameraToRenderer, } from './planarRenderCamera.js'; import { resolvePlanarRenderPathCurrentImageIdIndex, resolvePlanarRenderPathProjection, } from './planarRenderPathProjection.js'; export class VtkImageMapperRenderPath { async addData(ctx, data, options) { const payload = data; if (!payload.image) { throw new Error('[PlanarViewport] VTK image rendering requires an image'); } const mapper = vtkImageMapper.newInstance(); const actor = vtkImageSlice.newInstance(); const imageData = payload.imageData ?? createVTKImageDataFromImage(payload.image); ctx.display.activateRenderMode(ActorRenderMode.VTK_IMAGE); mapper.setInputData(imageData); actor.setMapper(mapper); if (imageData.getPointData().getScalars().getNumberOfComponents() > 1) { actor.getProperty().setIndependentComponents(false); } ctx.vtk.renderer.addActor(actor); const rendering = { renderMode: ActorRenderMode.VTK_IMAGE, actorEntryUID: uuidv4(), actor, currentImage: payload.image, mapper, imageData, useWorldCoordinateImageData: payload.useWorldCoordinateImageData, currentImageIdIndex: payload.initialImageIdIndex ?? 0, defaultVOIRange: getDefaultImageVOIRange(payload.image), dataPresentation: undefined, loadRequestId: 0, }; if (options.role !== 'overlay') { triggerPlanarNewImage(ctx, { image: payload.image, imageIdIndex: payload.initialImageIdIndex ?? 0, }); } return { rendering, updateDataPresentation: (props) => { this.updateDataPresentation(rendering, props); }, applyViewState: (camera) => { this.applyViewState(ctx, rendering, data.id, camera, payload.imageIds); }, getFrameOfReferenceUID: () => { return this.getFrameOfReferenceUID(rendering); }, getActorEntry: (data) => { return buildPlanarActorEntry(data, { actor: rendering.actor, mapper: rendering.mapper, renderMode: ActorRenderMode.VTK_IMAGE, uid: rendering.actorEntryUID, referencedIdFallback: rendering.currentImage.imageId, }); }, getImageData: () => { return this.getImageData(rendering); }, resize: () => { this.resize(ctx, rendering, data.id); }, removeData: () => { this.removeData(ctx, rendering); }, }; } updateDataPresentation(rendering, props) { const dataPresentation = props; rendering.dataPresentation = dataPresentation; applyPlanarImagePresentation({ actor: rendering.actor, defaultVOIRange: rendering.defaultVOIRange, defaultVOILUTFunction: rendering.currentImage?.voiLUTFunction, props: { interpolationType: InterpolationType.LINEAR, ...dataPresentation, }, }); } applyViewState(ctx, rendering, dataId, camera, imageIds) { const planarCamera = camera; ctx.display.activateRenderMode(ActorRenderMode.VTK_IMAGE); if (rendering.useWorldCoordinateImageData) { return; } const projection = resolvePlanarRenderPathProjection({ ctx, dataId, imageIds, rendering, viewState: planarCamera, }); const nextImageIdIndex = resolvePlanarRenderPathCurrentImageIdIndex({ projection, rendering, viewState: planarCamera, }); if (projection) { applyPlanarImageActorTransforms(ctx, rendering, projection.resolvedICamera); if (projection.isSourceBinding) { applyPlanarICameraToRenderer({ renderer: ctx.vtk.renderer, activeSourceICamera: projection.resolvedICamera, }); } } const dedupTarget = rendering.lastRequestedImageIdIndex ?? rendering.currentImageIdIndex; if (nextImageIdIndex === dedupTarget) { return; } if (nextImageIdIndex < 0 || nextImageIdIndex >= imageIds.length) { return; } rendering.lastRequestedImageIdIndex = nextImageIdIndex; const requestId = ++rendering.loadRequestId; void loadAndCacheImage(imageIds[nextImageIdIndex]).then((image) => { if (requestId !== rendering.loadRequestId) { return; } void updateRenderedImage({ ctx, dataId, image, rendering, imageIdIndex: nextImageIdIndex, dataPresentation: rendering.dataPresentation, }); }); } canvasToWorld(ctx, canvasPos) { return canvasToWorldContextPool({ canvas: ctx.vtk.canvas, renderer: ctx.vtk.renderer, canvasPos, }); } worldToCanvas(ctx, worldPos) { return worldToCanvasContextPool({ canvas: ctx.vtk.canvas, renderer: ctx.vtk.renderer, worldPos, }); } getFrameOfReferenceUID(rendering) { const imageId = rendering.currentImage.imageId; const imagePlaneModule = imageId ? metaData.get(MetadataModules.IMAGE_PLANE, imageId) : undefined; return imagePlaneModule?.frameOfReferenceUID; } getImageData(rendering) { return buildPlanarImageData(rendering.currentImage, this.getFrameOfReferenceUID(rendering)); } removeData(ctx, rendering) { const { actor } = rendering; ctx.vtk.renderer.removeActor(actor); } resize(ctx, rendering, dataId) { if (rendering.useWorldCoordinateImageData) { ctx.display.requestRender(); return; } const camera = ctx.viewport.getViewState(); const projection = resolvePlanarRenderPathProjection({ ctx, dataId, rendering, viewState: camera, }); if (projection) { applyPlanarImageActorTransforms(ctx, rendering, projection.resolvedICamera); if (projection.isSourceBinding) { applyPlanarICameraToRenderer({ renderer: ctx.vtk.renderer, activeSourceICamera: projection.resolvedICamera, }); } } ctx.display.requestRender(); } } export class VtkImageMapperPath { constructor() { this.id = 'planar:vtk-image-mapper'; this.type = ViewportType.PLANAR_NEXT; } matches(data, options) { return (data.type === 'image' && options.renderMode === ActorRenderMode.VTK_IMAGE); } createRenderPath() { return new VtkImageMapperRenderPath(); } selectContext(rootContext) { return { viewportId: rootContext.viewportId, renderingEngineId: rootContext.renderingEngineId, type: rootContext.type, viewport: rootContext.viewport, renderPath: rootContext.renderPath, view: rootContext.view, display: rootContext.display, vtk: rootContext.vtk, }; } } async function updateRenderedImage(args) { const { ctx, dataId, image, rendering, imageIdIndex, dataPresentation } = args; const camera = ctx.viewport.getViewState(); const { actor, mapper } = rendering; let imageData = rendering.imageData; if (imageData && canReuseImageDataForImage(imageData, image)) { updateVTKImageDataWithCornerstoneImage(imageData, image); updateVTKImageDataGeometryFromImage(imageData, image); } else { imageData = createVTKImageDataFromImage(image); mapper.setInputData(imageData); } rendering.currentImage = image; rendering.imageData = imageData; rendering.currentImageIdIndex = imageIdIndex; rendering.defaultVOIRange = getDefaultImageVOIRange(image); ctx.viewport.invalidateResolvedView(); if (imageData.getPointData().getScalars().getNumberOfComponents() > 1) { actor.getProperty().setIndependentComponents(false); } applyPlanarImagePresentation({ actor, defaultVOIRange: rendering.defaultVOIRange, defaultVOILUTFunction: image.voiLUTFunction, props: { interpolationType: dataPresentation?.interpolationType ?? InterpolationType.LINEAR, ...dataPresentation, }, }); const projection = resolvePlanarRenderPathProjection({ ctx, dataId, rendering, viewState: camera, }); if (projection) { applyPlanarImageActorTransforms(ctx, rendering, projection.resolvedICamera); if (projection.isSourceBinding) { applyPlanarICameraToRenderer({ renderer: ctx.vtk.renderer, activeSourceICamera: projection.resolvedICamera, }); } } if (projection?.isSourceBinding) { triggerPlanarNewImage(ctx, { image, imageIdIndex }); } ctx.display.requestRender(); } function canReuseImageDataForImage(imageData, image) { const scalars = imageData.getPointData?.()?.getScalars?.(); if (!scalars) { return false; } const [xVoxels, yVoxels] = imageData.getDimensions(); if (xVoxels !== image.columns || yVoxels !== image.rows) { return false; } if (scalars.getDataType() !== image.voxelManager.getScalarData().constructor.name) { return false; } const incomingComponents = image.color ? 3 : 1; return scalars.getNumberOfComponents() === incomingComponents; } function applyPlanarImageActorTransforms(ctx, rendering, activeSourceICamera) { applyPlanarICameraToActor({ actor: rendering.actor, activeSourceICamera, }); }