fix(sr): sr hydration and load was not working, Screenshot Comparison, and Testing (#4814)

This commit is contained in:
Alireza 2025-02-26 11:23:40 -05:00 committed by GitHub
parent 1f5c60bbda
commit 9233143b9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 213 additions and 21 deletions

View File

@ -140,19 +140,16 @@ function commandsModule({
)?.[0]; )?.[0];
if (previousReferencedDisplaySetStoreKey) { if (previousReferencedDisplaySetStoreKey) {
if (referencedImageId) { const presentationData = referencedImageId
setPositionPresentation(previousReferencedDisplaySetStoreKey, { ? {
viewReference: { ...presentations.positionPresentation,
referencedImageId, viewReference: {
}, referencedImageId,
}); },
} else { }
setPositionPresentation( : presentations.positionPresentation;
previousReferencedDisplaySetStoreKey,
presentations.positionPresentation
);
}
setPositionPresentation(previousReferencedDisplaySetStoreKey, presentationData);
return; return;
} }

View File

@ -47,9 +47,19 @@ const RowComponent = ({
{isChildOrParent && ( {isChildOrParent && (
<div style={{ paddingLeft: `${padding}px`, opacity: onToggle ? 1 : 0 }}> <div style={{ paddingLeft: `${padding}px`, opacity: onToggle ? 1 : 0 }}>
{row.areChildrenVisible ? ( {row.areChildrenVisible ? (
<Icons.ChevronDown onClick={handleToggle} /> <div
className="cursor-pointer p-1"
onClick={handleToggle}
>
<Icons.ChevronDown />
</div>
) : ( ) : (
<Icons.ChevronRight onClick={handleToggle} /> <div
className="cursor-pointer p-1"
onClick={handleToggle}
>
<Icons.ChevronRight />
</div>
)} )}
</div> </div>
)} )}

View File

@ -88,7 +88,8 @@ function TrackedMeasurementsContextProvider(
m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID) m => trackedStudy === m.referenceStudyUID && trackedSeries.includes(m.referenceSeriesUID)
); );
const trackedMeasurement = trackedMeasurements[0]; // Jump to the last tracked measurement - most recent
const trackedMeasurement = trackedMeasurements[trackedMeasurements.length - 1];
const referencedDisplaySetUID = trackedMeasurement.displaySetInstanceUID; const referencedDisplaySetUID = trackedMeasurement.displaySetInstanceUID;
// update the previously stored positionPresentation with the new viewportId // update the previously stored positionPresentation with the new viewportId
@ -166,11 +167,13 @@ function TrackedMeasurementsContextProvider(
hydrateStructuredReport: hydrateStructuredReport.bind(null, { hydrateStructuredReport: hydrateStructuredReport.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
commandsManager,
appConfig, appConfig,
}), }),
promptLabelAnnotation: promptLabelAnnotation.bind(null, { promptLabelAnnotation: promptLabelAnnotation.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
commandsManager,
}), }),
}); });
machineOptions.guards = Object.assign({}, machineOptions.guards, { machineOptions.guards = Object.assign({}, machineOptions.guards, {

View File

@ -216,7 +216,7 @@ export default function PanelStudyBrowserTracking({
thumbnailSrc = await displaySet.getThumbnailSrc(); thumbnailSrc = await displaySet.getThumbnailSrc();
} }
if (!thumbnailSrc) { if (!thumbnailSrc) {
let thumbnailSrc = await getImageSrc(imageId); const thumbnailSrc = await getImageSrc(imageId);
displaySet.thumbnailSrc = thumbnailSrc; displaySet.thumbnailSrc = thumbnailSrc;
} }
newImageSrcEntry[dSet.displaySetInstanceUID] = thumbnailSrc; newImageSrcEntry[dSet.displaySetInstanceUID] = thumbnailSrc;

View File

@ -57,6 +57,7 @@
"test:e2e:ci": "npx playwright test", "test:e2e:ci": "npx playwright test",
"test:e2e:ui": "npx playwright test --ui", "test:e2e:ui": "npx playwright test --ui",
"test:e2e:headed": "npx playwright test --headed", "test:e2e:headed": "npx playwright test --headed",
"test:e2e:debug": "npx playwright test --debug",
"test:e2e:dist": "lerna run test:e2e:dist --stream", "test:e2e:dist": "lerna run test:e2e:dist --stream",
"test:e2e:serve": "yarn test:data && lerna run test:e2e:serve --stream", "test:e2e:serve": "yarn test:data && lerna run test:e2e:serve --stream",
"see-changed": "lerna changed", "see-changed": "lerna changed",

View File

@ -0,0 +1,42 @@
import { test } from '@playwright/test';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.6.1.4.1.5962.99.1.2968617883.1314880426.1493322302363.3.0';
const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should hydrate RT reports correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await checkForScreenshot(page, page, screenShotPaths.rtHydration2.rtPreHydration);
// wait for 3 seconds
await page.evaluate(() => {
// Access cornerstone directly from the window object
const cornerstone = window.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();
}
});
await page.waitForTimeout(3000);
//
// should preserve zoom and pan and scroll position after hydration
await page.getByTestId('yes-hydrate-btn').click();
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration);
});

View File

@ -11,8 +11,26 @@ test('should hydrate SEG reports correctly', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click(); await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration); await checkForScreenshot(page, page, screenShotPaths.segHydration.segPreHydration);
await page.evaluate(() => {
// Access cornerstone directly from the window object
const cornerstone = window.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();
}
});
await page.getByTestId('yes-hydrate-btn').click(); await page.getByTestId('yes-hydrate-btn').click();
await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration); await checkForScreenshot(page, page, screenShotPaths.segHydration.segPostHydration);
await page.getByText('Esophagus').click();
await checkForScreenshot(page, page, screenShotPaths.segHydration.segJumpToSegment);
}); });

View File

@ -0,0 +1,25 @@
import { test } from '@playwright/test';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should properly display MPR for MR', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await page.getByTestId('yes-hydrate-btn').click();
await checkForScreenshot(page, page, screenShotPaths.segHydrationMPR.segPostHydration);
await page.getByTestId('Layout').click();
await page.getByTestId('Axial Primary').click();
await checkForScreenshot(
page,
page,
screenShotPaths.segHydrationMPR.segPostHydrationMPRAxialPrimary
);
});

View File

@ -12,8 +12,48 @@ test('should hydrate SR reports correctly', async ({ page }) => {
await page.getByTestId('trackedMeasurements-btn').click(); await page.getByTestId('trackedMeasurements-btn').click();
await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await checkForScreenshot(page, page, screenShotPaths.srHydration.srPreHydration); await checkForScreenshot(page, page, screenShotPaths.srHydration.srPreHydration);
await page.evaluate(() => {
// Access cornerstone directly from the window object
const cornerstone = window.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();
}
});
await page.getByTestId('yes-hydrate-btn').click(); await page.getByTestId('yes-hydrate-btn').click();
await checkForScreenshot(page, page, screenShotPaths.srHydration.srPostHydration); await checkForScreenshot(page, page, screenShotPaths.srHydration.srPostHydration);
await page.evaluate(() => {
// Access cornerstone directly from the window object
const cornerstone = window.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();
}
});
await page.getByTestId('data-row').first().click(); await page.getByTestId('data-row').first().click();
await checkForScreenshot(page, page, screenShotPaths.srHydration.srJumpToMeasurement); await checkForScreenshot(page, page, screenShotPaths.srHydration.srJumpToMeasurement);
}); });

39
tests/mpr2.spec.ts Normal file
View File

@ -0,0 +1,39 @@
import { test } from '@playwright/test';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID =
'1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046&hangingprotocolid=mpr';
const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should properly display MPR for MR', async ({ page }) => {
await page.getByTestId('side-panel-header-right').click();
// await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await checkForScreenshot(page, page, screenShotPaths.mpr2.mprDisplayedCorrectly);
await page.evaluate(() => {
// Access cornerstone directly from the window object
const cornerstone = window.cornerstone;
if (!cornerstone) {
return;
}
const enabledElements = cornerstone.getEnabledElements();
if (enabledElements.length === 0) {
return;
}
// Apply zoom to all viewports
for (let i = 0; i < enabledElements.length; i++) {
const viewport = enabledElements[i].viewport;
if (viewport) {
viewport.setZoom(4);
viewport.render();
}
}
});
await checkForScreenshot(page, page, screenShotPaths.mpr2.mprDisplayedCorrectlyZoomed);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View File

@ -17,19 +17,24 @@ const checkForScreenshot = async (
delay = 100 delay = 100
) => { ) => {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
for (let i = 1; i < attempts; i++) {
for (let i = 0; i < attempts; i++) {
try { try {
await expect(locator).toHaveScreenshot(screenshotPath, { await expect(locator).toHaveScreenshot(screenshotPath, {
maxDiffPixelRatio: 0.1, maxDiffPixelRatio: 0.1,
}); });
return true; return true;
} catch (error) { } catch (error) {
if (i === attempts) { if (i === attempts - 1) {
throw new Error('Screenshot does not match.'); console.debug('Screenshot comparison failed after all attempts');
throw error; // Throw the original error with details instead of a generic message
} }
await new Promise(resolve => setTimeout(resolve, delay)); await new Promise(resolve => setTimeout(resolve, delay));
} }
} }
// This is a fallback in case the loop exits unexpectedly
throw new Error('Screenshot comparison failed: loop exited without match or proper error');
}; };
export { checkForScreenshot }; export { checkForScreenshot };

View File

@ -26,6 +26,10 @@ const screenShotPaths = {
mpr: { mpr: {
mprDisplayedCorrectly: 'mprDisplayedCorrectly.png', mprDisplayedCorrectly: 'mprDisplayedCorrectly.png',
}, },
mpr2: {
mprDisplayedCorrectly: 'mprDisplayedCorrectly.png',
mprDisplayedCorrectlyZoomed: 'mprDisplayedCorrectlyZoomed.png',
},
threeDFourUp: { threeDFourUp: {
threeDFourUpDisplayedCorrectly: 'threeDFourUpDisplayedCorrectly.png', threeDFourUpDisplayedCorrectly: 'threeDFourUpDisplayedCorrectly.png',
}, },
@ -75,11 +79,19 @@ const screenShotPaths = {
segPreHydration: 'segPreHydration.png', segPreHydration: 'segPreHydration.png',
segJumpToSegment: 'segJumpToSegment.png', segJumpToSegment: 'segJumpToSegment.png',
}, },
segHydrationMPR: {
segPostHydration: 'segPostHydration.png',
segPostHydrationMPRAxialPrimary: 'segPostHydrationMPRAxialPrimary.png',
},
rtHydration: { rtHydration: {
rtPostHydration: 'rtPostHydration.png', rtPostHydration: 'rtPostHydration.png',
rtPreHydration: 'rtPreHydration.png', rtPreHydration: 'rtPreHydration.png',
rtJumpToStructure: 'rtJumpToStructure.png', rtJumpToStructure: 'rtJumpToStructure.png',
}, },
rtHydration2: {
rtPostHydration: 'rtPostHydration.png',
rtPreHydration: 'rtPreHydration.png',
},
crosshairs: { crosshairs: {
crosshairsRendered: 'crosshairsRendered.png', crosshairsRendered: 'crosshairsRendered.png',
crosshairsRotated: 'crosshairsRotated.png', crosshairsRotated: 'crosshairsRotated.png',