Skip to content

Commit

Permalink
feat(annotations): rework annotation manager api and enable multi-man…
Browse files Browse the repository at this point in the history
…ager setup (#442)

* fix the get annotations parameters

* remove unnecessary element from remove annotation api

* fix tests

* fix karma

* apply review comments

* add feature to add and get annotation manager

* initial re-write of the annotation manager

* modifty annotation state

* add annotations

* add annotation

* finish

* update api

* fix tests

* useless param

* go back to element

* go back to element for add annotation

* fix tests
  • Loading branch information
sedghi authored Mar 1, 2023
1 parent b540539 commit 60bd013
Show file tree
Hide file tree
Showing 59 changed files with 963 additions and 804 deletions.
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 @@ -3927,8 +3956,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 @@ -4024,10 +4051,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 @@ -4056,6 +4083,9 @@ type RepresentationPublicInput = {
type: Enums.SegmentationRepresentations;
};

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

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

Expand Down Expand Up @@ -4333,6 +4363,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 @@ -4543,8 +4576,9 @@ declare namespace state_2 {
getAnnotation,
removeAnnotation,
removeAllAnnotations,
getViewportSpecificAnnotationManager,
getDefaultAnnotationManager
setAnnotationManager,
getAnnotationManager,
resetAnnotationManager
}
}

Expand Down Expand Up @@ -4750,7 +4784,8 @@ declare namespace ToolGroupManager {
destroyToolGroup,
getToolGroup,
getToolGroupForViewport,
getAllToolGroups
getAllToolGroups,
getToolGroupsWithToolName
}
}
export { ToolGroupManager }
Expand Down Expand Up @@ -4968,11 +5003,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

0 comments on commit 60bd013

Please sign in to comment.