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
14 changes: 10 additions & 4 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, element?: HTMLDivElement): string;
sedghi marked this conversation as resolved.
Show resolved Hide resolved

// @public (undocumented)
const addCanvasPointsToArray: (element: HTMLDivElement, canvasPoints: Types_2.Point2[], newCanvasPoint: Types_2.Point2, commonData: PlanarFreehandROICommonData) => number;
Expand Down Expand Up @@ -1145,6 +1145,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 @@ -1865,7 +1867,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, FrameOfReferenceUID: string, element?: HTMLDivElement): Annotations;

// @public (undocumented)
function getAnnotationsLocked(): Array<Annotation>;
Expand Down Expand Up @@ -2030,6 +2032,9 @@ function getToolGroupSpecificConfig(toolGroupId: string): SegmentationRepresenta
// @public (undocumented)
function getToolGroupSpecificConfig_2(toolGroupId: string): SegmentationRepresentationConfig;

// @public (undocumented)
function getToolGroupsWithToolName(toolName: string): IToolGroup[] | [];

// @public (undocumented)
function getToolState(element: HTMLDivElement): CINETypes.ToolData | undefined;

Expand Down Expand Up @@ -3914,7 +3919,7 @@ interface ReferenceCursor extends Annotation {
export class ReferenceCursors extends AnnotationDisplayTool {
constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
// (undocumented)
_addAnnotation(element: HTMLDivElement, annotation: Annotation): string | null;
_addAnnotation(annotation: Annotation, FrameOfReferenceUID: string): string | null;
// (undocumented)
createInitialAnnotation: (worldPos: Types_2.Point3, element: HTMLDivElement) => void;
// (undocumented)
Expand Down Expand Up @@ -4737,7 +4742,8 @@ declare namespace ToolGroupManager {
destroyToolGroup,
getToolGroup,
getToolGroupForViewport,
getAllToolGroups
getAllToolGroups,
getToolGroupsWithToolName
}
}
export { ToolGroupManager }
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/stackViewport_cpu_render_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const {
setUseCPURendering,
resetUseCPURendering,
CONSTANTS,
getEnabledElement,
sedghi marked this conversation as resolved.
Show resolved Hide resolved
} = cornerstone3D;

const { Events, ViewportType } = Enums;
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/volumeViewport_gpu_render_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const {
volumeLoader,
utilities,
setVolumesForViewports,
getEnabledElement,
sedghi marked this conversation as resolved.
Show resolved Hide resolved
} = cornerstone3D;

const { ViewportType, Events } = Enums;
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
82 changes: 37 additions & 45 deletions packages/tools/src/stateManagement/annotation/annotationState.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {
getEnabledElement,
triggerEvent,
eventTarget,
utilities as csUtils,
} from '@cornerstonejs/core';
import { Events } from '../../enums';
import { Types } from '@cornerstonejs/core';
import { Events } from '../../enums';
import { defaultFrameOfReferenceSpecificAnnotationManager } from './FrameOfReferenceSpecificAnnotationManager';
import { Annotations, Annotation } from '../../types/AnnotationTypes';

import { AnnotationRemovedEventDetail } from '../../types/EventTypes';
import {
AnnotationAddedEventDetail,
AnnotationRemovedEventDetail,
} from '../../types/EventTypes';
triggerAnnotationAddedForElement,
triggerAnnotationAddedForFOR,
} from './helpers/state';

/**
* It returns the default annotations manager.
Expand Down Expand Up @@ -40,58 +39,59 @@ function getViewportSpecificAnnotationManager(
return defaultFrameOfReferenceSpecificAnnotationManager;
}

function getAnnotationManager(element?: HTMLDivElement) {
sedghi marked this conversation as resolved.
Show resolved Hide resolved
if (element) {
return getViewportSpecificAnnotationManager(element);
}

return getDefaultAnnotationManager();
}

/**
* Returns the annotations for the `FrameOfReference` of the `Viewport`
* being viewed by the cornerstone3D enabled `element`.
* Returns the annotations for a given tool in the specified frame of reference.
* If element is provided, it will return the annotations for the viewport specific
* annotation manager.
*
* @param element - The HTML element.
* @param toolName - The name of the tool.
* @param FrameOfReferenceUID - The Frame of Reference UID.
* @param element - The HTML element.
* @returns The annotations corresponding to the Frame of Reference and the toolName.
*/
function getAnnotations(
element: HTMLDivElement,
toolName: string
toolName: string,
FrameOfReferenceUID: string,
sedghi marked this conversation as resolved.
Show resolved Hide resolved
element?: HTMLDivElement
): Annotations {
const enabledElement = getEnabledElement(element);
const annotationManager =
getViewportSpecificAnnotationManager(enabledElement);
const { FrameOfReferenceUID } = enabledElement;

const annotationManager = getAnnotationManager(element);
return annotationManager.get(FrameOfReferenceUID, toolName);
}

/**
* Add the annotation to the annotations for the `FrameOfReference` of the `Viewport`
* being viewed by the cornerstone3D enabled `element`.
* Add the annotation to the annotation manager. If an element is provided,
* the annotation will be added to the viewport specific annotation manager.
*
* @param element - HTMLDivElement
* @param annotation - The annotation that is being added to the annotations manager.
* @param element - HTMLDivElement
*/
function addAnnotation(
element: HTMLDivElement,
annotation: Annotation
annotation: Annotation,
element?: HTMLDivElement
sedghi marked this conversation as resolved.
Show resolved Hide resolved
): string {
const annotationManager = getViewportSpecificAnnotationManager(element);
const annotationManager = getAnnotationManager(element);

if (annotation.annotationUID === undefined) {
annotation.annotationUID = csUtils.uuidv4() as string;
}

annotationManager.addAnnotation(annotation);

const enabledElement = getEnabledElement(element);
const { renderingEngine } = enabledElement;
const { viewportId } = enabledElement;

const eventType = Events.ANNOTATION_ADDED;

const eventDetail: AnnotationAddedEventDetail = {
annotation,
viewportId,
renderingEngineId: renderingEngine.id,
};
if (element) {
triggerAnnotationAddedForElement(annotation, element);
}

triggerEvent(eventTarget, eventType, eventDetail);
// if no element is provided, render all viewports that have the
// same frame of reference.
triggerAnnotationAddedForFOR(annotation);

return annotation.annotationUID;
}
Expand Down Expand Up @@ -126,11 +126,7 @@ function removeAnnotation(
annotationUID: string,
element?: HTMLDivElement
): void {
let annotationManager = getDefaultAnnotationManager();
if (element) {
annotationManager = getViewportSpecificAnnotationManager(element);
}

const annotationManager = getAnnotationManager(element);
const annotation = annotationManager.getAnnotation(annotationUID);

// no need to continue in case there is no annotation.
Expand Down Expand Up @@ -161,7 +157,7 @@ function getAnnotation(
annotationUID: string,
element?: HTMLDivElement
): Annotation {
const annotationManager = getViewportSpecificAnnotationManager(element);
const annotationManager = getAnnotationManager(element);
const annotation = annotationManager.getAnnotation(annotationUID);

return annotation;
Expand All @@ -173,11 +169,7 @@ function getAnnotation(
* specified it will use the default annotation manager.
*/
function removeAllAnnotations(element?: HTMLDivElement): void {
let annotationManager = getDefaultAnnotationManager();
if (element) {
annotationManager = getViewportSpecificAnnotationManager(element);
}

const annotationManager = getAnnotationManager(element);
annotationManager.removeAllAnnotations();
}

Expand Down
83 changes: 83 additions & 0 deletions packages/tools/src/stateManagement/annotation/helpers/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
getEnabledElement,
triggerEvent,
eventTarget,
getEnabledElementByIds,
} from '@cornerstonejs/core';
import { Events } from '../../../enums';
import { Annotation } from '../../../types/AnnotationTypes';
import { getToolGroupsWithToolName } from '../../../store/ToolGroupManager';
import { AnnotationAddedEventDetail } from '../../../types/EventTypes';

/**
* It triggers an event for the element when an annotation is added
* @param annotation - Annotation - The annotation that was added.
* @param element - The element that the annotation was added to.
*/
function triggerAnnotationAddedForElement(
annotation: Annotation,
element: HTMLDivElement
) {
const enabledElement = getEnabledElement(element);
const { renderingEngine, viewportId } = enabledElement;

const eventType = Events.ANNOTATION_ADDED;

const eventDetail: AnnotationAddedEventDetail = {
annotation,
viewportId,
renderingEngineId: renderingEngine.id,
};

triggerEvent(eventTarget, eventType, eventDetail);
}

/**
* If the annotation has a FrameOfReferenceUID, it triggers the ANNOTATION_ADDED
* event for all the viewports that has the same FrameOfReferenceUID.
* @param annotation - Annotation - The annotation that was added
*/
function triggerAnnotationAddedForFOR(annotation: Annotation) {
const { toolName } = annotation.metadata;

const toolGroups = getToolGroupsWithToolName(toolName);

if (!toolGroups.length) {
return;
}

// Find the viewports in the toolGroups who has the same FrameOfReferenceUID
const viewportsToRender = [];

toolGroups.forEach((toolGroup) => {
toolGroup.viewportsInfo.forEach((viewportInfo) => {
const { renderingEngineId, viewportId } = viewportInfo;
const { FrameOfReferenceUID } = getEnabledElementByIds(
viewportId,
renderingEngineId
);

if (annotation.metadata.FrameOfReferenceUID === FrameOfReferenceUID) {
viewportsToRender.push(viewportInfo);
}
});
});

if (!viewportsToRender.length) {
return;
}

const eventType = Events.ANNOTATION_ADDED;

viewportsToRender.forEach(({ renderingEngineId, viewportId }) => {
const eventDetail: AnnotationAddedEventDetail = {
annotation,
viewportId,
renderingEngineId,
};

triggerEvent(eventTarget, eventType, eventDetail);
});
}

export { triggerAnnotationAddedForElement, triggerAnnotationAddedForFOR };
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { state } from '../index';
import { IToolGroup } from '../../types';
import { ToolModes } from '../../enums';

const MODES = [ToolModes.Active, ToolModes.Passive, ToolModes.Enabled];

/**
* Returns the toolGroups that has the given toolName as active, passive
* or enabled.
* @param toolName - The name of the tool
* @returns An array of tool groups.
*/
function getToolGroupsWithToolName(toolName: string): IToolGroup[] | [] {
return state.toolGroups.filter(({ toolOptions }) => {
const toolGroupToolNames = Object.keys(toolOptions);

for (let i = 0; i < toolGroupToolNames.length; i++) {
if (toolName !== toolGroupToolNames[i]) continue;

/* filter out tools that don't have options */
if (!toolOptions[toolName]) {
continue;
}

if (MODES.includes(toolOptions[toolName].mode)) {
return true;
}
}
return false;
});
}

export default getToolGroupsWithToolName;
2 changes: 2 additions & 0 deletions packages/tools/src/store/ToolGroupManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import destroy from './destroy';
import getToolGroup from './getToolGroup';
import getToolGroupForViewport from './getToolGroupForViewport';
import getAllToolGroups from './getAllToolGroups';
import getToolGroupsWithToolName from './getToolGroupsWithToolName';

export {
createToolGroup,
Expand All @@ -12,4 +13,5 @@ export {
getToolGroup,
getToolGroupForViewport,
getAllToolGroups,
getToolGroupsWithToolName,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getAnnotations } from '../stateManagement/annotation/annotationState';
import { ToolAnnotationsPair } from '../types/InternalToolTypes';
import type AnnotationTool from '../tools/base/AnnotationTool';
import BaseTool from '../tools/base/BaseTool';
import { getEnabledElement } from '@cornerstonejs/core';

/**
* Filters an array of tools, returning only tools which have annotation.
Expand All @@ -16,6 +17,7 @@ export default function filterToolsWithAnnotationsForElement(
tools: AnnotationTool[]
): ToolAnnotationsPair[] {
const result = [];
const {FrameOfReferenceUID} = getEnabledElement(element)

for (let i = 0; i < tools.length; i++) {
const tool = tools[i];
Expand All @@ -26,8 +28,8 @@ export default function filterToolsWithAnnotationsForElement(
}

let annotations = getAnnotations(
element,
(tool.constructor as typeof BaseTool).toolName
(tool.constructor as typeof BaseTool).toolName,
FrameOfReferenceUID,
);

if (!annotations) {
Expand Down
2 changes: 1 addition & 1 deletion packages/tools/src/store/removeEnabledElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const _removeAllToolsForElement = function (element) {
const toolsWithData = filterToolsWithAnnotationsForElement(element, tools);
toolsWithData.forEach(({ annotations }) => {
annotations.forEach((annotation) => {
removeAnnotation(annotation.annotationUID, element);
removeAnnotation(annotation.annotationUID);
});
});
};
Expand Down
Loading