feat: state sync service and hanging protocol updates to preserve state (#3131)
* feat: Add state sync and use it to remember viewport grid info fix: Version updates Fixes for toggling MPR mode Fix the display when the interleaved load module fails Fix the memory of the state to restore correctly PR fixes for the state sync service PR fixes PR fixes PR fixes Added a hack warning to remove volumeDeactivate Fixes for TMTV colormap setting Fix the casing Missed renames fix: tests not running due to variance in ordering Reverting some fixes to change case PR changes - mostly comments and minor improvements fix: All display sets were being updated on drag and drop PR fixes - mostly renames PR fixes Test support for OHIF, for HP branch test: Add at least a minimal set of automated tests for hanging protocols Docs PR fixes Merge fixes DOCS updates Add an example of the mn hanging protocol PR fixes PR fixes PR fixes * Fix the drag and drop PR fixes * PR changes - update default keys for next/previous stage * fix: Was storing the custom viewport grid too aggressively Caused by a PR change misspelling a variable
This commit is contained in:
1 parent
b7fff77e17
commit
803f638401
87 files changed
+3413
-1919
No files matched your search
@@ -168,9 +168,11 @@ const SplitButton = ({
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light'
|
||||
)}
|
||||
>
|
||||
<span className="mr-4">
|
||||
<Icon name={icon} className="w-5 h-5" />
|
||||
</span>
|
||||
{icon && (
|
||||
<span className="mr-4">
|
||||
<Icon name={icon} className="w-5 h-5" />
|
||||
</span>
|
||||
)}
|
||||
<span className="mr-5">{t(label)}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Icon } from '../';
|
||||
import { StringNumber } from '../../types';
|
||||
|
||||
/**
|
||||
*
|
||||
* Display a thumbnail for a display set.
|
||||
*/
|
||||
const Thumbnail = ({
|
||||
displaySetInstanceUID,
|
||||
@@ -16,11 +16,12 @@ const Thumbnail = ({
|
||||
description,
|
||||
seriesNumber,
|
||||
numInstances,
|
||||
countIcon,
|
||||
dragData,
|
||||
isActive,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
}) => {
|
||||
}): React.ReactNode => {
|
||||
// TODO: We should wrap our thumbnail to create a "DraggableThumbnail", as
|
||||
// this will still allow for "drag", even if there is no drop target for the
|
||||
// specified item.
|
||||
@@ -73,7 +74,8 @@ const Thumbnail = ({
|
||||
{seriesNumber}
|
||||
</div>
|
||||
<div className="flex flex-row items-center flex-1">
|
||||
<Icon name="group-layers" className="w-3 mr-2" /> {numInstances}
|
||||
<Icon name={countIcon || 'group-layers'} className="w-3 mr-2" />
|
||||
{` ${numInstances}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-base text-white break-all">{description}</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ const ThumbnailList = ({
|
||||
modality,
|
||||
componentType,
|
||||
seriesDate,
|
||||
countIcon,
|
||||
viewportIdentificator,
|
||||
isTracked,
|
||||
canReject,
|
||||
@@ -33,7 +34,6 @@ const ThumbnailList = ({
|
||||
const isActive = activeDisplaySetInstanceUIDs.includes(
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
switch (componentType) {
|
||||
case 'thumbnail':
|
||||
return (
|
||||
@@ -44,6 +44,7 @@ const ThumbnailList = ({
|
||||
description={description}
|
||||
seriesNumber={seriesNumber}
|
||||
numInstances={numInstances}
|
||||
countIcon={countIcon}
|
||||
imageSrc={imageSrc}
|
||||
imageAltText={imageAltText}
|
||||
viewportIdentificator={viewportIdentificator}
|
||||
@@ -63,6 +64,7 @@ const ThumbnailList = ({
|
||||
description={description}
|
||||
seriesNumber={seriesNumber}
|
||||
numInstances={numInstances}
|
||||
countIcon={countIcon}
|
||||
imageSrc={imageSrc}
|
||||
imageAltText={imageAltText}
|
||||
viewportIdentificator={viewportIdentificator}
|
||||
|
||||
@@ -13,6 +13,7 @@ const ThumbnailTracked = ({
|
||||
description,
|
||||
seriesNumber,
|
||||
numInstances,
|
||||
countIcon,
|
||||
dragData,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
@@ -117,6 +118,7 @@ const ThumbnailTracked = ({
|
||||
description={description}
|
||||
seriesNumber={seriesNumber}
|
||||
numInstances={numInstances}
|
||||
countIcon={countIcon}
|
||||
isActive={isActive}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
|
||||
@@ -39,6 +39,11 @@ const ToolbarButton = ({
|
||||
|
||||
const activeClass = isActive ? 'active' : '';
|
||||
const shouldShowDropdown = !!isActive && !!dropdownContent;
|
||||
const iconEl = icon ? (
|
||||
<Icon name={icon} />
|
||||
) : (
|
||||
<div>{label || 'Missing icon and label'}</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={id}>
|
||||
@@ -64,7 +69,7 @@ const ToolbarButton = ({
|
||||
id={id}
|
||||
{...rest}
|
||||
>
|
||||
<Icon name={icon} />
|
||||
{iconEl}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,16 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LegacyViewportActionBar, Notification } from '../';
|
||||
|
||||
const Viewport = ({ viewportIndex, onArrowsClick, studyData, children }) => {
|
||||
const Viewport = ({
|
||||
viewportId,
|
||||
viewportIndex,
|
||||
onArrowsClick,
|
||||
studyData,
|
||||
children,
|
||||
}) => {
|
||||
if (!viewportId) {
|
||||
viewportId = `viewport-${viewportIndex}`;
|
||||
}
|
||||
return (
|
||||
<div className="relative flex flex-col h-full">
|
||||
<div className="absolute top-0 left-0 w-full">
|
||||
@@ -39,7 +48,7 @@ const Viewport = ({ viewportIndex, onArrowsClick, studyData, children }) => {
|
||||
</div>
|
||||
|
||||
{/* STUDY IMAGE */}
|
||||
<div className="w-full h-full" id={`viewport-${viewportIndex}`}>
|
||||
<div className="w-full h-full" id={viewportId}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,17 +6,22 @@ import React, {
|
||||
useReducer,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import isEqual from 'lodash.isequal';
|
||||
import viewportLabels from '../utils/viewportLabels';
|
||||
import getPresentationId from './getPresentationId';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
numRows: null,
|
||||
numCols: null,
|
||||
layoutType: 'grid',
|
||||
activeViewportIndex: 0,
|
||||
layout: {
|
||||
numRows: 0,
|
||||
numCols: 0,
|
||||
layoutType: 'grid',
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
displaySetInstanceUIDs: [],
|
||||
viewportOptions: {},
|
||||
displaySetSelectors: [],
|
||||
displaySetOptions: [{}],
|
||||
x: 0, // left
|
||||
y: 0, // top
|
||||
@@ -25,20 +30,58 @@ const DEFAULT_STATE = {
|
||||
viewportLabel: null,
|
||||
},
|
||||
],
|
||||
activeViewportIndex: 0,
|
||||
cachedLayout: {},
|
||||
};
|
||||
|
||||
export const ViewportGridContext = createContext(DEFAULT_STATE);
|
||||
|
||||
/**
|
||||
* Given the flatten index, and rows and column, it returns the
|
||||
* row and column index
|
||||
* Find a viewport to re-use, and then set the viewportId
|
||||
*
|
||||
* @param idSet
|
||||
* @param viewport
|
||||
* @param stateViewports
|
||||
* @returns
|
||||
*/
|
||||
const unravelIndex = (index, numRows, numCols) => {
|
||||
const row = Math.floor(index / numCols);
|
||||
const col = index % numCols;
|
||||
return { row, col };
|
||||
const reuseViewport = (idSet, viewport, stateViewports) => {
|
||||
const oldIds = {};
|
||||
for (const oldViewport of stateViewports) {
|
||||
const { viewportId: oldId } = oldViewport;
|
||||
oldIds[oldId] = true;
|
||||
if (!oldId || idSet[oldId]) continue;
|
||||
if (
|
||||
!isEqual(
|
||||
oldViewport.displaySetInstanceUIDs,
|
||||
viewport.displaySetInstanceUIDs
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
idSet[oldId] = true;
|
||||
// TODO re-use viewports once the flickering/wrong size redraw is fixed
|
||||
// return {
|
||||
// ...oldViewport,
|
||||
// ...viewport,
|
||||
// viewportOptions: {
|
||||
// ...oldViewport.viewportOptions,
|
||||
|
||||
// viewportId: oldViewport.viewportId,
|
||||
// },
|
||||
// };
|
||||
}
|
||||
// Find a viewport instance number different from earlier viewports having
|
||||
// the same presentationId as this one would - will be less than 10k
|
||||
// viewports hopefully :-)
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
const viewportId = 'viewport-' + i;
|
||||
if (idSet[viewportId] || oldIds[viewportId]) continue;
|
||||
idSet[viewportId] = true;
|
||||
return {
|
||||
...viewport,
|
||||
viewportId,
|
||||
viewportOptions: { ...viewport.viewportOptions, viewportId },
|
||||
};
|
||||
}
|
||||
throw new Error('No ID found');
|
||||
};
|
||||
|
||||
export function ViewportGridProvider({ children, service }) {
|
||||
@@ -59,26 +102,40 @@ export function ViewportGridProvider({ children, service }) {
|
||||
// which might have been a PDF Viewport. The viewport itself
|
||||
// will deal with inheritance if required. Here is just a simple
|
||||
// provider.
|
||||
const viewportOptions = payload.viewportOptions || {};
|
||||
const displaySetOptions = payload.displaySetOptions || [{}];
|
||||
const viewport = state.viewports[viewportIndex] || {};
|
||||
const viewportOptions = { ...payload.viewportOptions };
|
||||
|
||||
const displaySetOptions = payload.displaySetOptions || [];
|
||||
if (displaySetOptions.length === 0) {
|
||||
// Only copy index 0, as that is all that is currently supported by this
|
||||
// method call.
|
||||
displaySetOptions.push({ ...viewport.displaySetOptions?.[0] });
|
||||
}
|
||||
|
||||
const viewports = state.viewports.slice();
|
||||
|
||||
if (!viewportOptions.viewportId) {
|
||||
viewportOptions.viewportId = `viewport-${viewportIndex}`;
|
||||
}
|
||||
|
||||
// merge the displaySetOptions and viewportOptions and displaySetInstanceUIDs
|
||||
// into the viewport object at the given index
|
||||
viewports[viewportIndex] = {
|
||||
...viewports[viewportIndex],
|
||||
let newView = {
|
||||
...viewport,
|
||||
displaySetInstanceUIDs,
|
||||
viewportOptions,
|
||||
displaySetOptions,
|
||||
viewportLabel: viewportLabels[viewportIndex],
|
||||
};
|
||||
viewportOptions.presentationId = getPresentationId(newView, viewports);
|
||||
|
||||
return { ...state, ...{ viewports } };
|
||||
// Make sure we assign a viewport id
|
||||
newView = reuseViewport({}, newView, state.viewports);
|
||||
console.log(
|
||||
'Creating new viewport',
|
||||
viewportIndex,
|
||||
newView.viewportOptions.viewportId,
|
||||
displaySetInstanceUIDs,
|
||||
displaySetOptions
|
||||
);
|
||||
|
||||
viewports[viewportIndex] = newView;
|
||||
|
||||
return { ...state, viewports };
|
||||
}
|
||||
case 'SET_LAYOUT': {
|
||||
const {
|
||||
@@ -86,108 +143,96 @@ export function ViewportGridProvider({ children, service }) {
|
||||
numRows,
|
||||
layoutOptions,
|
||||
layoutType = 'grid',
|
||||
keepExtraViewports = false,
|
||||
findOrCreateViewport,
|
||||
} = action.payload;
|
||||
|
||||
// If empty viewportOptions, we use numRow and numCols to calculate number of viewports
|
||||
const numPanes = layoutOptions.length || numRows * numCols;
|
||||
const viewports = state.viewports.slice();
|
||||
const activeViewportIndex =
|
||||
state.activeViewportIndex >= numPanes ? 0 : state.activeViewportIndex;
|
||||
const hasOptions = layoutOptions?.length;
|
||||
const viewports = [];
|
||||
|
||||
while (viewports.length < numPanes) {
|
||||
viewports.push({});
|
||||
}
|
||||
// Options is a temporary state store which can be used by the
|
||||
// findOrCreate to store state about already found viewports. Typically,
|
||||
// it will be used to store the display set UID's which are already
|
||||
// in view so that the find or create can decide which display sets
|
||||
// haven't been viewed yet, and add them in the appropriate order.
|
||||
const options = {};
|
||||
|
||||
// Extra viewports are kept when the grid layout is changed in the UI
|
||||
// because the user populated those viewports and if the viewports were to
|
||||
// return on screen their contents should be maintained.
|
||||
if (!keepExtraViewports) {
|
||||
while (viewports.length > numPanes) {
|
||||
viewports.pop();
|
||||
let activeViewportIndex;
|
||||
for (let row = 0; row < numRows; row++) {
|
||||
for (let col = 0; col < numCols; col++) {
|
||||
const pos = col + row * numCols;
|
||||
const layoutOption = layoutOptions[pos];
|
||||
const positionId = layoutOption?.positionId || `${col}-${row}`;
|
||||
if (hasOptions && pos >= layoutOptions.length) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!activeViewportIndex ||
|
||||
state.viewports[pos]?.positionId === positionId
|
||||
) {
|
||||
activeViewportIndex = pos;
|
||||
}
|
||||
const viewport = findOrCreateViewport(pos, positionId, options);
|
||||
if (!viewport) continue;
|
||||
viewport.positionId = positionId;
|
||||
// Create a new viewport object as it is getting updated here
|
||||
// and it is part of the read only state
|
||||
viewports.push(viewport);
|
||||
let xPos, yPos, w, h;
|
||||
|
||||
if (layoutOptions && layoutOptions[pos]) {
|
||||
({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[pos]);
|
||||
} else {
|
||||
w = 1 / numCols;
|
||||
h = 1 / numRows;
|
||||
xPos = col * w;
|
||||
yPos = row * h;
|
||||
}
|
||||
|
||||
viewport.width = w;
|
||||
viewport.height = h;
|
||||
viewport.x = xPos;
|
||||
viewport.y = yPos;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < numPanes; i++) {
|
||||
let xPos, yPos, w, h;
|
||||
|
||||
if (layoutOptions && layoutOptions[i]) {
|
||||
({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[i]);
|
||||
} else {
|
||||
const { row, col } = unravelIndex(i, numRows, numCols);
|
||||
w = 1 / numCols;
|
||||
h = 1 / numRows;
|
||||
xPos = col * w;
|
||||
yPos = row * h;
|
||||
const viewportIdSet = {};
|
||||
for (
|
||||
let viewportIndex = 0;
|
||||
viewportIndex < viewports.length;
|
||||
viewportIndex++
|
||||
) {
|
||||
const viewport = reuseViewport(
|
||||
viewportIdSet,
|
||||
viewports[viewportIndex],
|
||||
state.viewports
|
||||
);
|
||||
if (!viewport.viewportOptions.presentationId) {
|
||||
viewport.viewportOptions.presentationId = getPresentationId(
|
||||
viewport,
|
||||
viewports
|
||||
);
|
||||
}
|
||||
|
||||
viewports[i].width = w;
|
||||
viewports[i].height = h;
|
||||
viewports[i].x = xPos;
|
||||
viewports[i].y = yPos;
|
||||
viewport.viewportIndex = viewportIndex;
|
||||
viewport.viewportLabel = viewportLabels[viewportIndex];
|
||||
viewports[viewportIndex] = viewport;
|
||||
}
|
||||
|
||||
return {
|
||||
const ret = {
|
||||
...state,
|
||||
...{
|
||||
activeViewportIndex,
|
||||
activeViewportIndex,
|
||||
layout: {
|
||||
...state.layout,
|
||||
numCols,
|
||||
numRows,
|
||||
layoutType,
|
||||
viewports,
|
||||
},
|
||||
viewports,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
case 'RESET': {
|
||||
return {
|
||||
numCols: null,
|
||||
numRows: null,
|
||||
layoutType: 'grid',
|
||||
activeViewportIndex: 0,
|
||||
viewports: [
|
||||
{
|
||||
displaySetInstanceUIDs: [],
|
||||
displaySetOptions: [],
|
||||
viewportOptions: {},
|
||||
x: 0, // left
|
||||
y: 0, // top
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
],
|
||||
cachedLayout: {},
|
||||
};
|
||||
}
|
||||
|
||||
// The SET_CACHE_LAYOUT action can be used for caching a layout
|
||||
// for instance double clicking a viewport to maximize it.
|
||||
// and then restoring the previous layout when the viewport is
|
||||
// double clicked again.
|
||||
case 'SET_CACHED_LAYOUT': {
|
||||
const { cacheId, cachedLayout } = action.payload;
|
||||
|
||||
// deep copy the cachedLayout into the state
|
||||
return {
|
||||
...state,
|
||||
cachedLayout: {
|
||||
...state.cachedLayout,
|
||||
[cacheId]: JSON.parse(JSON.stringify(cachedLayout)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'RESTORE_CACHED_LAYOUT': {
|
||||
const cacheId = action.payload;
|
||||
|
||||
if (!state.cachedLayout[cacheId]) {
|
||||
console.warn(
|
||||
`No cached layout found for cacheId: ${cacheId}. Ignoring...`
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
const cachedLayout = state.cachedLayout;
|
||||
return { ...state.cachedLayout[cacheId], cachedLayout };
|
||||
return DEFAULT_STATE;
|
||||
}
|
||||
|
||||
case 'SET': {
|
||||
@@ -221,6 +266,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
viewportIndex,
|
||||
displaySetInstanceUIDs,
|
||||
viewportOptions,
|
||||
displaySetSelectors,
|
||||
displaySetOptions,
|
||||
}) =>
|
||||
dispatch({
|
||||
@@ -229,6 +275,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
viewportIndex,
|
||||
displaySetInstanceUIDs,
|
||||
viewportOptions,
|
||||
displaySetSelectors,
|
||||
displaySetOptions,
|
||||
},
|
||||
}),
|
||||
@@ -250,7 +297,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
numRows,
|
||||
numCols,
|
||||
layoutOptions = [],
|
||||
keepExtraViewports = false,
|
||||
findOrCreateViewport,
|
||||
}) =>
|
||||
dispatch({
|
||||
type: 'SET_LAYOUT',
|
||||
@@ -259,7 +306,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
numRows,
|
||||
numCols,
|
||||
layoutOptions,
|
||||
keepExtraViewports,
|
||||
findOrCreateViewport,
|
||||
},
|
||||
}),
|
||||
[dispatch]
|
||||
@@ -274,25 +321,6 @@ export function ViewportGridProvider({ children, service }) {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setCachedLayout = useCallback(
|
||||
payload =>
|
||||
dispatch({
|
||||
type: 'SET_CACHED_LAYOUT',
|
||||
payload,
|
||||
}),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const restoreCachedLayout = useCallback(
|
||||
cacheId => {
|
||||
dispatch({
|
||||
type: 'RESTORE_CACHED_LAYOUT',
|
||||
payload: cacheId,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const set = useCallback(
|
||||
payload =>
|
||||
dispatch({
|
||||
@@ -303,7 +331,8 @@ export function ViewportGridProvider({ children, service }) {
|
||||
);
|
||||
|
||||
const getNumViewportPanes = useCallback(() => {
|
||||
const { numCols, numRows, viewports } = viewportGridState;
|
||||
const { layout, viewports } = viewportGridState;
|
||||
const { numRows, numCols } = layout;
|
||||
return Math.min(viewports.length, numCols * numRows);
|
||||
}, [viewportGridState]);
|
||||
|
||||
@@ -322,8 +351,6 @@ export function ViewportGridProvider({ children, service }) {
|
||||
setLayout,
|
||||
reset,
|
||||
onModeExit: reset,
|
||||
setCachedLayout,
|
||||
restoreCachedLayout,
|
||||
set,
|
||||
getNumViewportPanes,
|
||||
});
|
||||
@@ -336,8 +363,6 @@ export function ViewportGridProvider({ children, service }) {
|
||||
setDisplaySetsForViewports,
|
||||
setLayout,
|
||||
reset,
|
||||
setCachedLayout,
|
||||
restoreCachedLayout,
|
||||
set,
|
||||
getNumViewportPanes,
|
||||
]);
|
||||
@@ -348,8 +373,6 @@ export function ViewportGridProvider({ children, service }) {
|
||||
setDisplaySetsForViewport,
|
||||
setDisplaySetsForViewports,
|
||||
setLayout,
|
||||
setCachedLayout,
|
||||
restoreCachedLayout,
|
||||
reset,
|
||||
set,
|
||||
getNumViewportPanes,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Selects a presentation ID to use for this viewport.
|
||||
* This is done to allow the same display set to be displayed more than once
|
||||
* on screen, with different attributes such as window level and initial position.
|
||||
* Then, when redisplaying that, the nearest/most common attribute is re-used.
|
||||
*
|
||||
* For example, for display set <displaySetUID>, in a viewport of type volume,
|
||||
* the generated presentationID might be
|
||||
* `volume:axial:<displaySetUID>`. This can then be used to store and retrieve
|
||||
* presentation information in state sync service 'presentationSync' state.
|
||||
*
|
||||
* The generated value attempts to generate a unique value for every type
|
||||
* of viewport which should have it's own presentation information. Thus, the
|
||||
* following values are used for presentation ID:
|
||||
*
|
||||
* 1. viewportType - since the presentation information for a volume is different than for a stack
|
||||
* 2. orientation - since the camera is different for different orientations
|
||||
* 3. display set instance UID - since different display sets should get displayed differently
|
||||
* 4. instance count - since displaying the same series twice should allow applying different window level etc
|
||||
*
|
||||
* @param viewport requiring a presentation Id
|
||||
* @param viewports is the list of viewports being shown. Any presentation ID's
|
||||
* among them must not be re-used in order to have each viewport have it's own presentation ID.
|
||||
* @returns Presentation ID id, or undefined if nothing displayed
|
||||
*/
|
||||
const getPresentationId = (viewport, viewports): string => {
|
||||
if (!viewport) return;
|
||||
const { viewportOptions, displaySetInstanceUIDs } = viewport;
|
||||
if (!viewportOptions || !displaySetInstanceUIDs?.length) {
|
||||
console.log('No viewport type or display sets in', viewport);
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportType = viewportOptions.viewportType || 'stack';
|
||||
const idArr = [viewportType, 0, ...displaySetInstanceUIDs];
|
||||
if (viewportOptions.orientation) {
|
||||
idArr.splice(2, 0, viewportOptions.orientation);
|
||||
}
|
||||
|
||||
// Allow setting a custom presentation prefix in the hanging protocol
|
||||
// This allows defining new
|
||||
// presentation groups to be set automatically when one knows that the
|
||||
// same display set will be displayed in different ways.
|
||||
// This is the recommended way to manage a hanging protocol which displays
|
||||
// multiple views of a single display set, eg to display brain, bone, soft
|
||||
// tissue views in different viewports.
|
||||
if (viewportOptions.presentationPrefix) {
|
||||
idArr.push(viewportOptions.presentationPrefix);
|
||||
}
|
||||
if (!viewports) {
|
||||
console.log('viewports not defined', idArr.join(','));
|
||||
return idArr.join('&');
|
||||
}
|
||||
|
||||
// This code finds the first unique index to add to the presentation id so that
|
||||
// two viewports containing the same display set in the same type of viewport
|
||||
// can have different presentation information. This allows comparison of
|
||||
// a single display set in two or more viewports, when the user has simply
|
||||
// dragged and dropped the view in twice. For example, it allows displaying
|
||||
// bone, brain and soft tissue views of a single display set, and to still
|
||||
// remember the specific changes to each viewport.
|
||||
for (let displayInstance = 0; displayInstance < 128; displayInstance++) {
|
||||
idArr[1] = displayInstance;
|
||||
const testId = idArr.join('&');
|
||||
if (!viewports.find(it => it.viewportOptions?.presentationId === testId)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const id = idArr.join('&');
|
||||
return id;
|
||||
};
|
||||
|
||||
export default getPresentationId;
|
||||
Reference in new issue
Block a user