From 5a8e38d665b246a569c5ff40cfbe214a2fde42d5 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:22:35 -0500 Subject: [PATCH 01/17] Add eraser tool. --- packages/tools/src/tools/EraserTool.ts | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/tools/src/tools/EraserTool.ts diff --git a/packages/tools/src/tools/EraserTool.ts b/packages/tools/src/tools/EraserTool.ts new file mode 100644 index 0000000000..ad0d26f71e --- /dev/null +++ b/packages/tools/src/tools/EraserTool.ts @@ -0,0 +1,78 @@ +import BaseTool from './base/BaseTool'; +import { EventTypes, PublicToolProps, ToolProps } from '../types'; +import * as ToolGroupManager from '../store/ToolGroupManager'; +import { annotation } from '../index'; +import { state } from '../stateManagement/annotation'; + +class EraserTool extends BaseTool { + static toolName; + constructor( + toolProps: PublicToolProps = {}, + defaultToolProps: ToolProps = { + supportedInteractionTypes: ['Mouse', 'Touch'], + } + ) { + super(toolProps, defaultToolProps); + } + preMouseDownCallback = (evt: EventTypes.InteractionEventType): boolean => { + return this._deleteNearbyAnnotations(evt, 'mouse'); + }; + preTouchStartCallback = (evt: EventTypes.InteractionEventType): boolean => { + return this._deleteNearbyAnnotations(evt, 'touch'); + }; + + _deleteNearbyAnnotations( + evt: EventTypes.InteractionEventType, + interactionType: string + ): boolean { + const { renderingEngineId, viewportId, element, currentPoints } = + evt.detail; + + const toolGroup = ToolGroupManager.getToolGroupForViewport( + viewportId, + renderingEngineId + ); + + if (!toolGroup) { + return false; + } + + const tools = toolGroup._toolInstances; + const annotationsToRemove = []; + + for (const toolName in tools) { + const toolInstance = tools[toolName]; + + if (typeof toolInstance.isPointNearTool !== 'function') { + continue; + } + + const annotations = state.getAnnotations(toolName, viewportId); + + for (const annotation of annotations) { + if ( + toolInstance.isPointNearTool( + element, + annotation, + currentPoints.canvas, + 6, + interactionType + ) + ) { + annotationsToRemove.push(annotation.annotationUID); + } + } + } + + for (const annotationUID of annotationsToRemove) { + annotation.state.removeAnnotation(annotationUID); + } + + evt.preventDefault(); + + return true; + } +} + +EraserTool.toolName = 'Eraser'; +export default EraserTool; From b979f60fb816528eb7c5e6bc54af31ea9ca47b46 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:23:59 -0500 Subject: [PATCH 02/17] Add eraser tool test. --- packages/tools/test/EraserTool_test.js | 209 +++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 packages/tools/test/EraserTool_test.js diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js new file mode 100644 index 0000000000..0d82170b65 --- /dev/null +++ b/packages/tools/test/EraserTool_test.js @@ -0,0 +1,209 @@ +import * as cornerstone3D from '@cornerstonejs/core'; +import * as csTools3d from '../src/index'; +import * as testUtils from '../../../utils/test/testUtils'; +import { EraserTool } from '@cornerstonejs/tools'; +import { state } from '../src/index'; + +const { + cache, + RenderingEngine, + Enums, + eventTarget, + utilities, + imageLoader, + metaData, + volumeLoader, +} = cornerstone3D; + +const { Events, ViewportType } = Enums; + +const { + LengthTool, + ToolGroupManager, + Enums: csToolsEnums, + annotation, +} = csTools3d; + +const { Events: csToolsEvents } = csToolsEnums; + +const { + fakeImageLoader, + fakeVolumeLoader, + fakeMetaDataProvider, + createNormalizedMouseEvent, +} = testUtils; + +const renderingEngineId = utilities.uuidv4(); + +const viewportId = 'VIEWPORT'; + +function createViewport(renderingEngine, viewportType, width, height) { + const element = document.createElement('div'); + + element.style.width = `${width}px`; + element.style.height = `${height}px`; + document.body.appendChild(element); + + renderingEngine.setViewports([ + { + viewportId: viewportId, + type: viewportType, + element, + defaultOptions: { + background: [1, 0, 1], // pinkish background + orientation: Enums.OrientationAxis.AXIAL, + }, + }, + ]); + return element; +} + +const volumeId = `fakeVolumeLoader:volumeURI_100_100_10_1_1_1_0`; + +describe('EraserTool:', () => { + beforeAll(() => { + cornerstone3D.setUseCPURendering(false); + }); + + describe('Cornerstone Tools: -- Eraser', () => { + beforeEach(function () { + csTools3d.init(); + csTools3d.addTool(EraserTool); + csTools3d.addTool(LengthTool); + + cache.purgeCache(); + this.DOMElements = []; + + this.stackToolGroup = ToolGroupManager.createToolGroup('stack'); + this.stackToolGroup.addTool(EraserTool.toolName, {}); + this.stackToolGroup.setToolActive(EraserTool.toolName, { + bindings: [{ mouseButton: 1 }], + }); + + this.renderingEngine = new RenderingEngine(renderingEngineId); + imageLoader.registerImageLoader('fakeImageLoader', fakeImageLoader); + volumeLoader.registerVolumeLoader('fakeVolumeLoader', fakeVolumeLoader); + metaData.addProvider(fakeMetaDataProvider, 10000); + }); + + afterEach(function () { + csTools3d.destroy(); + eventTarget.reset(); + cache.purgeCache(); + + this.renderingEngine.destroy(); + metaData.removeProvider(fakeMetaDataProvider); + imageLoader.unregisterAllImageLoaders(); + ToolGroupManager.destroyToolGroup('stack'); + + this.DOMElements.forEach((el) => { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + }); + }); + + it('Should successfully delete a length annotation on a canvas with mouse down - 512 x 128', function (done) { + const element = createViewport( + this.renderingEngine, + ViewportType.STACK, + 512, + 128 + ); + + this.DOMElements.push(element); + + const imageId1 = 'fakeImageLoader:imageURI_64_64_10_5_1_1_0'; + const vp = this.renderingEngine.getViewport(viewportId); + + const addEventListenerForAnnotationRemoved = () => { + element.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { + const lengthAnnotations = annotation.state.getAnnotations( + LengthTool.toolName, + element + ); + expect(lengthAnnotations).toBeDefined(); + expect(lengthAnnotations.length).toBe(0); + done(); + }); + }; + + element.addEventListener(Events.IMAGE_RENDERED, () => { + const index1 = [32, 32, 0]; + const index2 = [10, 1, 0]; + + const { imageData } = vp.getImageData(); + + const { + pageX: pageX1, + pageY: pageY1, + clientX: clientX1, + clientY: clientY1, + worldCoord: worldCoord1, + } = createNormalizedMouseEvent(imageData, index1, element, vp); + const { worldCoord: worldCoord2 } = createNormalizedMouseEvent( + imageData, + index2, + element, + vp + ); + + const camera = vp.getCamera(); + const { viewPlaneNormal, viewUp } = camera; + + const lengthAnnotation = { + highlighted: true, + invalidated: true, + metadata: { + toolName: LengthTool.toolName, + viewPlaneNormal: [...viewPlaneNormal], + viewUp: [...viewUp], + FrameOfReferenceUID: vp.getFrameOfReferenceUID(), + referencedImageId: imageId1, + }, + data: { + handles: { + points: [[...worldCoord1], [...worldCoord2]], + activeHandleIndex: null, + textBox: { + hasMoved: false, + worldPosition: [0, 0, 0], + worldBoundingBox: { + topLeft: [0, 0, 0], + topRight: [0, 0, 0], + bottomLeft: [0, 0, 0], + bottomRight: [0, 0, 0], + }, + }, + }, + label: '', + cachedStats: {}, + }, + }; + annotation.state.addAnnotation(lengthAnnotation, element); + + let evt = new MouseEvent('mousedown', { + target: element, + buttons: 1, + clientX: clientX1, + clientY: clientY1, + pageX: pageX1, + pageY: pageY1, + }); + + addEventListenerForAnnotationRemoved(); + state.isInteractingWithTool = false; + document.dispatchEvent(evt); + }); + + this.stackToolGroup.addViewport(vp.id, this.renderingEngine.id); + + try { + vp.setStack([imageId1], 0); + this.renderingEngine.render(); + } catch (e) { + done.fail(e); + } + }); + }); +}); From 333657c16722bc1cad300cdb132da19cff319b94 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:24:20 -0500 Subject: [PATCH 03/17] Expose eraser tool. --- packages/tools/src/index.ts | 2 ++ packages/tools/src/tools/index.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 92e827e7e8..0965cc4289 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -60,6 +60,7 @@ import { ScaleOverlayTool, OrientationMarkerTool, OverlayGridTool, + EraserTool, } from './tools'; import * as Enums from './enums'; @@ -104,6 +105,7 @@ export { ReferenceCursors, ReferenceLines, ScaleOverlayTool, + EraserTool, // Segmentation Display SegmentationDisplayTool, // Segmentation Editing Tools diff --git a/packages/tools/src/tools/index.ts b/packages/tools/src/tools/index.ts index 56ec0171af..39d6db55ad 100644 --- a/packages/tools/src/tools/index.ts +++ b/packages/tools/src/tools/index.ts @@ -27,6 +27,7 @@ import CobbAngleTool from './annotation/CobbAngleTool'; import ReferenceCursors from './ReferenceCursors'; import ReferenceLines from './ReferenceLinesTool'; import ScaleOverlayTool from './ScaleOverlayTool'; +import EraserTool from './EraserTool'; // Segmentation DisplayTool import SegmentationDisplayTool from './displayTools/SegmentationDisplayTool'; @@ -72,6 +73,7 @@ export { AngleTool, CobbAngleTool, ReferenceCursors, + EraserTool, // Segmentations Display SegmentationDisplayTool, // Segmentations Tools From d81ed89fafc0eb260dc10738aac4ee993c0381bc Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:34:48 -0500 Subject: [PATCH 04/17] Remove state line. --- packages/tools/test/EraserTool_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js index 0d82170b65..68b0255f9f 100644 --- a/packages/tools/test/EraserTool_test.js +++ b/packages/tools/test/EraserTool_test.js @@ -192,7 +192,6 @@ describe('EraserTool:', () => { }); addEventListenerForAnnotationRemoved(); - state.isInteractingWithTool = false; document.dispatchEvent(evt); }); From 7daa01eef1a117aadbac44b286a6b6f6330ea74a Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Thu, 2 Nov 2023 08:04:06 -0500 Subject: [PATCH 05/17] Use interactable annotations and pass element. --- packages/tools/src/tools/EraserTool.ts | 48 ++++++++++++++++++-------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/tools/src/tools/EraserTool.ts b/packages/tools/src/tools/EraserTool.ts index ad0d26f71e..5e74c3518a 100644 --- a/packages/tools/src/tools/EraserTool.ts +++ b/packages/tools/src/tools/EraserTool.ts @@ -1,28 +1,33 @@ -import BaseTool from './base/BaseTool'; -import { EventTypes, PublicToolProps, ToolProps } from '../types'; -import * as ToolGroupManager from '../store/ToolGroupManager'; -import { annotation } from '../index'; -import { state } from '../stateManagement/annotation'; +import { + BaseTool, + Types, + ToolGroupManager, + annotation, +} from '@cornerstonejs/tools'; class EraserTool extends BaseTool { static toolName; constructor( - toolProps: PublicToolProps = {}, - defaultToolProps: ToolProps = { + toolProps: Types.PublicToolProps = {}, + defaultToolProps: Types.ToolProps = { supportedInteractionTypes: ['Mouse', 'Touch'], } ) { super(toolProps, defaultToolProps); } - preMouseDownCallback = (evt: EventTypes.InteractionEventType): boolean => { + preMouseDownCallback = ( + evt: Types.EventTypes.InteractionEventType + ): boolean => { return this._deleteNearbyAnnotations(evt, 'mouse'); }; - preTouchStartCallback = (evt: EventTypes.InteractionEventType): boolean => { + preTouchStartCallback = ( + evt: Types.EventTypes.InteractionEventType + ): boolean => { return this._deleteNearbyAnnotations(evt, 'touch'); }; _deleteNearbyAnnotations( - evt: EventTypes.InteractionEventType, + evt: Types.EventTypes.InteractionEventType, interactionType: string ): boolean { const { renderingEngineId, viewportId, element, currentPoints } = @@ -43,19 +48,33 @@ class EraserTool extends BaseTool { for (const toolName in tools) { const toolInstance = tools[toolName]; - if (typeof toolInstance.isPointNearTool !== 'function') { + if ( + typeof toolInstance.isPointNearTool !== 'function' || + typeof toolInstance.filterInteractableAnnotationsForElement !== + 'function' + ) { continue; } - const annotations = state.getAnnotations(toolName, viewportId); + const annotations = annotation.state.getAnnotations(toolName, element); - for (const annotation of annotations) { + if (!annotations) { + continue; + } + + const interactableAnnotations = + toolInstance.filterInteractableAnnotationsForElement( + element, + annotations + ); + + for (const annotation of interactableAnnotations) { if ( toolInstance.isPointNearTool( element, annotation, currentPoints.canvas, - 6, + 10, interactionType ) ) { @@ -65,6 +84,7 @@ class EraserTool extends BaseTool { } for (const annotationUID of annotationsToRemove) { + annotation.selection.setAnnotationSelected(annotationUID); annotation.state.removeAnnotation(annotationUID); } From 7cbfeecc6c1182b201d3f0f8c343e5563fe482f2 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Thu, 2 Nov 2023 08:07:40 -0500 Subject: [PATCH 06/17] Update test. --- packages/tools/test/EraserTool_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js index 68b0255f9f..d400844de3 100644 --- a/packages/tools/test/EraserTool_test.js +++ b/packages/tools/test/EraserTool_test.js @@ -117,7 +117,7 @@ describe('EraserTool:', () => { const vp = this.renderingEngine.getViewport(viewportId); const addEventListenerForAnnotationRemoved = () => { - element.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { + eventTarget.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { const lengthAnnotations = annotation.state.getAnnotations( LengthTool.toolName, element From e59efe47193b77f24409c9cc2ab3019b88940da2 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:22:35 -0500 Subject: [PATCH 07/17] Add eraser tool. --- packages/tools/src/tools/EraserTool.ts | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/tools/src/tools/EraserTool.ts diff --git a/packages/tools/src/tools/EraserTool.ts b/packages/tools/src/tools/EraserTool.ts new file mode 100644 index 0000000000..ad0d26f71e --- /dev/null +++ b/packages/tools/src/tools/EraserTool.ts @@ -0,0 +1,78 @@ +import BaseTool from './base/BaseTool'; +import { EventTypes, PublicToolProps, ToolProps } from '../types'; +import * as ToolGroupManager from '../store/ToolGroupManager'; +import { annotation } from '../index'; +import { state } from '../stateManagement/annotation'; + +class EraserTool extends BaseTool { + static toolName; + constructor( + toolProps: PublicToolProps = {}, + defaultToolProps: ToolProps = { + supportedInteractionTypes: ['Mouse', 'Touch'], + } + ) { + super(toolProps, defaultToolProps); + } + preMouseDownCallback = (evt: EventTypes.InteractionEventType): boolean => { + return this._deleteNearbyAnnotations(evt, 'mouse'); + }; + preTouchStartCallback = (evt: EventTypes.InteractionEventType): boolean => { + return this._deleteNearbyAnnotations(evt, 'touch'); + }; + + _deleteNearbyAnnotations( + evt: EventTypes.InteractionEventType, + interactionType: string + ): boolean { + const { renderingEngineId, viewportId, element, currentPoints } = + evt.detail; + + const toolGroup = ToolGroupManager.getToolGroupForViewport( + viewportId, + renderingEngineId + ); + + if (!toolGroup) { + return false; + } + + const tools = toolGroup._toolInstances; + const annotationsToRemove = []; + + for (const toolName in tools) { + const toolInstance = tools[toolName]; + + if (typeof toolInstance.isPointNearTool !== 'function') { + continue; + } + + const annotations = state.getAnnotations(toolName, viewportId); + + for (const annotation of annotations) { + if ( + toolInstance.isPointNearTool( + element, + annotation, + currentPoints.canvas, + 6, + interactionType + ) + ) { + annotationsToRemove.push(annotation.annotationUID); + } + } + } + + for (const annotationUID of annotationsToRemove) { + annotation.state.removeAnnotation(annotationUID); + } + + evt.preventDefault(); + + return true; + } +} + +EraserTool.toolName = 'Eraser'; +export default EraserTool; From b01d62d1c288748f2ef722ca802909c4c57518ff Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:23:59 -0500 Subject: [PATCH 08/17] Add eraser tool test. --- packages/tools/test/EraserTool_test.js | 209 +++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 packages/tools/test/EraserTool_test.js diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js new file mode 100644 index 0000000000..0d82170b65 --- /dev/null +++ b/packages/tools/test/EraserTool_test.js @@ -0,0 +1,209 @@ +import * as cornerstone3D from '@cornerstonejs/core'; +import * as csTools3d from '../src/index'; +import * as testUtils from '../../../utils/test/testUtils'; +import { EraserTool } from '@cornerstonejs/tools'; +import { state } from '../src/index'; + +const { + cache, + RenderingEngine, + Enums, + eventTarget, + utilities, + imageLoader, + metaData, + volumeLoader, +} = cornerstone3D; + +const { Events, ViewportType } = Enums; + +const { + LengthTool, + ToolGroupManager, + Enums: csToolsEnums, + annotation, +} = csTools3d; + +const { Events: csToolsEvents } = csToolsEnums; + +const { + fakeImageLoader, + fakeVolumeLoader, + fakeMetaDataProvider, + createNormalizedMouseEvent, +} = testUtils; + +const renderingEngineId = utilities.uuidv4(); + +const viewportId = 'VIEWPORT'; + +function createViewport(renderingEngine, viewportType, width, height) { + const element = document.createElement('div'); + + element.style.width = `${width}px`; + element.style.height = `${height}px`; + document.body.appendChild(element); + + renderingEngine.setViewports([ + { + viewportId: viewportId, + type: viewportType, + element, + defaultOptions: { + background: [1, 0, 1], // pinkish background + orientation: Enums.OrientationAxis.AXIAL, + }, + }, + ]); + return element; +} + +const volumeId = `fakeVolumeLoader:volumeURI_100_100_10_1_1_1_0`; + +describe('EraserTool:', () => { + beforeAll(() => { + cornerstone3D.setUseCPURendering(false); + }); + + describe('Cornerstone Tools: -- Eraser', () => { + beforeEach(function () { + csTools3d.init(); + csTools3d.addTool(EraserTool); + csTools3d.addTool(LengthTool); + + cache.purgeCache(); + this.DOMElements = []; + + this.stackToolGroup = ToolGroupManager.createToolGroup('stack'); + this.stackToolGroup.addTool(EraserTool.toolName, {}); + this.stackToolGroup.setToolActive(EraserTool.toolName, { + bindings: [{ mouseButton: 1 }], + }); + + this.renderingEngine = new RenderingEngine(renderingEngineId); + imageLoader.registerImageLoader('fakeImageLoader', fakeImageLoader); + volumeLoader.registerVolumeLoader('fakeVolumeLoader', fakeVolumeLoader); + metaData.addProvider(fakeMetaDataProvider, 10000); + }); + + afterEach(function () { + csTools3d.destroy(); + eventTarget.reset(); + cache.purgeCache(); + + this.renderingEngine.destroy(); + metaData.removeProvider(fakeMetaDataProvider); + imageLoader.unregisterAllImageLoaders(); + ToolGroupManager.destroyToolGroup('stack'); + + this.DOMElements.forEach((el) => { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + }); + }); + + it('Should successfully delete a length annotation on a canvas with mouse down - 512 x 128', function (done) { + const element = createViewport( + this.renderingEngine, + ViewportType.STACK, + 512, + 128 + ); + + this.DOMElements.push(element); + + const imageId1 = 'fakeImageLoader:imageURI_64_64_10_5_1_1_0'; + const vp = this.renderingEngine.getViewport(viewportId); + + const addEventListenerForAnnotationRemoved = () => { + element.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { + const lengthAnnotations = annotation.state.getAnnotations( + LengthTool.toolName, + element + ); + expect(lengthAnnotations).toBeDefined(); + expect(lengthAnnotations.length).toBe(0); + done(); + }); + }; + + element.addEventListener(Events.IMAGE_RENDERED, () => { + const index1 = [32, 32, 0]; + const index2 = [10, 1, 0]; + + const { imageData } = vp.getImageData(); + + const { + pageX: pageX1, + pageY: pageY1, + clientX: clientX1, + clientY: clientY1, + worldCoord: worldCoord1, + } = createNormalizedMouseEvent(imageData, index1, element, vp); + const { worldCoord: worldCoord2 } = createNormalizedMouseEvent( + imageData, + index2, + element, + vp + ); + + const camera = vp.getCamera(); + const { viewPlaneNormal, viewUp } = camera; + + const lengthAnnotation = { + highlighted: true, + invalidated: true, + metadata: { + toolName: LengthTool.toolName, + viewPlaneNormal: [...viewPlaneNormal], + viewUp: [...viewUp], + FrameOfReferenceUID: vp.getFrameOfReferenceUID(), + referencedImageId: imageId1, + }, + data: { + handles: { + points: [[...worldCoord1], [...worldCoord2]], + activeHandleIndex: null, + textBox: { + hasMoved: false, + worldPosition: [0, 0, 0], + worldBoundingBox: { + topLeft: [0, 0, 0], + topRight: [0, 0, 0], + bottomLeft: [0, 0, 0], + bottomRight: [0, 0, 0], + }, + }, + }, + label: '', + cachedStats: {}, + }, + }; + annotation.state.addAnnotation(lengthAnnotation, element); + + let evt = new MouseEvent('mousedown', { + target: element, + buttons: 1, + clientX: clientX1, + clientY: clientY1, + pageX: pageX1, + pageY: pageY1, + }); + + addEventListenerForAnnotationRemoved(); + state.isInteractingWithTool = false; + document.dispatchEvent(evt); + }); + + this.stackToolGroup.addViewport(vp.id, this.renderingEngine.id); + + try { + vp.setStack([imageId1], 0); + this.renderingEngine.render(); + } catch (e) { + done.fail(e); + } + }); + }); +}); From 15d05f0acffa95685e01560d9b8b1d78d3546bb2 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:24:20 -0500 Subject: [PATCH 09/17] Expose eraser tool. --- packages/tools/src/index.ts | 2 ++ packages/tools/src/tools/index.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 7b3d526f73..ea92be0f5b 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -62,6 +62,7 @@ import { OrientationMarkerTool, OverlayGridTool, SegmentationIntersectionTool, + EraserTool, } from './tools'; import VideoRedactionTool from './tools/annotation/VideoRedactionTool'; @@ -111,6 +112,7 @@ export { ReferenceCursors, ReferenceLines, ScaleOverlayTool, + EraserTool, // Segmentation Display SegmentationDisplayTool, // Segmentation Editing Tools diff --git a/packages/tools/src/tools/index.ts b/packages/tools/src/tools/index.ts index 765024573d..19161bb88b 100644 --- a/packages/tools/src/tools/index.ts +++ b/packages/tools/src/tools/index.ts @@ -29,6 +29,7 @@ import CobbAngleTool from './annotation/CobbAngleTool'; import ReferenceCursors from './ReferenceCursors'; import ReferenceLines from './ReferenceLinesTool'; import ScaleOverlayTool from './ScaleOverlayTool'; +import EraserTool from './EraserTool'; // Segmentation DisplayTool import SegmentationDisplayTool from './displayTools/SegmentationDisplayTool'; @@ -75,6 +76,7 @@ export { AngleTool, CobbAngleTool, ReferenceCursors, + EraserTool, // Segmentations Display SegmentationDisplayTool, // Segmentations Tools From d416d1e88e7e4576739fe54468fba22c6f20d064 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Tue, 3 Oct 2023 09:34:48 -0500 Subject: [PATCH 10/17] Remove state line. --- packages/tools/test/EraserTool_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js index 0d82170b65..68b0255f9f 100644 --- a/packages/tools/test/EraserTool_test.js +++ b/packages/tools/test/EraserTool_test.js @@ -192,7 +192,6 @@ describe('EraserTool:', () => { }); addEventListenerForAnnotationRemoved(); - state.isInteractingWithTool = false; document.dispatchEvent(evt); }); From 2abf0de214b4eb2fffa4c3f846941b72b8a684fa Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Thu, 2 Nov 2023 08:04:06 -0500 Subject: [PATCH 11/17] Use interactable annotations and pass element. --- packages/tools/src/tools/EraserTool.ts | 48 ++++++++++++++++++-------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/tools/src/tools/EraserTool.ts b/packages/tools/src/tools/EraserTool.ts index ad0d26f71e..5e74c3518a 100644 --- a/packages/tools/src/tools/EraserTool.ts +++ b/packages/tools/src/tools/EraserTool.ts @@ -1,28 +1,33 @@ -import BaseTool from './base/BaseTool'; -import { EventTypes, PublicToolProps, ToolProps } from '../types'; -import * as ToolGroupManager from '../store/ToolGroupManager'; -import { annotation } from '../index'; -import { state } from '../stateManagement/annotation'; +import { + BaseTool, + Types, + ToolGroupManager, + annotation, +} from '@cornerstonejs/tools'; class EraserTool extends BaseTool { static toolName; constructor( - toolProps: PublicToolProps = {}, - defaultToolProps: ToolProps = { + toolProps: Types.PublicToolProps = {}, + defaultToolProps: Types.ToolProps = { supportedInteractionTypes: ['Mouse', 'Touch'], } ) { super(toolProps, defaultToolProps); } - preMouseDownCallback = (evt: EventTypes.InteractionEventType): boolean => { + preMouseDownCallback = ( + evt: Types.EventTypes.InteractionEventType + ): boolean => { return this._deleteNearbyAnnotations(evt, 'mouse'); }; - preTouchStartCallback = (evt: EventTypes.InteractionEventType): boolean => { + preTouchStartCallback = ( + evt: Types.EventTypes.InteractionEventType + ): boolean => { return this._deleteNearbyAnnotations(evt, 'touch'); }; _deleteNearbyAnnotations( - evt: EventTypes.InteractionEventType, + evt: Types.EventTypes.InteractionEventType, interactionType: string ): boolean { const { renderingEngineId, viewportId, element, currentPoints } = @@ -43,19 +48,33 @@ class EraserTool extends BaseTool { for (const toolName in tools) { const toolInstance = tools[toolName]; - if (typeof toolInstance.isPointNearTool !== 'function') { + if ( + typeof toolInstance.isPointNearTool !== 'function' || + typeof toolInstance.filterInteractableAnnotationsForElement !== + 'function' + ) { continue; } - const annotations = state.getAnnotations(toolName, viewportId); + const annotations = annotation.state.getAnnotations(toolName, element); - for (const annotation of annotations) { + if (!annotations) { + continue; + } + + const interactableAnnotations = + toolInstance.filterInteractableAnnotationsForElement( + element, + annotations + ); + + for (const annotation of interactableAnnotations) { if ( toolInstance.isPointNearTool( element, annotation, currentPoints.canvas, - 6, + 10, interactionType ) ) { @@ -65,6 +84,7 @@ class EraserTool extends BaseTool { } for (const annotationUID of annotationsToRemove) { + annotation.selection.setAnnotationSelected(annotationUID); annotation.state.removeAnnotation(annotationUID); } From b9208c56f1499a9cac3e4627bf94751d87391aef Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Thu, 2 Nov 2023 08:07:40 -0500 Subject: [PATCH 12/17] Update test. --- packages/tools/test/EraserTool_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js index 68b0255f9f..d400844de3 100644 --- a/packages/tools/test/EraserTool_test.js +++ b/packages/tools/test/EraserTool_test.js @@ -117,7 +117,7 @@ describe('EraserTool:', () => { const vp = this.renderingEngine.getViewport(viewportId); const addEventListenerForAnnotationRemoved = () => { - element.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { + eventTarget.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { const lengthAnnotations = annotation.state.getAnnotations( LengthTool.toolName, element From b8367f158d0db5839c6d6ff12dc9bdf0371e9200 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Thu, 2 Nov 2023 09:33:05 -0500 Subject: [PATCH 13/17] Update imports. --- packages/tools/src/tools/EraserTool.ts | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/tools/src/tools/EraserTool.ts b/packages/tools/src/tools/EraserTool.ts index 5e74c3518a..2db2a68ced 100644 --- a/packages/tools/src/tools/EraserTool.ts +++ b/packages/tools/src/tools/EraserTool.ts @@ -1,33 +1,31 @@ +import { BaseTool } from './base'; +import { EventTypes, PublicToolProps, ToolProps } from '../types'; +import { ToolGroupManager } from '../store'; import { - BaseTool, - Types, - ToolGroupManager, - annotation, -} from '@cornerstonejs/tools'; + getAnnotations, + removeAnnotation, +} from '../stateManagement/annotation/annotationState'; +import { setAnnotationSelected } from '../stateManagement/annotation/annotationSelection'; class EraserTool extends BaseTool { static toolName; constructor( - toolProps: Types.PublicToolProps = {}, - defaultToolProps: Types.ToolProps = { + toolProps: PublicToolProps = {}, + defaultToolProps: ToolProps = { supportedInteractionTypes: ['Mouse', 'Touch'], } ) { super(toolProps, defaultToolProps); } - preMouseDownCallback = ( - evt: Types.EventTypes.InteractionEventType - ): boolean => { + preMouseDownCallback = (evt: EventTypes.InteractionEventType): boolean => { return this._deleteNearbyAnnotations(evt, 'mouse'); }; - preTouchStartCallback = ( - evt: Types.EventTypes.InteractionEventType - ): boolean => { + preTouchStartCallback = (evt: EventTypes.InteractionEventType): boolean => { return this._deleteNearbyAnnotations(evt, 'touch'); }; _deleteNearbyAnnotations( - evt: Types.EventTypes.InteractionEventType, + evt: EventTypes.InteractionEventType, interactionType: string ): boolean { const { renderingEngineId, viewportId, element, currentPoints } = @@ -56,7 +54,7 @@ class EraserTool extends BaseTool { continue; } - const annotations = annotation.state.getAnnotations(toolName, element); + const annotations = getAnnotations(toolName, element); if (!annotations) { continue; @@ -84,8 +82,8 @@ class EraserTool extends BaseTool { } for (const annotationUID of annotationsToRemove) { - annotation.selection.setAnnotationSelected(annotationUID); - annotation.state.removeAnnotation(annotationUID); + setAnnotationSelected(annotationUID); + removeAnnotation(annotationUID); } evt.preventDefault(); From c290e06e042913b57d09787c923f16cc44e2269f Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Thu, 2 Nov 2023 09:33:28 -0500 Subject: [PATCH 14/17] Update aoi. --- common/reviews/api/tools.api.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index f4c60d8bfb..92f4d4975c 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -1990,6 +1990,19 @@ declare namespace Enums_2 { } } +// @public (undocumented) +export class EraserTool extends BaseTool { + constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps); + // (undocumented) + _deleteNearbyAnnotations(evt: EventTypes_2.InteractionEventType, interactionType: string): boolean; + // (undocumented) + preMouseDownCallback: (evt: EventTypes_2.InteractionEventType) => boolean; + // (undocumented) + preTouchStartCallback: (evt: EventTypes_2.InteractionEventType) => boolean; + // (undocumented) + static toolName: any; +} + // @public (undocumented) enum Events { // (undocumented) From 9c093b3ad9c1ab15ac4f56090a4619407146b321 Mon Sep 17 00:00:00 2001 From: Ramon Emiliani Date: Mon, 6 Nov 2023 20:57:36 -0500 Subject: [PATCH 15/17] Add eraser tool to example. --- packages/tools/examples/stackAnnotationTools/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/tools/examples/stackAnnotationTools/index.ts b/packages/tools/examples/stackAnnotationTools/index.ts index 068d60c30d..c02a07f48c 100644 --- a/packages/tools/examples/stackAnnotationTools/index.ts +++ b/packages/tools/examples/stackAnnotationTools/index.ts @@ -30,6 +30,7 @@ const { ToolGroupManager, ArrowAnnotateTool, PlanarFreehandROITool, + EraserTool, Enums: csToolsEnums, } = cornerstoneTools; @@ -118,6 +119,7 @@ const toolsNames = [ CobbAngleTool.toolName, ArrowAnnotateTool.toolName, PlanarFreehandROITool.toolName, + EraserTool.toolName, ]; let selectedToolName = toolsNames[0]; @@ -216,6 +218,7 @@ async function run() { cornerstoneTools.addTool(CobbAngleTool); cornerstoneTools.addTool(ArrowAnnotateTool); cornerstoneTools.addTool(PlanarFreehandROITool); + cornerstoneTools.addTool(EraserTool); // Define a tool group, which defines how mouse events map to tool commands for // Any viewport using the group @@ -232,6 +235,7 @@ async function run() { toolGroup.addTool(CobbAngleTool.toolName); toolGroup.addTool(ArrowAnnotateTool.toolName); toolGroup.addTool(PlanarFreehandROITool.toolName); + toolGroup.addTool(EraserTool.toolName); // Set the initial state of the tools, here we set one tool active on left click. // This means left click will draw that tool. @@ -252,6 +256,7 @@ async function run() { toolGroup.setToolPassive(AngleTool.toolName); toolGroup.setToolPassive(ArrowAnnotateTool.toolName); toolGroup.setToolPassive(PlanarFreehandROITool.toolName); + toolGroup.setToolPassive(EraserTool.toolName); toolGroup.setToolConfiguration(PlanarFreehandROITool.toolName, { calculateStats: true, From afbe99d7e63b5a46b88784a452ae8ec81d953d81 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 15 Feb 2024 09:54:36 -0500 Subject: [PATCH 16/17] fix tests --- packages/tools/test/EraserTool_test.js | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/tools/test/EraserTool_test.js b/packages/tools/test/EraserTool_test.js index d400844de3..96e1b3ca8c 100644 --- a/packages/tools/test/EraserTool_test.js +++ b/packages/tools/test/EraserTool_test.js @@ -2,7 +2,7 @@ import * as cornerstone3D from '@cornerstonejs/core'; import * as csTools3d from '../src/index'; import * as testUtils from '../../../utils/test/testUtils'; import { EraserTool } from '@cornerstonejs/tools'; -import { state } from '../src/index'; +import { triggerAnnotationAddedForElement } from '../src/stateManagement/annotation/helpers/state'; const { cache, @@ -75,6 +75,8 @@ describe('EraserTool:', () => { this.DOMElements = []; this.stackToolGroup = ToolGroupManager.createToolGroup('stack'); + this.stackToolGroup.addTool(LengthTool.toolName, {}); + this.stackToolGroup.setToolEnabled(LengthTool.toolName, {}); this.stackToolGroup.addTool(EraserTool.toolName, {}); this.stackToolGroup.setToolActive(EraserTool.toolName, { bindings: [{ mouseButton: 1 }], @@ -116,17 +118,15 @@ describe('EraserTool:', () => { const imageId1 = 'fakeImageLoader:imageURI_64_64_10_5_1_1_0'; const vp = this.renderingEngine.getViewport(viewportId); - const addEventListenerForAnnotationRemoved = () => { - eventTarget.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { - const lengthAnnotations = annotation.state.getAnnotations( - LengthTool.toolName, - element - ); - expect(lengthAnnotations).toBeDefined(); - expect(lengthAnnotations.length).toBe(0); - done(); - }); - }; + eventTarget.addEventListener(csToolsEvents.ANNOTATION_REMOVED, () => { + const lengthAnnotations = annotation.state.getAnnotations( + LengthTool.toolName, + element + ); + expect(lengthAnnotations).toBeDefined(); + expect(lengthAnnotations.length).toBe(0); + done(); + }); element.addEventListener(Events.IMAGE_RENDERED, () => { const index1 = [32, 32, 0]; @@ -180,7 +180,9 @@ describe('EraserTool:', () => { cachedStats: {}, }, }; + annotation.state.addAnnotation(lengthAnnotation, element); + triggerAnnotationAddedForElement(lengthAnnotation, element); let evt = new MouseEvent('mousedown', { target: element, @@ -191,8 +193,7 @@ describe('EraserTool:', () => { pageY: pageY1, }); - addEventListenerForAnnotationRemoved(); - document.dispatchEvent(evt); + element.dispatchEvent(evt); }); this.stackToolGroup.addViewport(vp.id, this.renderingEngine.id); From cbca833df170edc9b87849d958213106cc720f1d Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 15 Feb 2024 09:57:12 -0500 Subject: [PATCH 17/17] api --- .../src/tools/{EraserTool.ts => AnnotationEraserTool.ts} | 6 +++--- packages/tools/src/tools/index.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename packages/tools/src/tools/{EraserTool.ts => AnnotationEraserTool.ts} (95%) diff --git a/packages/tools/src/tools/EraserTool.ts b/packages/tools/src/tools/AnnotationEraserTool.ts similarity index 95% rename from packages/tools/src/tools/EraserTool.ts rename to packages/tools/src/tools/AnnotationEraserTool.ts index 2db2a68ced..5f4d56e05f 100644 --- a/packages/tools/src/tools/EraserTool.ts +++ b/packages/tools/src/tools/AnnotationEraserTool.ts @@ -7,7 +7,7 @@ import { } from '../stateManagement/annotation/annotationState'; import { setAnnotationSelected } from '../stateManagement/annotation/annotationSelection'; -class EraserTool extends BaseTool { +class AnnotationEraserTool extends BaseTool { static toolName; constructor( toolProps: PublicToolProps = {}, @@ -92,5 +92,5 @@ class EraserTool extends BaseTool { } } -EraserTool.toolName = 'Eraser'; -export default EraserTool; +AnnotationEraserTool.toolName = 'Eraser'; +export default AnnotationEraserTool; diff --git a/packages/tools/src/tools/index.ts b/packages/tools/src/tools/index.ts index 7ae76ccfc0..6c62430d9d 100644 --- a/packages/tools/src/tools/index.ts +++ b/packages/tools/src/tools/index.ts @@ -37,7 +37,7 @@ import AngleTool from './annotation/AngleTool'; import CobbAngleTool from './annotation/CobbAngleTool'; import UltrasoundDirectionalTool from './annotation/UltrasoundDirectionalTool'; import KeyImageTool from './annotation/KeyImageTool'; -import EraserTool from './EraserTool'; +import AnnotationEraserTool from './AnnotationEraserTool'; // Segmentation DisplayTool import SegmentationDisplayTool from './displayTools/SegmentationDisplayTool'; @@ -91,7 +91,7 @@ export { CobbAngleTool, UltrasoundDirectionalTool, KeyImageTool, - EraserTool, + AnnotationEraserTool as EraserTool, // Segmentations Display SegmentationDisplayTool, // Segmentations Tools