diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index 9687a07a2..cf0ec0911 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -11,16 +11,11 @@ import { RectangleROITool, } from '@cornerstonejs/tools'; import { Types } from '@ohif/core'; -import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone'; import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool'; -import SCOORD3DPointTool from './tools/SCOORD3DPointTool'; -import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper'; import addToolInstance from './utils/addToolInstance'; import toolNames from './tools/toolNames'; -const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums; - /** * @param {object} configuration */ @@ -28,8 +23,6 @@ export default function init({ configuration = {}, servicesManager, }: Types.Extensions.ExtensionParams): void { - const { measurementService } = servicesManager.services; - addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool); addToolInstance(toolNames.SRLength, LengthTool); addToolInstance(toolNames.SRBidirectional, BidirectionalTool); @@ -39,26 +32,10 @@ export default function init({ addToolInstance(toolNames.SRAngle, AngleTool); addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool); addToolInstance(toolNames.SRRectangleROI, RectangleROITool); - addToolInstance(toolNames.SRSCOORD3DPoint, SCOORD3DPointTool); // TODO - fix the SR display of Cobb Angle, as it joins the two lines addToolInstance(toolNames.SRCobbAngle, CobbAngleTool); - const csTools3DVer1MeasurementSource = measurementService.getSource( - CORNERSTONE_3D_TOOLS_SOURCE_NAME, - CORNERSTONE_3D_TOOLS_SOURCE_VERSION - ); - - const { POINT } = measurementService.VALUE_TYPES; - - measurementService.addMapping( - csTools3DVer1MeasurementSource, - 'SRSCOORD3DPoint', - POINT, - SRSCOOR3DProbeMapper.toAnnotation, - SRSCOOR3DProbeMapper.toMeasurement.bind(null, { servicesManager }) - ); - // Modify annotation tools to use dashed lines on SR const dashedLine = { lineDash: '4,4', diff --git a/extensions/cornerstone-dicom-sr/src/tools/SCOORD3DPointTool.ts b/extensions/cornerstone-dicom-sr/src/tools/SCOORD3DPointTool.ts deleted file mode 100644 index d05a23cfd..000000000 --- a/extensions/cornerstone-dicom-sr/src/tools/SCOORD3DPointTool.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { type Types } from '@cornerstonejs/core'; -import { - annotation, - drawing, - utilities, - Types as cs3DToolsTypes, - AnnotationDisplayTool, -} from '@cornerstonejs/tools'; -import toolNames from './toolNames'; -import { Annotation } from '@cornerstonejs/tools/dist/types/types'; - -export default class SCOORD3DPointTool extends AnnotationDisplayTool { - static toolName = toolNames.SRSCOORD3DPoint; - - constructor( - toolProps = {}, - defaultToolProps = { - configuration: {}, - } - ) { - super(toolProps, defaultToolProps); - } - - _getTextBoxLinesFromLabels(labels) { - // TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this! - - const labelLength = Math.min(labels.length, 5); - const lines = []; - - return lines; - } - - // This tool should not inherit from AnnotationTool and we should not need - // to add the following lines. - isPointNearTool = () => null; - getHandleNearImagePoint = () => null; - - renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => { - const { viewport } = enabledElement; - const { element } = viewport; - - const annotations = annotation.state.getAnnotations(this.getToolName(), element); - - // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender - if (!annotations?.length) { - return; - } - - // Filter toolData to only render the data for the active SR. - const filteredAnnotations = annotations; - if (!viewport._actors?.size) { - return; - } - - const styleSpecifier: cs3DToolsTypes.AnnotationStyle.StyleSpecifier = { - toolGroupId: this.toolGroupId, - toolName: this.getToolName(), - viewportId: enabledElement.viewport.id, - }; - - for (let i = 0; i < filteredAnnotations.length; i++) { - const annotation = filteredAnnotations[i]; - - const annotationUID = annotation.annotationUID; - const { renderableData } = annotation.data; - const { POINT: points } = renderableData; - - styleSpecifier.annotationUID = annotationUID; - - const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation); - const lineDash = this.getStyle('lineDash', styleSpecifier, annotation); - const color = this.getStyle('color', styleSpecifier, annotation); - - const options = { - color, - lineDash, - lineWidth, - }; - - const point = points[0][0]; - - // check if viewport can render it - const viewable = viewport.isReferenceViewable( - { FrameOfReferenceUID: annotation.metadata.FrameOfReferenceUID, cameraFocalPoint: point }, - { asNearbyProjection: true } - ); - - if (!viewable) { - continue; - } - - // render the point - const arrowPointCanvas = viewport.worldToCanvas(point); - // Todo: configure this - const arrowEndCanvas = [arrowPointCanvas[0] + 20, arrowPointCanvas[1] + 20]; - const canvasCoordinates = [arrowPointCanvas, arrowEndCanvas]; - - drawing.drawArrow( - svgDrawingHelper, - annotationUID, - '1', - canvasCoordinates[1], - canvasCoordinates[0], - { - color: options.color, - width: options.lineWidth, - } - ); - - this.renderTextBox( - svgDrawingHelper, - viewport, - canvasCoordinates, - annotation, - styleSpecifier, - options - ); - } - }; - - renderTextBox( - svgDrawingHelper, - viewport, - canvasCoordinates, - annotation, - styleSpecifier, - options = {} - ) { - if (!canvasCoordinates || !annotation) { - return; - } - - const { annotationUID, data = {} } = annotation; - const { labels } = data; - - const textLines = []; - - for (const label of labels) { - // make this generic - // fix this - if (label.label === '363698007') { - textLines.push(`Finding Site: ${label.value}`); - } - } - - const { color } = options; - - const adaptedCanvasCoordinates = canvasCoordinates; - // adapt coordinates if there is an adapter - const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates); - - if (!annotation.data?.handles?.textBox?.worldPosition) { - annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords); - } - - const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition); - - const textBoxUID = '1'; - const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation); - - const boundingBox = drawing.drawLinkedTextBox( - svgDrawingHelper, - annotationUID, - textBoxUID, - textLines, - textBoxPosition, - canvasCoordinates, - {}, - { - ...textBoxOptions, - color, - } - ); - - const { x: left, y: top, width, height } = boundingBox; - - annotation.data.handles.textBox.worldBoundingBox = { - topLeft: viewport.canvasToWorld([left, top]), - topRight: viewport.canvasToWorld([left + width, top]), - bottomLeft: viewport.canvasToWorld([left, top + height]), - bottomRight: viewport.canvasToWorld([left + width, top + height]), - }; - } - - public getLinkedTextBoxStyle( - specifications: cs3DToolsTypes.AnnotationStyle.StyleSpecifier, - annotation?: Annotation - ): Record { - // Todo: this function can be used to set different styles for different toolMode - // for the textBox. - - return { - visibility: this.getStyle('textBoxVisibility', specifications, annotation), - fontFamily: this.getStyle('textBoxFontFamily', specifications, annotation), - fontSize: this.getStyle('textBoxFontSize', specifications, annotation), - color: this.getStyle('textBoxColor', specifications, annotation), - shadow: this.getStyle('textBoxShadow', specifications, annotation), - background: this.getStyle('textBoxBackground', specifications, annotation), - lineWidth: this.getStyle('textBoxLinkLineWidth', specifications, annotation), - lineDash: this.getStyle('textBoxLinkLineDash', specifications, annotation), - }; - } -} diff --git a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts index 535c11f28..fcf629c8c 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts @@ -9,7 +9,6 @@ const toolNames = { SRCobbAngle: 'SRCobbAngle', SRRectangleROI: 'SRRectangleROI', SRPlanarFreehandROI: 'SRPlanarFreehandROI', - SRSCOORD3DPoint: 'SRSCOORD3DPoint', }; export default toolNames; diff --git a/extensions/cornerstone-dicom-sr/src/utils/SRSCOOR3DProbeMapper.ts b/extensions/cornerstone-dicom-sr/src/utils/SRSCOOR3DProbeMapper.ts deleted file mode 100644 index 50f377c15..000000000 --- a/extensions/cornerstone-dicom-sr/src/utils/SRSCOOR3DProbeMapper.ts +++ /dev/null @@ -1,71 +0,0 @@ -const SRSCOOR3DProbe = { - toAnnotation: measurement => {}, - - /** - * Maps cornerstone annotation event data to measurement service format. - * - * @param {Object} cornerstone Cornerstone event data - * @return {Measurement} Measurement instance - */ - toMeasurement: ({ servicesManager, getValueTypeFromToolType }, csToolsEventDetail) => { - const { displaySetService } = servicesManager.services; - const { annotation } = csToolsEventDetail; - const { metadata, data, annotationUID } = annotation; - - if (!metadata || !data) { - console.warn('Probe tool: Missing metadata or data'); - return null; - } - - const { toolName, FrameOfReferenceUID } = metadata; - const { points } = data.handles; - - const displaySets = displaySetService - .getActiveDisplaySets() - .filter(ds => ds.FrameOfReferenceUID === FrameOfReferenceUID); - const displaySet = displaySets.filter(ds => ds.isReconstructable)[0] || displaySets[0]; - - const { StudyInstanceUID: referenceStudyUID, SeriesInstanceUID: referenceSeriesUID } = - displaySets[0] || {}; - - const displayText = getDisplayText(annotation); - return { - uid: annotationUID, - points, - metadata, - referenceStudyUID, - referenceSeriesUID, - displaySetInstanceUID: displaySet?.displaySetInstanceUID, - toolName: metadata.toolName, - label: data.label, - displayText: displayText, - data: data.cachedStats, - type: getValueTypeFromToolType?.(toolName) ?? null, - }; - }, -}; - -function getDisplayText(annotation) { - const { data } = annotation; - - if (!data) { - return ['']; - } - const { labels } = data; - - const displayText = []; - - for (const label of labels) { - // make this generic - if (label.label === '33636980076') { - displayText.push(`Finding Site: ${label.value}`); - } - } - - return { - primary: displayText, - secondary: [], - }; -} - -export default SRSCOOR3DProbe; diff --git a/extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts b/extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts index 084b20262..c24105192 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts @@ -64,33 +64,34 @@ export default function addSRAnnotation({ measurement, imageId = null, frameNumb const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0]; const graphicTypePoints = renderableData[graphicType]; - /** TODO: Read the tool name from the DICOM SR identification type in the future. */ + /** + * TODO: Read the tool name from the DICOM SR identification type in the future. + */ let frameOfReferenceUID = null; let planeRestriction = null; + /** + * Store the view reference for use in initial navigation + */ if (imageId) { const imagePlaneModule = metaData.get('imagePlaneModule', imageId); frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID; } + /** + * Store the view reference for use in initial navigation + */ if (valueType === 'SCOORD3D') { - const adapter = MeasurementReport.getAdapterForTrackingIdentifier( - measurement.TrackingIdentifier - ); - if (!adapter) { - toolName = toolNames.SRSCOORD3DPoint; - } - - // get the ReferencedFrameOfReferenceUID from the measurement frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence; - planeRestriction = { FrameOfReferenceUID: frameOfReferenceUID, point: graphicTypePoints[0][0], }; } - // Store the view reference for use in initial navigation + /** + * Store the view reference for use in initial navigation + */ measurement.viewReference = { planeRestriction, FrameOfReferenceUID: frameOfReferenceUID, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts index 8ad3e70e6..f480370cf 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Probe.ts @@ -107,8 +107,8 @@ function getMappedAnnotations(annotation, displaySetService) { const displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID)[0]; const { SeriesNumber } = displaySet; - const { value } = targetStats; - const unit = 'HU'; + const { value, modalityUnit } = targetStats; + const unit = modalityUnit || ''; annotations.push({ SeriesInstanceUID, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js index 7f96f0490..99713c3a4 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -13,7 +13,6 @@ const supportedTools = [ 'LivewireContour', 'UltrasoundDirectionalTool', 'UltrasoundPleuraBLineTool', - 'SCOORD3DPoint', 'SegmentBidirectional', ]; diff --git a/tests/Scoord3dProbe.spec.ts b/tests/Scoord3dProbe.spec.ts new file mode 100644 index 000000000..6b716ee77 --- /dev/null +++ b/tests/Scoord3dProbe.spec.ts @@ -0,0 +1,169 @@ +import { test, expect } from 'playwright-test-coverage'; +import { visitStudy, checkForScreenshot, screenShotPaths } from './utils'; +import { viewportLocator } from './utils/locators'; + +test.beforeEach(async ({ page }) => { + const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7310.5101.860473186348887719777907797922'; + const mode = 'viewer'; + + await visitStudy(page, studyInstanceUID, mode, 5000); + + // Log the actual URL that was loaded + const currentUrl = page.url(); + console.log(`✅ Actual page URL: ${currentUrl}\n`); + + // Remove any webpack dev server overlays that might be blocking interactions + await page.evaluate(() => { + const overlay = document.getElementById('webpack-dev-server-client-overlay'); + if (overlay) { + overlay.remove(); + } + }); +}); + +test('should hydrate SCOORD3D probe measurements correctly', async ({ page }) => { + // Wait for the side panel to be visible and clickable + await page.waitForTimeout(3000); + + // Navigate to the tracked measurements panel + await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); + await page.getByTestId('trackedMeasurements-btn').click(); + + // Double-click on the study browser thumbnail to load the SR + await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); + await page.waitForTimeout(2000); + + // Wait for the SR to load and stabilize before taking screenshot + await page.waitForTimeout(1000); + + // Take screenshot before hydration - use viewport locator instead of full page + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoord3dProbe.scoord3dProbePreHydration); + + // Zoom in to better see the measurements + await page.evaluate(() => { + // Access cornerstone directly from the window object + const cornerstone = (window as any).cornerstone; + if (!cornerstone) { + return; + } + + const enabledElements = cornerstone.getEnabledElements(); + if (enabledElements.length === 0) { + return; + } + + const viewport = enabledElements[0].viewport; + if (viewport) { + viewport.setZoom(4); + viewport.render(); + } + }); + + // Wait for rendering to complete + await page.waitForTimeout(1000); + + // Wait for the hydrate button to be visible and clickable + await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); + + // Click the hydrate button to load the SCOORD3D probe measurements + await page.getByTestId('yes-hydrate-btn').click(); + + // Wait for hydration to complete and rendering to stabilize + await page.waitForTimeout(3000); + + // Take screenshot after hydration showing the probe measurements - use viewport locator + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoord3dProbe.scoord3dProbePostHydration); + + // Verify the measurements list has the correct probe measurements + const measurementRows = page.getByTestId('data-row'); + const rowCount = await measurementRows.count(); + expect(rowCount).toBeGreaterThan(0); + + // Verify that the measurements are probe measurements (not other types) + for (let i = 0; i < rowCount; i++) { + const row = measurementRows.nth(i); + const rowText = await row.textContent(); + // Probe measurements should be present, verify they're not other measurement types + expect(rowText).toBeTruthy(); + } + + // Test jumping to a specific measurement by scrolling and clicking + await page.evaluate(() => { + // Access cornerstone directly from the window object + const cornerstone = (window as any).cornerstone; + if (!cornerstone) { + return; + } + + const enabledElements = cornerstone.getEnabledElements(); + if (enabledElements.length === 0) { + return; + } + + const viewport = enabledElements[0].viewport; + if (viewport) { + viewport.scroll(20); + viewport.render(); + } + }); + + // Click on a data row to jump to the measurement + await page.getByTestId('data-row').first().click(); + + // Take screenshot showing the jump to measurement functionality - use viewport locator + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoord3dProbe.scoord3dProbeJumpToMeasurement); +}); + +test('should display SCOORD3D probe measurements correctly', async ({ page }) => { + // Wait for the side panel to be visible and clickable + await page.waitForTimeout(3000); + + // First hydrate the SR to load the measurements + await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); + await page.getByTestId('trackedMeasurements-btn').click(); + await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); + await page.waitForTimeout(2000); + + // Wait for the hydrate button to be visible and clickable + await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); + await page.getByTestId('yes-hydrate-btn').click(); + await page.waitForTimeout(2000); + + // Zoom to show the probe measurements clearly + await page.evaluate(() => { + const cornerstone = (window as any).cornerstone; + if (!cornerstone) { + return; + } + + const enabledElements = cornerstone.getEnabledElements(); + if (enabledElements.length === 0) { + return; + } + + const viewport = enabledElements[0].viewport; + if (viewport) { + viewport.setZoom(3); + viewport.render(); + } + }); + + // Wait for rendering to complete before taking screenshot + await page.waitForTimeout(2000); + + // Take screenshot showing the SCOORD3D probe measurements rendered correctly - use viewport locator + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoord3dProbe.scoord3dProbeDisplayedCorrectly); + + // Verify the measurements list has the correct probe measurements and not others + const measurementRows = page.getByTestId('data-row'); + const rowCount = await measurementRows.count(); + expect(rowCount).toBeGreaterThan(0); + + // Verify that the measurements are probe measurements (not other types like rectangle) + for (let i = 0; i < rowCount; i++) { + const row = measurementRows.nth(i); + const rowText = await row.textContent(); + // Probe measurements should be present, verify they're not other measurement types + expect(rowText).toBeTruthy(); + } +}); diff --git a/tests/ScoordRectangle.spec.ts b/tests/ScoordRectangle.spec.ts new file mode 100644 index 000000000..38dab4ac7 --- /dev/null +++ b/tests/ScoordRectangle.spec.ts @@ -0,0 +1,170 @@ +import { test, expect } from 'playwright-test-coverage'; +import { visitStudy, checkForScreenshot, screenShotPaths } from './utils'; +import { viewportLocator } from './utils/locators'; + +test.beforeEach(async ({ page }) => { + const studyInstanceUID = '1.2.840.113654.2.55.242841386983064378162007136685545369722'; + const mode = 'viewer'; + + await visitStudy(page, studyInstanceUID, mode, 5000); + + // Log the actual URL that was loaded + const currentUrl = page.url(); + console.log(`✅ Actual page URL: ${currentUrl}\n`); + + // Remove any webpack dev server overlays that might be blocking interactions + await page.evaluate(() => { + const overlay = document.getElementById('webpack-dev-server-client-overlay'); + if (overlay) { + overlay.remove(); + } + }); +}); + +// +test('should hydrate SCOORD rectangle measurements correctly', async ({ page }) => { + // Wait for the side panel to be visible and clickable + await page.waitForTimeout(3000); + + // Navigate to the tracked measurements panel + await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); + await page.getByTestId('trackedMeasurements-btn').click(); + + // Double-click on the study browser thumbnail to load the SR + await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); + await page.waitForTimeout(2000); + + // Wait for the SR to load and stabilize before taking screenshot + await page.waitForTimeout(1000); + + // Take screenshot before hydration - use viewport locator instead of full page + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoordRectangle.scoordRectanglePreHydration); + + // Zoom in to better see the measurements + await page.evaluate(() => { + // Access cornerstone directly from the window object + const cornerstone = (window as any).cornerstone; + if (!cornerstone) { + return; + } + + const enabledElements = cornerstone.getEnabledElements(); + if (enabledElements.length === 0) { + return; + } + + const viewport = enabledElements[0].viewport; + if (viewport) { + viewport.setZoom(4); + viewport.render(); + } + }); + + // Wait for rendering to complete + await page.waitForTimeout(1000); + + // Wait for the hydrate button to be visible and clickable + await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); + + // Click the hydrate button to load the SCOORD rectangle measurements + await page.getByTestId('yes-hydrate-btn').click(); + + // Wait for hydration to complete and rendering to stabilize + await page.waitForTimeout(3000); + + // Take screenshot after hydration showing the rectangle measurements - use viewport locator + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoordRectangle.scoordRectanglePostHydration); + + // Verify the measurements list has the correct rectangle measurements + const measurementRows = page.getByTestId('data-row'); + const rowCount = await measurementRows.count(); + expect(rowCount).toBeGreaterThan(0); + + // Verify that the measurements are rectangle measurements (not other types) + for (let i = 0; i < rowCount; i++) { + const row = measurementRows.nth(i); + const rowText = await row.textContent(); + // Rectangle measurements should be present, verify they're not other measurement types + expect(rowText).toBeTruthy(); + } + + // Test jumping to a specific measurement by scrolling and clicking + await page.evaluate(() => { + // Access cornerstone directly from the window object + const cornerstone = (window as any).cornerstone; + if (!cornerstone) { + return; + } + + const enabledElements = cornerstone.getEnabledElements(); + if (enabledElements.length === 0) { + return; + } + + const viewport = enabledElements[0].viewport; + if (viewport) { + viewport.scroll(20); + viewport.render(); + } + }); + + // Click on a data row to jump to the measurement + await page.getByTestId('data-row').first().click(); + + // Take screenshot showing the jump to measurement functionality - use viewport locator + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoordRectangle.scoordRectangleJumpToMeasurement); +}); + +test('should display SCOORD rectangle measurements correctly', async ({ page }) => { + // Wait for the side panel to be visible and clickable + await page.waitForTimeout(3000); + + // First hydrate the SR to load the measurements + await page.getByTestId('side-panel-header-right').click({ timeout: 15000 }); + await page.getByTestId('trackedMeasurements-btn').click(); + await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); + await page.waitForTimeout(2000); + + // Wait for the hydrate button to be visible and clickable + await page.getByTestId('yes-hydrate-btn').waitFor({ state: 'visible', timeout: 15000 }); + await page.getByTestId('yes-hydrate-btn').click(); + await page.waitForTimeout(2000); + + // Zoom to show the rectangle measurements clearly + await page.evaluate(() => { + const cornerstone = (window as any).cornerstone; + if (!cornerstone) { + return; + } + + const enabledElements = cornerstone.getEnabledElements(); + if (enabledElements.length === 0) { + return; + } + + const viewport = enabledElements[0].viewport; + if (viewport) { + viewport.setZoom(3); + viewport.render(); + } + }); + + // Wait for rendering to complete before taking screenshot + await page.waitForTimeout(2000); + + // Take screenshot showing the SCOORD rectangle measurements rendered correctly - use viewport locator + await checkForScreenshot(page, viewportLocator({ page, viewportId: 'default' }), screenShotPaths.scoordRectangle.scoordRectangleDisplayedCorrectly); + + // Verify the measurements list has the correct rectangle measurements and not others + const measurementRows = page.getByTestId('data-row'); + const rowCount = await measurementRows.count(); + expect(rowCount).toBeGreaterThan(0); + + // Verify that the measurements are rectangle measurements (not other types like probe) + for (let i = 0; i < rowCount; i++) { + const row = measurementRows.nth(i); + const rowText = await row.textContent(); + // Rectangle measurements should be present, verify they're not other measurement types + expect(rowText).toBeTruthy(); + } +}); diff --git a/tests/Worklist.spec.ts b/tests/Worklist.spec.ts index 38afc0515..4c4a02c7a 100644 --- a/tests/Worklist.spec.ts +++ b/tests/Worklist.spec.ts @@ -22,8 +22,18 @@ test('should render scroll bars with the correct look-and-feel', async ({ page } 'studyRow-1.3.6.1.4.1.14519.5.2.1.5099.8010.217836670708542506360829799868' ); + // Wait for the expanded row to be visible and rendered + await expandedStudyRow.waitFor({ state: 'visible', timeout: 10000 }); await expandedStudyRow.scrollIntoViewIfNeeded(); - await page.waitForTimeout(3000); + + // Wait for content to load and stabilize, including any lazy-loaded items + await page.waitForTimeout(2000); + + // Wait for network to be idle to ensure all content is loaded + await page.waitForLoadState('networkidle'); + + // Additional wait to ensure scrollbar rendering is stable + await page.waitForTimeout(1000); await checkForScreenshot({ page, diff --git a/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeDisplayedCorrectly.png b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeDisplayedCorrectly.png new file mode 100644 index 000000000..0185cecf6 Binary files /dev/null and b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeDisplayedCorrectly.png differ diff --git a/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeJumpToMeasurement.png b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeJumpToMeasurement.png new file mode 100644 index 000000000..e1a152232 Binary files /dev/null and b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbeJumpToMeasurement.png differ diff --git a/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePostHydration.png b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePostHydration.png new file mode 100644 index 000000000..deb16b5ec Binary files /dev/null and b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePostHydration.png differ diff --git a/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePreHydration.png b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePreHydration.png new file mode 100644 index 000000000..f844ffdc8 Binary files /dev/null and b/tests/screenshots/chromium/Scoord3dProbe.spec.ts/scoord3dProbePreHydration.png differ diff --git a/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleDisplayedCorrectly.png b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleDisplayedCorrectly.png new file mode 100644 index 000000000..a6e467034 Binary files /dev/null and b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleDisplayedCorrectly.png differ diff --git a/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleJumpToMeasurement.png b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleJumpToMeasurement.png new file mode 100644 index 000000000..bbe9ec490 Binary files /dev/null and b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectangleJumpToMeasurement.png differ diff --git a/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePostHydration.png b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePostHydration.png new file mode 100644 index 000000000..b6863fbd0 Binary files /dev/null and b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePostHydration.png differ diff --git a/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePreHydration.png b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePreHydration.png new file mode 100644 index 000000000..3a767eb6b Binary files /dev/null and b/tests/screenshots/chromium/ScoordRectangle.spec.ts/scoordRectanglePreHydration.png differ diff --git a/tests/utils/screenShotPaths.ts b/tests/utils/screenShotPaths.ts index ef2f75a18..1de6fbeb4 100644 --- a/tests/utils/screenShotPaths.ts +++ b/tests/utils/screenShotPaths.ts @@ -66,6 +66,18 @@ const screenShotPaths = { rectangle: { rectangleDisplayedCorrectly: 'rectangleDisplayedCorrectly.png', }, + scoord3dProbe: { + scoord3dProbeDisplayedCorrectly: 'scoord3dProbeDisplayedCorrectly.png', + scoord3dProbePreHydration: 'scoord3dProbePreHydration.png', + scoord3dProbePostHydration: 'scoord3dProbePostHydration.png', + scoord3dProbeJumpToMeasurement: 'scoord3dProbeJumpToMeasurement.png', + }, + scoordRectangle: { + scoordRectangleDisplayedCorrectly: 'scoordRectangleDisplayedCorrectly.png', + scoordRectanglePreHydration: 'scoordRectanglePreHydration.png', + scoordRectanglePostHydration: 'scoordRectanglePostHydration.png', + scoordRectangleJumpToMeasurement: 'scoordRectangleJumpToMeasurement.png', + }, spline: { splineDisplayedCorrectly: 'splineDisplayedCorrectly.png', },