Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/core/src/RenderingEngine/BaseRenderingEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import type IStackViewport from '../types/IStackViewport';
import type IVolumeViewport from '../types/IVolumeViewport';
import viewportTypeToViewportClass from './helpers/viewportTypeToViewportClass';
import ViewportStatus from '../enums/ViewportStatus';

import type * as EventTypes from '../types/EventTypes';
import type {
Expand Down Expand Up @@ -689,6 +690,10 @@ abstract class BaseRenderingEngine {
// Add the viewports to the set of flagged viewports
viewportIds.forEach((viewportId) => {
this._needsRender.add(viewportId);
const viewport = this._viewports.get(viewportId);
if (viewport) {
viewport.viewportStatus = ViewportStatus.NEEDS_RENDER;
}
});

// Render any flagged viewports
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/RenderingEngine/ECGViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ class ECGViewport extends Viewport {
this.drawLabels(ctx, layouts);

ctx.resetTransform();
this.setRendered();

triggerEvent(this.element, EVENTS.IMAGE_RENDERED, {
element: this.element,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/RenderingEngine/VideoViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,7 @@ class VideoViewport extends Viewport {
(actor.actor as ICanvasActor).render(this, this.canvasContext);
}
this.canvasContext.resetTransform();
this.setRendered();

// This is stack new image to agree with stack/non-volume viewports
triggerEvent(this.element, EVENTS.STACK_NEW_IMAGE, {
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/RenderingEngine/Viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,14 @@ class Viewport {
this.viewportStatus = ViewportStatus.RENDERED;
}

/**
* Mark the viewport as needing a render pass (e.g. after external display-set / segmentation updates).
* Does not queue a render; the rendering engine’s normal RAF cycle will pick this up.
*/
public setNeedsRender(): void {
this.viewportStatus = ViewportStatus.NEEDS_RENDER;
}

/**
* This applies a color transform as an svg filter to the output image.
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/enums/ViewportStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ enum ViewportStatus {
LOADING = 'loading',
/** Ready to be rendered */
PRE_RENDER = 'preRender',
/** Render has been requested and is pending in RAF queue */
NEEDS_RENDER = 'needsRender',
/** In the midst of a resize */
RESIZE = 'resize',
/** Rendered image data */
Expand Down
8 changes: 8 additions & 0 deletions packages/tools/src/utilities/segmentation/utilsForWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export const getSegmentationDataForWorker = (
segmentIndices
) => {
const segmentation = getSegmentation(segmentationId);
if (!segmentation?.representationData) {
Comment thread
jbocce marked this conversation as resolved.
console.debug(
'getSegmentationDataForWorker: segmentation missing or not ready',
segmentationId
);
return null;
}

const { representationData } = segmentation;

const { Labelmap } = representationData;
Expand Down
7 changes: 4 additions & 3 deletions tests/labelmapsegmentationtools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {
checkForScreenshot,
screenShotPaths,
simulateClicksOnElement,
waitForViewportsRendered,
} from './utils/index';

const delayBetweenClicks = async (page: any) => {
await page.waitForTimeout(1500);
await waitForViewportsRendered(page);
};

test.beforeEach(async ({ page }) => {
Expand Down Expand Up @@ -621,7 +622,7 @@ test.describe('Basic manual labelmap Segmentation tools', async () => {
],
});

await page.waitForTimeout(3000);
await waitForViewportsRendered(page);
await checkForScreenshot(
page,
screenshotLocator,
Expand Down Expand Up @@ -682,7 +683,7 @@ test.describe('Basic manual labelmap Segmentation tools', async () => {
],
});

await page.waitForTimeout(3000);
await waitForViewportsRendered(page);
await checkForScreenshot(
page,
screenshotLocator,
Expand Down
1 change: 1 addition & 0 deletions tests/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { simulateClicksOnElement } from './simulateClicksOnElement';
export { simulateDrawPath } from './simulateDrawPath';
export { reduceViewportsSize } from './reduceViewportsSize';
export { attemptAction } from './attemptAction';
export { waitForViewportsRendered } from './waitForViewportsRendered';
94 changes: 94 additions & 0 deletions tests/utils/waitForViewportsRendered.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { Page } from '@playwright/test';

type WaitForViewportsRenderedOptions = {
timeout?: number;
/**
* If true (default), also waits for any volume actors referenced by the
* viewports to report loaded.
*/
waitVolumeLoad?: boolean;
};

/**
* Stabilize tests by waiting for a short tick, network idle, then viewport render completion.
* To use this method safely, you may need to make changes to OHIF and/or CS3D
* methods handling clicks (SHOULD be commands modules only). These should set the
* state to needs render synchronously so that this method can safely wait for the render to complete.
* Examples such as changing the hanging protocol currently don't set such a state
* and thus can't be rendered without a delay.
*
* If options.waitVolumeLoad is not false, then this method will wait for all volumes
* associated with viewports to be loaded.
*/
export const waitForViewportsRendered = async (
page: Page,
options: WaitForViewportsRenderedOptions = {}
) => {
const { timeout = 15000, waitVolumeLoad = true } = options;

await page.waitForFunction(
({ waitVolumeLoad }) => {
const cornerstone = (window as any).cornerstone;
if (!cornerstone?.getRenderingEngines) {
return false;
}

const renderingEngines = cornerstone.getRenderingEngines();
const viewports = renderingEngines.flatMap((engine) =>
engine.getViewports ? engine.getViewports() : []
);

if (!viewports.length) {
return false;
}

const allRendered = viewports.every(
(viewport) => viewport?.viewportStatus === 'rendered'
);

if (!allRendered) {
return false;
}

if (!waitVolumeLoad) {
return true;
}

const cache = cornerstone.cache;
if (!cache?.getVolume) {
return true;
}

const actorEntries = viewports.flatMap((viewport) =>
viewport?.getActors ? viewport.getActors() : []
);

for (const actorEntry of actorEntries) {
const id = actorEntry?.referencedId || actorEntry?.uid;
if (!id) {
continue;
}

let volume: any;
try {
volume = cache.getVolume(id);
} catch {
continue;
}

const loaded =
volume?.loadStatus && typeof volume.loadStatus.loaded === 'boolean'
? volume.loadStatus.loaded
: true;

if (!loaded) {
return false;
}
}

return true;
},
{ waitVolumeLoad },
{ timeout }
);
};
Loading