diff --git a/packages/tools/examples/petCt/index.ts b/packages/tools/examples/petCt/index.ts index 69352891df..c8c378d4f4 100644 --- a/packages/tools/examples/petCt/index.ts +++ b/packages/tools/examples/petCt/index.ts @@ -390,7 +390,9 @@ function setUpToolGroups() { // Only set CT volume to MIP in the fusion viewport filterActorUIDsToSetSlabThickness: [ctVolumeId], }); - fusionToolGroup.addTool(RectangleROITool.toolName); + fusionToolGroup.addTool(RectangleROITool.toolName, { + isPreferredTargetId: RectangleROITool.isSpecifiedTargetId(ptVolumeId), + }); // Here is the difference in the toolGroups used, that we need to specify the // volume to use for the WindowLevelTool for the fusion viewports diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 8a7ef406ba..3c9b109752 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -607,8 +607,6 @@ class CircleROITool extends AnnotationTool { return renderStatus; } - const targetId = this.getTargetId(viewport); - const renderingEngine = viewport.getRenderingEngine(); const styleSpecifier: StyleSpecifier = { @@ -623,6 +621,7 @@ class CircleROITool extends AnnotationTool { const { handles } = data; const { points, activeHandleIndex } = handles; + const targetId = this.getTargetId(viewport, data); styleSpecifier.annotationUID = annotationUID; const { color, lineWidth, lineDash } = this.getAnnotationStyle({ diff --git a/packages/tools/src/tools/annotation/CobbAngleTool.ts b/packages/tools/src/tools/annotation/CobbAngleTool.ts index d015113622..e183f2bf5a 100644 --- a/packages/tools/src/tools/annotation/CobbAngleTool.ts +++ b/packages/tools/src/tools/annotation/CobbAngleTool.ts @@ -667,7 +667,6 @@ class CobbAngleTool extends AnnotationTool { return renderStatus; } - const targetId = this.getTargetId(viewport); const renderingEngine = viewport.getRenderingEngine(); const styleSpecifier: StyleSpecifier = { @@ -682,6 +681,7 @@ class CobbAngleTool extends AnnotationTool { const { annotationUID, data } = annotation; const { points, activeHandleIndex } = data.handles; + const targetId = this.getTargetId(viewport, data); styleSpecifier.annotationUID = annotationUID; const { color, lineWidth, lineDash } = this.getAnnotationStyle({ diff --git a/packages/tools/src/tools/annotation/DragProbeTool.ts b/packages/tools/src/tools/annotation/DragProbeTool.ts index e96067ee29..0943e98c7c 100644 --- a/packages/tools/src/tools/annotation/DragProbeTool.ts +++ b/packages/tools/src/tools/annotation/DragProbeTool.ts @@ -136,7 +136,6 @@ class DragProbeTool extends ProbeTool { return renderStatus; } - const targetId = this.getTargetId(viewport); const renderingEngine = viewport.getRenderingEngine(); const styleSpecifier: StyleSpecifier = { @@ -151,6 +150,7 @@ class DragProbeTool extends ProbeTool { const point = data.handles.points[0]; const canvasCoordinates = viewport.worldToCanvas(point); + const targetId = this.getTargetId(viewport, data); styleSpecifier.annotationUID = annotationUID; const { color } = this.getAnnotationStyle({ diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 8a37a3a202..1b434ebc9d 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -773,8 +773,6 @@ class EllipticalROITool extends AnnotationTool { return renderStatus; } - const targetId = this.getTargetId(viewport); - const renderingEngine = viewport.getRenderingEngine(); const styleSpecifier: StyleSpecifier = { @@ -789,6 +787,7 @@ class EllipticalROITool extends AnnotationTool { const { handles } = data; const { points, activeHandleIndex } = handles; + const targetId = this.getTargetId(viewport, data); styleSpecifier.annotationUID = annotationUID; const { color, lineWidth, lineDash } = this.getAnnotationStyle({ diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index c6d727dde6..bb34971623 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -1064,7 +1064,7 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool { svgDrawingHelper ) => { const { data } = annotation; - const targetId = this.getTargetId(viewport); + const targetId = this.getTargetId(viewport, data); const styleSpecifier: AnnotationStyle.StyleSpecifier = { toolGroupId: this.toolGroupId, diff --git a/packages/tools/src/tools/annotation/ProbeTool.ts b/packages/tools/src/tools/annotation/ProbeTool.ts index a803d5632f..dee48a2ac6 100644 --- a/packages/tools/src/tools/annotation/ProbeTool.ts +++ b/packages/tools/src/tools/annotation/ProbeTool.ts @@ -436,7 +436,6 @@ class ProbeTool extends AnnotationTool { return renderStatus; } - const targetId = this.getTargetId(viewport); const renderingEngine = viewport.getRenderingEngine(); const styleSpecifier: StyleSpecifier = { @@ -452,6 +451,7 @@ class ProbeTool extends AnnotationTool { const point = data.handles.points[0]; const canvasCoordinates = viewport.worldToCanvas(point); + const targetId = this.getTargetId(viewport, data); styleSpecifier.annotationUID = annotationUID; const { color, lineWidth } = this.getAnnotationStyle({ diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index 30fc8de5dc..7e0c034c3f 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -607,7 +607,6 @@ class RectangleROITool extends AnnotationTool { return renderStatus; } - const targetId = this.getTargetId(viewport); const renderingEngine = viewport.getRenderingEngine(); const styleSpecifier: StyleSpecifier = { @@ -622,6 +621,7 @@ class RectangleROITool extends AnnotationTool { const { points, activeHandleIndex } = data.handles; const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p)); + const targetId = this.getTargetId(viewport, data); styleSpecifier.annotationUID = annotationUID; const { color, lineWidth, lineDash } = this.getAnnotationStyle({ diff --git a/packages/tools/src/tools/base/BaseTool.ts b/packages/tools/src/tools/base/BaseTool.ts index 3baf6d507b..ce3de62e75 100644 --- a/packages/tools/src/tools/base/BaseTool.ts +++ b/packages/tools/src/tools/base/BaseTool.ts @@ -2,7 +2,12 @@ import { utilities } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; import ToolModes from '../../enums/ToolModes'; import type StrategyCallbacks from '../../enums/StrategyCallbacks'; -import type { InteractionTypes, ToolProps, PublicToolProps } from '../../types'; +import type { + InteractionTypes, + ToolProps, + PublicToolProps, + ToolConfiguration, +} from '../../types'; const { DefaultHistoryMemo } = utilities.HistoryMemo; @@ -15,8 +20,17 @@ abstract class BaseTool { static toolName; /** Supported Interaction Types - currently only Mouse */ public supportedInteractionTypes: InteractionTypes[]; + /** + * The configuration for this tool. + * IBaseTool contains some default configuration values, and you can use + * configurationTyped to get the typed version of this. + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any public configuration: Record; + public get configurationTyped() { + return this.configuration; + } + /** ToolGroup ID the tool instance belongs to */ public toolGroupId: string; /** Tool Mode - Active/Passive/Enabled/Disabled/ */ @@ -76,6 +90,18 @@ abstract class BaseTool { return utilities.deepMerge(defaultProps, additionalProps); } + /** + * A function generator to test if the target id is the desired one. + * Used for deciding which set of cached stats is appropriate to display + * for a given viewport. + */ + public static isSpecifiedTargetId(desiredVolumeId: string) { + // imageId including the target id is a proxy for testing if the + // image id is a member of that volume. This may need to be fixed in the + // future to add more criteria. + return (_viewport, { imageId }) => imageId.includes(desiredVolumeId); + } + /** * Newer method for getting the tool name as a property */ @@ -228,18 +254,36 @@ abstract class BaseTool { /** * Get the target Id for the viewport which will be used to store the cached * statistics scoped to that target in the annotations. - * For StackViewport, targetId is the viewportId, but for the volume viewport, - * the targetId will be grabbed from the volumeId if particularly specified - * in the tool configuration, or if not, the first actorUID in the viewport. + * For StackViewport, targetId is usually derived from the imageId. + * For VolumeViewport, it's derived from the volumeId. + * This method allows prioritizing a specific volumeId from the tool's + * configuration if available in the cachedStats. * * @param viewport - viewport to get the targetId for + * @param data - Optional: The annotation's data object, containing cachedStats. * @returns targetId */ - protected getTargetId(viewport: Types.IViewport): string | undefined { - const targetId = viewport.getViewReferenceId?.(); - if (targetId) { - return targetId; + protected getTargetId( + viewport: Types.IViewport, + data?: unknown & { cachedStats?: Record } + ): string | undefined { + const { isPreferredTargetId } = this.configurationTyped; // Get preferred ID from config + + // Check if cachedStats is available and contains the preferredVolumeId + if (isPreferredTargetId && data?.cachedStats) { + for (const [imageId, cachedStat] of Object.entries(data.cachedStats)) { + if (isPreferredTargetId(viewport, { imageId, cachedStat })) { + return imageId; + } + } } + + // If not found or not applicable, use the viewport's default method + const defaultTargetId = viewport.getViewReferenceId?.(); + if (defaultTargetId) { + return defaultTargetId; + } + throw new Error( 'getTargetId: viewport must have a getViewReferenceId method' ); diff --git a/packages/tools/src/types/IBaseTool.ts b/packages/tools/src/types/IBaseTool.ts index 8eba34df01..9cf8c653fc 100644 --- a/packages/tools/src/types/IBaseTool.ts +++ b/packages/tools/src/types/IBaseTool.ts @@ -1,3 +1,37 @@ import type BaseTool from '../tools/base/BaseTool'; +/** + * General tool configuration. This is intended to be extended + * by various tools to add the different configuration options. + */ +export interface ToolConfiguration { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + strategies: any; + defaultStrategy?: string; + activeStrategy?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + strategyOptions: any; + + /** + * @returns true if the given targetId is preferred. + */ + isPreferredTargetId?: ( + viewport, + /** + * The target info is a specifier for different types of target information. + * Right now there is just the single option consisting of an image id and + * cached stat, but in the future other alternatives might be provided. + */ + targetInfo: { + /** + * The imageId of a cachedStat instance. This isn't the only way to + * identify data, but is one possible option. + */ + imageId: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cachedStat: any; + } + ) => boolean; +} + export type IBaseTool = BaseTool;