diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 39ab0f21a..c51d3030c 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -1000,6 +1000,8 @@ enum ChangeTypes { MetadataReferenceModified = "MetadataReferenceModified", // (undocumented) StatsUpdated = "StatsUpdated" + // (undocumented) + LabelChange = 'LabelChange', } // @public (undocumented) diff --git a/packages/tools/src/enums/ChangeTypes.ts b/packages/tools/src/enums/ChangeTypes.ts index cc7e05558..4f77cd978 100644 --- a/packages/tools/src/enums/ChangeTypes.ts +++ b/packages/tools/src/enums/ChangeTypes.ts @@ -42,6 +42,10 @@ enum ChangeTypes { * so as to cause it to be drawn on the change, or removed if it is no longer visible. */ MetadataReferenceModified = 'MetadataReferenceModified', + /** + * Occurs when an annotation label is updated. + */ + LabelChange = 'LabelChange', } export default ChangeTypes; diff --git a/packages/tools/src/tools/SculptorTool.ts b/packages/tools/src/tools/SculptorTool.ts index e4bb43585..0188bf77b 100644 --- a/packages/tools/src/tools/SculptorTool.ts +++ b/packages/tools/src/tools/SculptorTool.ts @@ -10,7 +10,12 @@ import type { ContourAnnotation, } from '../types'; import { point } from '../utilities/math'; -import { Events, ToolModes, AnnotationStyleStates } from '../enums'; +import { + Events, + ToolModes, + AnnotationStyleStates, + ChangeTypes, +} from '../enums'; import { triggerAnnotationRenderForViewportIds } from '../utilities/triggerAnnotationRenderForViewportIds'; import { hideElementCursor, @@ -428,7 +433,11 @@ class SculptorTool extends BaseTool { activeAnnotation.invalidated = true; } - triggerAnnotationModified(activeAnnotation, element); + triggerAnnotationModified( + activeAnnotation, + element, + ChangeTypes.HandlesUpdated + ); }; /** diff --git a/packages/tools/src/tools/annotation/AngleTool.ts b/packages/tools/src/tools/annotation/AngleTool.ts index a3ee1328b..3120398e1 100644 --- a/packages/tools/src/tools/annotation/AngleTool.ts +++ b/packages/tools/src/tools/annotation/AngleTool.ts @@ -1,4 +1,4 @@ -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getEnabledElement, utilities as csUtils, @@ -477,6 +477,14 @@ class AngleTool extends AnnotationTool { const { renderingEngine } = enabledElement; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; cancel = (element: HTMLDivElement) => { @@ -874,7 +882,7 @@ class AngleTool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; } diff --git a/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts b/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts index d2cc7951a..32b0a1fdd 100644 --- a/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts +++ b/packages/tools/src/tools/annotation/ArrowAnnotateTool.ts @@ -1,4 +1,4 @@ -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getEnabledElement, utilities as csUtils, @@ -45,6 +45,7 @@ import type { import type { ArrowAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import type { StyleSpecifier } from '../../types/AnnotationStyle'; import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility'; +import { setAnnotationLabel } from '../../utilities'; class ArrowAnnotateTool extends AnnotationTool { static toolName = 'ArrowAnnotate'; @@ -352,8 +353,13 @@ class ArrowAnnotateTool extends AnnotationTool { const eventDetail = evt.detail; const { element } = eventDetail; - const { annotation, viewportIdsToRender, newAnnotation, hasMoved } = - this.editData; + const { + annotation, + viewportIdsToRender, + newAnnotation, + hasMoved, + movingTextBox, + } = this.editData; const { data } = annotation; if (newAnnotation && !hasMoved) { @@ -389,11 +395,16 @@ class ArrowAnnotateTool extends AnnotationTool { triggerAnnotationCompleted(annotation); // This is only new if it wasn't already memoed this.createMemo(element, annotation, { newAnnotation: !!this.memo }); + setAnnotationLabel(annotation, element, text); triggerAnnotationRenderForViewportIds(viewportIdsToRender); }); - } else { - triggerAnnotationModified(annotation, element); + } else if (!movingTextBox) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); } this.doneEditMemo(); diff --git a/packages/tools/src/tools/annotation/BidirectionalTool.ts b/packages/tools/src/tools/annotation/BidirectionalTool.ts index 1123c3a81..f9162d2ed 100644 --- a/packages/tools/src/tools/annotation/BidirectionalTool.ts +++ b/packages/tools/src/tools/annotation/BidirectionalTool.ts @@ -22,7 +22,7 @@ import { drawLinkedTextBox as drawLinkedTextBoxSvg, } from '../../drawingSvg'; import { state } from '../../store/state'; -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; import * as lineSegment from '../../utilities/math/line'; import { getTextBoxCoordsCanvas } from '../../utilities/drawing'; @@ -568,6 +568,7 @@ class BidirectionalTool extends AnnotationTool { annotation.invalidated = true; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + triggerAnnotationModified(annotation, element, ChangeTypes.HandlesUpdated); this.editData.hasMoved = true; }; @@ -620,6 +621,14 @@ class BidirectionalTool extends AnnotationTool { } triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; /** @@ -1313,7 +1322,7 @@ class BidirectionalTool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; }; diff --git a/packages/tools/src/tools/annotation/CircleROITool.ts b/packages/tools/src/tools/annotation/CircleROITool.ts index 4f12b23c9..d54d567c9 100644 --- a/packages/tools/src/tools/annotation/CircleROITool.ts +++ b/packages/tools/src/tools/annotation/CircleROITool.ts @@ -30,7 +30,7 @@ import { drawLinkedTextBox as drawLinkedTextBoxSvg, } from '../../drawingSvg'; import { state } from '../../store/state'; -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; import { getTextBoxCoordsCanvas } from '../../utilities/drawing'; import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorldWidthAndHeightFromTwoPoints'; @@ -431,6 +431,8 @@ class CircleROITool extends AnnotationTool { this.editData.hasMoved = true; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + triggerAnnotationModified(annotation, element, ChangeTypes.HandlesUpdated); }; _dragModifyCallback = (evt: EventTypes.InteractionEventType): void => { @@ -482,6 +484,14 @@ class CircleROITool extends AnnotationTool { const { renderingEngine } = enabledElement; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; _dragHandle = (evt: EventTypes.InteractionEventType): void => { @@ -1012,7 +1022,7 @@ class CircleROITool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; }; diff --git a/packages/tools/src/tools/annotation/CobbAngleTool.ts b/packages/tools/src/tools/annotation/CobbAngleTool.ts index 1c70ff3ab..f7017f21b 100644 --- a/packages/tools/src/tools/annotation/CobbAngleTool.ts +++ b/packages/tools/src/tools/annotation/CobbAngleTool.ts @@ -1,5 +1,5 @@ import { vec3 } from 'gl-matrix'; -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getEnabledElement } from '@cornerstonejs/core'; import type { Types } from '@cornerstonejs/core'; @@ -477,6 +477,14 @@ class CobbAngleTool extends AnnotationTool { const { renderingEngine } = enabledElement; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; cancel = (element: HTMLDivElement) => { @@ -1024,7 +1032,7 @@ class CobbAngleTool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; } diff --git a/packages/tools/src/tools/annotation/EllipticalROITool.ts b/packages/tools/src/tools/annotation/EllipticalROITool.ts index 2a9b7b2b8..a9f69eba5 100644 --- a/packages/tools/src/tools/annotation/EllipticalROITool.ts +++ b/packages/tools/src/tools/annotation/EllipticalROITool.ts @@ -28,7 +28,7 @@ import { drawLinkedTextBox as drawLinkedTextBoxSvg, } from '../../drawingSvg'; import { state } from '../../store/state'; -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; import { getTextBoxCoordsCanvas } from '../../utilities/drawing'; import getWorldWidthAndHeightFromTwoPoints from '../../utilities/planar/getWorldWidthAndHeightFromTwoPoints'; @@ -550,6 +550,7 @@ class EllipticalROITool extends AnnotationTool { this.editData.hasMoved = true; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + triggerAnnotationModified(annotation, element, ChangeTypes.HandlesUpdated); }; _dragModifyCallback = (evt: EventTypes.InteractionEventType): void => { @@ -601,6 +602,14 @@ class EllipticalROITool extends AnnotationTool { const { renderingEngine } = enabledElement; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; _dragHandle = (evt: EventTypes.InteractionEventType): void => { @@ -1163,7 +1172,7 @@ class EllipticalROITool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; }; diff --git a/packages/tools/src/tools/annotation/LengthTool.ts b/packages/tools/src/tools/annotation/LengthTool.ts index e642f8388..f7b3a9d4d 100644 --- a/packages/tools/src/tools/annotation/LengthTool.ts +++ b/packages/tools/src/tools/annotation/LengthTool.ts @@ -1,4 +1,4 @@ -import { Events } from '../../enums'; +import { Events, ChangeTypes } from '../../enums'; import { getEnabledElement, utilities as csUtils, @@ -481,6 +481,14 @@ class LengthTool extends AnnotationTool { this.editData.hasMoved = true; triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; cancel = (element: HTMLDivElement) => { @@ -888,7 +896,7 @@ class LengthTool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; } diff --git a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts index d8e78e614..b47a06a56 100644 --- a/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts +++ b/packages/tools/src/tools/annotation/PlanarFreehandROITool.ts @@ -683,10 +683,7 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool { if (!this.commonData?.movingTextBox) { const { data } = annotation; - if ( - !data.cachedStats[targetId] || - data.cachedStats[targetId].areaUnit == null - ) { + if (!data.cachedStats[targetId]?.unit) { data.cachedStats[targetId] = { Modality: null, area: null, @@ -694,6 +691,7 @@ class PlanarFreehandROITool extends ContourSegmentationBaseTool { mean: null, stdDev: null, areaUnit: null, + unit: null, }; this._calculateCachedStats( diff --git a/packages/tools/src/tools/annotation/RectangleROITool.ts b/packages/tools/src/tools/annotation/RectangleROITool.ts index 6a5d0ba06..6ceb0acae 100644 --- a/packages/tools/src/tools/annotation/RectangleROITool.ts +++ b/packages/tools/src/tools/annotation/RectangleROITool.ts @@ -27,7 +27,7 @@ import { drawRectByCoordinates as drawRectSvg, } from '../../drawingSvg'; import { state } from '../../store/state'; -import { Events } from '../../enums'; +import { ChangeTypes, Events } from '../../enums'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; import * as rectangle from '../../utilities/math/rectangle'; import { getTextBoxCoordsCanvas } from '../../utilities/drawing'; @@ -477,6 +477,14 @@ class RectangleROITool extends AnnotationTool { const enabledElement = getEnabledElement(element); triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (annotation.invalidated) { + triggerAnnotationModified( + annotation, + element, + ChangeTypes.HandlesUpdated + ); + } }; cancel = (element: HTMLDivElement) => { @@ -954,7 +962,7 @@ class RectangleROITool extends AnnotationTool { annotation.invalidated = false; // Dispatching annotation modified - triggerAnnotationModified(annotation, element); + triggerAnnotationModified(annotation, element, ChangeTypes.StatsUpdated); return cachedStats; }; diff --git a/packages/tools/src/tools/annotation/planarFreehandROITool/drawLoop.ts b/packages/tools/src/tools/annotation/planarFreehandROITool/drawLoop.ts index d13ca26d1..97b502967 100644 --- a/packages/tools/src/tools/annotation/planarFreehandROITool/drawLoop.ts +++ b/packages/tools/src/tools/annotation/planarFreehandROITool/drawLoop.ts @@ -3,7 +3,7 @@ import { resetElementCursor, hideElementCursor, } from '../../../cursors/elementCursor'; -import { Events } from '../../../enums'; +import { ChangeTypes, Events } from '../../../enums'; import type { EventTypes } from '../../../types'; import { state } from '../../../store/state'; import { vec3 } from 'gl-matrix'; @@ -13,7 +13,10 @@ import { } from '../../../utilities/planarFreehandROITool/smoothPoints'; import getMouseModifierKey from '../../../eventDispatchers/shared/getMouseModifier'; import triggerAnnotationRenderForViewportIds from '../../../utilities/triggerAnnotationRenderForViewportIds'; -import { triggerContourAnnotationCompleted } from '../../../stateManagement/annotation/helpers/state'; +import { + triggerAnnotationModified, + triggerContourAnnotationCompleted, +} from '../../../stateManagement/annotation/helpers/state'; import type { PlanarFreehandROIAnnotation } from '../../../types/ToolSpecificAnnotationTypes'; import findOpenUShapedContourVectorToPeak from './findOpenUShapedContourVectorToPeak'; import { polyline } from '../../../utilities/math'; @@ -170,9 +173,13 @@ function mouseDragDrawCallback(evt: EventTypes.InteractionEventType): void { this.drawData.polylineIndex = polylineIndex + numPointsAdded; } + annotation.invalidated = true; } triggerAnnotationRenderForViewportIds(viewportIdsToRender); + if (annotation.invalidated) { + triggerAnnotationModified(annotation, element, ChangeTypes.HandlesUpdated); + } } /** diff --git a/packages/tools/src/types/AnnotationTypes.ts b/packages/tools/src/types/AnnotationTypes.ts index 87d16318a..0b6ead02e 100644 --- a/packages/tools/src/types/AnnotationTypes.ts +++ b/packages/tools/src/types/AnnotationTypes.ts @@ -53,34 +53,12 @@ type Annotation = { */ data: { /** Annotation handles that are grabbable for manipulation */ - handles?: { - /** world location of the handles in the space */ - points?: Types.Point3[]; - /** index of the active handle being manipulated */ - activeHandleIndex?: number | null; - /** annotation text box information */ - textBox?: { - /** whether the text box has moved */ - hasMoved?: boolean; - /** the world location of the text box */ - worldPosition?: Types.Point3; - /** text box bounding box information */ - worldBoundingBox?: { - /** Top left location of the text box in the world space */ - topLeft: Types.Point3; - /** Top right location of the text box in the world space */ - topRight: Types.Point3; - /** Bottom left location of the text box in the world space */ - bottomLeft: Types.Point3; - /** Bottom right location of the text box in the world space */ - bottomRight: Types.Point3; - }; - }; - [key: string]: unknown; - }; + handles?: Handles; [key: string]: unknown; /** Cached Annotation statistics which is specific to the tool */ cachedStats?: Record; + /** Label of an annotation */ + label?: string; }; }; @@ -106,9 +84,36 @@ type AnnotationState = { [key: string]: GroupSpecificAnnotations; }; +type Handles = { + /** world location of the handles in the space */ + points?: Types.Point3[]; + /** index of the active handle being manipulated */ + activeHandleIndex?: number | null; + /** annotation text box information */ + textBox?: { + /** whether the text box has moved */ + hasMoved?: boolean; + /** the world location of the text box */ + worldPosition?: Types.Point3; + /** text box bounding box information */ + worldBoundingBox?: { + /** Top left location of the text box in the world space */ + topLeft: Types.Point3; + /** Top right location of the text box in the world space */ + topRight: Types.Point3; + /** Bottom left location of the text box in the world space */ + bottomLeft: Types.Point3; + /** Bottom right location of the text box in the world space */ + bottomRight: Types.Point3; + }; + }; + [key: string]: unknown; +}; + export type { Annotation, Annotations, GroupSpecificAnnotations, AnnotationState, + Handles, }; diff --git a/packages/tools/src/types/ToolSpecificAnnotationTypes.ts b/packages/tools/src/types/ToolSpecificAnnotationTypes.ts index c8a73f2d9..e94ffdc53 100644 --- a/packages/tools/src/types/ToolSpecificAnnotationTypes.ts +++ b/packages/tools/src/types/ToolSpecificAnnotationTypes.ts @@ -12,6 +12,7 @@ export interface ROICachedStats { max: number; mean: number; stdDev: number; + unit?: number; }; } diff --git a/packages/tools/src/utilities/index.ts b/packages/tools/src/utilities/index.ts index fca1df2d2..edd8506b4 100644 --- a/packages/tools/src/utilities/index.ts +++ b/packages/tools/src/utilities/index.ts @@ -51,6 +51,7 @@ const roundNumber = utilities.roundNumber; import normalizeViewportPlane from './normalizeViewportPlane'; import IslandRemoval from './segmentation/islandRemoval'; import { getPixelValueUnits } from './getPixelValueUnits'; +import setAnnotationLabel from './setAnnotationLabel'; export { math, @@ -96,4 +97,5 @@ export { pointInSurroundingSphereCallback, normalizeViewportPlane, IslandRemoval, + setAnnotationLabel, }; diff --git a/packages/tools/src/utilities/setAnnotationLabel.ts b/packages/tools/src/utilities/setAnnotationLabel.ts new file mode 100644 index 000000000..9b68778bb --- /dev/null +++ b/packages/tools/src/utilities/setAnnotationLabel.ts @@ -0,0 +1,12 @@ +import type { Annotation } from '../types/AnnotationTypes'; +import { triggerAnnotationModified } from '../stateManagement/annotation/helpers/state'; +import { ChangeTypes } from '../enums'; + +export default function setAnnotationLabel( + annotation: Annotation, + element: HTMLDivElement, + updatedLabel: string +) { + annotation.data.label = updatedLabel; + triggerAnnotationModified(annotation, element, ChangeTypes.LabelChange); +}