Skip to content

Commit

Permalink
fix(voi): fix the voi setting when the stack is composed of different…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
sedghi authored Jul 28, 2023
1 parent adb0239 commit c2810dd
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 61 deletions.
24 changes: 20 additions & 4 deletions packages/core/src/RenderingEngine/StackViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down
163 changes: 106 additions & 57 deletions packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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++;

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit c2810dd

Please sign in to comment.