fix: Remove dead code and fix rectangle roi rehydration (#5490)

* Remove dead code and fix rectangle roi rehydration

* Revert bun.lock

* Remove hardcoded modality

* Add e2e tests

* Update screewnshots

* Adjustments to tests

* disabvle test for now

* Add test again

* Use ohif data soure

* Revert changes

* Test

* Update testdata to include point

* Improve tests

* Update screenshot

---------

Co-authored-by: Andrey Fedorov <andrey.fedorov@gmail.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
Igor Octaviano 2025-11-19 22:04:23 -03:00 committed by GitHub
parent 0be3b950b7
commit 9ca02b4071
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 376 additions and 313 deletions

View File

@ -11,16 +11,11 @@ import {
RectangleROITool, RectangleROITool,
} from '@cornerstonejs/tools'; } from '@cornerstonejs/tools';
import { Types } from '@ohif/core'; import { Types } from '@ohif/core';
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool'; import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
import SCOORD3DPointTool from './tools/SCOORD3DPointTool';
import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper';
import addToolInstance from './utils/addToolInstance'; import addToolInstance from './utils/addToolInstance';
import toolNames from './tools/toolNames'; import toolNames from './tools/toolNames';
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
/** /**
* @param {object} configuration * @param {object} configuration
*/ */
@ -28,8 +23,6 @@ export default function init({
configuration = {}, configuration = {},
servicesManager, servicesManager,
}: Types.Extensions.ExtensionParams): void { }: Types.Extensions.ExtensionParams): void {
const { measurementService } = servicesManager.services;
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool); addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
addToolInstance(toolNames.SRLength, LengthTool); addToolInstance(toolNames.SRLength, LengthTool);
addToolInstance(toolNames.SRBidirectional, BidirectionalTool); addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
@ -39,26 +32,10 @@ export default function init({
addToolInstance(toolNames.SRAngle, AngleTool); addToolInstance(toolNames.SRAngle, AngleTool);
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool); addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
addToolInstance(toolNames.SRRectangleROI, RectangleROITool); addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
addToolInstance(toolNames.SRSCOORD3DPoint, SCOORD3DPointTool);
// TODO - fix the SR display of Cobb Angle, as it joins the two lines // TODO - fix the SR display of Cobb Angle, as it joins the two lines
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool); 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 // Modify annotation tools to use dashed lines on SR
const dashedLine = { const dashedLine = {
lineDash: '4,4', lineDash: '4,4',

View File

@ -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<string, unknown> {
// 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),
};
}
}

View File

@ -9,7 +9,6 @@ const toolNames = {
SRCobbAngle: 'SRCobbAngle', SRCobbAngle: 'SRCobbAngle',
SRRectangleROI: 'SRRectangleROI', SRRectangleROI: 'SRRectangleROI',
SRPlanarFreehandROI: 'SRPlanarFreehandROI', SRPlanarFreehandROI: 'SRPlanarFreehandROI',
SRSCOORD3DPoint: 'SRSCOORD3DPoint',
}; };
export default toolNames; export default toolNames;

View File

@ -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;

View File

@ -64,33 +64,34 @@ export default function addSRAnnotation({ measurement, imageId = null, frameNumb
const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0]; const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0];
const graphicTypePoints = renderableData[graphicType]; 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 frameOfReferenceUID = null;
let planeRestriction = null; let planeRestriction = null;
/**
* Store the view reference for use in initial navigation
*/
if (imageId) { if (imageId) {
const imagePlaneModule = metaData.get('imagePlaneModule', imageId); const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID; frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID;
} }
/**
* Store the view reference for use in initial navigation
*/
if (valueType === 'SCOORD3D') { 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; frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
planeRestriction = { planeRestriction = {
FrameOfReferenceUID: frameOfReferenceUID, FrameOfReferenceUID: frameOfReferenceUID,
point: graphicTypePoints[0][0], point: graphicTypePoints[0][0],
}; };
} }
// Store the view reference for use in initial navigation /**
* Store the view reference for use in initial navigation
*/
measurement.viewReference = { measurement.viewReference = {
planeRestriction, planeRestriction,
FrameOfReferenceUID: frameOfReferenceUID, FrameOfReferenceUID: frameOfReferenceUID,

View File

@ -107,8 +107,8 @@ function getMappedAnnotations(annotation, displaySetService) {
const displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID)[0]; const displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID)[0];
const { SeriesNumber } = displaySet; const { SeriesNumber } = displaySet;
const { value } = targetStats; const { value, modalityUnit } = targetStats;
const unit = 'HU'; const unit = modalityUnit || '';
annotations.push({ annotations.push({
SeriesInstanceUID, SeriesInstanceUID,

View File

@ -13,7 +13,6 @@ const supportedTools = [
'LivewireContour', 'LivewireContour',
'UltrasoundDirectionalTool', 'UltrasoundDirectionalTool',
'UltrasoundPleuraBLineTool', 'UltrasoundPleuraBLineTool',
'SCOORD3DPoint',
'SegmentBidirectional', 'SegmentBidirectional',
]; ];

169
tests/Scoord3dProbe.spec.ts Normal file
View File

@ -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();
}
});

View File

@ -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();
}
});

View File

@ -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' '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 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({ await checkForScreenshot({
page, page,

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

View File

@ -66,6 +66,18 @@ const screenShotPaths = {
rectangle: { rectangle: {
rectangleDisplayedCorrectly: 'rectangleDisplayedCorrectly.png', 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: { spline: {
splineDisplayedCorrectly: 'splineDisplayedCorrectly.png', splineDisplayedCorrectly: 'splineDisplayedCorrectly.png',
}, },