From 420c8121cb0cdc4c321013ca807c6ca32901d7a6 Mon Sep 17 00:00:00 2001 From: Alireza Date: Mon, 6 Mar 2023 08:19:26 -0500 Subject: [PATCH] fix(voi): stack viewport should prioritize image metadata for windowlevel and not persist (#454) * new voi setup * set voi range locked * fix tests * fix the event firing * fix coming back and forth bw voi lut functions * fix build and tests * rework the rendering pipelines * fix window level number and number array def * fix tests --- common/reviews/api/core.api.md | 30 +- .../src/RenderingEngine/BaseVolumeViewport.ts | 3 +- .../core/src/RenderingEngine/StackViewport.ts | 863 +++++++++--------- packages/core/src/RenderingEngine/Viewport.ts | 2 +- .../cpuFallback/rendering/generateColorLUT.ts | 2 +- ...ageURI_100_100_0_10_1_1_1_linear_color.png | Bin 13485 -> 14008 bytes ...geURI_100_100_0_10_1_1_1_nearest_color.png | Bin 8933 -> 9025 bytes ...imageURI_256_256_100_100_1_1_0_nearest.png | Bin 3327 -> 3294 bytes .../imageURI_256_256_50_10_1_1_0.png | Bin 2388 -> 2389 bytes .../imageURI_64_33_20_5_1_1_0_nearest.png | Bin 2614 -> 2663 bytes .../imageURI_64_64_0_10_5_5_0_nearest.png | Bin 2063 -> 2176 bytes .../imageURI_64_64_20_5_1_1_0_nearest.png | Bin 2393 -> 2450 bytes ...imageURI_64_64_20_5_1_1_0_nearestFlipH.png | Bin 3021 -> 1779 bytes ..._64_64_20_5_1_1_0_nearestFlipHRotate90.png | Bin 3066 -> 2023 bytes .../imageURI_64_64_30_10_5_5_0_nearest.png | Bin 2057 -> 2117 bytes .../imageURI_64_64_54_10_5_5_0_nearest.png | Bin 2089 -> 1764 bytes .../.webpack/webpack-dynamic-import-debug.js | 31 + .../.webpack/webpack-dynamic-import.js | 14 +- packages/dicomImageLoader/package.json | 1 + .../src/imageLoader/createImage.ts | 2 + .../StackScrollToolMouseWheelTool_test.js | 11 +- .../imageURI_64_64_0_20_1_1_0_scrolled.png | Bin 2400 -> 2460 bytes yarn.lock | 31 +- 23 files changed, 531 insertions(+), 459 deletions(-) create mode 100644 packages/dicomImageLoader/.webpack/webpack-dynamic-import-debug.js diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index e2b611698e..a162ba8bdc 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -2012,9 +2012,9 @@ type StackNewImageEventDetail = { export class StackViewport extends Viewport implements IStackViewport { constructor(props: ViewportInput); // (undocumented) - addActor(actorEntry: ActorEntry): void; + addActor: (actorEntry: ActorEntry) => void; // (undocumented) - addActors(actors: Array): void; + addActors: (actors: Array) => void; // (undocumented) calibrateSpacing(imageId: string): void; // (undocumented) @@ -2027,27 +2027,27 @@ export class StackViewport extends Viewport implements IStackViewport { renderingEngineId: string; }; // (undocumented) - getActor(actorUID: string): ActorEntry; + getActor: (actorUID: string) => ActorEntry; // (undocumented) - getActors(): Array; + getActors: () => Array; // (undocumented) - getCamera(): ICamera; + getCamera: () => ICamera; // (undocumented) getCurrentImageId: () => string; // (undocumented) getCurrentImageIdIndex: () => number; // (undocumented) - getDefaultActor(): ActorEntry; + getDefaultActor: () => ActorEntry; // (undocumented) getFrameOfReferenceUID: () => string | undefined; // (undocumented) - getImageData(): IImageData | CPUIImageData; + getImageData: () => IImageData | CPUIImageData; // (undocumented) getImageIds: () => Array; // (undocumented) getProperties: () => StackViewportProperties; // (undocumented) - getRenderer(): any; + getRenderer: () => any; // (undocumented) getRotation: () => number; // (undocumented) @@ -2059,9 +2059,9 @@ export class StackViewport extends Viewport implements IStackViewport { // (undocumented) modality: string; // (undocumented) - removeAllActors(): void; + removeAllActors: () => void; // (undocumented) - resetCamera(resetPan?: boolean, resetZoom?: boolean): boolean; + resetCamera: (resetPan?: boolean, resetZoom?: boolean) => boolean; // (undocumented) resetProperties(): void; // (undocumented) @@ -2071,11 +2071,11 @@ export class StackViewport extends Viewport implements IStackViewport { // (undocumented) scroll(delta: number, debounce?: boolean, loop?: boolean): void; // (undocumented) - setActors(actors: Array): void; + setActors: (actors: Array) => void; // (undocumented) - setCamera(cameraInterface: ICamera, storeAsInitialCamera?: boolean): void; + setCamera: (cameraInterface: ICamera, storeAsInitialCamera?: boolean) => void; // (undocumented) - setColormap(colormap: CPUFallbackColormapData): void; + setColormap: (colormap: CPUFallbackColormapData) => void; // (undocumented) setImageIdIndex(imageIdIndex: number): Promise; // (undocumented) @@ -2083,7 +2083,9 @@ export class StackViewport extends Viewport implements IStackViewport { // (undocumented) setStack(imageIds: Array, currentImageIdIndex?: number): Promise; // (undocumented) - unsetColormap(): void; + setUseCPURendering(value: boolean): void; + // (undocumented) + unsetColormap: () => void; // (undocumented) static get useCustomRenderingPipeline(): boolean; // (undocumented) diff --git a/packages/core/src/RenderingEngine/BaseVolumeViewport.ts b/packages/core/src/RenderingEngine/BaseVolumeViewport.ts index 46dce7619d..3582d35bbe 100644 --- a/packages/core/src/RenderingEngine/BaseVolumeViewport.ts +++ b/packages/core/src/RenderingEngine/BaseVolumeViewport.ts @@ -35,7 +35,6 @@ import { triggerEvent, createSigmoidRGBTransferFunction, getVoiFromSigmoidRGBTransferFunction, - createLinearRGBTransferFunction, } from '../utilities'; import type { vtkSlabCamera as vtkSlabCameraType } from './vtkClasses/vtkSlabCamera'; import { VoiModifiedEventDetail } from '../types/EventTypes'; @@ -241,7 +240,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport { // const cfun = createLinearRGBTransferFunction(voiRangeToUse); // volumeActor.getProperty().setRGBTransferFunction(0, cfun); - // Moving from LINEAR to SIGMOID and back to LINEAR will not + // Todo: Moving from LINEAR to SIGMOID and back to LINEAR will not // work until we implement it in a different way because the // LINEAR transfer function is not recreated. const { lower, upper } = voiRangeToUse; diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index e4f717d173..b0dfcc66d8 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -78,8 +78,8 @@ interface ImagePixelModule { highBit: number; photometricInterpretation: string; pixelRepresentation: string; - windowWidth: number; - windowCenter: number; + windowWidth: number | number[]; + windowCenter: number | number[]; voiLUTFunction: VOILUTFunctionType; modality: string; } @@ -117,6 +117,12 @@ type CalibrationEvent = { columnScale: number; }; +type SetVOIOptions = { + suppressEvents?: boolean; + forceRecreateLUTFunction?: boolean; + voiUpdatedWithSetProperties?: boolean; +}; + /** * An object representing a single stack viewport, which is a camera * looking into an internal viewport, and an associated target output `canvas`. @@ -136,8 +142,9 @@ class StackViewport extends Viewport implements IStackViewport { // Viewport Properties private voiRange: VOIRange; + private voiUpdatedWithSetProperties = false; private VOILUTFunction: VOILUTFunctionType; - private initialVOIRange: VOIRange; + // private invert = false; private interpolationType: InterpolationType; @@ -145,7 +152,6 @@ class StackViewport extends Viewport implements IStackViewport { private _imageData: vtkImageDataType; private cameraFocalPointOnRender: Point3; // we use focalPoint since flip manipulates the position and makes it useless to track private stackInvalidated = false; // if true -> new actor is forced to be created for the stack - private voiApplied = false; private _publishCalibratedEvent = false; private _calibrationEvent: CalibrationEvent; private _cpuFallbackEnabledElement?: CPUFallbackEnabledElement; @@ -172,6 +178,8 @@ class StackViewport extends Viewport implements IStackViewport { this.modality = null; this.useCPURendering = getShouldUseCPURendering(); + this._configureRenderingPipeline(); + if (this.useCPURendering) { this._cpuFallbackEnabledElement = { canvas: this.canvas, @@ -195,7 +203,6 @@ class StackViewport extends Viewport implements IStackViewport { camera.setViewUp(...this.initialViewUp); camera.setParallelProjection(true); camera.setThicknessFromFocalPoint(0.1); - // @ts-ignore: vtkjs incorrect typing camera.setFreezeFocalPoint(true); } @@ -212,6 +219,141 @@ class StackViewport extends Viewport implements IStackViewport { return getShouldUseCPURendering(); } + public setUseCPURendering(value: boolean) { + this.useCPURendering = value; + this._configureRenderingPipeline(); + } + + private _configureRenderingPipeline() { + for (const [funcName, functions] of Object.entries( + this.renderingPipelineFunctions + )) { + this[funcName] = this.useCPURendering ? functions.cpu : functions.gpu; + } + } + + /** + * Returns the image and its properties that is being shown inside the + * stack viewport. It returns, the image dimensions, image direction, + * image scalar data, vtkImageData object, metadata, and scaling (e.g., PET suvbw) + * + * @returns IImageData: dimensions, direction, scalarData, vtkImageData, metadata, scaling + */ + public getImageData: () => IImageData | CPUIImageData; + + /** + * Sets the colormap for the current viewport. + * @param colormap - The colormap data to use. + */ + public setColormap: (colormap: CPUFallbackColormapData) => void; + + /** + * If the user has selected CPU rendering, return the CPU camera, otherwise + * return the default camera + * @returns The camera object. + */ + public getCamera: () => ICamera; + + /** + * Set the camera based on the provided camera object. + * @param cameraInterface - The camera interface that will be used to + * render the scene. + */ + public setCamera: ( + cameraInterface: ICamera, + storeAsInitialCamera?: boolean + ) => void; + + public getRotation: () => number; + + /** + * It sets the colormap to the default colormap. + */ + public unsetColormap: () => void; + + /** + * Centers Pan and resets the zoom for stack viewport. + */ + public resetCamera: (resetPan?: boolean, resetZoom?: boolean) => boolean; + + /** + * canvasToWorld Returns the world coordinates of the given `canvasPos` + * projected onto the plane defined by the `Viewport`'s camera. + * + * @param canvasPos - The position in canvas coordinates. + * @returns The corresponding world coordinates. + * @public + */ + public canvasToWorld: (canvasPos: Point2) => Point3; + + /** + * Returns the canvas coordinates of the given `worldPos` + * projected onto the `Viewport`'s `canvas`. + * + * @param worldPos - The position in world coordinates. + * @returns The corresponding canvas coordinates. + * @public + */ + public worldToCanvas: (worldPos: Point3) => Point2; + + /** + * If the renderer is CPU based, throw an error. Otherwise, returns the `vtkRenderer` responsible for rendering the `Viewport`. + * + * @returns The `vtkRenderer` for the `Viewport`. + */ + public getRenderer: () => any; + + /** + * If the renderer is CPU based, throw an error. Otherwise, return the default + * actor which is the first actor in the renderer. + * @returns An actor entry. + */ + public getDefaultActor: () => ActorEntry; + + /** + * If the renderer is CPU based, throw an error. Otherwise, return the actors in the viewport + * @returns An array of ActorEntry objects. + */ + public getActors: () => Array; + /** + * If the renderer is CPU based, throw an error. Otherwise, it returns the actor entry for the given actor UID. + * @param actorUID - The unique ID of the actor you want to get. + * @returns An ActorEntry object. + */ + public getActor: (actorUID: string) => ActorEntry; + + /** + * If the renderer is CPU-based, throw an error; otherwise, set the + * actors in the viewport. + * @param actors - An array of ActorEntry objects. + */ + public setActors: (actors: Array) => void; + + /** + * If the renderer is CPU based, throw an error. Otherwise, add a list of actors to the viewport + * @param actors - An array of ActorEntry objects. + */ + public addActors: (actors: Array) => void; + + /** + * If the renderer is CPU based, throw an error. Otherwise, add the + * actor to the viewport + * @param actorEntry - The ActorEntry object that was created by the + * user. + */ + public addActor: (actorEntry: ActorEntry) => void; + + /** + * It throws an error if the renderer is CPU based. Otherwise, it removes the actors from the viewport. + */ + public removeAllActors: () => void; + + private setVOI: (voiRange: VOIRange, options?: SetVOIOptions) => void; + + private setInterpolationType: (interpolationType: InterpolationType) => void; + + private setInvertColor: (invert: boolean) => void; + private initializeElementDisabledHandler() { eventTarget.addEventListener( Events.ELEMENT_DISABLED, @@ -237,21 +379,6 @@ class StackViewport extends Viewport implements IStackViewport { } }; - /** - * Returns the image and its properties that is being shown inside the - * stack viewport. It returns, the image dimensions, image direction, - * image scalar data, vtkImageData object, metadata, and scaling (e.g., PET suvbw) - * - * @returns IImageData: dimensions, direction, scalarData, vtkImageData, metadata, scaling - */ - public getImageData(): IImageData | CPUIImageData { - if (this.useCPURendering) { - return this.getImageDataCPU(); - } else { - return this.getImageDataGPU(); - } - } - private _resizeCPU = (): void => { if (this._cpuFallbackEnabledElement.viewport) { resize(this._cpuFallbackEnabledElement); @@ -364,18 +491,15 @@ class StackViewport extends Viewport implements IStackViewport { * @param imageData - vtkImageData for the viewport * @returns actor vtkActor */ - private createActorMapper = (imageData) => { const mapper = vtkImageMapper.newInstance(); mapper.setInputData(imageData); const actor = vtkImageSlice.newInstance(); - // @ts-ignore: vtkjs incorrect typing actor.setMapper(mapper); if (imageData.getPointData().getNumberOfComponents() > 1) { - // @ts-ignore: vtkjs incorrect typing actor.getProperty().setIndependentComponents(false); } @@ -389,7 +513,9 @@ class StackViewport extends Viewport implements IStackViewport { * @param imageId - a string representing the imageId for the image * @returns imagePlaneModule and imagePixelModule containing the metadata for the image */ - private buildMetadata(imageId: string) { + private buildMetadata(image: IImage) { + const imageId = image.imageId; + const { pixelRepresentation, bitsAllocated, @@ -399,26 +525,10 @@ class StackViewport extends Viewport implements IStackViewport { samplesPerPixel, } = metaData.get('imagePixelModule', imageId); - const voiLutModule = metaData.get('voiLutModule', imageId); - - let windowWidth, windowCenter, voiLUTFunction; - if (voiLutModule) { - ({ windowWidth, windowCenter, voiLUTFunction } = voiLutModule); - - if (Array.isArray(windowWidth)) { - windowWidth = windowWidth[0]; - } - - if (Array.isArray(windowCenter)) { - windowCenter = windowCenter[0]; - } - - // when cornerstoneDICOMImageLoader uses cornerstonejs/core types - // this marshalling step can be removed. - if (Object.values(VOILUTFunctionType).indexOf(voiLUTFunction) === -1) { - voiLUTFunction = VOILUTFunctionType.LINEAR; - } - } + // we can grab the window center and width from the image object + // since it the loader already has used the metadata provider + // to get the values + const { windowWidth, windowCenter, voiLUTFunction } = image; const { modality } = metaData.get('generalSeriesModule', imageId); const imageIdScalingFactor = metaData.get('scalingModule', imageId); @@ -427,12 +537,12 @@ class StackViewport extends Viewport implements IStackViewport { this._addScalingToViewport(imageIdScalingFactor); } - // todo: some tools rely on the modality this.modality = modality; + const voiLUTFunctionEnum = this._getValidVOILUTFunction(voiLUTFunction); + this.VOILUTFunction = voiLUTFunctionEnum; let imagePlaneModule = this._getImagePlaneModule(imageId); - // Todo: for now, it gives error for getImageData if (!this.useCPURendering) { imagePlaneModule = this.calibrateIfNecessary(imageId, imagePlaneModule); } @@ -448,8 +558,8 @@ class StackViewport extends Viewport implements IStackViewport { pixelRepresentation, windowWidth, windowCenter, - voiLUTFunction, modality, + voiLUTFunction: voiLUTFunctionEnum, }, }; } @@ -565,9 +675,10 @@ class StackViewport extends Viewport implements IStackViewport { suppressEvents = false ): void { // if voi is not applied for the first time, run the setVOI function - // which will apply the default voi - if (typeof voiRange !== 'undefined' || !this.voiApplied) { - this.setVOI(voiRange, suppressEvents); + // which will apply the default voi based on the range + if (typeof voiRange !== 'undefined') { + const voiUpdatedWithSetProperties = true; + this.setVOI(voiRange, { suppressEvents, voiUpdatedWithSetProperties }); } if (typeof VOILUTFunction !== 'undefined') { @@ -595,12 +706,15 @@ class StackViewport extends Viewport implements IStackViewport { * @returns viewport properties including voi, invert, interpolation type, rotation, flip */ public getProperties = (): StackViewportProperties => { + const { voiRange, VOILUTFunction, interpolationType, invert } = this; + const rotation = this.getRotation(); + return { - voiRange: this.voiRange, - rotation: this.getRotation(), - VOILUTFunction: this.VOILUTFunction, - interpolationType: this.interpolationType, - invert: this.invert, + voiRange, + VOILUTFunction, + interpolationType, + invert, + rotation, }; }; @@ -609,6 +723,7 @@ class StackViewport extends Viewport implements IStackViewport { */ public resetProperties(): void { this.cpuRenderingInvalidated = true; + this.voiUpdatedWithSetProperties = false; this.fillWithBackgroundColor(); @@ -621,57 +736,50 @@ class StackViewport extends Viewport implements IStackViewport { this.render(); } - /** - * If the user has selected CPU rendering, return the CPU camera, otherwise - * return the default camera - * @returns The camera object. - */ - public getCamera(): ICamera { - if (this.useCPURendering) { - return this.getCameraCPU(); + private _resetProperties() { + let voiRange; + if (this._isCurrentImagePTPrescaled()) { + // if not set via setProperties; if it is a PT image and is already prescaled, + // use the default range for PT + voiRange = this._getDefaultPTPrescaledVOIRange(); } else { - return super.getCamera(); + // if not set via setProperties; if it is not a PT image or is not prescaled, + // use the voiRange for the current image from its metadata if found + // otherwise, use the cached voiRange + voiRange = this._getVOIRangeForCurrentImage(); } - } - /** - * Set the camera based on the provided camera object. - * @param cameraInterface - The camera interface that will be used to - * render the scene. - */ - public setCamera( - cameraInterface: ICamera, - storeAsInitialCamera = false - ): void { - if (this.useCPURendering) { - this.setCameraCPU(cameraInterface); - } else { - super.setCamera(cameraInterface, storeAsInitialCamera); - } - } + this.setVOI(voiRange); - private _resetProperties() { - // to force the default voi to be applied on the next render - this.voiApplied = false; - - this.setProperties({ - voiRange: this.initialVOIRange, - rotation: 0, - interpolationType: InterpolationType.LINEAR, - invert: false, - }); + if (this.getRotation() !== 0) { + this.setRotation(0); + } + this.setInterpolationType(InterpolationType.LINEAR); + this.setInvertColor(false); } private _setPropertiesFromCache(): void { - const suppressEvents = true; - this.setProperties( - { - voiRange: this.voiRange, - interpolationType: this.interpolationType, - invert: this.invert, - }, - suppressEvents - ); + const { interpolationType, invert } = this; + + let voiRange; + if (this.voiUpdatedWithSetProperties) { + // use the cached voiRange if the voiRange is locked (if the user has + // manually set the voi with tools or setProperties api) + voiRange = this.voiRange; + } else if (this._isCurrentImagePTPrescaled()) { + // if not set via setProperties; if it is a PT image and is already prescaled, + // use the default range for PT + voiRange = this._getDefaultPTPrescaledVOIRange(); + } else { + // if not set via setProperties; if it is not a PT image or is not prescaled, + // use the voiRange for the current image from its metadata if found + // otherwise, use the cached voiRange + voiRange = this._getVOIRangeForCurrentImage() ?? this.voiRange; + } + + this.setVOI(voiRange); + this.setInterpolationType(interpolationType); + this.setInvertColor(invert); } private getCameraCPU(): Partial { @@ -824,23 +932,6 @@ class StackViewport extends Viewport implements IStackViewport { } } - private setVOI(voiRange: VOIRange, suppressEvents?: boolean): void { - if (this.useCPURendering) { - this.setVOICPU(voiRange, suppressEvents); - return; - } - - this.setVOIGPU(voiRange, suppressEvents); - } - - getRotation = (): number => { - if (this.useCPURendering) { - return this.getRotationCPU(); - } else { - return this.getRotationGPU(); - } - }; - private getRotationCPU = (): number => { const { viewport } = this._cpuFallbackEnabledElement; return viewport.rotation; @@ -892,11 +983,9 @@ class StackViewport extends Viewport implements IStackViewport { private setRotation(rotation: number): void { const previousCamera = this.getCamera(); - if (this.useCPURendering) { - this.setRotationCPU(rotation); - } else { - this.setRotationGPU(rotation); - } + this.useCPURendering + ? this.setRotationCPU(rotation) + : this.setRotationGPU(rotation); // New camera after rotation const camera = this.getCamera(); @@ -922,37 +1011,24 @@ class StackViewport extends Viewport implements IStackViewport { } // make sure the VOI LUT function is valid in the VOILUTFunctionType which is enum - if (Object.values(VOILUTFunctionType).indexOf(voiLUTFunction) === -1) { - voiLUTFunction = VOILUTFunctionType.LINEAR; - } + const newVOILUTFunction = this._getValidVOILUTFunction(voiLUTFunction); - this.VOILUTFunction = voiLUTFunction; - - const { voiRange } = this.getProperties(); - this.setVOI(voiRange, suppressEvents); - } - - private setInterpolationType(interpolationType: InterpolationType): void { - if (this.useCPURendering) { - this.setInterpolationTypeCPU(interpolationType); - return; + let forceRecreateLUTFunction = false; + if ( + this.VOILUTFunction !== VOILUTFunctionType.LINEAR && + newVOILUTFunction === VOILUTFunctionType.LINEAR + ) { + forceRecreateLUTFunction = true; } - this.setInterpolationTypeGPU(interpolationType); - } - - private setInvertColor(invert: boolean): void { - if (this.useCPURendering) { - this.setInvertColorCPU(invert); - return; - } + this.VOILUTFunction = newVOILUTFunction; - this.setInvertColorGPU(invert); + const { voiRange } = this.getProperties(); + this.setVOI(voiRange, { suppressEvents, forceRecreateLUTFunction }); } private setRotationCPU(rotation: number): void { const { viewport } = this._cpuFallbackEnabledElement; - viewport.rotation = rotation; } @@ -993,11 +1069,8 @@ class StackViewport extends Viewport implements IStackViewport { private setInterpolationTypeCPU(interpolationType: InterpolationType): void { const { viewport } = this._cpuFallbackEnabledElement; - if (interpolationType === InterpolationType.LINEAR) { - viewport.pixelReplication = false; - } else { - viewport.pixelReplication = true; - } + viewport.pixelReplication = + interpolationType === InterpolationType.LINEAR ? false : true; this.interpolationType = interpolationType; } @@ -1045,7 +1118,8 @@ class StackViewport extends Viewport implements IStackViewport { } } - private setVOICPU(voiRange: VOIRange, suppressEvents?: boolean): void { + private setVOICPU(voiRange: VOIRange, options: SetVOIOptions = {}): void { + const { suppressEvents = false } = options; // TODO: Account for VOILUTFunction const { viewport, image } = this._cpuFallbackEnabledElement; @@ -1083,7 +1157,6 @@ class StackViewport extends Viewport implements IStackViewport { viewport.voi.windowCenter = windowCenter; } - this.voiApplied = true; this.voiRange = voiRange; const eventDetail: VoiModifiedEventDetail = { viewportId: this.id, @@ -1095,7 +1168,23 @@ class StackViewport extends Viewport implements IStackViewport { } } - private setVOIGPU(voiRange: VOIRange, suppressEvents?: boolean): void { + private setVOIGPU(voiRange: VOIRange, options: SetVOIOptions = {}): void { + const { + suppressEvents = false, + forceRecreateLUTFunction = false, + voiUpdatedWithSetProperties = false, + } = options; + + if ( + voiRange && + this.voiRange && + this.voiRange.lower === voiRange.lower && + this.voiRange.upper === voiRange.upper && + !forceRecreateLUTFunction + ) { + return; + } + const defaultActor = this.getDefaultActor(); if (!defaultActor) { return; @@ -1104,9 +1193,10 @@ class StackViewport extends Viewport implements IStackViewport { if (!isImageActor(defaultActor)) { return; } - const { actor } = defaultActor; - const imageActor = actor as ImageActor; + const imageActor = defaultActor.actor as ImageActor; + let voiRangeToUse = voiRange; + if (typeof voiRangeToUse === 'undefined') { const imageData = imageActor.getMapper().getInputData(); const range = imageData.getPointData().getScalars().getRange(); @@ -1117,32 +1207,50 @@ class StackViewport extends Viewport implements IStackViewport { // scaling logic here // https://github.com/Kitware/vtk-js/blob/master/Sources/Rendering/OpenGL/ImageMapper/index.js#L540-L549 imageActor.getProperty().setUseLookupTableScalarRange(true); - if (this.VOILUTFunction === VOILUTFunctionType.SAMPLED_SIGMOID) { - const cfun = createSigmoidRGBTransferFunction(voiRangeToUse); - if (this.invert) { - invertRgbTransferFunction(cfun); - } - imageActor.getProperty().setRGBTransferFunction(0, cfun); - } else { - const cfun = createLinearRGBTransferFunction(voiRangeToUse); + + let transferFunction = imageActor.getProperty().getRGBTransferFunction(0); + + const isSigmoidTFun = + this.VOILUTFunction === VOILUTFunctionType.SAMPLED_SIGMOID; + + // use the old cfun if it exists for linear case + if (isSigmoidTFun || !transferFunction || forceRecreateLUTFunction) { + const transferFunctionCreator = isSigmoidTFun + ? createSigmoidRGBTransferFunction + : createLinearRGBTransferFunction; + + transferFunction = transferFunctionCreator(voiRangeToUse); + if (this.invert) { - invertRgbTransferFunction(cfun); + invertRgbTransferFunction(transferFunction); } - imageActor.getProperty().setRGBTransferFunction(0, cfun); + + imageActor.getProperty().setRGBTransferFunction(0, transferFunction); + } + + if (!isSigmoidTFun) { + // @ts-ignore vtk type error + transferFunction.setRange(voiRangeToUse.lower, voiRangeToUse.upper); } - this.voiApplied = true; this.voiRange = voiRangeToUse; - if (!suppressEvents) { - const eventDetail: VoiModifiedEventDetail = { - viewportId: this.id, - range: voiRangeToUse, - VOILUTFunction: this.VOILUTFunction, - }; + // if voiRange is set by setProperties we need to lock it if it is not locked already + if (!this.voiUpdatedWithSetProperties) { + this.voiUpdatedWithSetProperties = voiUpdatedWithSetProperties; + } - triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail); + if (suppressEvents) { + return; } + + const eventDetail: VoiModifiedEventDetail = { + viewportId: this.id, + range: voiRangeToUse, + VOILUTFunction: this.VOILUTFunction, + }; + + triggerEvent(this.element, Events.VOI_MODIFIED, eventDetail); } /** @@ -1151,22 +1259,25 @@ class StackViewport extends Viewport implements IStackViewport { * @param imageIdScalingFactor - suvbw, suvlbm, suvbsa */ private _addScalingToViewport(imageIdScalingFactor) { - if (!this.scaling.PET) { - // These ratios are constant across all frames, so only need one. - const { suvbw, suvlbm, suvbsa } = imageIdScalingFactor; + if (this.scaling.PET) { + return; + } - const petScaling = {}; + // if don't exist + // These ratios are constant across all frames, so only need one. + const { suvbw, suvlbm, suvbsa } = imageIdScalingFactor; - if (suvlbm) { - petScaling.suvbwToSuvlbm = suvlbm / suvbw; - } + const petScaling = {}; - if (suvbsa) { - petScaling.suvbwToSuvbsa = suvbsa / suvbw; - } + if (suvlbm) { + petScaling.suvbwToSuvlbm = suvlbm / suvbw; + } - this.scaling.PET = petScaling; + if (suvbsa) { + petScaling.suvbwToSuvbsa = suvbsa / suvbw; } + + this.scaling.PET = petScaling; } /** @@ -1206,9 +1317,7 @@ class StackViewport extends Viewport implements IStackViewport { // the Image object itself. Additional stuff (e.g. pixel spacing, direction, origin, etc) // should be optional and used if provided through a metadata provider. - const { imagePlaneModule, imagePixelModule } = this.buildMetadata( - image.imageId - ); + const { imagePlaneModule, imagePixelModule } = this.buildMetadata(image); let rowCosines, columnCosines; @@ -1361,12 +1470,14 @@ class StackViewport extends Viewport implements IStackViewport { this.imageIds = imageIds; this.currentImageIdIndex = currentImageIdIndex; this.targetImageIdIndex = currentImageIdIndex; + + // reset the stack this.stackInvalidated = true; this.flipVertical = false; this.flipHorizontal = false; - this.voiApplied = false; - - this._resetProperties(); + this.voiRange = null; + this.interpolationType = InterpolationType.LINEAR; + this.invert = false; this.fillWithBackgroundColor(); @@ -1487,11 +1598,9 @@ class StackViewport extends Viewport implements IStackViewport { imageId: string, imageIdIndex: number ): Promise { - if (this.useCPURendering) { - await this._loadAndDisplayImageCPU(imageId, imageIdIndex); - } else { - await this._loadAndDisplayImageGPU(imageId, imageIdIndex); - } + await (this.useCPURendering + ? this._loadAndDisplayImageCPU(imageId, imageIdIndex) + : this._loadAndDisplayImageGPU(imageId, imageIdIndex)); return imageId; } @@ -1514,6 +1623,7 @@ class StackViewport extends Viewport implements IStackViewport { return; } + image.isPreScaled = image.preScale?.scaled; this.csImage = image; const eventDetail: EventTypes.StackNewImageEventDetail = { @@ -1528,8 +1638,6 @@ class StackViewport extends Viewport implements IStackViewport { const metadata = this._getImageDataMetadata(image) as ImageDataMetaData; - image.isPreScaled = image.preScale?.scaled; - const viewport = getDefaultViewport( this.canvas, image, @@ -1649,6 +1757,7 @@ class StackViewport extends Viewport implements IStackViewport { } // cornerstone image + image.isPreScaled = image.preScale?.scaled; this.csImage = image; const eventDetail: EventTypes.StackNewImageEventDetail = { @@ -1861,67 +1970,62 @@ class StackViewport extends Viewport implements IStackViewport { // @ts-ignore: vtkjs incorrect typing activeCamera.setFreezeFocalPoint(true); - // set voi for the first time - const { windowCenter, windowWidth, voiLUTFunction } = imagePixelModule; - - let voiRange = - typeof windowCenter === 'number' && typeof windowWidth === 'number' - ? windowLevelUtil.toLowHighRange(windowWidth, windowCenter) - : undefined; + this.setVOI(this._getInitialVOIRange(image)); + this.setInvertColor( + imagePixelModule.photometricInterpretation === 'MONOCHROME1' + ); - // check if the image is already prescaled - const isPreScaled = - this.csImage.isPreScaled || this.csImage.preScale?.scaled; + // Saving position of camera on render, to cache the panning + this.cameraFocalPointOnRender = this.getCamera().focalPoint; + this.stackInvalidated = false; - if (imagePixelModule.modality === 'PT' && isPreScaled) { - voiRange = { lower: 0, upper: 5 }; + if (this._publishCalibratedEvent) { + this.triggerCalibrationEvent(); } + } - this.initialVOIRange = voiRange; - - if (this.voiApplied && typeof voiRange === 'undefined') { - // There are some cases when different frames within the same multi-frame - // file are not hitting the actor cache because above - // this.__checkVTKImageDataMatchesCornerstoneImage() call results in - // "false". - // In that case we want to keep the applied VOI range. - voiRange = this.voiRange; + private _getInitialVOIRange(image: IImage) { + if (this.voiRange && this.voiUpdatedWithSetProperties) { + return this.voiRange; } + const { windowCenter, windowWidth } = image; - // make sure the VOI LUT function is valid in the VOILUTFunctionType which is enum - if (Object.values(VOILUTFunctionType).indexOf(voiLUTFunction) === -1) { - this.VOILUTFunction = VOILUTFunctionType.LINEAR; - } else { - this.VOILUTFunction = voiLUTFunction; - } + let voiRange = this._getVOIRangeFromWindowLevel(windowWidth, windowCenter); - this.setProperties({ voiRange }); + // Get the range for the PT since if it is prescaled + // we set a default range of 0-5 + voiRange = this._getPTPreScaledRange() || voiRange; - // At the moment it appears that vtkImageSlice actors do not automatically - // have an RGB Transfer Function created, so we need to create one. - // Note: the 1024 here is what VTK would normally do to resample a color transfer function - // before it is put into the GPU. Setting it with a length of 1024 allows us to - // avoid that resampling step. - if (actor.getProperty().getRGBTransferFunction(0) === null) { - const cfun = createLinearRGBTransferFunction(voiRange); - actor.getProperty().setRGBTransferFunction(0, cfun); + return voiRange; + } + + private _getPTPreScaledRange() { + if (!this._isCurrentImagePTPrescaled()) { + return undefined; } - let invert = false; - if (imagePixelModule.photometricInterpretation === 'MONOCHROME1') { - invert = true; + return this._getDefaultPTPrescaledVOIRange(); + } + + private _isCurrentImagePTPrescaled() { + if (this.modality !== 'PT' || !this.csImage.isPreScaled) { + return false; } - this.setProperties({ invert }); + return true; + } - // Saving position of camera on render, to cache the panning - const { focalPoint } = this.getCamera(); - this.cameraFocalPointOnRender = focalPoint; - this.stackInvalidated = false; + private _getDefaultPTPrescaledVOIRange() { + return { lower: 0, upper: 5 }; + } - if (this._publishCalibratedEvent) { - this.triggerCalibrationEvent(); - } + private _getVOIRangeFromWindowLevel( + windowWidth: number | number[], + windowCenter: number | number[] + ): { lower: number; upper: number } | undefined { + return typeof windowCenter === 'number' && typeof windowWidth === 'number' + ? windowLevelUtil.toLowHighRange(windowWidth, windowCenter) + : undefined; } /** @@ -1949,19 +2053,6 @@ class StackViewport extends Viewport implements IStackViewport { return imageId; } - /** - * Centers Pan and resets the zoom for stack viewport. - */ - public resetCamera(resetPan = true, resetZoom = true): boolean { - if (this.useCPURendering) { - this.resetCameraCPU(resetPan, resetZoom); - } else { - this.resetCameraGPU(resetPan, resetZoom); - } - - return true; - } - private resetCameraCPU(resetPan, resetZoom) { const { image } = this._cpuFallbackEnabledElement; @@ -2173,38 +2264,6 @@ class StackViewport extends Viewport implements IStackViewport { this._publishCalibratedEvent = false; } - /** - * canvasToWorld Returns the world coordinates of the given `canvasPos` - * projected onto the plane defined by the `Viewport`'s camera. - * - * @param canvasPos - The position in canvas coordinates. - * @returns The corresponding world coordinates. - * @public - */ - public canvasToWorld = (canvasPos: Point2): Point3 => { - if (this.useCPURendering) { - return this.canvasToWorldCPU(canvasPos); - } - - return this.canvasToWorldGPU(canvasPos); - }; - - /** - * Returns the canvas coordinates of the given `worldPos` - * projected onto the `Viewport`'s `canvas`. - * - * @param worldPos - The position in world coordinates. - * @returns The corresponding canvas coordinates. - * @public - */ - public worldToCanvas = (worldPos: Point3): Point2 => { - if (this.useCPURendering) { - return this.worldToCanvasCPU(worldPos); - } - - return this.worldToCanvasGPU(worldPos); - }; - private canvasToWorldCPU = (canvasPos: Point2): Point3 => { if (!this._cpuFallbackEnabledElement.image) { return; @@ -2295,7 +2354,7 @@ class StackViewport extends Viewport implements IStackViewport { return [worldCoord[0], worldCoord[1], worldCoord[2]]; }; - private worldToCanvasGPU = (worldPos: Point3) => { + private worldToCanvasGPU = (worldPos: Point3): Point2 => { const renderer = this.getRenderer(); // Temporary setting the clipping range to the distance and distance + 0.1 @@ -2338,6 +2397,19 @@ class StackViewport extends Viewport implements IStackViewport { return canvasCoordWithDPR; }; + private _getVOIRangeForCurrentImage() { + const { windowCenter, windowWidth } = this.csImage; + + return this._getVOIRangeFromWindowLevel(windowWidth, windowCenter); + } + + private _getValidVOILUTFunction(voiLUTFunction: any) { + if (Object.values(VOILUTFunctionType).indexOf(voiLUTFunction) === -1) { + voiLUTFunction = VOILUTFunctionType.LINEAR; + } + return voiLUTFunction; + } + /** * Returns the index of the imageId being renderer * @@ -2396,107 +2468,6 @@ class StackViewport extends Viewport implements IStackViewport { return false; }; - /** - * If the renderer is CPU based, throw an error. Otherwise, returns the `vtkRenderer` responsible for rendering the `Viewport`. - * - * @returns The `vtkRenderer` for the `Viewport`. - */ - public getRenderer() { - if (this.useCPURendering) { - throw this.getCPUFallbackError('getRenderer'); - } - - return super.getRenderer(); - } - - /** - * If the renderer is CPU based, throw an error. Otherwise, return the default - * actor which is the first actor in the renderer. - * @returns An actor entry. - */ - public getDefaultActor(): ActorEntry { - if (this.useCPURendering) { - throw this.getCPUFallbackError('getDefaultActor'); - } - - return super.getDefaultActor(); - } - - /** - * If the renderer is CPU based, throw an error. Otherwise, return the actors in the viewport - * @returns An array of ActorEntry objects. - */ - public getActors(): Array { - if (this.useCPURendering) { - throw this.getCPUFallbackError('getActors'); - } - - return super.getActors(); - } - - /** - * If the renderer is CPU based, throw an error. Otherwise, it returns the actor entry for the given actor UID. - * @param actorUID - The unique ID of the actor you want to get. - * @returns An ActorEntry object. - */ - public getActor(actorUID: string): ActorEntry { - if (this.useCPURendering) { - throw this.getCPUFallbackError('getActor'); - } - - return super.getActor(actorUID); - } - - /** - * If the renderer is CPU-based, throw an error; otherwise, set the - * actors in the viewport. - * @param actors - An array of ActorEntry objects. - */ - public setActors(actors: Array): void { - if (this.useCPURendering) { - throw this.getCPUFallbackError('setActors'); - } - - return super.setActors(actors); - } - - /** - * If the renderer is CPU based, throw an error. Otherwise, add a list of actors to the viewport - * @param actors - An array of ActorEntry objects. - */ - public addActors(actors: Array): void { - if (this.useCPURendering) { - throw this.getCPUFallbackError('addActors'); - } - - return super.addActors(actors); - } - - /** - * If the renderer is CPU based, throw an error. Otherwise, add the - * actor to the viewport - * @param actorEntry - The ActorEntry object that was created by the - * user. - */ - public addActor(actorEntry: ActorEntry): void { - if (this.useCPURendering) { - throw this.getCPUFallbackError('addActor'); - } - - return super.addActor(actorEntry); - } - - /** - * It throws an error if the renderer is CPU based. Otherwise, it removes the actors from the viewport. - */ - public removeAllActors(): void { - if (this.useCPURendering) { - throw this.getCPUFallbackError('removeAllActors'); - } - - return super.removeAllActors(); - } - private getCPUFallbackError(method: string): Error { return new Error( `method ${method} cannot be used during CPU Fallback mode` @@ -2540,29 +2511,6 @@ class StackViewport extends Viewport implements IStackViewport { }; }; - /** - * Sets the colormap for the current viewport. - * @param colormap - The colormap data to use. - */ - public setColormap(colormap: CPUFallbackColormapData): void { - if (this.useCPURendering) { - this.setColormapCPU(colormap); - } else { - this.setColormapGPU(colormap); - } - } - - /** - * It sets the colormap to the default colormap. - */ - public unsetColormap(): void { - if (this.useCPURendering) { - this.unsetColormapCPU(); - } else { - this.unsetColormapGPU(); - } - } - private unsetColormapCPU() { delete this._cpuFallbackEnabledElement.viewport.colormap; this._cpuFallbackEnabledElement.renderingTools = {}; @@ -2636,6 +2584,95 @@ class StackViewport extends Viewport implements IStackViewport { return newImagePlaneModule; } + + private 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, + }, + 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: (resetPan = true, resetZoom = true): boolean => { + this.resetCameraCPU(resetPan, resetZoom); + return true; + }, + gpu: (resetPan = true, resetZoom = true): boolean => { + this.resetCameraGPU(resetPan, resetZoom); + return true; + }, + }, + canvasToWorld: { + cpu: this.canvasToWorldCPU, + gpu: this.canvasToWorldGPU, + }, + worldToCanvas: { + cpu: this.worldToCanvasCPU, + gpu: this.worldToCanvasGPU, + }, + getRenderer: { + cpu: () => this.getCPUFallbackError('getRenderer'), + gpu: super.getRenderer, + }, + 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, + }, + }; } export default StackViewport; diff --git a/packages/core/src/RenderingEngine/Viewport.ts b/packages/core/src/RenderingEngine/Viewport.ts index 3e584935be..73b31ca2f3 100644 --- a/packages/core/src/RenderingEngine/Viewport.ts +++ b/packages/core/src/RenderingEngine/Viewport.ts @@ -124,7 +124,7 @@ class Viewport implements IViewport { * * @returns The `vtkRenderer` for the `Viewport`. */ - public getRenderer() { + public getRenderer(): any { const renderingEngine = this.getRenderingEngine(); if (!renderingEngine || renderingEngine.hasBeenDestroyed) { diff --git a/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts b/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts index 3bbc66de6a..f5c30bd4b4 100644 --- a/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts +++ b/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/generateColorLUT.ts @@ -13,7 +13,7 @@ import { IImage, CPUFallbackLUT } from '../../../../types'; * * @returns A lookup table to apply to the image */ -export default function ( +export default function generateColorLUT( image: IImage, windowWidth: number | number[], windowCenter: number | number[], diff --git a/packages/core/test/groundTruth/imageURI_100_100_0_10_1_1_1_linear_color.png b/packages/core/test/groundTruth/imageURI_100_100_0_10_1_1_1_linear_color.png index d24bdb41f0a7a58d24689b3332b23b386d67955d..653e432d2e06f55b0606e50d710adb15648bdbd2 100644 GIT binary patch literal 14008 zcmeI3ZEO@p7{}+91CJ8gUI_wKxZ|}%5{@_16y&yGOG_XeA)1;HJhWmBM$be`A#8WH zRXhn)tOZLXc8QTlFrkK~!Pul*AXbzdYZb(#l)IzYrKRm%J6G#t99e(wj-{#Xy z_Ia6SC;ReC=J_Apxua}mR(=)$VCL5Hl3f6BbjktSG&;l%-kPL?<^5e{o597K&kh3M z1Y1isy>#%^k-q{L+Xt(^(v~*fKe2Gy^2g;9*VmS3_Z4Nw=S$n~u9{uAea$8N^p7j2 zzWm1P3qM&4TUO8a-DyYxV=whGA0(} zIYIDR^XQ7jY1d*foQ?oo@I}rVI-K~uc3&pBO`8924doILa=bsb9?!U1zMt}>mYnIi zQY5@Be#)M9b%F!wszF_3p@oZX6b3ujq{;!DwJSSay)(LTNipd3yZS1&q+Oi_KuI*q zstbSHS{iG0%Rz{ABdp1)qti448p`2~d~evr%c=(w-FT2gCyHrcq=$K-p^A89hlnu> zXc3+##H3d2>`z zx|>MT?L;q4KB7G=C;-IdiGcl3jGZoP=S@LGk5uilf>Fd<>xM>8PcZ9_*l|5*Jod;k z89@3#A-y|17cVZWCS&Li)!|kqX@!qSGR_O45W91=J%}AhBS@BrH>%98b45|VLv$;6 zz$1H2J${*d0~=%q#La3ePq$(S_3{LpHKcNK5l@Ebx{4l<-HJJ)As-o;b13Q%I-)`R zHuAX1@U|C{-dZuLPiSW%bm`H!%6t2NAr3Ce-wQ7T*3i2hct*e)Qr3@0u+x$t{$ve2 z1&j*62kd`fmB6jtc`Z)x+S$N*?l)`o>Mda9c?=KaN%=(?7a#0eJe&R4)KKyJRQ1>|C3j*R$>g*2}>tA9-#E6Iy5hEf-M2v_S5iufSM8t@Q5z&7V(T%Jw7ujddA-lg!^k}F<(FHMngBW_8 zH++K@azh;-sl>xRUY5;Ko@z`YN_QJ^q_iJ>S~FF&+($JsRe<wd zS;i*>QE%fRp7ba%wzS3Q^&34JG>vi8p}O2k(g@TjWEAzgP)vr%M>RPn)vb8xCB0WM z1w0Cqi&Wfql{Bed=$2*SXcY--E?zN=VN@u~wc?omNOkd~Z_c4{SRb#EY4qLj&FNiz zYxielKcH@vh0C3GAv-Nks%tH#3Zo;JAI!_>h-|Y`8j_(Wilr{N;dwwCTFxwv_6{XZ5zFHisg literal 13485 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 RU_E(DP*bI^99U`G|1Xv&BW?fy diff --git a/packages/core/test/groundTruth/imageURI_256_256_100_100_1_1_0_nearest.png b/packages/core/test/groundTruth/imageURI_256_256_100_100_1_1_0_nearest.png index 8d48a9c5ec97fe708c4b2a8cc035561338f94cf1..1db20a370378b963f2482c3ef1558624be0c64b7 100644 GIT binary patch literal 3294 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=BLoJY5_^ zD&pQ=5A2Oi7hrj4+M}=|rRgvW5cMdhh&3kWi0pTN8S1t8#Pi+f7fa6lRUN%G-tL~A z-Mv2o4i6@>?9*s)-?zKp?SVQYs{#jy&5Q=7iVu%}m#~O5@F;vpVB&gku>bvK!2kxM zh6Bx<8V&6B`~2M<7*8-T@vALhko)t)PPWls^uX`@_Z_l=6?@~{r_ZUZ|6f}4_t(dr zch0bW_%8o{`~Cf%rv)3|-`_7ETlDPp_4WICTI~P*dHmK`bpgY_<^Jm#-pRQ!@~n|O zz@RA;$e?(pK5{7o+cmC)29Z9E27zTvKirxAvFoga5-{{)c%I4jgA-^LW_7 z#F1#kz*Zo>{MjU?3=6$zi31Luj64$#39Vig;r%Q zg_e^H5(i@E%<|@1@Nh>?A_KE3n}GwDyNCnFWJZIA-4>?PMH~(n-aN#>=EVbbf*NxI zLwR8q(E7H=n_3xprbs}Y>ce(`;oYMvF+mKx$5XkPB{U2h7=tVpF*wcOX<)o_bmh(^ z3{rik#n=o2k{Fnm6u2<97)UZO@8}LL_GC1XnXb!|u;9q3M@D00G--|IkZ{NNz_MqJP!Plz0GDT0{{{4IXgTe~DWM4f1ou&Y literal 3327 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=BLwJzX3_ zD&pSWa?G2ZF2EA_Dkacw+K-fPXGK(gEBNj<;{KF*chJzI^=p<5$%WM~24j zDjyay-1~I5)`aQ5KmY?DW23p+0)~4(KGs?>aUI}Q_;8Fx&~+8PwO|qm&YFiLC*F6dtdy1{QL1tp#X;e-@E@jsbT#v`9a;I>mmmj zdKq~p9O_`=Nc?wPgVkU|7z6VXgGCHZGq@habNHo*4NVy@gy8L&cNo8 z=)%}yAj!bI!+LqzWTp%YyFndzE52@8&ldSo<4Mw8ZP9)Xm7qovkp z@jF_NjMnUow^ZYFr3DOeuPkeMn9d6ZFr5E5 z$?d^X4vmKCReti#2a16`hT=U-1S>R|xE?H>clo%&2N_1z4>J0h*p2!iepVvAV|UBl RW?+wo!PC{xWt~$(69CD7XORE^ diff --git a/packages/core/test/groundTruth/imageURI_256_256_50_10_1_1_0.png b/packages/core/test/groundTruth/imageURI_256_256_50_10_1_1_0.png index 21ff0726ff601c449924d6faa6089ee06a51c32e..0077adb7bef369768e1d633e39e37228b5b6b527 100644 GIT binary patch delta 446 zcmca2bX91AVLiX6i(^Pd+}mphePuW$SRX#~V-jO5P@crwI{R^ffrqz&%Y)Mb%-ZiS zvTxD-z4nyx{PQONU%y)PWOx7L$B#eoGqU7OIxv^v&Xe-*mpJ}-J1}%K9N-leXpsK* zBTkl)@c{#qxsL-w-jB!Cr7SEB%p7+N7#Vjw?0m3(JEx`+ec=4?-C3 z?c4LW@Q9q8T;K2Czx~Vi@82&!{q)oRYV&5nA2Sa;{(d|-;9oIA-Jg&3zhB>886WrK z;q&LuAHRJ0^0Mao_3MwHKJA^jj+f()P~-agdfRg@4h(&d1sdXu&K*`{V)}6T_y_xj z33Hd7;pAvwIHOv;0O(eh1=*YDGqSKxHee~OH{d?d|1>}T-ZT9VyZLQwY-GNF|33fU zEg<>0Xy>1Im+R!2{#!R5f8W16?_RBpy!`Tc9}aUU6qo`%kY%@2-0^`8BcsLnGr}A( zyg)C`@_*?nVBzn;aL)KM3sbnW1H-y!!Bv7R=YV1J`HW5DfvW-n4Xb}niDPN}3^c*U gSkE83S&j7!XFvZray@v1Ap;P2y85}Sb4q9e0I&@*7r3q6fiVut0^#q|M|Griizm}Bg?uO4cQDaACG^3#W9EL!0C^l zt_$woz1uo3KmY#Szn`Cn1vEI&Z{;#L4?fdKh*V)+F z?X!M*dwc%z%a?-_*YR@v5o>(@_@{+1Bcny1Geg~J%X&)}2Zn#24gc^fq%7CmY{*#7 zJ~@F&u>K6whi!jee=PmSRR8P1^!j}=-@ku<|L>N(eVvT{dHwodTkf|X`0w}N^2e7I z7IJnq-$B0p`{A;j3qxbKk^+PHSIfOTOwTzu4p_#`b9=A|$Z)Oo({Dat3G{m3^yPv( zG=Pl2`Ik>C6af7_=ke1_7QLz~xwU;h2^x$XV`zvFlO=VV||STC}9 zBC8t9CZ=aB^$q&#pZ$Do`Fqi{eK+2gRiD0IX?On1zQ48Szt?-W9RAP3&>r7#T)SWs z1B2hS?>Ve|;tU5Kia+?sHAjb`Ve(ho=*GjW3K01H;Y=tFyJyqVvf!S{vEA7m0_VL>luK+)78&qol`;+0QN1BCjbBd delta 282 zcmaDZvQ1=yay{o?PZ!6KinzD89JB5Qh`2ar{_mc4FkwTw-Gg@N5Xt9Ce;51p7TI6_ z9P>|O!2xaVI}8<1%fBxc{h`Ca;2iMaC@aH=xU>^g+IrDEUtZCc~3EwVP7_y!dXE^Zl z*t`V?au^xT)Jf?tJMfcFVdQ&MBb@09}rMvj6}9 diff --git a/packages/core/test/groundTruth/imageURI_64_64_0_10_5_5_0_nearest.png b/packages/core/test/groundTruth/imageURI_64_64_0_10_5_5_0_nearest.png index 8110ac025a8987be686ddfb1c6478caf9ac71864..d7e404477dadb58fa4dbae643186f65c8789fef5 100644 GIT binary patch literal 2176 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=AAro-U3d z6>)EG9n6#o6mY#5XfnBN@l2K}vnJ2v5OI^8)r5!oXOx-yC5#m@UJ(=BSB4p!8od!^9w7>f_i0Xp&6VU2YRk0jA|bZ zy3u4anq@|di_xNT3fH`>*l#Dd0GkvHp00i_ I>zopr05dxgssI20 literal 2063 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJFtYo0ESAr*0NZy9ze2MV}2 ze*MdTm-&;JSX+zfv~b1Ub9(-Gt)Bn=^FDcdCKf*~8@2~2@12Bya7i%mHy%i2WG>iI zbCFYmA-@4T=Z_u3|A*mB=RarkF*NSy*fakve>Q`SGLs@Bvw&8x2-E5Y#)zYLggH1R z8Uki65{O{sIiU4yo`c5$hL%$ztp~Ic7{Uy{vp6xaDTFN3UhR`#%jT0IeIi_lIHHIEwSd;XQvxSAnVTDg1#~LQK1kqxD1(gH_r_?T|1W^Nq z)ibI`tsV{V(PTE7xkihW(Smf~tB(Ww9v8!PC{xWt~$(69742j7k6i diff --git a/packages/core/test/groundTruth/imageURI_64_64_20_5_1_1_0_nearest.png b/packages/core/test/groundTruth/imageURI_64_64_20_5_1_1_0_nearest.png index fcbe2d2e239bdeb5c7c5ce2f3eed24c8135c613d..2d353f83d1199a12836fe4e53baac399d2f5fa9c 100644 GIT binary patch literal 2450 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=Et;o-U3d z6>)E`-OUXNlyJVdghy+}DG%n7$yOO^q5;PgKDN2cILV*mo&NFE*&o*1wmv=n`ta9Z z59HY`3YIYUGZdt}lRMiO&&SZ9RPZ2yo#DZQ54Lle85S_hSlnP_u&}7{f6l=0f`Of{ zjDdlV@BU@W0$~RK`1w0Z+L_OvKVM!``g`(n^}73IC&kxWet7$Pd;97B(|6ez58Ms= z``4M_hl=vwGwm`A4g5F1|FdUn_+I^7-~K!!!w(z%`(L~nKB(=k`O^=Su1|Thc0OxE zG~dtv{~t0i{NbPee*q)I#E&WU#S9FKoERKZY$6#VUNA7|EP24jt?-J0!R6TGtqfvT z3=G_wI}S2+02x5lx(%&-3Il8wt9Md4>taG{qI*8 z`=_q??!PDUe0Tc4|BdEn*t5cm;e(QL?b+or3=RG_zklRsK5%^R=abigX0HGIDfT`K z!~0FAmdKI;Vst E07OmBI{*Lx diff --git a/packages/core/test/groundTruth/imageURI_64_64_20_5_1_1_0_nearestFlipH.png b/packages/core/test/groundTruth/imageURI_64_64_20_5_1_1_0_nearestFlipH.png index 7c138fc26f465ad2dc4648798e6a1a70431a4ac2..589fad77d0ee1a0fc59a06230c9fba6ae9428b48 100644 GIT binary patch literal 1779 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=C{*JzX3_ zD&pQ=3CuklAi&_r?j|t9r?ADBW5R4G0C<<_Jb2wD|bW>Mg$ZK$ja9}ue+T4nfv4EN75EGL?+`fyPe>54M z|GhI$-am_lV~>DEU-Fr!A1iwHl-BI~uf`~m_>H46SC}E(K|x1>;o(#1xeW|E7?~0o z89DUtzhGfGz)RQyjs|H30WpDw51)Gd92o8}G;U~MXqld0!o>7|jiZH?MPX0PVqpOW z^HD2C18OuyjphrC5^A&nA+n_X^PAz%njd@SoB1{z;BKtFe>~<~@w57E^?Uy>yS+8` z`i=1F4u;01EG!8tOMgZ%ZDwLJc>U6p$(xhoK+x+?8(Gp=SrV>Y&Sse=B+#(*+S6pt zGh7@8)-1oyp`)t6;Jxjfjc92llWP48DV&{kmBJTFdp!;}N&C5Ka{ U^IAM80oGy+p00i_>zopr05WSc-2eap literal 3021 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 diff --git a/packages/core/test/groundTruth/imageURI_64_64_30_10_5_5_0_nearest.png b/packages/core/test/groundTruth/imageURI_64_64_30_10_5_5_0_nearest.png index dfe1470121787c3cde23e2eb6910ad4a3b8335d2..cdcc16fa34d6ca918b391ffc4d9468f5aff297c8 100644 GIT binary patch literal 2117 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=Hi5JzX3_ zD&pSWxS0DWK!PKss50Vk=-T2-7U+&ZWnvNzhNF<9n+T?zd0Lr&S#MMAl$%Q$5a3m-U6hTe2ll>Q_f&3 z$+F=19czX&dzl|Jlmiv_m zWHPe=Wtmny7H`@V7T-{cU0?WFpVaO(VR0{N(|c) z@&9**IdOhpXWPB`y#BS--E#Z6KY;aM)j#Racemx-KB52Qe;K3rjPFcL2Kv(X&#<<| zJ214(zrnuaz#egdhRT~i()0}e044t%u{*%}Y;Oa@nbXJ2iyI#TB_FDPW7IQv&&rYz zXSQ!9H_!eC1|Iv3`~{7bz$*CjrjKW15`F+Be{|M1aG$AeEPubKq9Y-ayA0SaVDNPH Kb6Mw<&;$UfAs|Hn literal 2057 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJFt3!W~HAr*0NZ{5y%;vvF% z@y!4KFHC+MoNja|^_0`}h|hO+M5{L?ct75GEb4H*-UFtS+&+vRMc*&zeW+t(RA}OE z(N|zd{2_Ojm8F4MPhejIL(9YJ5^(_rz8wxfn3x3G-d~J;aGl|dVeHFA;t}e9MEfm& zZdvY^{paoVDwYI)Dfa(4j203H80+^k$P|3p{BJ5#L736+zq?ry_TP?sr_O!g>4d$1 z_{AEg`&Z1DS77*_`18$Vjsu&gbN#Vo1V0~fuovQ(88eawL zg9b3=Vf{cN&pyU0AGsawGH@&8HZUyt7|l@nhbh4P0F#Hb z0#H`WL3TgG72XXDm)JNQr1rB09M@+kW!7of!pJn?SpKNi(O?=)5~De1w3HYICE|ho z>*f#J{x+g;oo z4D2xqHH?fLhrhqjQvg<$jUQN96nO40-rc}ZZ~&nx|Imb4Svq^C9RpVJ44$rjF6*2U FngDtb;k*C< diff --git a/packages/core/test/groundTruth/imageURI_64_64_54_10_5_5_0_nearest.png b/packages/core/test/groundTruth/imageURI_64_64_54_10_5_5_0_nearest.png index 78f43c3117bead649a46672eb0d68b8f223f5af2..c5c4e63dd8168f18165704f7285b0a478b1a6d36 100644 GIT binary patch literal 1764 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=C}ZJY5_^ zD&pQ=Ihf02DButn?51_YD#%A8YxP3jRfS9LC@C`9DgHk$>200w_TT9EoWuPLjY|%= zGEAyAH)*tIWGvudImE;y5V!9lCr5*{f`FJn!-r43ehv(G7#cS;Ftkk1FJba$F8I4M zNXg+r#fkw6L-$ z?5SBSEWlvyprE6`@bIbh+y;gnj7*7)j2!y+U$C$o;1%HD=5VO^=?1hSuVK`R(SX94 zLPt~7Xx1AoKrl;gu? literal 2089 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJFt51uZLAr*0NZyn5f;vm3! zaOVI28M;6AY{+qH{;gS~zp(4llau$Kf3}gYXW`fr^gx?o{hr-RIsb5QH1Ibb=w@Yk zaP;_gD@Mi-yaE*)8yNQd_z|YAz_5>zNncE$L0ta+E0%wf4ey_OcQQ2Y=djuTF+Stp z=jU1scDYP}4h%|j44Jr@m^Ku+S_^bAF$p~E5sVNJXlT{G)#xB1(7=)2bD*h#K_})S zAIqW!1{aHD7BLoHz+7DaEEVUZq!g<5IiT*$jZnVv15@ehYKTP zN5L@;9S)8IheY2xC~$E&v>3Z3I65$ht;=8^wR$wbN0ZrT<{B+hMhnt`uRcC>F}O%4 zvv9;He5kcMUwp8-%9V+Q4_I?ChV-ixbT=^EDbjrJ`JkbJLB`>$2h(<-jPCSL^T { function secondImageRendered() { // Second render is as a result of scrolling const image = canvas.toDataURL('image/png'); + element.removeEventListener( + Events.IMAGE_RENDERED, + secondImageRendered + ); + compareImages( image, imageURI_64_64_0_20_1_1_0_scrolled, 'imageURI_64_64_0_20_1_1_0_scrolled' ).then(done, done.fail); - - element.removeEventListener( - Events.IMAGE_RENDERED, - secondImageRendered - ); } ); } diff --git a/packages/tools/test/groundTruth/imageURI_64_64_0_20_1_1_0_scrolled.png b/packages/tools/test/groundTruth/imageURI_64_64_0_20_1_1_0_scrolled.png index 1930e96377b61619cedf90568f1360416fbe78f2..cf7c798cbcf0c4ff56ce09ee32b91558424f4b40 100644 GIT binary patch literal 2460 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=EuMo-U3d z6>)EG9n8JWAmRKlMQZ)U%uNpAuZ?CZB=2y@XYD%X^~lEQUsZ3)Kc!A~xnDK4yW-~m zdCb5rcd0R*q2k1yS`+qv77Pqs2O8PS7#QR%YUZC~WVpb{XS0Es;lqK4zf1TS8u$tx zG|DnGu=CwtUi_ez;YU1^`PL^-pFUmo+~!DQwbh5u*Uv9Mefwz|C&P}LtNz<_FvO=U zng4SN&;ZTfcK?hRYVKI)*UbfTOXK8zsWMokOmBbxgn{AC$s6D6#2D@spSZvOFeAec zzUluLGBQm3nDW1ffnkvogF}i2V28Km^U`^Z!3z-=@b}%y(+%e8Ccwho-0c0-vq9|kWj)6hu`x$8&i(a7IER9+Y_C9L{hU2!M+1ULa tGce3i^O8T*_!y|X@cf>G*p2$b)}uTx%i#fIJ+QgO;OXk;vd$@?2>_HiDM)EG9rTM16mh=zi6G&gXe!%*CQjV2JZnVE^z_y$OSY(Y!bId<+gD91I;j z>f8+6K>J%o?j$g}02wDLHi;c@VrNKDWHpaq2xJGTUc%QjT$o=HlyigG&77A6r+XcXw5K)Yt8lX3^q?0*z49Ze}7#T zyZ>9M+%ZPJnis%^2?ucv6NUxMG8Q)&87wSn{GT&0ykJ0R`@qh0|Bz35Ms6~&$;9C4 L>gTe~DWM4f`5ed8 diff --git a/yarn.lock b/yarn.lock index 82c20200b5..e607670520 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2547,21 +2547,6 @@ detect-gpu "^4.0.45" lodash.clonedeep "4.5.0" -"@cornerstonejs/dicom-image-loader@^0.0.1": - version "0.1.5" - dependencies: - "@babel/eslint-parser" "^7.19.1" - "@cornerstonejs/codec-charls" "^1.2.3" - "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" - "@cornerstonejs/codec-openjpeg" "^1.2.2" - "@cornerstonejs/codec-openjph" "^2.4.2" - "@cornerstonejs/core" "^0.31.0" - coverage-istanbul-loader "^3.0.5" - date-format "^4.0.14" - dicom-parser "^1.8.9" - pako "^2.0.4" - uuid "^9.0.0" - "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -8923,6 +8908,22 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cornerstone-wado-image-loader@^4.10.0, cornerstone-wado-image-loader@^4.8.0: + version "4.10.0" + resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.10.0.tgz#25c367cfc54a2c92ebbb5c64dba4fe38439112a1" + integrity sha512-XZcgB8DpUxnsTA3vU/zbPtB2uFLL4ght70BPrQHykkX/Jlg/r6Ob7ztX2dEEAAb4ELE/d4icfGuSy10Acg/kyw== + dependencies: + "@babel/eslint-parser" "^7.19.1" + "@cornerstonejs/codec-charls" "^1.2.3" + "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" + "@cornerstonejs/codec-openjpeg" "^1.2.2" + "@cornerstonejs/codec-openjph" "^2.4.2" + coverage-istanbul-loader "^3.0.5" + date-format "^4.0.14" + dicom-parser "^1.8.9" + pako "^2.0.4" + uuid "^9.0.0" + cors@~2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"