fix(window level): The window level value is not displayed by default on all the viewports when selecting common/custom layout and TMTV. (#5865)
* fix(window level): Set up listener for viewport availability such that the initial window level can be read and displayed. * PR feedback. * PR feedback. --------- Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
parent
54d9ea39bf
commit
fe1ecfe0cc
@ -67,10 +67,9 @@ function CustomizableViewportOverlay({
|
||||
}) {
|
||||
const { cornerstoneViewportService, customizationService, toolGroupService, displaySetService } =
|
||||
servicesManager.services;
|
||||
const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null });
|
||||
const [scale, setScale] = useState(1);
|
||||
const [annotationState, setAnnotationState] = useState(0);
|
||||
const { isViewportBackgroundLight: isLight } = useViewportRendering(viewportId);
|
||||
const { isViewportBackgroundLight: isLight, windowLevel: voi } = useViewportRendering(viewportId);
|
||||
const { imageIndex } = imageSliceData;
|
||||
|
||||
// Historical usage defined the overlays as separate items due to lack of
|
||||
@ -110,30 +109,6 @@ function CustomizableViewportOverlay({
|
||||
};
|
||||
}, [viewportData, viewportId, instanceNumber, cornerstoneViewportService]);
|
||||
|
||||
/**
|
||||
* Updating the VOI when the viewport changes its voi
|
||||
*/
|
||||
useEffect(() => {
|
||||
const updateVOI = eventDetail => {
|
||||
const { range } = eventDetail.detail;
|
||||
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lower, upper } = range;
|
||||
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper);
|
||||
|
||||
setVOI({ windowCenter, windowWidth });
|
||||
};
|
||||
|
||||
element.addEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||
};
|
||||
}, [viewportId, viewportData, voi, element]);
|
||||
|
||||
const annotationModified = useCallback(evt => {
|
||||
if (evt.detail.annotation.metadata.toolName === UltrasoundPleuraBLineTool.toolName) {
|
||||
// Update the annotation state to trigger a re-render
|
||||
@ -226,6 +201,7 @@ function CustomizableViewportOverlay({
|
||||
scale,
|
||||
instanceNumber,
|
||||
annotationState,
|
||||
isLight,
|
||||
]
|
||||
);
|
||||
|
||||
@ -402,6 +378,7 @@ function OverlayItem(props) {
|
||||
*/
|
||||
function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
|
||||
const { windowWidth, windowCenter } = voi;
|
||||
const { title } = customization;
|
||||
if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
|
||||
return null;
|
||||
}
|
||||
@ -410,6 +387,7 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: customization?.color }}
|
||||
title={title}
|
||||
>
|
||||
<span className="mr-0.5 shrink-0 opacity-[0.70]">W:</span>
|
||||
<span className="mr-2.5 shrink-0">{windowWidth.toFixed(0)}</span>
|
||||
@ -443,11 +421,13 @@ function InstanceNumberOverlayItem({
|
||||
customization,
|
||||
}: OverlayItemProps) {
|
||||
const { imageIndex, numberOfSlices } = imageSliceData;
|
||||
const { title } = customization;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overlay-item flex flex-row"
|
||||
style={{ color: (customization && customization.color) || undefined }}
|
||||
title={title}
|
||||
>
|
||||
<span>
|
||||
{instanceNumber !== undefined && instanceNumber !== null ? (
|
||||
|
||||
@ -25,6 +25,7 @@ export default {
|
||||
{
|
||||
id: 'WindowLevel',
|
||||
inheritsFrom: 'ohif.overlayItem.windowLevel',
|
||||
title: 'Window Level',
|
||||
},
|
||||
{
|
||||
id: 'ZoomLevel',
|
||||
@ -39,6 +40,7 @@ export default {
|
||||
{
|
||||
id: 'InstanceNumber',
|
||||
inheritsFrom: 'ohif.overlayItem.instanceNumber',
|
||||
title: 'Instance Number',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
BaseVolumeViewport,
|
||||
cache,
|
||||
} from '@cornerstonejs/core';
|
||||
import { getDataIdForViewport } from '../utils/getDataIdForViewport';
|
||||
import { WindowLevelPreset } from '../types/WindowLevel';
|
||||
import { ColorbarPositionType, ColorbarOptions, ColorbarProperties } from '../types/Colorbar';
|
||||
import { VolumeRenderingConfig } from '../types/VolumeRenderingConfig';
|
||||
@ -104,11 +105,6 @@ const opacityToLinear = (opacityValue: number): number => {
|
||||
return Math.pow(opacityValue, 1.0 / GAMMA);
|
||||
};
|
||||
|
||||
const is3DViewport = ({ viewportId, cornerstoneViewportService }) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
return viewport instanceof VolumeViewport3D;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to access window level functionality for a specific viewport
|
||||
*
|
||||
@ -124,15 +120,17 @@ export function useViewportRendering(
|
||||
const { cornerstoneViewportService, colorbarService, customizationService } =
|
||||
servicesManager.services;
|
||||
|
||||
const [is3DVolume, setIs3DVolume] = useState(
|
||||
is3DViewport({ viewportId, cornerstoneViewportService })
|
||||
);
|
||||
const [hasColorbar, setHasColorbar] = useState(colorbarService.hasColorbar(viewportId));
|
||||
const [colorbarPosition, setColorbarPosition] = useState<ColorbarPositionType>(
|
||||
options?.location ? getPosition(options.location) : 'bottom'
|
||||
);
|
||||
const [voiRange, setVoiRange] = useState<{ lower: number; upper: number } | undefined>();
|
||||
const voiRangeRef = React.useRef<{ lower: number; upper: number } | undefined>();
|
||||
// Viewport from service; kept in state so we can subscribe to VIEWPORT_DATA_CHANGED when null and re-run effects when it becomes available
|
||||
const [viewport, setViewport] = useState<Types.IViewport | null>(() =>
|
||||
viewportId ? (cornerstoneViewportService.getCornerstoneViewport(viewportId) ?? null) : null
|
||||
);
|
||||
const [is3DVolume, setIs3DVolume] = useState(viewport instanceof VolumeViewport3D);
|
||||
const [opacity, setOpacityState] = useState<number | undefined>();
|
||||
const [opacityLinear, setOpacityLinearState] = useState<number | undefined>();
|
||||
const [threshold, setThresholdState] = useState<number | undefined>();
|
||||
@ -178,6 +176,34 @@ export function useViewportRendering(
|
||||
);
|
||||
}, [viewportDisplaySets, presets]);
|
||||
|
||||
// Keep viewport in state; when not available, subscribe to VIEWPORT_DATA_CHANGED so we set it when the viewport is ready
|
||||
useEffect(() => {
|
||||
if (!viewportId) {
|
||||
setViewport(null);
|
||||
return;
|
||||
}
|
||||
const vp = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
setViewport(vp ?? null);
|
||||
if (vp) {
|
||||
return;
|
||||
}
|
||||
const { unsubscribe } = cornerstoneViewportService.subscribe(
|
||||
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
|
||||
({ viewportId: eventViewportId }) => {
|
||||
if (eventViewportId !== viewportId) {
|
||||
return;
|
||||
}
|
||||
const next = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (next) {
|
||||
setViewport(next);
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewportId, cornerstoneViewportService]);
|
||||
|
||||
// Calculate pixel value range for the active display set
|
||||
useEffect(() => {
|
||||
if (!activeDisplaySetInstanceUID) {
|
||||
@ -189,17 +215,15 @@ export function useViewportRendering(
|
||||
return;
|
||||
}
|
||||
|
||||
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!csViewport) {
|
||||
if (!viewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(csViewport instanceof BaseVolumeViewport)) {
|
||||
if (!(viewport instanceof BaseVolumeViewport)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const volumeIds = csViewport.getAllVolumeIds();
|
||||
const volumeIds = viewport.getAllVolumeIds();
|
||||
const volumeId = volumeIds.find(id => id.includes(activeDisplaySetInstanceUID));
|
||||
|
||||
if (!volumeId) {
|
||||
@ -207,7 +231,7 @@ export function useViewportRendering(
|
||||
}
|
||||
|
||||
// only handle volume viewports for now
|
||||
const imageData = csViewport.getImageData(volumeId);
|
||||
const imageData = viewport.getImageData(volumeId);
|
||||
|
||||
if (!imageData) {
|
||||
return;
|
||||
@ -220,7 +244,7 @@ export function useViewportRendering(
|
||||
const range = voxelManager.getRange();
|
||||
|
||||
setPixelValueRange({ min: range[0], max: range[1] });
|
||||
}, [activeDisplaySetInstanceUID, displaySetService, cornerstoneViewportService, viewportId]);
|
||||
}, [activeDisplaySetInstanceUID, displaySetService, viewport]);
|
||||
|
||||
// Get the presets specifically for the active display set
|
||||
const activeDisplaySetPresets = useMemo(() => {
|
||||
@ -240,55 +264,47 @@ export function useViewportRendering(
|
||||
}, [allWindowLevelPresets, activeDisplaySetInstanceUID]);
|
||||
|
||||
useEffect(() => {
|
||||
setIs3DVolume(is3DViewport({ viewportId, cornerstoneViewportService }));
|
||||
setIs3DVolume(viewport instanceof VolumeViewport3D);
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
// Initialize the VOI range from the viewport
|
||||
if (viewport && activeDisplaySetInstanceUID) {
|
||||
try {
|
||||
let properties;
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
properties = viewport.getProperties();
|
||||
if (properties.voiRange) {
|
||||
setVoiRange(properties.voiRange);
|
||||
voiRangeRef.current = properties.voiRange;
|
||||
}
|
||||
} else if (viewport instanceof BaseVolumeViewport) {
|
||||
// For volume viewports, find the actor for the active display set
|
||||
const volumeIds = viewport.getAllVolumeIds();
|
||||
const volumeId = volumeIds.find(id => id.includes(activeDisplaySetInstanceUID));
|
||||
|
||||
if (volumeId) {
|
||||
properties = viewport.getProperties(volumeId);
|
||||
if (properties?.voiRange) {
|
||||
setVoiRange(properties.voiRange);
|
||||
voiRangeRef.current = properties.voiRange;
|
||||
}
|
||||
|
||||
// Get opacity from colormap if available
|
||||
if (properties?.colormap?.opacity !== undefined) {
|
||||
const isArray = Array.isArray(properties.colormap.opacity);
|
||||
const opacity = isArray
|
||||
? properties.colormap.opacity.reduce((max, current) => Math.max(max, current), 0)
|
||||
: properties.colormap.opacity;
|
||||
|
||||
setOpacityState(opacity);
|
||||
setOpacityLinearState(opacityToLinear(opacity));
|
||||
}
|
||||
|
||||
// Get threshold from colormap if available
|
||||
if (properties?.colormap && properties.colormap.threshold !== undefined) {
|
||||
setThresholdState(properties.colormap.threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error initializing VOI range:', error);
|
||||
}
|
||||
if (!viewport || !activeDisplaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
}, [cornerstoneViewportService, viewportId, activeDisplaySetInstanceUID]);
|
||||
try {
|
||||
const dataId = getDataIdForViewport(viewport as unknown, activeDisplaySetInstanceUID);
|
||||
|
||||
const properties =
|
||||
dataId != null
|
||||
? (viewport as Types.IBaseVolumeViewport).getProperties(dataId)
|
||||
: viewport.getProperties();
|
||||
|
||||
if (!properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (properties.voiRange) {
|
||||
setVoiRange(properties.voiRange);
|
||||
voiRangeRef.current = properties.voiRange;
|
||||
}
|
||||
|
||||
if (properties.colormap?.opacity !== undefined) {
|
||||
const opacityVal = properties.colormap.opacity;
|
||||
const opacity = Array.isArray(opacityVal)
|
||||
? (opacityVal as unknown as number[]).reduce(
|
||||
(max, current) => Math.max(max, current),
|
||||
0
|
||||
)
|
||||
: opacityVal;
|
||||
setOpacityState(opacity);
|
||||
setOpacityLinearState(opacityToLinear(opacity));
|
||||
}
|
||||
|
||||
if (properties.colormap?.threshold !== undefined) {
|
||||
setThresholdState(properties.colormap.threshold);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error initializing VOI range:', error);
|
||||
}
|
||||
}, [activeDisplaySetInstanceUID, viewport]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewportId) {
|
||||
@ -313,19 +329,11 @@ export function useViewportRendering(
|
||||
}, [colorbarService, viewportId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewportId || !activeDisplaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport) {
|
||||
if (!activeDisplaySetInstanceUID || !viewport?.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = viewport.element;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateVOI = eventDetail => {
|
||||
const { range } = eventDetail.detail;
|
||||
@ -369,7 +377,7 @@ export function useViewportRendering(
|
||||
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
|
||||
element.removeEventListener(Enums.Events.COLORMAP_MODIFIED, updateColormap);
|
||||
};
|
||||
}, [viewportId, activeDisplaySetInstanceUID, cornerstoneViewportService, opacityToLinear]);
|
||||
}, [activeDisplaySetInstanceUID, viewport, opacityToLinear]);
|
||||
|
||||
const validateActiveDisplaySet = useCallback(() => {
|
||||
if (!activeDisplaySetInstanceUID) {
|
||||
@ -560,16 +568,11 @@ export function useViewportRendering(
|
||||
context: 'CORNERSTONE',
|
||||
});
|
||||
},
|
||||
[commandsManager, viewportId, validateActiveDisplaySet]
|
||||
[commandsManager, validateActiveDisplaySet, viewport, viewportId]
|
||||
);
|
||||
|
||||
const setOpacity = useCallback(
|
||||
(opacityValue: number) => {
|
||||
if (!viewportId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport || !(viewport instanceof BaseVolumeViewport)) {
|
||||
return;
|
||||
}
|
||||
@ -607,7 +610,7 @@ export function useViewportRendering(
|
||||
|
||||
viewport.render();
|
||||
},
|
||||
[cornerstoneViewportService, viewportId, validateActiveDisplaySet, opacityToLinear]
|
||||
[validateActiveDisplaySet, opacityToLinear, viewport]
|
||||
);
|
||||
|
||||
const setOpacityLinear = useCallback(
|
||||
@ -621,11 +624,6 @@ export function useViewportRendering(
|
||||
|
||||
const setThreshold = useCallback(
|
||||
(thresholdValue: number) => {
|
||||
if (!viewportId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport || !(viewport instanceof BaseVolumeViewport)) {
|
||||
return;
|
||||
}
|
||||
@ -653,18 +651,16 @@ export function useViewportRendering(
|
||||
|
||||
viewport.render();
|
||||
},
|
||||
[cornerstoneViewportService, viewportId, validateActiveDisplaySet]
|
||||
[validateActiveDisplaySet, viewport]
|
||||
);
|
||||
|
||||
// Get the current colormap for the active display set
|
||||
const colormap = useMemo(() => {
|
||||
if (!viewportId || !activeDisplaySetInstanceUID || !viewportDisplaySets?.length) {
|
||||
if (!activeDisplaySetInstanceUID || !viewportDisplaySets?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport) {
|
||||
return null;
|
||||
}
|
||||
@ -711,13 +707,7 @@ export function useViewportRendering(
|
||||
colorbarProperties?.colormaps?.[0]
|
||||
);
|
||||
}
|
||||
}, [
|
||||
cornerstoneViewportService,
|
||||
viewportId,
|
||||
activeDisplaySetInstanceUID,
|
||||
viewportDisplaySets,
|
||||
colorbarProperties?.colormaps,
|
||||
]);
|
||||
}, [activeDisplaySetInstanceUID, viewportDisplaySets, colorbarProperties?.colormaps, viewport]);
|
||||
|
||||
// 3D volume rendering functions
|
||||
const setVolumeRenderingPreset = useCallback(
|
||||
@ -801,7 +791,9 @@ export function useViewportRendering(
|
||||
setWindowLevel,
|
||||
setVOIRange,
|
||||
voiRange,
|
||||
windowLevel: utilities.windowLevel.toWindowLevel(voiRange?.lower, voiRange?.upper),
|
||||
windowLevel: voiRange
|
||||
? utilities.windowLevel.toWindowLevel(voiRange?.lower, voiRange?.upper)
|
||||
: { windowCenter: null, windowWidth: null },
|
||||
|
||||
// Colorbar functions
|
||||
hasColorbar,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { PubSubService, Types as OhifTypes } from '@ohif/core';
|
||||
import { RENDERING_ENGINE_ID } from '../ViewportService/constants';
|
||||
import { getRenderingEngine } from '@cornerstonejs/core';
|
||||
import { getDataIdForViewport } from '../../utils/getDataIdForViewport';
|
||||
import { ColorbarOptions, ChangeTypes } from '../../types/Colorbar';
|
||||
|
||||
export default class ColorbarService extends PubSubService {
|
||||
@ -38,25 +39,6 @@ export default class ColorbarService extends PubSubService {
|
||||
this.servicesManager = servicesManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate data ID for a viewport and display set
|
||||
* @param viewport - The viewport instance
|
||||
* @param displaySetInstanceUID - The display set instance UID to identify data
|
||||
* @returns The appropriate data ID for the viewport type (volumeId for volume viewports, undefined for stack)
|
||||
*/
|
||||
private getDataIdForViewport(viewport, displaySetInstanceUID: string): string | undefined {
|
||||
// For volume viewports, find the matching volumeId
|
||||
if (viewport.getAllVolumeIds) {
|
||||
const volumeIds = viewport.getAllVolumeIds() || [];
|
||||
return volumeIds.length > 0
|
||||
? volumeIds.find(id => id.includes(displaySetInstanceUID)) || undefined
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// For other viewports, no specific dataId is needed for now
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a colorbar to a specific viewport identified by `viewportId`, using the provided `displaySetInstanceUIDs` and `options`.
|
||||
* This method prepares the colorbar state that will be used by the ViewportColorbarsContainer component.
|
||||
@ -92,7 +74,7 @@ export default class ColorbarService extends PubSubService {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataId = this.getDataIdForViewport(viewport, displaySetInstanceUID);
|
||||
const dataId = getDataIdForViewport(viewport, displaySetInstanceUID);
|
||||
const properties = dataId ? viewport.getProperties(dataId) : viewport.getProperties();
|
||||
const colormap = properties?.colormap;
|
||||
|
||||
@ -246,7 +228,7 @@ export default class ColorbarService extends PubSubService {
|
||||
}
|
||||
|
||||
// Get the appropriate dataId for this viewport/displaySet combination
|
||||
const dataId = this.getDataIdForViewport(viewport, displaySetInstanceUID);
|
||||
const dataId = getDataIdForViewport(viewport, displaySetInstanceUID);
|
||||
|
||||
// Set properties with or without dataId based on what the viewport supports
|
||||
viewport.setProperties({ colormap }, dataId);
|
||||
|
||||
22
extensions/cornerstone/src/utils/getDataIdForViewport.ts
Normal file
22
extensions/cornerstone/src/utils/getDataIdForViewport.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Resolves the data ID (e.g. volumeId) for a viewport and display set.
|
||||
* For viewports with multiple volumes/actors, returns the id that matches the display set; otherwise undefined.
|
||||
* Use this to call viewport.getProperties(dataId) in a viewport-type-agnostic way.
|
||||
*
|
||||
* @param viewport - Viewport instance (stack, volume, or future types with optional getAllVolumeIds)
|
||||
* @param displaySetInstanceUID - Display set instance UID to match
|
||||
* @returns volumeId (or equivalent) for multi-actor viewports, undefined for single-actor
|
||||
*/
|
||||
export function getDataIdForViewport(
|
||||
viewport: unknown,
|
||||
displaySetInstanceUID: string
|
||||
): string | undefined {
|
||||
const vp = viewport as { getAllVolumeIds?: () => string[] };
|
||||
if (typeof vp.getAllVolumeIds !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
const volumeIds = vp.getAllVolumeIds() || [];
|
||||
return volumeIds.length > 0
|
||||
? volumeIds.find(id => id.includes(displaySetInstanceUID)) ?? undefined
|
||||
: undefined;
|
||||
}
|
||||
@ -117,7 +117,7 @@ test('checks if image would jump when clicked on a measurement item', async ({
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight;
|
||||
const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight.instanceNumber;
|
||||
|
||||
// Image loads on slice 1, confirm on slice 1 then add measurement
|
||||
await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 });
|
||||
|
||||
@ -44,7 +44,7 @@ test('checks saved segmentations loads and jumps to slices', async ({
|
||||
rightPanelPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight;
|
||||
const viewportInfoBottomRight = viewportPageObject.active.overlayText.bottomRight.instanceNumber;
|
||||
// Image loads on slice 1, confirm on slice 1
|
||||
await expect(viewportInfoBottomRight).toContainText('1/', { timeout: 10000 });
|
||||
|
||||
|
||||
40
tests/WindowLevelOverlayText.spec.ts
Normal file
40
tests/WindowLevelOverlayText.spec.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { expect, test, visitStudy } from './utils';
|
||||
|
||||
test('should display the window level text as an overlay in various viewport layouts', async ({
|
||||
page,
|
||||
mainToolbarPageObject,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095258.1';
|
||||
const mode = 'viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
|
||||
await expect(viewportPageObject.getNth(0).overlayText.bottomLeft.windowLevel).toBeVisible();
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.click();
|
||||
await page.getByTestId('Layout-3-0').click();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await expect(viewportPageObject.getNth(i).overlayText.bottomLeft.windowLevel).toBeVisible();
|
||||
}
|
||||
|
||||
await mainToolbarPageObject.layoutSelection.click();
|
||||
await page.getByTestId('Layout-1-1').click();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await expect(viewportPageObject.getNth(i).overlayText.bottomLeft.windowLevel).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('should display the window level text as an overlay in the various TMTV viewports', async ({
|
||||
page,
|
||||
viewportPageObject,
|
||||
}) => {
|
||||
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.7009.2403.871108593056125491804754960339';
|
||||
const mode = 'tmtv';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
await expect(viewportPageObject.getNth(i).overlayText.bottomLeft.windowLevel).toBeVisible();
|
||||
}
|
||||
});
|
||||
@ -17,6 +17,23 @@ type NormalizedDragParams = {
|
||||
config?: { button?: 'left' | 'right' | 'middle'; delay?: number; steps?: number };
|
||||
};
|
||||
|
||||
export interface IOverlayText {
|
||||
get windowLevel(): Locator;
|
||||
get instanceNumber(): Locator;
|
||||
}
|
||||
|
||||
function overlayTextFactory(viewport: Locator, id: string): IOverlayText {
|
||||
const locator = viewport.getByTestId(id);
|
||||
return {
|
||||
get windowLevel() {
|
||||
return locator.getByTitle('Window Level');
|
||||
},
|
||||
get instanceNumber() {
|
||||
return locator.getByTitle('Instance Number');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface IViewportPageObject {
|
||||
nthAnnotation(nth: number): {
|
||||
locator: Locator;
|
||||
@ -46,10 +63,10 @@ export interface IViewportPageObject {
|
||||
bottomMid: Locator;
|
||||
};
|
||||
overlayText: {
|
||||
topLeft: Locator;
|
||||
topRight: Locator;
|
||||
bottomLeft: Locator;
|
||||
bottomRight: Locator;
|
||||
topLeft: IOverlayText;
|
||||
topRight: IOverlayText;
|
||||
bottomLeft: IOverlayText;
|
||||
bottomRight: IOverlayText;
|
||||
};
|
||||
overlayMenu: {
|
||||
dataOverlay: DataOverlayPageObject;
|
||||
@ -122,10 +139,10 @@ export class ViewportPageObject {
|
||||
|
||||
private getOverlayText(viewport: Locator) {
|
||||
return {
|
||||
topLeft: viewport.getByTestId('viewport-overlay-top-left'),
|
||||
topRight: viewport.getByTestId('viewport-overlay-top-right'),
|
||||
bottomLeft: viewport.getByTestId('viewport-overlay-bottom-left'),
|
||||
bottomRight: viewport.getByTestId('viewport-overlay-bottom-right'),
|
||||
topLeft: overlayTextFactory(viewport, 'viewport-overlay-top-left'),
|
||||
topRight: overlayTextFactory(viewport, 'viewport-overlay-top-right'),
|
||||
bottomLeft: overlayTextFactory(viewport, 'viewport-overlay-bottom-left'),
|
||||
bottomRight: overlayTextFactory(viewport, 'viewport-overlay-bottom-right'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user