Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(annotations): rework annotation manager api and enable multi-manager setup #442

Merged
merged 17 commits into from
Mar 1, 2023
107 changes: 72 additions & 35 deletions common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type ActorSliceRange = {
};

// @public (undocumented)
function addAnnotation(element: HTMLDivElement, annotation: Annotation): string;
function addAnnotation(annotation: Annotation, annotationGroupSelector: AnnotationGroupSelector): string;

// @public (undocumented)
const addCanvasPointsToArray: (element: HTMLDivElement, canvasPoints: Types_2.Point2[], newCanvasPoint: Types_2.Point2, commonData: PlanarFreehandROICommonData) => number;
Expand Down Expand Up @@ -221,6 +221,9 @@ type AnnotationCompletedEventDetail = {
// @public (undocumented)
type AnnotationCompletedEventType = Types_2.CustomEventType<AnnotationCompletedEventDetail>;

// @public (undocumented)
type AnnotationGroupSelector = HTMLDivElement | string;

// @public (undocumented)
type AnnotationHandle = Types_2.Point3;

Expand Down Expand Up @@ -278,7 +281,7 @@ type AnnotationSelectionChangeEventType = Types_2.CustomEventType<AnnotationSele

// @public (undocumented)
type AnnotationState = {
[key: string]: FrameOfReferenceSpecificAnnotations;
[key: string]: GroupSpecificAnnotations;
};

declare namespace AnnotationStyle {
Expand Down Expand Up @@ -1145,6 +1148,8 @@ export class CrosshairsTool extends AnnotationTool {
// (undocumented)
_filterViewportWithSameOrientation: (enabledElement: any, referenceAnnotation: any, annotations: any) => any;
// (undocumented)
_getAnnotations: (enabledElement: Types_2.IEnabledElement) => Annotations;
// (undocumented)
_getAnnotationsForViewportsWithDifferentCameras: (enabledElement: any, annotations: any) => any;
// (undocumented)
getHandleNearImagePoint(element: HTMLDivElement, annotation: Annotation, canvasCoords: Types_2.Point2, proximity: number): ToolHandle | undefined;
Expand Down Expand Up @@ -1808,41 +1813,38 @@ type FloodFillResult = {
};

// @public (undocumented)
class FrameOfReferenceSpecificAnnotationManager {
class FrameOfReferenceSpecificAnnotationManager implements IAnnotationManager {
constructor(uid?: string);
// (undocumented)
addAnnotation: (annotation: Annotation) => void;
addAnnotation: (annotation: Annotation, groupKey?: string) => void;
// (undocumented)
get: (FrameOfReferenceUID: string, toolName: string) => Annotations | undefined;
getAnnotation: (annotationUID: string) => Annotation | undefined;
// (undocumented)
getAnnotation: (annotationUID: string, filter?: FilterInterface) => Annotation | undefined;
// (undocumented)
getFrameOfReferenceAnnotations: (FrameOfReferenceUID: string) => FrameOfReferenceSpecificAnnotations;
getAnnotations: (groupKey: string, toolName?: string) => GroupSpecificAnnotations | Annotations;
// (undocumented)
getFramesOfReference: () => Array<string>;
// (undocumented)
getNumberOfAnnotations: (toolName: string, frameOfReferenceUID?: string) => number;
getGroupKey: (annotationGroupSelector: AnnotationGroupSelector) => string;
// (undocumented)
getNumberOfAllAnnotations: () => number;
// (undocumented)
getNumberOfAnnotationsInFrameOfReference: (toolName: string, frameOfReferenceUID: string) => number;
getNumberOfAnnotations: (groupKey: string, toolName?: string) => number;
// (undocumented)
_imageVolumeModifiedHandler: (evt: Types_2.EventTypes.ImageVolumeModifiedEvent) => void;
// (undocumented)
removeAllAnnotations: () => void;
// (undocumented)
removeAnnotation: (annotationUID: string, filter?: FilterInterface) => void;
removeAnnotation: (annotationUID: string) => void;
// (undocumented)
restoreAnnotations: (state: AnnotationState | FrameOfReferenceSpecificAnnotations | Annotations, FrameOfReferenceUID?: string, toolName?: string) => void;
removeAnnotations: (groupKey: string, toolName?: string) => void;
// (undocumented)
saveAnnotations: (FrameOfReferenceUID?: string, toolName?: string) => AnnotationState | FrameOfReferenceSpecificAnnotations | Annotations;
restoreAnnotations: (state: AnnotationState | GroupSpecificAnnotations | Annotations, groupKey?: string, toolName?: string) => void;
// (undocumented)
saveAnnotations: (groupKey?: string, toolName?: string) => AnnotationState | GroupSpecificAnnotations | Annotations;
// (undocumented)
readonly uid: string;
}

// @public (undocumented)
type FrameOfReferenceSpecificAnnotations = {
[key: string]: Annotations;
};

// @public (undocumented)
function getActiveSegmentationRepresentation(toolGroupId: string): ToolGroupSpecificRepresentation;

Expand All @@ -1856,7 +1858,10 @@ function getAllSynchronizers(): Array<Synchronizer>;
function getAllToolGroups(): Array<IToolGroup>;

// @public (undocumented)
function getAnnotation(annotationUID: string, element?: HTMLDivElement): Annotation;
function getAnnotation(annotationUID: string): Annotation;

// @public (undocumented)
function getAnnotationManager(): FrameOfReferenceSpecificAnnotationManager;

// @public (undocumented)
function getAnnotationNearPoint(element: HTMLDivElement, canvasPoint: Types_2.Point2, proximity?: number): Annotation | null;
Expand All @@ -1865,7 +1870,7 @@ function getAnnotationNearPoint(element: HTMLDivElement, canvasPoint: Types_2.Po
function getAnnotationNearPointOnEnabledElement(enabledElement: Types_2.IEnabledElement, point: Types_2.Point2, proximity: number): Annotation | null;

// @public (undocumented)
function getAnnotations(element: HTMLDivElement, toolName: string): Annotations;
function getAnnotations(toolName: string, annotationGroupSelector: AnnotationGroupSelector): Annotations;

// @public (undocumented)
function getAnnotationsLocked(): Array<Annotation>;
Expand Down Expand Up @@ -1915,9 +1920,6 @@ function getConfiguration(): {
preserveExistingPool: boolean;
};

// @public (undocumented)
function getDefaultAnnotationManager(): FrameOfReferenceSpecificAnnotationManager;

// @public (undocumented)
function getDefaultRepresentationConfig(segmentation: Segmentation): LabelmapConfig;

Expand Down Expand Up @@ -1961,7 +1963,7 @@ function getMeanPoints(points: IPoints[]): IPoints;
function getMeanTouchPoints(points: ITouchPoints[]): ITouchPoints;

// @public (undocumented)
function getNumberOfAnnotations(toolName: string, frameOfReferenceUID?: string): number;
function getNumberOfAnnotations(toolName: string, annotationGroupSelector: AnnotationGroupSelector): number;

// @public (undocumented)
function getOrientationStringLPS(vector: Types_2.Point3): string;
Expand Down Expand Up @@ -2031,23 +2033,50 @@ function getToolGroupSpecificConfig(toolGroupId: string): SegmentationRepresenta
function getToolGroupSpecificConfig_2(toolGroupId: string): SegmentationRepresentationConfig;

// @public (undocumented)
function getToolState(element: HTMLDivElement): CINETypes.ToolData | undefined;
function getToolGroupsWithToolName(toolName: string): IToolGroup[] | [];

// @public (undocumented)
function getViewportIdsWithToolToRender(element: HTMLDivElement, toolName: string, requireParallelNormals?: boolean): string[];
function getToolState(element: HTMLDivElement): CINETypes.ToolData | undefined;

// @public (undocumented)
function getViewportSpecificAnnotationManager(element?: Types_2.IEnabledElement | HTMLDivElement): FrameOfReferenceSpecificAnnotationManager;
function getViewportIdsWithToolToRender(element: HTMLDivElement, toolName: string, requireParallelNormals?: boolean): string[];

// @public (undocumented)
function getWorldWidthAndHeightFromCorners(viewPlaneNormal: Types_2.Point3, viewUp: Types_2.Point3, topLeftWorld: Types_2.Point3, bottomRightWorld: Types_2.Point3): {
worldWidth: number;
worldHeight: number;
};

// @public (undocumented)
type GroupSpecificAnnotations = {
[toolName: string]: Annotations;
};

// @public (undocumented)
function hideElementCursor(element: HTMLDivElement): void;

// @public (undocumented)
interface IAnnotationManager {
// (undocumented)
addAnnotation: (annotation: Annotation, groupKey: string) => void;
// (undocumented)
getAnnotation: (annotationUID: string) => Annotation;
// (undocumented)
getAnnotations: (groupKey: string, toolName?: string) => GroupSpecificAnnotations | Annotations;
// (undocumented)
getGroupKey: (annotationGroupSelector: AnnotationGroupSelector) => string;
// (undocumented)
getNumberOfAllAnnotations: () => number;
// (undocumented)
getNumberOfAnnotations: (groupKey: string, toolName?: string) => number;
// (undocumented)
removeAllAnnotations: () => void;
// (undocumented)
removeAnnotation: (annotationUID: string) => void;
// (undocumented)
removeAnnotations: (groupKey: string) => void;
}

// @public (undocumented)
interface ICache {
getCacheSize: () => number;
Expand Down Expand Up @@ -3914,8 +3943,6 @@ interface ReferenceCursor extends Annotation {
export class ReferenceCursors extends AnnotationDisplayTool {
constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
// (undocumented)
_addAnnotation(element: HTMLDivElement, annotation: Annotation): string | null;
// (undocumented)
createInitialAnnotation: (worldPos: Types_2.Point3, element: HTMLDivElement) => void;
// (undocumented)
_currentCanvasPosition: null | Types_2.Point2;
Expand Down Expand Up @@ -4011,10 +4038,10 @@ function registerCursor(toolName: string, iconContent: string, viewBox: {
}): void;

// @public (undocumented)
function removeAllAnnotations(element?: HTMLDivElement): void;
function removeAllAnnotations(): void;

// @public (undocumented)
function removeAnnotation(annotationUID: string, element?: HTMLDivElement): void;
function removeAnnotation(annotationUID: string): void;

// @public (undocumented)
function removeColorLUT(colorLUTIndex: number): void;
Expand Down Expand Up @@ -4043,6 +4070,9 @@ type RepresentationPublicInput = {
type: Enums.SegmentationRepresentations;
};

// @public (undocumented)
function resetAnnotationManager(): void;

// @public (undocumented)
function resetElementCursor(element: HTMLDivElement): void;

Expand Down Expand Up @@ -4320,6 +4350,9 @@ function setActiveSegmentIndex(segmentationId: string, segmentIndex: number): vo
// @public (undocumented)
function setAnnotationLocked(annotation: Annotation, locked?: boolean): void;

// @public (undocumented)
function setAnnotationManager(annotationManager: any): void;

// @public (undocumented)
function setAnnotationSelected(annotationUID: string, selected?: boolean, preserveSelected?: boolean): void;

Expand Down Expand Up @@ -4530,8 +4563,9 @@ declare namespace state_2 {
getAnnotation,
removeAnnotation,
removeAllAnnotations,
getViewportSpecificAnnotationManager,
getDefaultAnnotationManager
setAnnotationManager,
getAnnotationManager,
resetAnnotationManager
}
}

Expand Down Expand Up @@ -4737,7 +4771,8 @@ declare namespace ToolGroupManager {
destroyToolGroup,
getToolGroup,
getToolGroupForViewport,
getAllToolGroups
getAllToolGroups,
getToolGroupsWithToolName
}
}
export { ToolGroupManager }
Expand Down Expand Up @@ -4955,11 +4990,13 @@ declare namespace Types {
export {
Annotation,
Annotations,
FrameOfReferenceSpecificAnnotations,
IAnnotationManager,
GroupSpecificAnnotations,
AnnotationState,
AnnotationStyle,
ToolSpecificAnnotationTypes,
JumpToSliceOptions,
AnnotationGroupSelector,
PlanarBoundingBox,
ToolProps,
PublicToolProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
id: annotationManager
title: Annotation Manager
---

The Annotation Manager is a singleton class that manages annotations in Cornerstone Tools.
We use the Annotation Manager to store annotations, retrieve annotations, and save and restore annotations.

## Default Annotation Manager
The default Annotation Manager, `FrameOfReferenceSpecificAnnotationManager`, stores annotations based on the FrameOfReferenceUID.
This means that annotations are stored separately for each FrameOfReferenceUID.

Currently in our rendering pipeline, if two VolumeViewports share the same
FrameOfReferenceUID, they will share the same annotations. However, StackViewports
works on the per imageId basis, so annotations are not shared between StackViewports.

### GroupKey
Annotation groups are identified by a groupKey. The groupKey is a string that is used to identify the group of annotations.
As mentioned above, the default Annotation Manager stores annotations based on the FrameOfReferenceUID, so the groupKey is the `FrameOfReferenceUID`.



## Custom Annotation Manager

You can create your own custom Annotation Manager by implementing the `IAnnotationManager` interface:

```ts
interface IAnnotationManager {
getGroupKey: (annotationGroupSelector: any) => string;
getAnnotations: (
groupKey: string,
toolName?: string
) => Annotations | GroupSpecificAnnotations | undefined;
addAnnotation: (annotation: Annotation, groupKey?: string) => void;
removeAnnotation: (annotationUID: string) => void;
removeAnnotations: (groupKey: string, toolName?: string) => void;
saveAnnotations: (
groupKey?: string,
toolName?: string
) => AnnotationState | GroupSpecificAnnotations | Annotations;
restoreAnnotations: (
state: AnnotationState | GroupSpecificAnnotations | Annotations,
groupKey?: string,
toolName?: string
) => void;
getNumberOfAllAnnotations: () => number;
removeAllAnnotations: () => void;
}
```

To use the Annotation Manager, you can set it as the default Annotation Manager using

```js
import { annotation } from '@cornerstonejs/tools';
import myCustomAnnotationManager from './myCustomAnnotationManager';

annotation.state.setAnnotationManager(myCustomAnnotationManager);
```

The most important method in a custom Annotation Manager is the `getGroupKey` method.
This method is used to determine the groupKey for a given element. For instance,
if you have a usecase to show two separate annotations (e.g. two different readers)
on two viewports that share the same FrameOfReferenceUID, you can use the `getGroupKey`
method to return a different groupKey for each viewport given the element. (certainly
you don't want to share the same annotations between the two viewports).
1 change: 1 addition & 0 deletions packages/docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ module.exports = {
},
items: [
'concepts/cornerstone-tools/annotation/state',
'concepts/cornerstone-tools/annotation/annotationManager',
'concepts/cornerstone-tools/annotation/selection',
'concepts/cornerstone-tools/annotation/locking',
'concepts/cornerstone-tools/annotation/config',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const { locking, selection } = annotation;
const { ViewportType } = Enums;

const defaultFrameOfReferenceSpecificAnnotationManager =
annotation.state.getDefaultAnnotationManager();
annotation.state.getAnnotationManager();

const viewportId = 'CT_STACK';

Expand Down
2 changes: 1 addition & 1 deletion packages/tools/examples/cancelAnnotationDrawing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const cancelToolDrawingAndRemove = (evt) => {
const { element, key } = evt.detail;
if (key === 'Escape') {
const annotationUID = cornerstoneTools.cancelActiveManipulations(element);
cornerstoneTools.annotation.state.removeAnnotation(annotationUID, element);
cornerstoneTools.annotation.state.removeAnnotation(annotationUID);
}
};

Expand Down
2 changes: 1 addition & 1 deletion packages/tools/examples/planarFreehandROITool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const { ViewportType } = Enums;
const { MouseBindings } = csToolsEnums;
const { selection } = annotation;
const defaultFrameOfReferenceSpecificAnnotationManager =
annotation.state.getDefaultAnnotationManager();
annotation.state.getAnnotationManager();

// Define a unique id for the volume
const volumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix
Expand Down
4 changes: 2 additions & 2 deletions packages/tools/src/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { eventTarget, Enums } from '@cornerstonejs/core';
import { getDefaultAnnotationManager } from './stateManagement/annotation/annotationState';
import { getAnnotationManager } from './stateManagement/annotation/annotationState';
import { getDefaultSegmentationStateManager } from './stateManagement/segmentation/segmentationState';
import { Events as TOOLS_EVENTS } from './enums';
import { addEnabledElement, removeEnabledElement } from './store';
Expand Down Expand Up @@ -51,7 +51,7 @@ export function destroy(): void {
resetCornerstoneToolsState();

// remove all annotation.
const annotationManager = getDefaultAnnotationManager();
const annotationManager = getAnnotationManager();
const segmentationStateManager = getDefaultSegmentationStateManager();

annotationManager.restoreAnnotations({});
Expand Down
Loading