diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index a860a8574..f8aca880c 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -620,6 +620,8 @@ export enum EVENTS { // (undocumented) VOLUME_NEW_IMAGE = "CORNERSTONE_VOLUME_NEW_IMAGE", // (undocumented) + VOLUME_SCROLL_OUT_OF_BOUNDS = "CORNERSTONE_VOLUME_SCROLL_OUT_OF_BOUNDS", + // (undocumented) VOLUME_VIEWPORT_NEW_VOLUME = "CORNERSTONE_VOLUME_VIEWPORT_NEW_VOLUME" } @@ -791,6 +793,11 @@ function getVolumeViewportsContainingSameVolumes(targetViewport: IVolumeViewport function getVolumeViewportScrollInfo(viewport: IVolumeViewport, volumeId: string): { numScrollSteps: number; currentStepIndex: number; + sliceRangeInfo: { + sliceRange: ActorSliceRange; + spacingInNormalDirection: number; + camera: ICamera; + }; }; // @public (undocumented) diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index e4875cddc..a0e9a8fdb 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -463,8 +463,9 @@ enum Events { VOLUME_NEW_IMAGE = 'CORNERSTONE_VOLUME_NEW_IMAGE', - VOLUME_VIEWPORT_NEW_VOLUME = 'CORNERSTONE_VOLUME_VIEWPORT_NEW_VOLUME', + VOLUME_SCROLL_OUT_OF_BOUNDS = 'CORNERSTONE_VOLUME_SCROLL_OUT_OF_BOUNDS', + VOLUME_VIEWPORT_NEW_VOLUME = 'CORNERSTONE_VOLUME_VIEWPORT_NEW_VOLUME', // IMAGE_CACHE_FULL = 'CORNERSTONE_IMAGE_CACHE_FULL', // PRE_RENDER = 'CORNERSTONE_PRE_RENDER', // ELEMENT_RESIZED = 'CORNERSTONE_ELEMENT_RESIZED', diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 0820f3678..88ba8e8f3 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -1897,7 +1897,9 @@ declare namespace EventTypes_2 { MouseDoubleClickEventDetail, MouseDoubleClickEventType, MouseWheelEventDetail, - MouseWheelEventType + MouseWheelEventType, + VolumeScrollOutOfBoundsEventDetail, + VolumeScrollOutOfBoundsEventType } } @@ -5438,6 +5440,20 @@ export class VolumeRotateMouseWheelTool extends BaseTool { // @public (undocumented) type VolumeScalarData = Float32Array | Uint8Array | Uint16Array | Int16Array; +// @public (undocumented) +type VolumeScrollOutOfBoundsEventDetail = { + volumeId: string; + viewport: Types_2.IVolumeViewport; + desiredStepIndex: number; + currentStepIndex: number; + delta: number; + numScrollSteps: number; + currentImageId: string; +}; + +// @public (undocumented) +type VolumeScrollOutOfBoundsEventType = Types_2.CustomEventType; + // @public type VolumeViewportProperties = { voiRange?: VOIRange; diff --git a/packages/core/src/enums/Events.ts b/packages/core/src/enums/Events.ts index eb7e3890c..fd7f8c2e7 100644 --- a/packages/core/src/enums/Events.ts +++ b/packages/core/src/enums/Events.ts @@ -188,6 +188,11 @@ enum Events { */ GEOMETRY_CACHE_GEOMETRY_ADDED = 'CORNERSTONE_GEOMETRY_CACHE_GEOMETRY_ADDED', + /** + * Triggers when the scroll function is called with a delta that is out of bounds. + * This is usually for signaling that the user may want a different volume for partially loaded volumes which is meant to optimize memory. + */ + VOLUME_SCROLL_OUT_OF_BOUNDS = 'CORNERSTONE_VOLUME_SCROLL_OUT_OF_BOUNDS', // IMAGE_CACHE_FULL = 'CORNERSTONE_IMAGE_CACHE_FULL', // PRE_RENDER = 'CORNERSTONE_PRE_RENDER', // ELEMENT_RESIZED = 'CORNERSTONE_ELEMENT_RESIZED', diff --git a/packages/core/src/utilities/getVolumeViewportScrollInfo.ts b/packages/core/src/utilities/getVolumeViewportScrollInfo.ts index 4d7999cd2..e5a1677e4 100644 --- a/packages/core/src/utilities/getVolumeViewportScrollInfo.ts +++ b/packages/core/src/utilities/getVolumeViewportScrollInfo.ts @@ -11,10 +11,8 @@ function getVolumeViewportScrollInfo( viewport: IVolumeViewport, volumeId: string ) { - const { sliceRange, spacingInNormalDirection } = getVolumeSliceRangeInfo( - viewport, - volumeId - ); + const { sliceRange, spacingInNormalDirection, camera } = + getVolumeSliceRangeInfo(viewport, volumeId); const { min, max, current } = sliceRange; @@ -26,7 +24,15 @@ function getVolumeViewportScrollInfo( const floatingStepNumber = fraction * numScrollSteps; const currentStepIndex = Math.round(floatingStepNumber); - return { numScrollSteps, currentStepIndex }; + return { + numScrollSteps, + currentStepIndex, + sliceRangeInfo: { + sliceRange, + spacingInNormalDirection, + camera, + }, + }; } export default getVolumeViewportScrollInfo; diff --git a/packages/tools/src/types/EventTypes.ts b/packages/tools/src/types/EventTypes.ts index 81db58f72..e1414ecd8 100644 --- a/packages/tools/src/types/EventTypes.ts +++ b/packages/tools/src/types/EventTypes.ts @@ -380,6 +380,19 @@ type MouseWheelEventDetail = NormalizedInteractionEventDetail & points: IPoints; }; +/** + * Volume Scroll Out of Bounds event detail + */ +type VolumeScrollOutOfBoundsEventDetail = { + volumeId: string; + viewport: Types.IVolumeViewport; + desiredStepIndex: number; + currentStepIndex: number; + delta: number; // difference between the desired and current frame + numScrollSteps: number; // total scroll steps in the volume + currentImageId: string; // get ImageId (ImageIndex for in-plane acquisition) +}; + ///////////////////////////// // // @@ -585,6 +598,12 @@ type MouseDoubleClickEventType = */ type MouseWheelEventType = Types.CustomEventType; +/** + * Event for volume scroll out of bounds + */ +type VolumeScrollOutOfBoundsEventType = + Types.CustomEventType; + export { InteractionStartType, InteractionEndType, @@ -654,4 +673,6 @@ export { MouseDoubleClickEventType, MouseWheelEventDetail, MouseWheelEventType, + VolumeScrollOutOfBoundsEventDetail, + VolumeScrollOutOfBoundsEventType, }; diff --git a/packages/tools/src/utilities/scroll.ts b/packages/tools/src/utilities/scroll.ts index 48f96d808..605d5ce59 100644 --- a/packages/tools/src/utilities/scroll.ts +++ b/packages/tools/src/utilities/scroll.ts @@ -2,9 +2,11 @@ import { StackViewport, Types, VolumeViewport, + eventTarget, + EVENTS, utilities as csUtils, } from '@cornerstonejs/core'; -import { ScrollOptions } from '../types'; +import { ScrollOptions, EventTypes } from '../types'; /** * It scrolls one slice in the Stack or Volume Viewport, it uses the options provided @@ -36,7 +38,8 @@ export function scrollVolume( volumeId: string, delta: number ) { - const sliceRangeInfo = csUtils.getVolumeSliceRangeInfo(viewport, volumeId); + const { numScrollSteps, currentStepIndex, sliceRangeInfo } = + csUtils.getVolumeViewportScrollInfo(viewport, volumeId); if (!sliceRangeInfo) { return; @@ -59,4 +62,30 @@ export function scrollVolume( position: newPosition, }); viewport.render(); + + const desiredStepIndex = currentStepIndex + delta; + + if ( + (desiredStepIndex > numScrollSteps || desiredStepIndex < 0) && + viewport.getCurrentImageId() // Check that we are in the plane of acquistion + ) { + // One common use case of this trigger might be to load the next + // volume in a time series or the next segment of a partially loaded volume. + + const VolumeScrollEventDetail = { + volumeId, + viewport, + delta, + desiredStepIndex, + currentStepIndex, + numScrollSteps, + currentImageId: viewport.getCurrentImageId(), + }; + + csUtils.triggerEvent( + eventTarget, + EVENTS.VOLUME_SCROLL_OUT_OF_BOUNDS, + VolumeScrollEventDetail as EventTypes.VolumeScrollOutOfBoundsEventDetail + ); + } }