From b9029ef6f8d63a0e36ec929310c1c5ad3f563ef8 Mon Sep 17 00:00:00 2001 From: Ghadeer Albattarni <165973963+GhadeerAlbattarni@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:52:23 -0400 Subject: [PATCH] fix(segmentation): restrict overlay segmentation menu to same frame of reference as viewport background display set (#5900) - Add FrameOfReferenceUID to SEG and RTSTRUCT displaySet in SOP Class Handlers so the FOR is available for filtering - Sync optimisticOverlayDisplaySets when background display set changes so the overlay menu reflects the correct state after a background switch - Add FOR matching guard to the hydrate segmentation synchronizer to prevent the hydration synchronizer from blindly mirroring segmentations from a source viewport to a target viewport if their primary Frames of Reference do not align. - fix segmentation overlay order reversal on viewport re-render --- .../src/getSopClassHandlerModule.ts | 6 ++ .../src/getSopClassHandlerModule.ts | 4 + .../src/getSopClassHandlerModule.ts | 2 +- .../src/utils/hydrateStructuredReport.ts | 4 +- .../ViewportDataOverlayMenu.tsx | 11 ++- .../ViewportDataOverlaySettingMenu/utils.ts | 4 +- .../src/getSopClassHandlerModule.js | 2 +- .../createHydrateSegmentationSynchronizer.ts | 10 +- .../CornerstoneViewportService.ts | 87 ++++++++-------- .../default/src/getSopClassHandlerModule.js | 2 +- .../src/services/MicroscopyService.ts | 6 +- .../src/utils/getSourceDisplaySet.js | 2 +- platform/core/src/types/DisplaySet.ts | 6 ++ tests/DataOverlayMenuFrameOfReference.spec.ts | 98 +++++++++++++++++++ tests/pages/DataOverlayPageObject.ts | 35 ++++++- 15 files changed, 223 insertions(+), 56 deletions(-) create mode 100644 tests/DataOverlayMenuFrameOfReference.spec.ts diff --git a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.ts index 964e34341..7774b094c 100644 --- a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.ts @@ -56,6 +56,7 @@ function _getDisplaySetsFromSeries( StudyInstanceUID, SOPClassHandlerId, SOPClassUID, + frameOfReferenceUID: null, referencedImages: null, referencedSeriesInstanceUID: null, referencedDisplaySetInstanceUID: null, @@ -95,6 +96,9 @@ function _getDisplaySetsFromSeries( displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; + displaySet.frameOfReferenceUID = + instance.ReferencedFrameOfReferenceSequence?.[0]?.FrameOfReferenceUID; + const { displaySetService } = servicesManager.services; const referencedDisplaySets = displaySetService.getDisplaySetsForReferences(referencedSeriesSequence); @@ -113,6 +117,7 @@ function _getDisplaySetsFromSeries( if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) { displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = addedDisplaySet.isReconstructable; + displaySet.frameOfReferenceUID = addedDisplaySet.frameOfReferenceUID; unsubscribe(); } } @@ -121,6 +126,7 @@ function _getDisplaySetsFromSeries( const [referencedDisplaySet] = referencedDisplaySets; displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = referencedDisplaySet.isReconstructable; + displaySet.frameOfReferenceUID = referencedDisplaySet.frameOfReferenceUID; } displaySet.load = ({ headers, createSegmentation = true }) => diff --git a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts index 903d0216a..21b9596e4 100644 --- a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.ts @@ -30,6 +30,7 @@ function _getDisplaySetsFromSeries( SeriesDate, StructureSetDate, SOPClassUID, + FrameOfReferenceUID, wadoRoot, wadoUri, wadoUriRoot, @@ -49,6 +50,7 @@ function _getDisplaySetsFromSeries( StudyInstanceUID, SOPClassHandlerId, SOPClassUID, + frameOfReferenceUID: FrameOfReferenceUID, referencedImages: null, referencedSeriesInstanceUID: null, referencedDisplaySetInstanceUID: null, @@ -104,6 +106,7 @@ function _getDisplaySetsFromSeries( if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) { displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = addedDisplaySet.isReconstructable; + displaySet.frameOfReferenceUID = addedDisplaySet.frameOfReferenceUID; unsubscribe(); } } @@ -111,6 +114,7 @@ function _getDisplaySetsFromSeries( } else { displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; displaySet.isReconstructable = referencedDisplaySet.isReconstructable; + displaySet.frameOfReferenceUID = referencedDisplaySet.frameOfReferenceUID; } displaySet.load = async ({ headers }) => diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index f30dda6cb..50f75f234 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -230,7 +230,7 @@ async function _load( function _measurementBelongsToDisplaySet({ measurement, displaySet }) { return ( - measurement.coords[0].ReferencedFrameOfReferenceSequence === displaySet.FrameOfReferenceUID + measurement.coords[0].ReferencedFrameOfReferenceSequence === displaySet.frameOfReferenceUID ); } diff --git a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts index b934f6562..92878a758 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts @@ -145,7 +145,7 @@ export default function hydrateStructuredReport( for (const FrameOfReferenceUID of frameOfReferenceUIDs) { const displaySetsFOR = displaySetService.getDisplaySetsBy( - ds => ds.FrameOfReferenceUID === FrameOfReferenceUID && !ds.isDerivedDisplaySet + ds => ds.frameOfReferenceUID === FrameOfReferenceUID && !ds.isDerivedDisplaySet ); const ds = chooseDisplaySet(displaySetsFOR, FrameOfReferenceUID); if (!ds) { @@ -335,7 +335,7 @@ function getReferenceData3D(toolData, servicesManager: Types.ServicesManager) { const { points } = toolData.annotation.data.handles; const { displaySetService } = servicesManager.services; const displaySetsFOR = displaySetService.getDisplaySetsBy( - ds => ds.FrameOfReferenceUID === FrameOfReferenceUID + ds => ds.frameOfReferenceUID === FrameOfReferenceUID ); if (!displaySetsFOR.length || !points?.length) { return { diff --git a/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx b/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx index 0d58e705c..01d0a35ed 100644 --- a/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx +++ b/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/ViewportDataOverlayMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Button, Icons, @@ -43,6 +43,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri const [thresholdOpacityEnabled, setThresholdOpacityEnabled] = useState(false); + useEffect(() => { + setOptimisticOverlayDisplaySets(overlayDisplaySets); + }, [backgroundDisplaySet?.displaySetInstanceUID, overlayDisplaySets]); + /** * Change the background display set */ @@ -482,7 +486,10 @@ function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: stri } }} > - + {( backgroundDisplaySet?.SeriesDescription || diff --git a/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/utils.ts b/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/utils.ts index d797e773c..36803c917 100644 --- a/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/utils.ts +++ b/extensions/cornerstone/src/components/ViewportDataOverlaySettingMenu/utils.ts @@ -80,8 +80,8 @@ export function getEnhancedDisplaySets({ viewportId, services }) { // Check if Frame of Reference matches if ( - displaySet.FrameOfReferenceUID && - displaySet.FrameOfReferenceUID !== backgroundDisplaySet.FrameOfReferenceUID + displaySet.frameOfReferenceUID && + displaySet.frameOfReferenceUID !== backgroundDisplaySet.frameOfReferenceUID ) { return { ...displaySet, diff --git a/extensions/cornerstone/src/getSopClassHandlerModule.js b/extensions/cornerstone/src/getSopClassHandlerModule.js index 157b7aef2..3f8ca69ea 100644 --- a/extensions/cornerstone/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone/src/getSopClassHandlerModule.js @@ -82,7 +82,7 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID, - FrameOfReferenceUID, + frameOfReferenceUID: FrameOfReferenceUID, SOPClassHandlerId, SOPClassUID, SeriesDescription: SeriesDescription || 'Microscopy Data', diff --git a/extensions/cornerstone/src/services/SyncGroupService/createHydrateSegmentationSynchronizer.ts b/extensions/cornerstone/src/services/SyncGroupService/createHydrateSegmentationSynchronizer.ts index f29e086d3..1197922bc 100644 --- a/extensions/cornerstone/src/services/SyncGroupService/createHydrateSegmentationSynchronizer.ts +++ b/extensions/cornerstone/src/services/SyncGroupService/createHydrateSegmentationSynchronizer.ts @@ -66,7 +66,15 @@ const segmentationRepresentationModifiedCallback = async ( const sharedDisplaySetExists = isAnyDisplaySetCommon(sourceDisplaySetUIDs, targetDisplaySetUIDs); - if (!sharedDisplaySetExists && !viewport.getFrameOfReferenceUID()) { + const targetFrameOfReferenceUID = viewport.getFrameOfReferenceUID(); + const sourceFrameOfReferenceUID = + getEnabledElementByViewportId(sourceViewportId)?.viewport?.getFrameOfReferenceUID(); + + if (!sharedDisplaySetExists && !targetFrameOfReferenceUID) { + return; + } + + if (!sharedDisplaySetExists && targetFrameOfReferenceUID !== sourceFrameOfReferenceUID) { return; } diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index ed281997a..e3751b78c 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -870,28 +870,21 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0; } - return viewport.setStack(imageIds, initialImageIndexToUse).then(() => { - viewport.setProperties({ ...properties }); - this.setPresentations(viewport.id, presentations, viewportInfo); + await viewport.setStack(imageIds, initialImageIndexToUse); + viewport.setProperties({ ...properties }); + this.setPresentations(viewport.id, presentations, viewportInfo); - if (overlayProcessingResults?.length) { - overlayProcessingResults.forEach(overlayProcessingResult => { - if (overlayProcessingResult?.addOverlayFn) { - overlayProcessingResult.addOverlayFn(); - } - }); - } + await this._addOverlayRepresentations(overlayProcessingResults); - if (displayArea) { - viewport.setDisplayArea(displayArea); - } - if (rotation) { - viewport.setProperties({ rotation }); - } - if (flipHorizontal) { - viewport.setCamera({ flipHorizontal: true }); - } - }); + if (displayArea) { + viewport.setDisplayArea(displayArea); + } + if (rotation) { + viewport.setProperties({ rotation }); + } + if (flipHorizontal) { + viewport.setCamera({ flipHorizontal: true }); + } } private _getInitialImageIndexForViewport( @@ -1120,14 +1113,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi } await viewport.setVolumes(volumeInputArray); - - if (overlayProcessingResults?.length) { - overlayProcessingResults.forEach(({ addOverlayFn }) => { - if (addOverlayFn) { - addOverlayFn(); - } - }); - } + await this._addOverlayRepresentations(overlayProcessingResults); viewport.render(); volumesProperties.forEach(({ properties, volumeId }) => { @@ -1193,7 +1179,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi private addOverlayRepresentationForDisplaySet( displaySet: OhifTypes.DisplaySet, viewport: Types.IViewport - ) { + ): Promise { const { segmentationService } = this.servicesManager.services; const segmentationId = displaySet.displaySetInstanceUID; @@ -1203,18 +1189,36 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi : csToolsEnums.SegmentationRepresentations.Contour; const { predecessorImageId } = displaySet; - segmentationService.addSegmentationRepresentation(viewport.id, { - segmentationId, - predecessorImageId, - type: representationType, - config: { - blendMode: - viewport?.getBlendMode?.() === 1 ? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND : undefined, - }, - }); - + const segmentationRepresentationPromise = segmentationService.addSegmentationRepresentation( + viewport.id, + { + segmentationId, + predecessorImageId, + type: representationType, + config: { + blendMode: + viewport?.getBlendMode?.() === 1 + ? BlendModes.LABELMAP_EDGE_PROJECTION_BLEND + : undefined, + }, + } + ); // store the segmentation presentation id in the viewport info this.storePresentation({ viewportId: viewport.id }); + return segmentationRepresentationPromise; + } + + private async _addOverlayRepresentations( + overlayProcessingResults?: Array<{ addOverlayFn?: () => Promise }> + ): Promise { + if (!overlayProcessingResults?.length) { + return; + } + for (const overlayProcessingResult of overlayProcessingResults) { + if (overlayProcessingResult?.addOverlayFn) { + await overlayProcessingResult.addOverlayFn(); + } + } } // Todo: keepCamera is an interim solution until we have a better solution for @@ -1272,7 +1276,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi } if (viewport instanceof ECGViewport) { - return this._setEcgViewport(viewport as unknown as Types.IECGViewport, viewportData as StackViewportData); + return this._setEcgViewport( + viewport as unknown as Types.IECGViewport, + viewportData as StackViewportData + ); } return this._setOtherViewport( diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js index b45a85134..debc72c27 100644 --- a/extensions/default/src/getSopClassHandlerModule.js +++ b/extensions/default/src/getSopClassHandlerModule.js @@ -113,7 +113,7 @@ const makeDisplaySet = (instances, index) => { label: instance.SeriesDescription || `${i18n.t('Series')} ${instance.SeriesNumber} - ${i18n.t(instance.Modality)}`, - FrameOfReferenceUID: instance.FrameOfReferenceUID, + frameOfReferenceUID: instance.FrameOfReferenceUID, }); const imageIds = dataSource.getImageIdsForDisplaySet(imageSet); diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.ts b/extensions/dicom-microscopy/src/services/MicroscopyService.ts index 5a25b20fc..f15c9ce03 100644 --- a/extensions/dicom-microscopy/src/services/MicroscopyService.ts +++ b/extensions/dicom-microscopy/src/services/MicroscopyService.ts @@ -329,16 +329,16 @@ export default class MicroscopyService extends PubSubService { const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); const smDisplaySet = displaySets.find(ds => ds.Modality === 'SM'); - const { FrameOfReferenceUID, othersFrameOfReferenceUID } = smDisplaySet; + const { frameOfReferenceUID, othersFrameOfReferenceUID } = smDisplaySet; if (!studyMetadata) { return; } - let derivedDisplaySets = FrameOfReferenceUID + let derivedDisplaySets = frameOfReferenceUID ? displaySets.filter( ds => - ds.ReferencedFrameOfReferenceUID === FrameOfReferenceUID || + ds.ReferencedFrameOfReferenceUID === frameOfReferenceUID || // sometimes each depth instance has the different FrameOfReferenceID othersFrameOfReferenceUID.includes(ds.ReferencedFrameOfReferenceUID) ) diff --git a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js index 5a42a34f3..1bae0852e 100644 --- a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js +++ b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js @@ -22,7 +22,7 @@ export default function getSourceDisplaySet(allDisplaySets, microscopySRDisplayS const referencedDisplaySet = otherDisplaySets.find( displaySet => displaySet.Modality === 'SM' && - (displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID || + (displaySet.frameOfReferenceUID === ReferencedFrameOfReferenceUID || // sometimes each depth instance has the different FrameOfReferenceID displaySet.othersFrameOfReferenceUID.includes(ReferencedFrameOfReferenceUID)) ); diff --git a/platform/core/src/types/DisplaySet.ts b/platform/core/src/types/DisplaySet.ts index 3e39e9aea..a07a5e2c8 100644 --- a/platform/core/src/types/DisplaySet.ts +++ b/platform/core/src/types/DisplaySet.ts @@ -57,6 +57,12 @@ export type DisplaySet = { */ referencedDisplaySetInstanceUID?: string; + /** + * The FrameOfReferenceUID shared by every frame within this display set. + * It will be undefined if the frames do not all share the same Frame of Reference. + */ + frameOfReferenceUID?: string; + SeriesDate?: string; SeriesTime?: string; instance?: InstanceMetadata; diff --git a/tests/DataOverlayMenuFrameOfReference.spec.ts b/tests/DataOverlayMenuFrameOfReference.spec.ts new file mode 100644 index 000000000..2363de454 --- /dev/null +++ b/tests/DataOverlayMenuFrameOfReference.spec.ts @@ -0,0 +1,98 @@ +import { test, visitStudy, expect } from './utils'; + +test.beforeEach(async ({ page }) => { + const studyInstanceUID = '1.3.6.1.4.1.12201.1091.126683095609223531686845324113579088978'; + const mode = 'segmentation'; + await visitStudy(page, studyInstanceUID, mode, 2000); +}); + +test('should only show segmentations matching the background Frame of Reference', async ({ + page, + viewportPageObject, +}) => { + const segmentationLabelsFOR1 = ['Segmentation FOR1SEG', 'FoR 1 RTstructRTSTRUCT']; + const segmentationLabelsFOR2 = ['Segmentation FOR2SEG', 'FoR 2 RTstructRTSTRUCT']; + + const dataOverlay = (await viewportPageObject.getNth(0)).overlayMenu.dataOverlay; + + await dataOverlay.toggle(); + await dataOverlay.openAddSegmentationDropdown(); + + const segmentationLabels = await dataOverlay.getDropdownOptionLabels(); + expect(segmentationLabels.sort()).toEqual([...segmentationLabelsFOR1].sort()); + + await page.keyboard.press('Escape'); + + await dataOverlay.changeBackgroundDisplaySet('CT Std (FoR 2)-CT'); + await dataOverlay.clickSelectSegmentation(); + const segmentationLabelsAfterBgChange = await dataOverlay.getDropdownOptionLabels(); + expect(segmentationLabelsAfterBgChange).not.toEqual(segmentationLabelsFOR1); + expect(segmentationLabelsAfterBgChange.sort()).toEqual([...segmentationLabelsFOR2].sort()); +}); + +test('should only sync segmentations to viewports with same Frame of Reference', async ({ + viewportPageObject, +}) => { + const segmentationFOR1Label = 'Segmentation FOR1'; + + const viewportA = await viewportPageObject.getNth(0); + const viewportB = await viewportPageObject.getNth(1); + const viewportC = await viewportPageObject.getNth(2); + + const dataOverlayA = viewportA.overlayMenu.dataOverlay; + const dataOverlayB = viewportB.overlayMenu.dataOverlay; + const dataOverlayC = viewportC.overlayMenu.dataOverlay; + + // In FOR1 viewport A: add a segmentation FOR1 overlay + await viewportA.pane.hover(); + await dataOverlayA.toggle(); + await dataOverlayA.addSegmentation(segmentationFOR1Label); + await expect(dataOverlayA.getOverlaySegmentationRow(segmentationFOR1Label)).toBeVisible(); + await dataOverlayA.toggle(); + await expect(dataOverlayA.menu).not.toBeVisible(); + + // In another FOR1 viewport B: segmentation FOR1 should be visible (synced) + await viewportB.pane.hover(); + await dataOverlayB.toggle(); + const activeSegmentationViewportB = dataOverlayB.getOverlaySegmentationRow(segmentationFOR1Label); + await expect(activeSegmentationViewportB).toBeVisible(); + await expect(activeSegmentationViewportB).toHaveText(segmentationFOR1Label.toUpperCase()); + await dataOverlayB.toggle(); + await expect(dataOverlayB.menu).not.toBeVisible(); + + // In FOR2 viewport C: segmentation FOR1 should NOT be visible + await viewportC.pane.hover(); + await dataOverlayC.toggle(); + await expect(dataOverlayC.getOverlaySegmentationRow(segmentationFOR1Label)).not.toBeVisible(); + await dataOverlayC.toggle(); + await expect(dataOverlayC.menu).not.toBeVisible(); +}); + +test('should preserve segmentation order after menu close and reopen', async ({ + viewportPageObject, +}) => { + const segmentation1Label = 'Segmentation FOR1'; + const segmentation2Label = 'FoR 1 RTstruct'; + + const dataOverlay = (await viewportPageObject.getNth(0)).overlayMenu.dataOverlay; + + await dataOverlay.toggle(); + await dataOverlay.addSegmentation(segmentation1Label); + await expect(dataOverlay.getOverlaySegmentationRow(segmentation1Label)).toBeVisible(); + + await dataOverlay.addSegmentation(segmentation2Label); + await expect(dataOverlay.getOverlaySegmentationRow(segmentation2Label)).toBeVisible(); + + await dataOverlay.toggle(); + await expect(dataOverlay.menu).not.toBeVisible(); + + // Reopen menu and verify segmentation order is preserved + await dataOverlay.toggle(); + await expect(dataOverlay.overlaySegmentationRows).toHaveCount(2); + await expect(dataOverlay.overlaySegmentationRows.nth(0)).toHaveText( + segmentation1Label.toUpperCase() + ); + await expect(dataOverlay.overlaySegmentationRows.nth(1)).toHaveText( + segmentation2Label.toUpperCase() + ); +}); diff --git a/tests/pages/DataOverlayPageObject.ts b/tests/pages/DataOverlayPageObject.ts index e26d4632b..b3c560442 100644 --- a/tests/pages/DataOverlayPageObject.ts +++ b/tests/pages/DataOverlayPageObject.ts @@ -9,13 +9,16 @@ export class DataOverlayPageObject { this.viewportId = viewportId; } + get menu() { + return this.page.getByTestId(`viewport-data-overlay-menu-${this.viewportId}`); + } + async toggle() { await this.page.getByTestId(`dataOverlayMenu-${this.viewportId}-btn`).click(); } async addSegmentation(segmentation: string) { - await this.page.getByTestId(`AddSegmentationDataOverlay-${this.viewportId}`).click(); - await this.page.getByText('SELECT A SEGMENTATION').click(); + await this.openAddSegmentationDropdown(); await this.page.getByTestId(segmentation).click(); } @@ -28,4 +31,32 @@ export class DataOverlayPageObject { await this.page.getByTestId(`overlay-ds-more-button-${segmentation}`).click(); await this.page.getByTestId(`overlay-ds-remove-button-${segmentation}`).click(); } + + async openAddSegmentationDropdown() { + await this.page.getByTestId(`AddSegmentationDataOverlay-${this.viewportId}`).click(); + await this.clickSelectSegmentation(); + } + + async clickSelectSegmentation() { + await this.page.getByText('SELECT A SEGMENTATION').click(); + } + + async getDropdownOptionLabels(): Promise { + const options = this.page.getByRole('option'); + const labels = (await options.allTextContents()).map(text => text.trim()).filter(Boolean); + return labels; + } + + getOverlaySegmentationRow(label: string) { + return this.menu.getByTestId(`overlay-ds-select-value-${label.toUpperCase()}`); + } + + get overlaySegmentationRows() { + return this.menu.locator('[data-cy^="overlay-ds-select-value-"]'); + } + + async changeBackgroundDisplaySet(backgroundLabel: string) { + await this.page.getByTestId(`overlay-background-ds-select-${this.viewportId}`).click(); + await this.page.getByTestId(backgroundLabel).click(); + } }