diff --git a/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx b/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx index dd695b921..b843bbc7a 100644 --- a/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx +++ b/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx @@ -36,6 +36,9 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri foregroundDisplaySets, } = useViewportDisplaySets(viewportId); + const [optimisticOverlayDisplaySets, setOptimisticOverlayDisplaySets] = + useState(overlayDisplaySets); + const [thresholdOpacityEnabled, setThresholdOpacityEnabled] = useState(false); /** @@ -77,6 +80,18 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri * Remove a display set layer */ const handleRemoveDisplaySetLayer = (displaySetInstanceUID: string) => { + const optimisticOverlayDisplaySetsIndex = optimisticOverlayDisplaySets.findIndex( + displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID + ); + + if (optimisticOverlayDisplaySetsIndex !== -1) { + setOptimisticOverlayDisplaySets(prevOptimisticOverlayDisplaySets => { + return prevOptimisticOverlayDisplaySets.filter( + displaySet => displaySet.displaySetInstanceUID !== displaySetInstanceUID + ); + }); + } + commandsManager.runCommand('removeDisplaySetLayer', { viewportId, displaySetInstanceUID, @@ -110,6 +125,17 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri ); if (selectedDisplaySet) { + setOptimisticOverlayDisplaySets(prevOptimisticOverlayDisplaySets => { + const currentDisplaySetIndex = prevOptimisticOverlayDisplaySets.findIndex( + displaySet => displaySet.displaySetInstanceUID === currentDisplaySet.displaySetInstanceUID + ); + return [ + ...prevOptimisticOverlayDisplaySets.slice(0, currentDisplaySetIndex), + selectedDisplaySet, + ...prevOptimisticOverlayDisplaySets.slice(currentDisplaySetIndex + 1), + ]; + }); + handleReplaceDisplaySetLayer( currentDisplaySet.displaySetInstanceUID, selectedDisplaySet.displaySetInstanceUID @@ -150,6 +176,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri ); if (selectedDisplaySet) { + setOptimisticOverlayDisplaySets(prevOptimisticOverlayDisplaySets => [ + ...prevOptimisticOverlayDisplaySets, + selectedDisplaySet, + ]); handleAddDisplaySetAsLayer(selectedDisplaySet.displaySetInstanceUID); // Remove this pending segmentation from the list setPendingSegmentations(pendingSegmentations.filter(id => id !== pendingId)); @@ -184,7 +214,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri }; return ( -
+
{/* Top buttons row */}
handleRemoveDisplaySetLayer(displaySet.displaySetInstanceUID)} > Remove diff --git a/extensions/cornerstone/src/hooks/useViewportDisplaySets.ts b/extensions/cornerstone/src/hooks/useViewportDisplaySets.ts index b9bd56580..0eceb97b7 100644 --- a/extensions/cornerstone/src/hooks/useViewportDisplaySets.ts +++ b/extensions/cornerstone/src/hooks/useViewportDisplaySets.ts @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useSystem, utils } from '@ohif/core'; import { useViewportGrid } from '@ohif/ui-next'; import { @@ -122,12 +122,34 @@ export function useViewportDisplaySets( // Get all available segmentations (only if needed) const needsSegmentations = includeOverlay; - const segmentationRepresentations = useMemo( - () => - needsSegmentations ? segmentationService.getSegmentationRepresentations(viewportIdToUse) : [], - [segmentationService, viewportIdToUse, needsSegmentations] + + const [segmentationRepresentations, setSegmentationRepresentations] = useState( + needsSegmentations ? segmentationService.getSegmentationRepresentations(viewportIdToUse) : [] ); + useEffect(() => { + setSegmentationRepresentations( + needsSegmentations ? segmentationService.getSegmentationRepresentations(viewportIdToUse) : [] + ); + + const unsubscribeArr = needsSegmentations + ? [ + segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED, + segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_REMOVED, + ].map(event => + segmentationService.subscribe(event, () => { + setSegmentationRepresentations( + segmentationService.getSegmentationRepresentations(viewportIdToUse) + ); + }) + ) + : []; + + return () => { + unsubscribeArr.forEach(item => item.unsubscribe()); + }; + }, [segmentationService, viewportIdToUse, needsSegmentations]); + const overlayDisplaySets = useMemo(() => { if (!includeOverlay) { return []; diff --git a/tests/DataOverlayMenu.spec.ts b/tests/DataOverlayMenu.spec.ts new file mode 100644 index 000000000..991ab0583 --- /dev/null +++ b/tests/DataOverlayMenu.spec.ts @@ -0,0 +1,78 @@ +import { test } from 'playwright-test-coverage'; +import { visitStudy, checkForScreenshot, screenShotPaths } from './utils'; +import { press } from './utils/keyboardUtils'; + +test('should display added, selected and removed segmentation promptly', async ({ page }) => { + const studyInstanceUID = '1.3.6.1.4.1.32722.99.99.239341353911714368772597187099978969331'; + const mode = 'segmentation'; + await visitStudy(page, studyInstanceUID, mode, 2000); + + // Add a segmentation overlay and ensure the overlay menu reflects this change. + await page.getByTestId('dataOverlayMenu-default-btn').click(); + await page.getByTestId('AddSegmentationDataOverlay-default').click(); + await page.getByText('SELECT A SEGMENTATION').click(); + await page.getByTestId('2d-tta_nnU-Net_Segmentation').click(); + + await checkForScreenshot({ + page, + screenshotPath: + screenShotPaths.dataOverlayMenu.overlayMenuWith2d_tta_nnU_Net_SegmentationSelected, + }); + + // Hide the overlay menu. + await page.getByTestId('dataOverlayMenu-default-btn').click(); + + // navigate to the 51st image and ensure the correct overlay is displayed + await press({ page, key: 'ArrowDown', nTimes: 50 }); + + await checkForScreenshot({ + page, + screenshotPath: screenShotPaths.dataOverlayMenu.overlay2d_tta_nnU_Net_Segmentation, + }); + + // Show the overlay menu. + await page.getByTestId('dataOverlayMenu-default-btn').click(); + + // Change the segmentation overlay to a different one and ensure the overlay menu reflects this change. + await page.getByTestId('overlay-ds-select-value-2D-TTA_NNU-NET_SEGMENTATION').click(); + await page.getByTestId('Segmentation-SEG').click(); + + await checkForScreenshot({ + page, + screenshotPath: screenShotPaths.dataOverlayMenu.overlayMenuWithSegmentationSelected, + }); + + // Hide the overlay menu. + await page.getByTestId('dataOverlayMenu-default-btn').click(); + + // navigate to the 51st image and ensure the correct overlay is displayed + await press({ page, key: 'ArrowDown', nTimes: 50 }); + + await checkForScreenshot({ + page, + screenshotPath: screenShotPaths.dataOverlayMenu.overlaySegmentation, + }); + + // Show the overlay menu. + await page.getByTestId('dataOverlayMenu-default-btn').click(); + + // Remove the segmentation overlay and ensure the overlay menu reflects this change. + await page.getByTestId('overlay-ds-more-button-SEGMENTATION').click(); + await page.getByTestId('overlay-ds-remove-button-SEGMENTATION').click(); + + await checkForScreenshot({ + page, + screenshotPath: screenShotPaths.dataOverlayMenu.overlayMenuWithSegmentationOverlaysRemoved, + }); + + // Hide the overlay menu. + await page.getByTestId('dataOverlayMenu-default-btn').click(); + + // navigate to the 51st image and ensure no overlay is displayed + await press({ page, key: 'ArrowDown', nTimes: 50 }); + + await checkForScreenshot({ + page, + screenshotPath: screenShotPaths.dataOverlayMenu.noOverlay, + }); +}); diff --git a/tests/MPRThenRTOverlayNoHydration.spec.ts b/tests/MPRThenRTOverlayNoHydration.spec.ts index 8a03e0804..14192ae60 100644 --- a/tests/MPRThenRTOverlayNoHydration.spec.ts +++ b/tests/MPRThenRTOverlayNoHydration.spec.ts @@ -10,11 +10,12 @@ test.beforeEach(async ({ page }) => { test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay menu', async ({ page, }) => { - await page.getByTestId('side-panel-header-right').click(); - await page.getByTestId('Layout').click(); await page.getByTestId('MPR').click(); + // Wait 5 seconds for MPR to load. This is necessary in particular when screen shots are added or replaced. + await page.waitForTimeout(5000); + await checkForScreenshot( page, page, @@ -31,6 +32,9 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me // Hide the overlay menu. await page.getByTestId('dataOverlayMenu-mpr-sagittal-btn').click(); + // Wait 5 seconds for RT to load. This is necessary in particular when screen shots are added or replaced. + await page.waitForTimeout(5000); + await checkForScreenshot( page, page, diff --git a/tests/screenshots/chromium/DataOverlayMenu.spec.ts/noOverlay.png b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/noOverlay.png new file mode 100644 index 000000000..996b874d4 Binary files /dev/null and b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/noOverlay.png differ diff --git a/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlay2d-tta-nnU-Net-Segmentation.png b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlay2d-tta-nnU-Net-Segmentation.png new file mode 100644 index 000000000..114a4fbc3 Binary files /dev/null and b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlay2d-tta-nnU-Net-Segmentation.png differ diff --git a/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWith2d-tta-nnU-Net-SegmentationSelected.png b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWith2d-tta-nnU-Net-SegmentationSelected.png new file mode 100644 index 000000000..e50a700fc Binary files /dev/null and b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWith2d-tta-nnU-Net-SegmentationSelected.png differ diff --git a/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationOverlaysRemoved.png b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationOverlaysRemoved.png new file mode 100644 index 000000000..fb92d7a09 Binary files /dev/null and b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationOverlaysRemoved.png differ diff --git a/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationSelected.png b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationSelected.png new file mode 100644 index 000000000..b1f56f027 Binary files /dev/null and b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlayMenuWithSegmentationSelected.png differ diff --git a/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlaySegmentation.png b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlaySegmentation.png new file mode 100644 index 000000000..7b6b3a718 Binary files /dev/null and b/tests/screenshots/chromium/DataOverlayMenu.spec.ts/overlaySegmentation.png differ diff --git a/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPostRTOverlayNoHydration.png b/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPostRTOverlayNoHydration.png index 181839d0a..2ff004d43 100644 Binary files a/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPostRTOverlayNoHydration.png and b/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPostRTOverlayNoHydration.png differ diff --git a/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPreRTOverlayNoHydration.png b/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPreRTOverlayNoHydration.png index ce94c3bbb..c0789a5c8 100644 Binary files a/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPreRTOverlayNoHydration.png and b/tests/screenshots/chromium/MPRThenRTOverlayNoHydration.spec.ts/mprPreRTOverlayNoHydration.png differ diff --git a/tests/screenshots/chromium/MPRThenSEGOverlayNoHydration.spec.ts/mprPostSEGOverlayNoHydration.png b/tests/screenshots/chromium/MPRThenSEGOverlayNoHydration.spec.ts/mprPostSEGOverlayNoHydration.png index 3eaea10fa..5fbcdd9ef 100644 Binary files a/tests/screenshots/chromium/MPRThenSEGOverlayNoHydration.spec.ts/mprPostSEGOverlayNoHydration.png and b/tests/screenshots/chromium/MPRThenSEGOverlayNoHydration.spec.ts/mprPostSEGOverlayNoHydration.png differ diff --git a/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png b/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png index 231df8c8d..432304668 100644 Binary files a/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png and b/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png differ diff --git a/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png b/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png index 30654c0e4..cd3e12ed0 100644 Binary files a/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png and b/tests/screenshots/chromium/RTDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png differ diff --git a/tests/screenshots/chromium/RTDataOverlayNoHydrationThenMPR.spec.ts/rtDataOverlayNoHydrationPreMpr.png b/tests/screenshots/chromium/RTDataOverlayNoHydrationThenMPR.spec.ts/rtDataOverlayNoHydrationPreMpr.png index 1005f2a65..28df1882e 100644 Binary files a/tests/screenshots/chromium/RTDataOverlayNoHydrationThenMPR.spec.ts/rtDataOverlayNoHydrationPreMpr.png and b/tests/screenshots/chromium/RTDataOverlayNoHydrationThenMPR.spec.ts/rtDataOverlayNoHydrationPreMpr.png differ diff --git a/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png b/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png index ea0e0a45f..353343514 100644 Binary files a/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png and b/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayFirstImage.png differ diff --git a/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png b/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png index cfcef4517..e21de24b9 100644 Binary files a/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png and b/tests/screenshots/chromium/SEGDataOverlayForUnreferencedDisplaySetNoHydration.spec.ts/overlayMiddleImage.png differ diff --git a/tests/screenshots/chromium/SEGDataOverlayNoHydrationThenMPR.spec.ts/segDataOverlayNoHydrationPreMpr.png b/tests/screenshots/chromium/SEGDataOverlayNoHydrationThenMPR.spec.ts/segDataOverlayNoHydrationPreMpr.png index f0ae1d8cc..2de21f278 100644 Binary files a/tests/screenshots/chromium/SEGDataOverlayNoHydrationThenMPR.spec.ts/segDataOverlayNoHydrationPreMpr.png and b/tests/screenshots/chromium/SEGDataOverlayNoHydrationThenMPR.spec.ts/segDataOverlayNoHydrationPreMpr.png differ diff --git a/tests/utils/checkForScreenshot.ts b/tests/utils/checkForScreenshot.ts index 84d1caf7c..931cffe21 100644 --- a/tests/utils/checkForScreenshot.ts +++ b/tests/utils/checkForScreenshot.ts @@ -3,7 +3,7 @@ import { Locator, Page } from 'playwright'; type CheckForScreenshotProps = { page: Page; - locator: Locator | Page; + locator?: Locator | Page; screenshotPath: string; attempts?: number; delay?: number; @@ -14,7 +14,7 @@ type CheckForScreenshotProps = { const _checkForScreenshot = async (props: CheckForScreenshotProps) => { const { page, - locator, + locator = page, screenshotPath, attempts = 10, delay = 500, diff --git a/tests/utils/screenShotPaths.ts b/tests/utils/screenShotPaths.ts index 4fb83c1d0..1d1459643 100644 --- a/tests/utils/screenShotPaths.ts +++ b/tests/utils/screenShotPaths.ts @@ -164,6 +164,15 @@ const screenShotPaths = { jumpToMeasurementAfterSeriesChange: 'jumpToMeasurementMPR-jumpToMeasurementAfterSeriesChange.png', }, + dataOverlayMenu: { + overlayMenuWithSegmentationSelected: 'overlayMenuWithSegmentationSelected.png', + overlayMenuWith2d_tta_nnU_Net_SegmentationSelected: + 'overlayMenuWith2d_tta_nnU_Net_SegmentationSelected.png', + overlayMenuWithSegmentationOverlaysRemoved: 'overlayMenuWithSegmentationOverlaysRemoved.png', + overlay2d_tta_nnU_Net_Segmentation: 'overlay2d_tta_nnU_Net_Segmentation.png', + overlaySegmentation: 'overlaySegmentation.png', + noOverlay: 'noOverlay.png', + }, }; export { screenShotPaths };