Skip to content

Commit 7929f08

Browse files
fix(measurement): Restore viewport interactivity when deleting in-progress Spline or Livewire measurement (#5905)
1 parent f999f3e commit 7929f08

File tree

3 files changed

+147
-3
lines changed

3 files changed

+147
-3
lines changed

extensions/cornerstone/src/initMeasurementService.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { eventTarget, Types } from '@cornerstonejs/core';
2-
import { Enums, annotation } from '@cornerstonejs/tools';
2+
import { Enums, annotation, cancelActiveManipulations } from '@cornerstonejs/tools';
33
import { DicomMetadataStore } from '@ohif/core';
44

55
import * as CSExtensionEnums from './enums';
@@ -504,6 +504,12 @@ const connectMeasurementServiceToTools = ({
504504
if (source?.name && source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) {
505505
return;
506506
}
507+
// Cancel any active tool manipulation (e.g., Spline/Livewire) to avoid leaving the tool
508+
// in a drawing state after deleting a not completed measurement, which can block viewport interactivity.
509+
const element = getActiveViewportEnabledElement(viewportGridService)?.viewport?.element;
510+
if (element) {
511+
cancelActiveManipulations(element);
512+
}
507513
const removedAnnotation = annotation.state.getAnnotation(removedMeasurementId);
508514
removeAnnotation(removedMeasurementId);
509515
// Ensure `removedAnnotation` is available before triggering the memo,

tests/Livewire.spec.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
1+
import { checkForScreenshot, screenShotPaths, test, visitStudy, expect } from './utils';
2+
import { press } from './utils/keyboardUtils';
23

34
test.beforeEach(async ({ page }) => {
45
const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5';
@@ -25,3 +26,70 @@ test('should display the livewire tool', async ({
2526
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
2627
await checkForScreenshot(page, page, screenShotPaths.livewire.livewireDisplayedCorrectly);
2728
});
29+
30+
test('should restore viewport interactivity after deleting an in-progress Livewire annotation via context menu', async ({
31+
DOMOverlayPageObject,
32+
mainToolbarPageObject,
33+
viewportPageObject,
34+
}) => {
35+
await mainToolbarPageObject.measurementTools.livewireContour.click();
36+
const activeViewport = await viewportPageObject.active;
37+
await activeViewport.clickAt([
38+
{ x: 380, y: 299 },
39+
{ x: 420, y: 236 },
40+
{ x: 523, y: 232 },
41+
]);
42+
43+
await activeViewport.clickAt([{ x: 550, y: 312 }], 'right');
44+
45+
const deleteButton = DOMOverlayPageObject.viewport.annotationContextMenu.delete;
46+
await expect(deleteButton.locator).toBeVisible();
47+
await deleteButton.click();
48+
49+
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
50+
51+
// Draw and complete a new Livewire annotation to verify interactivity is restored
52+
await activeViewport.clickAt([
53+
{ x: 380, y: 299 },
54+
{ x: 420, y: 236 },
55+
{ x: 523, y: 232 },
56+
{ x: 581, y: 287 },
57+
{ x: 482, y: 333 },
58+
{ x: 383, y: 301 },
59+
]);
60+
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
61+
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
62+
});
63+
64+
test('should restore viewport interactivity after deleting an in-progress Livewire annotation via Backspace', async ({
65+
page,
66+
DOMOverlayPageObject,
67+
mainToolbarPageObject,
68+
viewportPageObject,
69+
}) => {
70+
await mainToolbarPageObject.measurementTools.livewireContour.click();
71+
const activeViewport = await viewportPageObject.active;
72+
await activeViewport.clickAt([
73+
{ x: 380, y: 299 },
74+
{ x: 420, y: 236 },
75+
{ x: 523, y: 232 },
76+
]);
77+
78+
// Ensure the three points clicked above are rendered in the DOM before pressing Backspace
79+
await expect(activeViewport.svg('circle')).toHaveCount(3);
80+
await press({ page, key: 'Backspace' });
81+
82+
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
83+
84+
// Draw and complete a new Livewire annotation to verify interactivity is restored
85+
await activeViewport.clickAt([
86+
{ x: 380, y: 299 },
87+
{ x: 420, y: 236 },
88+
{ x: 523, y: 232 },
89+
{ x: 581, y: 287 },
90+
{ x: 482, y: 333 },
91+
{ x: 383, y: 301 },
92+
]);
93+
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
94+
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
95+
});

tests/Spline.spec.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { checkForScreenshot, screenShotPaths, test, visitStudy } from './utils';
1+
import { checkForScreenshot, screenShotPaths, test, visitStudy, expect } from './utils';
2+
import { press } from './utils/keyboardUtils';
23

34
test.beforeEach(async ({ page }) => {
45
const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5';
@@ -25,3 +26,72 @@ test('should display the spline tool', async ({
2526
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
2627
await checkForScreenshot(page, page, screenShotPaths.spline.splineDisplayedCorrectly);
2728
});
29+
30+
test('should restore viewport interactivity after deleting an in-progress Spline annotation via context menu', async ({
31+
DOMOverlayPageObject,
32+
mainToolbarPageObject,
33+
viewportPageObject,
34+
}) => {
35+
await mainToolbarPageObject.measurementTools.splineROI.click();
36+
37+
const activeViewport = await viewportPageObject.active;
38+
await activeViewport.clickAt([
39+
{ x: 380, y: 299 },
40+
{ x: 420, y: 236 },
41+
{ x: 523, y: 232 },
42+
]);
43+
44+
await activeViewport.clickAt([{ x: 550, y: 312 }], 'right');
45+
46+
const deleteButton = DOMOverlayPageObject.viewport.annotationContextMenu.delete;
47+
await expect(deleteButton.locator).toBeVisible();
48+
await deleteButton.click();
49+
50+
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
51+
52+
// Draw and complete a new Spline annotation to verify interactivity is restored
53+
await activeViewport.clickAt([
54+
{ x: 380, y: 299 },
55+
{ x: 420, y: 236 },
56+
{ x: 523, y: 232 },
57+
{ x: 581, y: 287 },
58+
{ x: 482, y: 333 },
59+
{ x: 383, y: 301 },
60+
]);
61+
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
62+
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
63+
});
64+
65+
test('should restore viewport interactivity after deleting an in-progress Spline annotation via Backspace', async ({
66+
page,
67+
DOMOverlayPageObject,
68+
mainToolbarPageObject,
69+
viewportPageObject,
70+
}) => {
71+
await mainToolbarPageObject.measurementTools.splineROI.click();
72+
73+
const activeViewport = await viewportPageObject.active;
74+
await activeViewport.clickAt([
75+
{ x: 380, y: 299 },
76+
{ x: 420, y: 236 },
77+
{ x: 523, y: 232 },
78+
]);
79+
80+
// Ensure the three points clicked above are rendered in the DOM before pressing Backspace
81+
await expect(activeViewport.svg('circle')).toHaveCount(3);
82+
await press({ page, key: 'Backspace' });
83+
84+
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
85+
86+
// Draw and complete a new Spline annotation to verify interactivity is restored
87+
await activeViewport.clickAt([
88+
{ x: 380, y: 299 },
89+
{ x: 420, y: 236 },
90+
{ x: 523, y: 232 },
91+
{ x: 581, y: 287 },
92+
{ x: 482, y: 333 },
93+
{ x: 383, y: 301 },
94+
]);
95+
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
96+
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
97+
});

0 commit comments

Comments
 (0)