@cornerstonejs/core
Version:
Cornerstone3D Core
1,169 lines • 88.3 kB
JavaScript
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData.js';
import extendedVtkCamera from './vtkClasses/extendedVtkCamera.js';
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction.js';
import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper.js';
import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice.js';
import { mat4, vec2, vec3 } from 'gl-matrix';
import eventTarget from '../eventTarget.js';
import * as metaData from '../metaData.js';
import { getImageDataMetadata as getImageDataMetadataUtil } from '../utilities/getImageDataMetadata.js';
import { coreLog } from '../utilities/logger.js';
import { getGenericViewportImageDisplaySet } from './GenericViewport/genericViewportDisplaySetAccess.js';
import { actorIsA, isImageActor } from '../utilities/actorCheck.js';
import * as colormapUtils from '../utilities/colormap.js';
import { getTransferFunctionNodes, setTransferFunctionNodes, } from '../utilities/transferFunctionUtils.js';
import * as windowLevelUtil from '../utilities/windowLevel.js';
import createLinearRGBTransferFunction from '../utilities/createLinearRGBTransferFunction.js';
import createSigmoidRGBTransferFunction from '../utilities/createSigmoidRGBTransferFunction.js';
import { updateVTKImageDataWithCornerstoneImage } from '../utilities/updateVTKImageDataWithCornerstoneImage.js';
import triggerEvent from '../utilities/triggerEvent.js';
import { isEqual } from '../utilities/isEqual.js';
import invertRgbTransferFunction from '../utilities/invertRgbTransferFunction.js';
import imageRetrieveMetadataProvider from '../utilities/imageRetrieveMetadataProvider.js';
import imageIdToURI from '../utilities/imageIdToURI.js';
import getVOIRangeFromWindowLevel from '../utilities/getVOIRangeFromWindowLevel.js';
import Viewport from './Viewport.js';
import drawImageSync from './helpers/cpuFallback/drawImageSync.js';
import { resolveCPUFallbackColormap } from './helpers/cpuFallback/colors/index.js';
import { getImagePlaneModule } from '../utilities/buildMetadata.js';
import { createEmptyVTKImageData } from './helpers/planarImageRendering.js';
import { Events, InterpolationType, MetadataModules, RequestType, VOILUTFunctionType, ViewportStatus, } from '../enums/index.js';
import { loadAndCacheImage } from '../loaders/imageLoader.js';
import imageLoadPoolManager from '../requestPool/imageLoadPoolManager.js';
import calculateTransform from './helpers/cpuFallback/rendering/calculateTransform.js';
import canvasToPixel from './helpers/cpuFallback/rendering/canvasToPixel.js';
import getDefaultViewport from './helpers/cpuFallback/rendering/getDefaultViewport.js';
import pixelToCanvas from './helpers/cpuFallback/rendering/pixelToCanvas.js';
import { cssToDevicePixels, deviceToCssPixels, } from './helpers/cpuFallback/rendering/cssPixelConversion.js';
import resize from './helpers/cpuFallback/rendering/resize.js';
import cache from '../cache/cache.js';
import { getShouldUseCPURendering } from '../init.js';
import { createProgressive } from '../loaders/ProgressiveRetrieveImages.js';
import correctShift from './helpers/cpuFallback/rendering/correctShift.js';
import resetCamera from './helpers/cpuFallback/rendering/resetCamera.js';
import { Transform } from './helpers/cpuFallback/rendering/transform.js';
import uuidv4 from '../utilities/uuidv4.js';
import getSpacingInNormalDirection from '../utilities/getSpacingInNormalDirection.js';
import getClosestImageId from '../utilities/getClosestImageId.js';
import { adjustInitialViewUp } from '../utilities/adjustInitialViewUp.js';
import { isContextPoolRenderingEngine } from './helpers/isContextPoolRenderingEngine.js';
import { createSharpeningRenderPass, createSmoothingRenderPass, } from './renderPasses/index.js';
const log = coreLog.getLogger('RenderingEngine', 'StackViewport');
class StackViewport extends Viewport {
constructor(props) {
super(props);
this.imageIds = [];
this.imageKeyToIndexMap = new Map();
this.currentImageIdIndex = 0;
this.targetImageIdIndex = 0;
this.imagesLoader = this;
this.globalDefaultProperties = {};
this.perImageIdDefaultProperties = new Map();
this.voiUpdatedWithSetProperties = false;
this.sharpening = 0;
this.smoothing = 0;
this.invert = false;
this.initialInvert = false;
this.initialTransferFunctionNodes = null;
this.stackInvalidated = false;
this._publishCalibratedEvent = false;
this.updateRenderingPipeline = () => {
this._configureRenderingPipeline();
};
this.setSharpening = (sharpening) => {
this.sharpening = sharpening;
this.render();
};
this.setSmoothing = (smoothing) => {
this.smoothing = smoothing;
this.render();
};
this.getRenderPasses = () => {
if (!this.shouldUseCustomRenderPass()) {
return null;
}
const renderPasses = [];
try {
if (this.smoothing > 0) {
renderPasses.push(createSmoothingRenderPass(this.smoothing));
}
if (this.sharpening > 0) {
renderPasses.push(createSharpeningRenderPass(this.sharpening));
}
return renderPasses.length ? renderPasses : null;
}
catch (e) {
console.warn('Failed to create custom render passes:', e);
return null;
}
};
this.resize = () => {
if (this.useCPURendering) {
this._resizeCPU();
}
};
this._resizeCPU = () => {
if (this._cpuFallbackEnabledElement.viewport) {
resize(this._cpuFallbackEnabledElement);
}
};
this.getFrameOfReferenceUID = (sliceIndex) => this.getImagePlaneReferenceData(sliceIndex)?.FrameOfReferenceUID;
this.getCornerstoneImage = () => this.csImage;
this.createActorMapper = (imageData) => {
const mapper = vtkImageMapper.newInstance();
mapper.setInputData(imageData);
const actor = vtkImageSlice.newInstance();
actor.setMapper(mapper);
if (imageData.getPointData().getScalars().getNumberOfComponents() > 1) {
actor.getProperty().setIndependentComponents(false);
}
return actor;
};
this.getNumberOfSlices = () => {
return this.imageIds.length;
};
this.getDefaultProperties = (imageId) => {
let imageProperties;
if (imageId !== undefined) {
imageProperties = this.perImageIdDefaultProperties.get(imageId);
}
if (imageProperties !== undefined) {
return imageProperties;
}
return {
...this.globalDefaultProperties,
};
};
this.getProperties = () => {
const { colormap, voiRange, VOILUTFunction, interpolationType, invert, voiUpdatedWithSetProperties, } = this;
return {
colormap,
voiRange,
VOILUTFunction,
interpolationType,
invert,
isComputedVOI: !voiUpdatedWithSetProperties,
sharpening: this.sharpening,
smoothing: this.smoothing,
};
};
this.resetCameraForResize = () => {
return this.resetCamera({
resetPan: true,
resetZoom: true,
resetToCenter: true,
resetAspectRatio: true,
suppressEvents: true,
});
};
this.getRotationCPU = () => {
const { viewport } = this._cpuFallbackEnabledElement;
return viewport.rotation;
};
this.getRotationGPU = () => {
const { viewUp: currentViewUp, viewPlaneNormal, flipVertical, flipHorizontal, } = this.getCameraNoRotation();
const adjustedViewUp = adjustInitialViewUp(this.initialViewUp, flipHorizontal, flipVertical, viewPlaneNormal);
const angleRad = vec3.angle(adjustedViewUp, currentViewUp);
const initialToCurrentViewUpAngle = (angleRad * 180) / Math.PI;
const initialToCurrentViewUpCross = vec3.cross(vec3.create(), adjustedViewUp, currentViewUp);
const normalDot = vec3.dot(initialToCurrentViewUpCross, viewPlaneNormal);
return normalDot >= 0
? initialToCurrentViewUpAngle
: (360 - initialToCurrentViewUpAngle) % 360;
};
this.setRotation = (rotation) => {
const previousCamera = this.getCamera();
if (this.useCPURendering) {
this.setRotationCPU(rotation);
}
else {
this.setRotationGPU(rotation);
}
if (this._suppressCameraModifiedEvents) {
return;
}
const camera = this.getCamera();
const eventDetail = {
previousCamera,
camera,
element: this.element,
viewportId: this.id,
renderingEngineId: this.renderingEngineId,
};
triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail);
};
this.renderImageObject = (image) => {
this._setCSImage(image);
const renderFn = this.useCPURendering
? this._updateToDisplayImageCPU
: this._updateActorToDisplayImageId;
renderFn.call(this, image);
};
this._setCSImage = (image) => {
image.isPreScaled = image.preScale?.scaled;
this.csImage = image;
};
this.canvasToWorldCPU = (canvasPos, worldPos = [0, 0, 0]) => {
if (!this._cpuFallbackEnabledElement.image) {
return;
}
const [px, py] = canvasToPixel(this._cpuFallbackEnabledElement, cssToDevicePixels(canvasPos));
const { origin, spacing, direction } = this.getImageData();
const iVector = direction.slice(0, 3);
const jVector = direction.slice(3, 6);
vec3.scaleAndAdd(worldPos, origin, iVector, px * spacing[0]);
vec3.scaleAndAdd(worldPos, worldPos, jVector, py * spacing[1]);
return worldPos;
};
this.worldToCanvasCPU = (worldPos) => {
const { spacing, direction, origin } = this.getImageData();
const iVector = direction.slice(0, 3);
const jVector = direction.slice(3, 6);
const diff = vec3.subtract(vec3.create(), worldPos, origin);
const indexPoint = [
vec3.dot(diff, iVector) / spacing[0],
vec3.dot(diff, jVector) / spacing[1],
];
const canvasPoint = pixelToCanvas(this._cpuFallbackEnabledElement, indexPoint);
return deviceToCssPixels(canvasPoint);
};
this.canvasToWorldGPUContextPool = (canvasPos) => {
const renderer = this.getRenderer();
const vtkCamera = this.getVtkActiveCamera();
const crange = vtkCamera.getClippingRange();
const distance = vtkCamera.getDistance();
vtkCamera.setClippingRange(distance, distance + 0.1);
const devicePixelRatio = window.devicePixelRatio || 1;
const { width, height } = this.canvas;
const aspectRatio = width / height;
const canvasPosWithDPR = [
canvasPos[0] * devicePixelRatio,
canvasPos[1] * devicePixelRatio,
];
const viewport = renderer.getViewport();
const [xStart, yStart, xEnd, yEnd] = viewport;
const viewportWidth = xEnd - xStart;
const viewportHeight = yEnd - yStart;
const normalizedDisplay = [
xStart + (canvasPosWithDPR[0] / width) * viewportWidth,
yStart + (1 - canvasPosWithDPR[1] / height) * viewportHeight,
0,
];
const projCoords = renderer.normalizedDisplayToProjection(normalizedDisplay[0], normalizedDisplay[1], normalizedDisplay[2]);
const viewCoords = renderer.projectionToView(projCoords[0], projCoords[1], projCoords[2], aspectRatio);
const worldCoord = renderer.viewToWorld(viewCoords[0], viewCoords[1], viewCoords[2]);
vtkCamera.setClippingRange(crange[0], crange[1]);
return [worldCoord[0], worldCoord[1], worldCoord[2]];
};
this.canvasToWorldGPUTiled = (canvasPos) => {
const renderer = this.getRenderer();
const vtkCamera = this.getVtkActiveCamera();
const crange = vtkCamera.getClippingRange();
const distance = vtkCamera.getDistance();
vtkCamera.setClippingRange(distance, distance + 0.1);
const offscreenMultiRenderWindow = this.getRenderingEngine().offscreenMultiRenderWindow;
const openGLRenderWindow = offscreenMultiRenderWindow.getOpenGLRenderWindow();
const size = openGLRenderWindow.getSize();
const devicePixelRatio = window.devicePixelRatio || 1;
const canvasPosWithDPR = [
canvasPos[0] * devicePixelRatio,
canvasPos[1] * devicePixelRatio,
];
const displayCoord = [
canvasPosWithDPR[0] + this.sx,
canvasPosWithDPR[1] + this.sy,
];
displayCoord[1] = size[1] - displayCoord[1];
const worldCoord = openGLRenderWindow.displayToWorld(displayCoord[0], displayCoord[1], 0, renderer);
vtkCamera.setClippingRange(crange[0], crange[1]);
return [worldCoord[0], worldCoord[1], worldCoord[2]];
};
this.worldToCanvasGPUContextPool = (worldPos) => {
const renderer = this.getRenderer();
const vtkCamera = this.getVtkActiveCamera();
const crange = vtkCamera.getClippingRange();
const distance = vtkCamera.getDistance();
vtkCamera.setClippingRange(distance, distance + 0.1);
const devicePixelRatio = window.devicePixelRatio || 1;
const { width, height } = this.canvas;
const aspectRatio = width / height;
const viewCoords = renderer.worldToView(worldPos[0], worldPos[1], worldPos[2]);
const projCoords = renderer.viewToProjection(viewCoords[0], viewCoords[1], viewCoords[2], aspectRatio);
const normalizedDisplay = renderer.projectionToNormalizedDisplay(projCoords[0], projCoords[1], projCoords[2]);
const viewport = renderer.getViewport();
const [xStart, yStart, xEnd, yEnd] = viewport;
const viewportWidth = xEnd - xStart;
const viewportHeight = yEnd - yStart;
const canvasX = ((normalizedDisplay[0] - xStart) / viewportWidth) * width;
const canvasY = (1 - (normalizedDisplay[1] - yStart) / viewportHeight) * height;
vtkCamera.setClippingRange(crange[0], crange[1]);
const canvasCoordWithDPR = [
canvasX / devicePixelRatio,
canvasY / devicePixelRatio,
];
return canvasCoordWithDPR;
};
this.worldToCanvasGPUTiled = (worldPos) => {
const renderer = this.getRenderer();
const vtkCamera = this.getVtkActiveCamera();
const crange = vtkCamera.getClippingRange();
const distance = vtkCamera.getDistance();
vtkCamera.setClippingRange(distance, distance + 0.1);
const offscreenMultiRenderWindow = this.getRenderingEngine().offscreenMultiRenderWindow;
const openGLRenderWindow = offscreenMultiRenderWindow.getOpenGLRenderWindow();
const size = openGLRenderWindow.getSize();
const displayCoord = openGLRenderWindow.worldToDisplay(...worldPos, renderer);
displayCoord[1] = size[1] - displayCoord[1];
const canvasCoord = [
displayCoord[0] - this.sx,
displayCoord[1] - this.sy,
];
vtkCamera.setClippingRange(crange[0], crange[1]);
const devicePixelRatio = window.devicePixelRatio || 1;
const canvasCoordWithDPR = [
canvasCoord[0] / devicePixelRatio,
canvasCoord[1] / devicePixelRatio,
];
return canvasCoordWithDPR;
};
this.getCurrentImageIdIndex = () => {
return this.currentImageIdIndex;
};
this.getSliceIndex = () => {
return this.currentImageIdIndex;
};
this.getTargetImageIdIndex = () => {
return this.targetImageIdIndex;
};
this.getImageIds = () => {
return this.imageIds;
};
this.getCurrentImageId = (index = this.getCurrentImageIdIndex()) => {
return this.imageIds[index];
};
this.hasImageId = (imageId) => {
return this.imageKeyToIndexMap.has(imageId);
};
this.hasImageURI = (imageURI) => {
return this.imageKeyToIndexMap.has(imageURI);
};
this.customRenderViewportToCanvas = () => {
if (!this.useCPURendering) {
throw new Error('Custom cpu rendering pipeline should only be hit in CPU rendering mode');
}
if (this._cpuFallbackEnabledElement.image) {
drawImageSync(this._cpuFallbackEnabledElement, this.cpuRenderingInvalidated);
this.cpuRenderingInvalidated = false;
}
else {
this.fillWithBackgroundColor();
}
return {
canvas: this.canvas,
element: this.element,
viewportId: this.id,
renderingEngineId: this.renderingEngineId,
viewportStatus: this.viewportStatus,
};
};
this.renderingPipelineFunctions = {
getImageData: {
cpu: this.getImageDataCPU,
gpu: this.getImageDataGPU,
},
setColormap: {
cpu: this.setColormapCPU,
gpu: this.setColormapGPU,
},
getCamera: {
cpu: this.getCameraCPU,
gpu: super.getCamera,
},
setCamera: {
cpu: this.setCameraCPU,
gpu: super.setCamera,
},
getPan: {
cpu: this.getPanCPU,
gpu: super.getPan,
},
setPan: {
cpu: this.setPanCPU,
gpu: super.setPan,
},
getZoom: {
cpu: this.getZoomCPU,
gpu: super.getZoom,
},
setZoom: {
cpu: this.setZoomCPU,
gpu: super.setZoom,
},
getAspectRatio: {
cpu: this.getAspectRatioCPU,
gpu: super.getAspectRatio,
},
setAspectRatio: {
cpu: this.setAspectRatioCPU,
gpu: super.setAspectRatio,
},
setVOI: {
cpu: this.setVOICPU,
gpu: this.setVOIGPU,
},
getRotation: {
cpu: this.getRotationCPU,
gpu: this.getRotationGPU,
},
setInterpolationType: {
cpu: this.setInterpolationTypeCPU,
gpu: this.setInterpolationTypeGPU,
},
setInvertColor: {
cpu: this.setInvertColorCPU,
gpu: this.setInvertColorGPU,
},
resetCamera: {
cpu: (options = {}) => {
const { resetPan = true, resetZoom = true } = options;
this.resetCameraCPU({ resetPan, resetZoom });
return true;
},
gpu: (options = {}) => {
const { resetPan = true, resetZoom = true, resetAspectRatio = true, } = options;
this.resetCameraGPU({ resetPan, resetZoom, resetAspectRatio });
return true;
},
},
canvasToWorld: {
cpu: this.canvasToWorldCPU,
gpu: {
tiled: this.canvasToWorldGPUTiled,
contextPool: this.canvasToWorldGPUContextPool,
},
},
worldToCanvas: {
cpu: this.worldToCanvasCPU,
gpu: {
tiled: this.worldToCanvasGPUTiled,
contextPool: this.worldToCanvasGPUContextPool,
},
},
getRenderer: {
cpu: () => this.getCPUFallbackError('getRenderer'),
gpu: {
tiled: this.getRendererTiled,
contextPool: this.getRendererContextPool,
},
},
getDefaultActor: {
cpu: () => this.getCPUFallbackError('getDefaultActor'),
gpu: super.getDefaultActor,
},
getActors: {
cpu: () => this.getCPUFallbackError('getActors'),
gpu: super.getActors,
},
getActor: {
cpu: () => this.getCPUFallbackError('getActor'),
gpu: super.getActor,
},
setActors: {
cpu: () => this.getCPUFallbackError('setActors'),
gpu: super.setActors,
},
addActors: {
cpu: () => this.getCPUFallbackError('addActors'),
gpu: super.addActors,
},
addActor: {
cpu: () => this.getCPUFallbackError('addActor'),
gpu: super.addActor,
},
removeAllActors: {
cpu: () => this.getCPUFallbackError('removeAllActors'),
gpu: super.removeAllActors,
},
unsetColormap: {
cpu: this.unsetColormapCPU,
gpu: this.unsetColormapGPU,
},
};
this.scaling = {};
this.modality = null;
this.useCPURendering = getShouldUseCPURendering();
this._configureRenderingPipeline();
if (this.useCPURendering) {
this._resetCPUFallbackElement();
}
else {
this._resetGPUViewport();
}
this.currentImageIdIndex = 0;
this.targetImageIdIndex = 0;
this.resetCamera();
this.initializeElementDisabledHandler();
}
setUseCPURendering(value) {
this.useCPURendering = value;
this._configureRenderingPipeline(value);
}
static get useCustomRenderingPipeline() {
return getShouldUseCPURendering();
}
_configureRenderingPipeline(value) {
const isContextPool = isContextPoolRenderingEngine();
this.useCPURendering = value ?? getShouldUseCPURendering();
for (const key in this.renderingPipelineFunctions) {
if (Object.prototype.hasOwnProperty.call(this.renderingPipelineFunctions, key)) {
const functions = this.renderingPipelineFunctions[key];
if (this.useCPURendering) {
this[key] = functions.cpu;
}
else {
if (typeof functions.gpu === 'object' &&
functions.gpu.tiled &&
functions.gpu.contextPool) {
this[key] = isContextPool
? functions.gpu.contextPool
: functions.gpu.tiled;
}
else {
this[key] = functions.gpu;
}
}
}
}
if (this.useCPURendering) {
this._resetCPUFallbackElement();
}
else {
this._resetGPUViewport();
}
}
_resetCPUFallbackElement() {
this._cpuFallbackEnabledElement = {
canvas: this.canvas,
renderingTools: {},
transform: new Transform(),
viewport: { rotation: 0, aspectRatio: [1, 1] },
};
}
_resetGPUViewport() {
const renderer = this.getRenderer();
const camera = extendedVtkCamera.newInstance();
renderer.setActiveCamera(camera);
const viewPlaneNormal = [0, 0, -1];
this.initialViewUp = [0, -1, 0];
camera.setDirectionOfProjection(-viewPlaneNormal[0], -viewPlaneNormal[1], -viewPlaneNormal[2]);
camera.setViewUp(...this.initialViewUp);
camera.setParallelProjection(true);
camera.setThicknessFromFocalPoint(0.1);
camera.setFreezeFocalPoint(true);
}
shouldUseCustomRenderPass() {
return !this.useCPURendering;
}
initializeElementDisabledHandler() {
eventTarget.addEventListener(Events.ELEMENT_DISABLED, function elementDisabledHandler() {
clearTimeout(this.debouncedTimeout);
eventTarget.removeEventListener(Events.ELEMENT_DISABLED, elementDisabledHandler);
});
}
getImageDataGPU() {
const defaultActor = this.getDefaultActor();
if (!defaultActor) {
return;
}
if (!isImageActor(defaultActor)) {
return;
}
const { actor } = defaultActor;
const vtkImageData = actor.getMapper().getInputData();
const csImage = this.csImage;
return {
dimensions: vtkImageData.getDimensions(),
spacing: vtkImageData.getSpacing(),
origin: vtkImageData.getOrigin(),
direction: vtkImageData.getDirection(),
get scalarData() {
return csImage?.voxelManager.getScalarData();
},
imageData: actor.getMapper().getInputData(),
metadata: {
Modality: this.modality,
FrameOfReferenceUID: this.getFrameOfReferenceUID(),
},
scaling: this.scaling,
hasPixelSpacing: this.hasPixelSpacing,
calibration: { ...csImage?.calibration, ...this.calibration },
preScale: {
...csImage?.preScale,
},
voxelManager: csImage?.voxelManager,
};
}
getImageDataCPU() {
const { metadata } = this._cpuFallbackEnabledElement;
if (!metadata) {
return;
}
const spacing = metadata.spacing;
const csImage = this.csImage;
return {
dimensions: metadata.dimensions,
spacing,
origin: metadata.origin,
direction: metadata.direction,
metadata: {
Modality: this.modality,
FrameOfReferenceUID: this.getFrameOfReferenceUID(),
},
scaling: this.scaling,
imageData: {
getDirection: () => metadata.direction,
getDimensions: () => metadata.dimensions,
getScalarData: () => this.cpuImagePixelData,
getSpacing: () => spacing,
worldToIndex: (point) => {
const canvasPoint = this.worldToCanvasCPU(point);
const pixelCoord = canvasToPixel(this._cpuFallbackEnabledElement, cssToDevicePixels(canvasPoint));
return [pixelCoord[0], pixelCoord[1], 0];
},
indexToWorld: (point, destPoint) => {
const canvasPoint = pixelToCanvas(this._cpuFallbackEnabledElement, [
point[0],
point[1],
]);
return this.canvasToWorldCPU(deviceToCssPixels(canvasPoint), destPoint);
},
},
scalarData: this.cpuImagePixelData,
hasPixelSpacing: this.hasPixelSpacing,
calibration: { ...csImage?.calibration, ...this.calibration },
preScale: {
...csImage?.preScale,
},
voxelManager: csImage?.voxelManager,
};
}
calibrateIfNecessary(imageId, imagePlaneModule) {
const calibration = metaData.get('calibratedPixelSpacing', imageId);
const isUpdated = this.calibration !== calibration;
const scale = calibration?.scale;
this.hasPixelSpacing =
scale > 0 ||
(!imagePlaneModule.usingDefaultValues &&
imagePlaneModule.rowPixelSpacing > 0);
imagePlaneModule.calibration = calibration;
if (!isUpdated) {
return imagePlaneModule;
}
this.calibration = calibration;
this._publishCalibratedEvent = true;
this._calibrationEvent = {
scale,
calibration,
};
return imagePlaneModule;
}
setDefaultProperties(ViewportProperties, imageId) {
if (imageId == null) {
this.globalDefaultProperties = ViewportProperties;
}
else {
this.perImageIdDefaultProperties.set(imageId, ViewportProperties);
if (this.getCurrentImageId() === imageId) {
this.setProperties(ViewportProperties);
}
}
}
clearDefaultProperties(imageId) {
if (imageId == null) {
this.globalDefaultProperties = {};
this.resetProperties();
}
else {
this.perImageIdDefaultProperties.delete(imageId);
this.resetToDefaultProperties();
}
}
setProperties({ colormap, voiRange, VOILUTFunction, invert, interpolationType, sharpening, smoothing, } = {}, suppressEvents = false) {
this.viewportStatus = this.csImage
? ViewportStatus.PRE_RENDER
: ViewportStatus.LOADING;
this.globalDefaultProperties = {
colormap: this.globalDefaultProperties.colormap ?? colormap,
voiRange: this.globalDefaultProperties.voiRange ?? voiRange,
VOILUTFunction: this.globalDefaultProperties.VOILUTFunction ?? VOILUTFunction,
invert: this.globalDefaultProperties.invert ?? invert,
interpolationType: this.globalDefaultProperties.interpolationType ?? interpolationType,
sharpening: this.globalDefaultProperties.sharpening ?? sharpening,
smoothing: this.globalDefaultProperties.smoothing ?? smoothing,
};
if (typeof colormap !== 'undefined') {
this.setColormap(colormap);
}
if (typeof voiRange !== 'undefined') {
const voiUpdatedWithSetProperties = true;
this.setVOI(voiRange, { suppressEvents, voiUpdatedWithSetProperties });
}
if (typeof VOILUTFunction !== 'undefined') {
this.setVOILUTFunction(VOILUTFunction, suppressEvents);
}
if (typeof invert !== 'undefined') {
this.setInvertColor(invert);
}
if (typeof interpolationType !== 'undefined') {
this.setInterpolationType(interpolationType);
}
if (typeof sharpening !== 'undefined') {
this.setSharpening(sharpening);
}
if (typeof smoothing !== 'undefined') {
this.setSmoothing(smoothing);
}
}
resetProperties() {
this.cpuRenderingInvalidated = true;
this.voiUpdatedWithSetProperties = false;
this.viewportStatus = ViewportStatus.PRE_RENDER;
this.fillWithBackgroundColor();
if (this.useCPURendering) {
this._cpuFallbackEnabledElement.renderingTools = {};
}
this._resetProperties();
this.render();
}
_resetProperties() {
let voiRange;
if (this._isCurrentImagePTPrescaled()) {
voiRange = this._getDefaultPTPrescaledVOIRange();
}
else {
voiRange = this._getVOIRangeForCurrentImage();
}
this.setVOI(voiRange);
this.setInvertColor(this.initialInvert);
this.setInterpolationType(InterpolationType.LINEAR);
if (!this.useCPURendering) {
const transferFunction = this.getTransferFunction();
setTransferFunctionNodes(transferFunction, this.initialTransferFunctionNodes);
const nodes = getTransferFunctionNodes(transferFunction);
const RGBPoints = nodes.reduce((acc, node) => {
acc.push(node[0], node[1], node[2], node[3]);
return acc;
}, []);
const defaultActor = this.getDefaultActor();
const matchedColormap = colormapUtils.findMatchingColormap(RGBPoints, defaultActor.actor);
this.setColormap(matchedColormap);
}
}
resetToDefaultProperties() {
this.cpuRenderingInvalidated = true;
this.viewportStatus = ViewportStatus.PRE_RENDER;
this.fillWithBackgroundColor();
if (this.useCPURendering) {
this._cpuFallbackEnabledElement.renderingTools = {};
}
const currentImageId = this.getCurrentImageId();
const properties = this.perImageIdDefaultProperties.get(currentImageId) ||
this.globalDefaultProperties;
if (properties.colormap?.name) {
this.setColormap(properties.colormap);
}
let voiRange;
if (properties.voiRange == undefined) {
voiRange = this._getVOIRangeForCurrentImage();
}
else {
voiRange = properties.voiRange;
}
this.setVOI(voiRange);
this.setInterpolationType(InterpolationType.LINEAR);
this.setInvertColor(false);
this.render();
}
_getVOIFromCache() {
let voiRange;
if (this.voiUpdatedWithSetProperties) {
voiRange = this.voiRange;
}
else if (this._isCurrentImagePTPrescaled()) {
voiRange = this._getDefaultPTPrescaledVOIRange();
}
else {
voiRange = this._getVOIRangeForCurrentImage() ?? this.voiRange;
}
return voiRange;
}
_setPropertiesFromCache() {
const voiRange = this._getVOIFromCache();
const { colormap, VOILUTFunction, interpolationType, invert, sharpening, smoothing, } = this.getProperties();
if (typeof VOILUTFunction !== 'undefined') {
this.setVOILUTFunction(VOILUTFunction, true);
}
this.setVOI(voiRange);
if (typeof colormap !== 'undefined') {
this.setColormap(colormap);
this.invert = false;
}
this.setInterpolationType(interpolationType);
this.setInvertColor(invert);
if (typeof sharpening !== 'undefined') {
this.setSharpening(sharpening);
}
if (typeof smoothing !== 'undefined') {
this.setSmoothing(smoothing);
}
}
getCameraCPU() {
const { metadata, viewport } = this._cpuFallbackEnabledElement;
if (!metadata) {
return {};
}
const { direction } = metadata;
const viewPlaneNormal = direction.slice(6, 9).map((x) => -x);
let viewUp = direction.slice(3, 6).map((x) => -x);
if (viewport.rotation) {
const rotationMatrix = mat4.fromRotation(mat4.create(), (viewport.rotation * Math.PI) / 180, viewPlaneNormal);
viewUp = vec3.transformMat4(vec3.create(), viewUp, rotationMatrix);
}
const canvasCenter = [
this.element.clientWidth / 2,
this.element.clientHeight / 2,
];
const canvasCenterWorld = this.canvasToWorld(canvasCenter);
const topLeftWorld = this.canvasToWorld([0, 0]);
const bottomLeftWorld = this.canvasToWorld([0, this.element.clientHeight]);
const parallelScale = vec3.distance(topLeftWorld, bottomLeftWorld) / 2;
return {
parallelProjection: true,
focalPoint: canvasCenterWorld,
position: [0, 0, 0],
parallelScale,
scale: viewport.scale,
viewPlaneNormal: [
viewPlaneNormal[0],
viewPlaneNormal[1],
viewPlaneNormal[2],
],
aspectRatio: viewport.aspectRatio ?? [1, 1],
viewUp: [viewUp[0], viewUp[1], viewUp[2]],
flipHorizontal: this.flipHorizontal,
flipVertical: this.flipVertical,
};
}
setAspectRatioForViewport(aspectRatio, isFitViewportAfterStretch = true) {
const { viewport, image } = this._cpuFallbackEnabledElement;
if (!isFitViewportAfterStretch) {
viewport.aspectRatio = aspectRatio;
return;
}
const { clientWidth, clientHeight } = this.element;
const { rowPixelSpacing, columnPixelSpacing, width, height } = image;
const getRatioValue = ([x, y]) => x / y;
const oldRatioValue = getRatioValue(viewport.aspectRatio || [1, 1]);
const newRatioValue = getRatioValue(aspectRatio);
const canvasRatio = clientWidth / clientHeight;
const baseHeight = clientHeight * rowPixelSpacing * 0.5;
const calculateFitParallelScale = (rVal) => {
const effectiveWidth = width * columnPixelSpacing * rVal;
const effectiveHeight = height * rowPixelSpacing;
if (effectiveWidth / effectiveHeight > canvasRatio) {
return baseHeight / (clientWidth / effectiveWidth);
}
const fitPScale = effectiveHeight * 0.5;
return rVal < 1 ? fitPScale / rVal : fitPScale;
};
const ratioFactor = calculateFitParallelScale(newRatioValue) /
calculateFitParallelScale(oldRatioValue);
viewport.aspectRatio = aspectRatio;
if (viewport.parallelScale) {
viewport.parallelScale *= ratioFactor;
viewport.scale = baseHeight / viewport.parallelScale;
}
}
setCameraCPU(cameraInterface) {
const { viewport, image } = this._cpuFallbackEnabledElement;
const previousCamera = this.getCameraCPU();
const { focalPoint, parallelScale, scale, flipHorizontal, flipVertical, aspectRatio, isFitViewportAfterStretch, } = cameraInterface;
const { clientHeight } = this.element;
if (focalPoint) {
const focalPointCanvas = this.worldToCanvasCPU(focalPoint);
const focalPointPixel = canvasToPixel(this._cpuFallbackEnabledElement, cssToDevicePixels(focalPointCanvas));
const prevFocalPointCanvas = this.worldToCanvasCPU(previousCamera.focalPoint);
const prevFocalPointPixel = canvasToPixel(this._cpuFallbackEnabledElement, cssToDevicePixels(prevFocalPointCanvas));
const deltaPixel = vec2.create();
vec2.subtract(deltaPixel, vec2.fromValues(focalPointPixel[0], focalPointPixel[1]), vec2.fromValues(prevFocalPointPixel[0], prevFocalPointPixel[1]));
const shift = correctShift({ x: deltaPixel[0], y: deltaPixel[1] }, viewport);
viewport.translation.x -= shift.x;
viewport.translation.y -= shift.y;
}
if (parallelScale) {
const { rowPixelSpacing } = image;
const scale = (clientHeight * rowPixelSpacing * 0.5) / parallelScale;
viewport.scale = scale;
viewport.parallelScale = parallelScale;
}
if (scale) {
const { rowPixelSpacing } = image;
viewport.scale = scale;
viewport.parallelScale = (clientHeight * rowPixelSpacing * 0.5) / scale;
}
if (aspectRatio) {
this.setAspectRatioForViewport(aspectRatio, isFitViewportAfterStretch);
}
if (flipHorizontal !== undefined || flipVertical !== undefined) {
this.setFlipCPU({ flipHorizontal, flipVertical });
}
this._cpuFallbackEnabledElement.transform = calculateTransform(this._cpuFallbackEnabledElement);
const eventDetail = {
previousCamera,
camera: this.getCamera(),
element: this.element,
viewportId: this.id,
renderingEngineId: this.renderingEngineId,
};
triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail);
}
getPanCPU() {
const { viewport } = this._cpuFallbackEnabledElement;
return [viewport.translation?.x ?? 0, viewport.translation?.y ?? 0];
}
setPanCPU(pan) {
const camera = this.getCameraCPU();
this.setCameraCPU({
...camera,
focalPoint: [...pan.map((p) => -p), 0],
});
}
getZoomCPU() {
const { viewport } = this._cpuFallbackEnabledElement;
return viewport.scale;
}
setZoomCPU(zoom) {
const camera = this.getCameraCPU();
this.setCameraCPU({ ...camera, scale: zoom });
}
getAspectRatioCPU() {
const { aspectRatio } = this.getCameraCPU();
return aspectRatio ?? this.options?.aspectRatio ?? [1, 1];
}
setAspectRatioCPU(value, isFitViewportAfterStretch = true, storeAsInitialCamera = false) {
const camera = this.getCameraCPU();
if (storeAsInitialCamera) {
this.options.aspectRatio = value;
}
this.setCameraCPU({
...camera,
aspectRatio: value,
isFitViewportAfterStretch,
});
}
setFlipCPU({ flipHorizontal, flipVertical }) {
const { viewport } = this._cpuFallbackEnabledElement;
if (flipHorizontal !== undefined) {
viewport.hflip = flipHorizontal;
this.flipHorizontal = viewport.hflip;
}
if (flipVertical !== undefined) {
viewport.vflip = flipVertical;
this.flipVertical = viewport.vflip;
}
}
setVOILUTFunction(voiLUTFunction, suppressEvents) {
if (this.useCPURendering) {
throw new Error('VOI LUT function is not supported in CPU rendering');
}
const newVOILUTFunction = this._getValidVOILUTFunction(voiLUTFunction);
let forceRecreateLUTFunction = false;
if (this.VOILUTFunction !== newVOILUTFunction) {
forceRecreateLUTFunction = true;
}
this.VOILUTFunction = newVOILUTFunction;
const { voiRange } = this.getProperties();
this.setVOI(voiRange, { suppressEvents, forceRecreateLUTFunction });
}
setRotationCPU(rotation) {
const { viewport } = this._cpuFallbackEnabledElement;
viewport.rotation = rotation;
}
setRotationGPU(rotation) {
const panFit = this.getPan(this.fitToCanvasCamera);
const pan = this.getPan();
const panSub = vec2.sub([0, 0], panFit, pan);
this.setPan(panSub, false);
const { flipVertical, flipHorizontal, viewPlaneNormal } = this.getCamera();
const adjustedViewUp = adjustInitialViewUp(this.initialViewUp, flipHorizontal, flipVertical, viewPlaneNormal);
this.setCameraNoEvent({
viewUp: adjustedViewUp,
});
this.getVtkActiveCamera().roll(-rotation);
const afterPan = this.getPan();
const afterPanFit = this.getPan(this.fitToCanvasCamera);
const newCenter = vec2.sub([0, 0], afterPan, afterPanFit);
const newOffset = vec2.add([0, 0], panFit, newCenter);
this.setPan(newOffset, false);
}
setInterpolationTypeGPU(interpolationType) {
const defaultActor = this.getDefaultActor();
if (!defaultActor) {
return;
}
if (!isImageActor(defaultActor)) {
return;
}
const { actor } = defaultActor;
const volumeProperty = actor.getProperty();
volumeProperty.setInterpolationType(interpolationType);
this.interpolationType = interpolationType;
}
setInterpolationTypeCPU(interpolationType) {
const { viewport } = this._cpuFallbackEnabledElement;
viewport.pixelReplication =
interpolationType === InterpolationType.LINEAR ? false : true;
this.interpolationType = interpolationType;
}
setInvertColorCPU(invert) {
const { viewport } = this._cpuFallbackEnabledElement;
if (!viewport) {
return;
}
viewport.invert = invert;
this.invert = invert;
}
setInvertColorGPU(invert) {
const defaultActor = this.getDefaultActor();
if (!defaultActor) {
return;
}
if (!isImageActor(defaultActor)) {
return;
}
if (actorIsA(defaultActor, 'vtkVolume')) {
const volumeActor = defaultActor.actor;
const tfunc = volumeActor.getProperty().getRGBTransferFunction(0);
if ((!this.invert && invert) || (this.invert && !invert)) {
invertRgbTransferFunction(tfunc);
}
this.invert = invert;
}
else if (actorIsA(defaultActor, 'vtkImageSlice')) {
const imageSliceActor = defaultActor.actor;
const tfunc = imageSliceActor.getProperty().getRGBTransferFunction(0);
if ((!this.invert && invert) || (this.invert && !invert)) {
invertRgbTransferFunction(tfunc);
}
this.invert = invert;
}
}
setVOICPU(voiRange, options = {}) {
const { suppressEvents = false } = options;
const { viewport, image } = this._cpuFallbackEnabledElement;
if (!viewport || !image) {
return;
}
if (typeof voiRange === 'undefined') {
const { windowWidth: ww, windowCenter: wc } = image;
const wwToUse = Array.isArray(ww) ? ww[0] : ww;
const wcToUse = Array.isArray(wc) ? wc[0] : wc;
viewport.voi = {
windowWidth: wwToUse,
windowCenter: wcToUse,
voiLUTFunction: image.voiLUTFunction,
};
const { lower, upper } = getVOIRangeFromWindowLevel(wwToUse, wcToUse, image.voiLUTFunction);
voiRange = { lower, upper };
}
else {
const { lower, upper } = voiRange;
const { windowCenter, windowWidth } = windowLevelUtil.toWindowLevel(lower, upper);
if (!viewport.voi) {
viewport.voi = {
windowWidth: 0,
windowCenter: 0,
voiLUTFunction: image.voiLUTFunction,
};
}
viewport.voi.windowWidth = windowWidth;
viewport.voi.windowCenter = windowCenter;
}
this.voiRange = voiRange;
const eventDetail = {
viewportId: this.id,
range: voiRange,
};
if (!suppressEvents) {
triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail);
}
}
getTransferFunction() {
const defaultActor = this.getDefaultActor();
if (!defaultActor) {
return;
}
if (!isImageActor(defaultActor)) {
return;
}
const imageActor = defaultActor.actor;
return imageActor.getProperty().getRGBTransferFunction(0);
}
setVOIGPU(voiRange, options = {}) {
const { suppressEvents = false, forceRecreateLUTFunction = false, voiUpdatedWithSetProperties = false, } = options;
if (voiRange &&
this.voiRange &&
this.voiRange.lower === voiRange.lower &&
this.voiRange.upper === voiRange.upper &&
!forceRecreateLUTFunction &&
!this.stackInvalidated) {
return;
}
const defaultActor = this.getDefaultActor();
if (!defaultActor) {
return;
}
if (!isImageActor(defaultActor)) {
return;
}
const imageActor = defaultActor.actor;
let voiRangeToUse = voiRange;
if (typeof voiRangeToUse === 'undefined') {
const imageData = imageActor.getMapper().getInputData();
const range = imageData.getPointData().getScalars().getRange();
const maxVoiRange = { lower: range[0], upper: range[1] };
voiRangeToUse = maxVoiRange;
}
imageActor.getProperty().setUseLookupTableScalarRange(true);
let transferFunction = imageActor.getProperty().getRGBTransferFunction(0);
const isSigmoidTFun = this.VOILUTFunction === VOILUTFunctionType.SAMPLED_SIGMOID;
if (isSigmoidTFun || !transferFunction || forceRecreateLUTFunction) {
const transferFunctionCreator = isSigmoidTFun
? createSigmoidRGBTransferFunction
: createLinearRGBTransferFunction;
transferFunction = transferFunctionCreator(voiRangeToUse);
if (this.invert) {
invertRgbTransferFunction(transferFunction);
}
imageActor.getProperty().setRGBTransferFunction(0, transferFunction);
this.initialTransferFunctionNodes =
getTransferFunctionNodes(transferFunction);
}
if (!isSigmoidTFun) {
transferFunction.setRange(voiRangeToUse.lower, voiRangeToUse.upper);
}
this.voiRange = voiRangeToUse;
if (!this.voiUpdatedWithSetProperties) {
this.voiUpdatedWithSetProperties = voiUpdatedWithSetProperties;
}
if (suppressEvents) {
return;
}
const eventDetail = {
viewportId: this.id,
range: voiRangeToUse,
VOILUTFunction: this.VOILUTFunction,
};
triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail);
}
_addScalingToViewport(imageId