fix(ViewportGrid) fill blank viewports with display sets not yet in grid. (#3154)

* fix(ViewportGrid): In ViewportGrid, fill blank viewports with display sets not yet in grid.
ViewportGridService now allows off-screen viewports to remain so that
so as to maintain continuity if they were filled by the UI/user.

* PR feedback: moved getNumViewportPanes into the ViewportGridService API.

* PR feedback:
- renamed some variables
- proper import/export of IDisplaySet
- added some comments for clarification
- fixed broken e2e tests

* Some missed rename of Services.
This commit is contained in:
Joe Boccanfuso 2023-02-14 10:01:39 -05:00 committed by GitHub
parent 5e66faa45d
commit f791a4cafb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 33 deletions

View File

@ -59,6 +59,13 @@ export default function toggleMPRHangingProtocol({
viewports[activeViewportIndex].displaySetInstanceUIDs;
const errorCallback = error => {
// Unable to create MPR, so be sure to return to the cached/original protocol.
hangingProtocolService.setProtocol(
cachedState.protocol.id,
viewportMatchDetails,
restoreErrorCallback
);
uiNotificationService.show({
title: 'Multiplanar reconstruction (MPR) ',
message:
@ -276,13 +283,17 @@ function _getViewportsInfo({ protocol, stage, viewports, servicesManager }) {
.filter(Boolean);
if (viewportIds.length) {
toolOptions = viewportIds.map(viewportId => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
return {
toolGroupId: toolGroup.id,
toolOptions: toolGroup.toolOptions,
};
});
toolOptions = viewportIds
.map(viewportId => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
return toolGroup
? {
toolGroupId: toolGroup.id,
toolOptions: toolGroup.toolOptions,
}
: null;
})
.filter(Boolean);
}
return { viewportMatchDetails, viewportStructure, toolOptions };

View File

@ -79,7 +79,15 @@ function LayoutSelector({
],
});
}
viewportGridService.setLayout({ numRows, numCols });
// When a new layout is selected, keep any extra/offscreen viewports
// so that if any of those viewports were populated via the UI then they
// will be maintained in case those viewports are redisplayed later.
viewportGridService.setLayout({
numRows,
numCols,
keepExtraViewports: true,
});
};
return (

View File

@ -182,7 +182,7 @@ function PanelStudyBrowserTracking({
thumbnailImageSrcMap,
trackedSeries,
viewports,
isSingleViewport,
viewportGridService,
dataSource,
displaySetService,
uiDialogService,
@ -245,7 +245,7 @@ function PanelStudyBrowserTracking({
thumbnailImageSrcMap,
trackedSeries,
viewports,
isSingleViewport,
viewportGridService,
dataSource,
displaySetService,
uiDialogService,
@ -412,7 +412,7 @@ function _mapDisplaySets(
thumbnailImageSrcMap,
trackedSeriesInstanceUIDs,
viewports, // TODO: make array of `displaySetInstanceUIDs`?
isSingleViewport,
viewportGridService,
dataSource,
displaySetService,
uiDialogService,
@ -423,18 +423,21 @@ function _mapDisplaySets(
displaySets.forEach(ds => {
const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID];
const componentType = _getComponentType(ds.Modality);
const viewportIdentificator = isSingleViewport
? []
: viewports.reduce((acc, viewportData, index) => {
if (
viewportData?.displaySetInstanceUIDs?.includes(
ds.displaySetInstanceUID
)
) {
acc.push(viewportData.viewportLabel);
}
return acc;
}, []);
const numPanes = viewportGridService.getNumViewportPanes();
const viewportIdentificator =
numPanes === 1
? []
: viewports.reduce((acc, viewportData, index) => {
if (
index < numPanes &&
viewportData?.displaySetInstanceUIDs?.includes(
ds.displaySetInstanceUID
)
) {
acc.push(viewportData.viewportLabel);
}
return acc;
}, []);
const array =
componentType === 'thumbnailTracked'

View File

@ -31,6 +31,7 @@ class ViewportGridService extends PubSubService {
reset: resetImplementation,
onModeExit: onModeExitImplementation,
set: setImplementation,
getNumViewportPanes: getNumViewportPanesImplementation,
}): void {
if (getStateImplementation) {
this.serviceImplementation._getState = getStateImplementation;
@ -62,6 +63,9 @@ class ViewportGridService extends PubSubService {
if (setImplementation) {
this.serviceImplementation._set = setImplementation;
}
if (getNumViewportPanesImplementation) {
this.serviceImplementation._getNumViewportPanes = getNumViewportPanesImplementation;
}
}
public setActiveViewportIndex(index) {
@ -122,6 +126,10 @@ class ViewportGridService extends PubSubService {
public set(state) {
this.serviceImplementation._set(state);
}
public getNumViewportPanes() {
return this.serviceImplementation._getNumViewportPanes();
}
}
export default ViewportGridService;

View File

@ -19,8 +19,9 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
| `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
| `getState()` | Gets the states of the viewport (see below) |
| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id |
| `setLayout({numCols, numRows})` | Sets rows and columns |
| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. |
| `reset()` | Resets the default states |
| `getNumViewportPanes()` | Gets the number of visible viewport panes |
## Implementations

View File

@ -19,8 +19,9 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
| `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
| `getState()` | Gets the states of the viewport (see below) |
| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id |
| `setLayout({numCols, numRows})` | Sets rows and columns |
| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. |
| `reset()` | Resets the default states |
| `getNumViewportPanes()` | Gets the number of visible viewport panes |
## Implementations

View File

@ -86,6 +86,7 @@ export function ViewportGridProvider({ children, service }) {
numRows,
layoutOptions,
layoutType = 'grid',
keepExtraViewports = false,
} = action.payload;
// If empty viewportOptions, we use numRow and numCols to calculate number of viewports
@ -97,8 +98,14 @@ export function ViewportGridProvider({ children, service }) {
while (viewports.length < numPanes) {
viewports.push({});
}
while (viewports.length > numPanes) {
viewports.pop();
// 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();
}
}
for (let i = 0; i < numPanes; i++) {
@ -238,7 +245,13 @@ export function ViewportGridProvider({ children, service }) {
);
const setLayout = useCallback(
({ layoutType, numRows, numCols, layoutOptions = [] }) =>
({
layoutType,
numRows,
numCols,
layoutOptions = [],
keepExtraViewports = false,
}) =>
dispatch({
type: 'SET_LAYOUT',
payload: {
@ -246,6 +259,7 @@ export function ViewportGridProvider({ children, service }) {
numRows,
numCols,
layoutOptions,
keepExtraViewports,
},
}),
[dispatch]
@ -288,6 +302,11 @@ export function ViewportGridProvider({ children, service }) {
[dispatch]
);
const getNumViewportPanes = useCallback(() => {
const { numCols, numRows, viewports } = viewportGridState;
return Math.min(viewports.length, numCols * numRows);
}, [viewportGridState]);
/**
* Sets the implementation of ViewportGridService that can be used by extensions.
*
@ -306,6 +325,7 @@ export function ViewportGridProvider({ children, service }) {
setCachedLayout,
restoreCachedLayout,
set,
getNumViewportPanes,
});
}
}, [
@ -319,6 +339,7 @@ export function ViewportGridProvider({ children, service }) {
setCachedLayout,
restoreCachedLayout,
set,
getNumViewportPanes,
]);
const api = {
@ -331,6 +352,7 @@ export function ViewportGridProvider({ children, service }) {
restoreCachedLayout,
reset,
set,
getNumViewportPanes,
};
return (

View File

@ -54,20 +54,35 @@ function ViewerViewportGrid(props) {
return;
}
// Match each viewport individually
const numViewports = viewportGrid.numRows * viewportGrid.numCols;
const gridDisplaySetUIDs = [];
const blankViewportIndices = [];
// Match each viewport individually.
const numViewports = viewportGridService.getNumViewportPanes();
for (
let viewportIndex = 0;
viewportIndex < numViewports;
viewportIndex++
) {
const viewportDisplaySetUIDs =
viewports[viewportIndex]?.displaySetInstanceUIDs ?? [];
if (hpAlreadyApplied.get(viewportIndex)) {
gridDisplaySetUIDs.push(...viewportDisplaySetUIDs);
continue;
}
// if current viewport doesn't have a match
if (viewportMatchDetails.get(viewportIndex) === undefined) {
return;
// if the current viewport is empty/blank
if (viewportDisplaySetUIDs.length === 0) {
blankViewportIndices.push(viewportIndex);
} else {
gridDisplaySetUIDs.push(...viewportDisplaySetUIDs);
}
continue;
}
const { displaySetsInfo, viewportOptions } = viewportMatchDetails.get(
@ -87,6 +102,8 @@ function ViewerViewportGrid(props) {
}
);
gridDisplaySetUIDs.push(...displaySetUIDsToHang);
viewportGridService.setDisplaySetsForViewport({
viewportIndex: viewportIndex,
displaySetInstanceUIDs: displaySetUIDsToHang,
@ -111,6 +128,25 @@ function ViewerViewportGrid(props) {
);
}
}
blankViewportIndices.forEach((blankVPIndex: number) => {
// try to fill the empty viewport with a display set not already in the grid
const displaySetsNotInGrid = availableDisplaySets.filter(
displaySet =>
gridDisplaySetUIDs.indexOf(displaySet.displaySetInstanceUID) === -1
);
if (displaySetsNotInGrid.length > 0) {
const displaySetUIDToAdd =
displaySetsNotInGrid[0].displaySetInstanceUID;
gridDisplaySetUIDs.push(displaySetUIDToAdd);
viewportGridService.setDisplaySetsForViewport({
viewportIndex: blankVPIndex,
displaySetInstanceUIDs: [displaySetUIDToAdd],
});
}
});
},
[viewportGrid, numRows, numCols]
);
@ -178,6 +214,20 @@ function ViewerViewportGrid(props) {
};
}, [viewports]);
useEffect(() => {
const { unsubscribe } = hangingProtocolService.subscribe(
hangingProtocolService.EVENTS.STAGE_CHANGE,
() => {
const displaySets = DisplaySetService.getActiveDisplaySets();
updateDisplaySetsForViewports(displaySets);
}
);
return () => {
unsubscribe();
};
}, [viewports]);
useEffect(() => {
const { unsubscribe } = measurementService.subscribe(
measurementService.EVENTS.JUMP_TO_MEASUREMENT,
@ -314,7 +364,8 @@ function ViewerViewportGrid(props) {
const getViewportPanes = useCallback(() => {
const viewportPanes = [];
for (let i = 0; i < viewports.length; i++) {
const numViewports = viewportGridService.getNumViewportPanes();
for (let i = 0; i < numViewports; i++) {
const viewportIndex = i;
const isActive = activeViewportIndex === viewportIndex;
const paneMetadata = viewports[i] || {};
@ -388,7 +439,7 @@ function ViewerViewportGrid(props) {
<ViewportComponent
displaySets={displaySets}
viewportIndex={viewportIndex}
viewportLabel={viewports.length > 1 ? viewportLabel : ''}
viewportLabel={numViewports > 1 ? viewportLabel : ''}
dataSource={dataSource}
viewportOptions={viewportOptions}
displaySetOptions={displaySetOptions}