@cornerstonejs/core
Version:
Cornerstone3D Core
293 lines (292 loc) • 11.4 kB
JavaScript
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,
});
}