From c2810dd5799caf21869c323631802cec3e599ca7 Mon Sep 17 00:00:00 2001 From: Alireza Date: Fri, 28 Jul 2023 12:01:49 -0400 Subject: [PATCH] fix(voi): fix the voi setting when the stack is composed of different orientations (#703) * fix(voi): fix the voi setting when the stack is composed of different orientations * fix the volume conversion to stack voi bug * fix(volumes): a volume should be able to grab data from another cached volume when needed * revert example change --- .../core/src/RenderingEngine/StackViewport.ts | 24 ++- .../src/BaseStreamingImageVolume.ts | 163 ++++++++++++------ 2 files changed, 126 insertions(+), 61 deletions(-) diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index 34a5e06e5..481aad655 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -2050,10 +2050,15 @@ class StackViewport extends Viewport implements IStackViewport { const monochrome1 = imagePixelModule.photometricInterpretation === 'MONOCHROME1'; + // invalidate the stack so that we can set the voi range + this.stackInvalidated = true; + this.setVOI(this._getInitialVOIRange(image), { forceRecreateLUTFunction: !!monochrome1, }); - this.setInvertColor(!!monochrome1); + + // should carry over the invert color from the previous image if has been applied + this.setInvertColor(this.invert || !!monochrome1); // Saving position of camera on render, to cache the panning this.cameraFocalPointOnRender = this.getCamera().focalPoint; @@ -2107,9 +2112,20 @@ class StackViewport extends Viewport implements IStackViewport { windowWidth: number | number[], windowCenter: number | number[] ): { lower: number; upper: number } | undefined { - return typeof windowCenter === 'number' && typeof windowWidth === 'number' - ? windowLevelUtil.toLowHighRange(windowWidth, windowCenter) - : undefined; + let center, width; + + if (typeof windowCenter === 'number' && typeof windowWidth === 'number') { + center = windowCenter; + width = windowWidth; + } else if (Array.isArray(windowCenter) && Array.isArray(windowWidth)) { + center = windowCenter[0]; + width = windowWidth[0]; + } + + // If center and width are defined, convert them to low-high range + if (center !== undefined && width !== undefined) { + return windowLevelUtil.toLowHighRange(width, center); + } } /** diff --git a/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts b/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts index 88f5eebe6..31998e36a 100644 --- a/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts +++ b/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts @@ -321,63 +321,6 @@ export default class BaseStreamingImageVolume extends ImageVolume { } } - const successCallback = ( - imageIdIndex: number, - imageId: string, - scalingParameters - ) => { - const frameIndex = this._imageIdIndexToFrameIndex(imageIdIndex); - - // Check if there is a cached image for the same imageURI (different - // data loader scheme) - const cachedImage = cache.getCachedImageBasedOnImageURI(imageId); - - // check if the load was cancelled while we were waiting for the image - // if so we don't want to do anything - if (loadStatus.cancelled) { - console.warn( - 'volume load cancelled, returning for imageIdIndex: ', - imageIdIndex - ); - return; - } - - if (!cachedImage || !cachedImage.image) { - return updateTextureAndTriggerEvents(this, imageIdIndex, imageId); - } - const imageScalarData = this._scaleIfNecessary( - cachedImage.image, - scalingParameters - ); - // todo add scaling and slope - const { pixelsPerImage, bytesPerImage } = this.cornerstoneImageMetaData; - const TypedArray = scalarData.constructor; - let byteOffset = bytesPerImage * frameIndex; - - // create a view on the volume arraybuffer - const bytePerPixel = bytesPerImage / pixelsPerImage; - - if (scalarData.BYTES_PER_ELEMENT !== bytePerPixel) { - byteOffset *= scalarData.BYTES_PER_ELEMENT / bytePerPixel; - } - - // @ts-ignore - const volumeBufferView = new TypedArray( - arrayBuffer, - byteOffset, - pixelsPerImage - ); - cachedImage.imageLoadObject.promise - .then((image) => { - volumeBufferView.set(imageScalarData); - updateTextureAndTriggerEvents(this, imageIdIndex, imageId); - }) - .catch((err) => { - errorCallback.call(this, err, imageIdIndex, imageId); - }); - return; - }; - const updateTextureAndTriggerEvents = ( volume: BaseStreamingImageVolume, imageIdIndex, @@ -431,6 +374,59 @@ export default class BaseStreamingImageVolume extends ImageVolume { } }; + const successCallback = ( + imageIdIndex: number, + imageId: string, + scalingParameters + ) => { + const frameIndex = this._imageIdIndexToFrameIndex(imageIdIndex); + + // Check if there is a cached image for the same imageURI (different + // data loader scheme) + const cachedImage = cache.getCachedImageBasedOnImageURI(imageId); + + // Check if the image was already loaded by another volume and we are here + // since we got the imageLoadObject from the cache from the other already loaded + // volume + const cachedVolume = cache.getVolumeContainingImageId(imageId); + + // check if the load was cancelled while we were waiting for the image + // if so we don't want to do anything + if (loadStatus.cancelled) { + console.warn( + 'volume load cancelled, returning for imageIdIndex: ', + imageIdIndex + ); + return; + } + + // if it is not a cached image or volume + if ( + !cachedImage?.image && + !(cachedVolume && cachedVolume.volume !== this) + ) { + return updateTextureAndTriggerEvents(this, imageIdIndex, imageId); + } + + // it is either cachedImage or cachedVolume + const isFromImageCache = !!cachedImage; + + const cachedImageOrVolume = cachedImage || cachedVolume.volume; + + this.handleImageComingFromCache( + cachedImageOrVolume, + isFromImageCache, + scalingParameters, + scalarData, + frameIndex, + arrayBuffer, + updateTextureAndTriggerEvents, + imageIdIndex, + imageId, + errorCallback + ); + }; + function errorCallback(error, imageIdIndex, imageId) { this.framesProcessed++; @@ -634,6 +630,59 @@ export default class BaseStreamingImageVolume extends ImageVolume { return requests; }; + private handleImageComingFromCache( + cachedImageOrVolume, + isFromImageCache: boolean, + scalingParameters: any, + scalarData: Types.VolumeScalarData, + frameIndex: number, + arrayBuffer: ArrayBufferLike, + updateTextureAndTriggerEvents: ( + volume: BaseStreamingImageVolume, + imageIdIndex: any, + imageId: any + ) => void, + imageIdIndex: number, + imageId: string, + errorCallback: (error: any, imageIdIndex: any, imageId: any) => void + ) { + const imageLoadObject = isFromImageCache + ? cachedImageOrVolume.imageLoadObject + : cachedImageOrVolume.convertToCornerstoneImage(imageId, imageIdIndex); + + imageLoadObject.promise + .then((cachedImage) => { + const imageSource = isFromImageCache ? cachedImage.image : cachedImage; + const imageScalarData = this._scaleIfNecessary( + imageSource, + scalingParameters + ); + // todo add scaling and slope + const { pixelsPerImage, bytesPerImage } = this.cornerstoneImageMetaData; + const TypedArray = scalarData.constructor; + let byteOffset = bytesPerImage * frameIndex; + + // create a view on the volume arraybuffer + const bytePerPixel = bytesPerImage / pixelsPerImage; + + if (scalarData.BYTES_PER_ELEMENT !== bytePerPixel) { + byteOffset *= scalarData.BYTES_PER_ELEMENT / bytePerPixel; + } + + // @ts-ignore + const volumeBufferView = new TypedArray( + arrayBuffer, + byteOffset, + pixelsPerImage + ); + volumeBufferView.set(imageScalarData); + updateTextureAndTriggerEvents(this, imageIdIndex, imageId); + }) + .catch((err) => { + errorCallback.call(this, err, imageIdIndex, imageId); + }); + } + /** * It returns the imageLoad requests for the streaming image volume instance. * It involves getting all the imageIds of the volume and creating a success callback