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; viewports[activeViewportIndex].displaySetInstanceUIDs;
const errorCallback = error => { 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({ uiNotificationService.show({
title: 'Multiplanar reconstruction (MPR) ', title: 'Multiplanar reconstruction (MPR) ',
message: message:
@ -276,13 +283,17 @@ function _getViewportsInfo({ protocol, stage, viewports, servicesManager }) {
.filter(Boolean); .filter(Boolean);
if (viewportIds.length) { if (viewportIds.length) {
toolOptions = viewportIds.map(viewportId => { toolOptions = viewportIds
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); .map(viewportId => {
return { const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
toolGroupId: toolGroup.id, return toolGroup
toolOptions: toolGroup.toolOptions, ? {
}; toolGroupId: toolGroup.id,
}); toolOptions: toolGroup.toolOptions,
}
: null;
})
.filter(Boolean);
} }
return { viewportMatchDetails, viewportStructure, toolOptions }; 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 ( return (

View File

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

View File

@ -31,6 +31,7 @@ class ViewportGridService extends PubSubService {
reset: resetImplementation, reset: resetImplementation,
onModeExit: onModeExitImplementation, onModeExit: onModeExitImplementation,
set: setImplementation, set: setImplementation,
getNumViewportPanes: getNumViewportPanesImplementation,
}): void { }): void {
if (getStateImplementation) { if (getStateImplementation) {
this.serviceImplementation._getState = getStateImplementation; this.serviceImplementation._getState = getStateImplementation;
@ -62,6 +63,9 @@ class ViewportGridService extends PubSubService {
if (setImplementation) { if (setImplementation) {
this.serviceImplementation._set = setImplementation; this.serviceImplementation._set = setImplementation;
} }
if (getNumViewportPanesImplementation) {
this.serviceImplementation._getNumViewportPanes = getNumViewportPanesImplementation;
}
} }
public setActiveViewportIndex(index) { public setActiveViewportIndex(index) {
@ -122,6 +126,10 @@ class ViewportGridService extends PubSubService {
public set(state) { public set(state) {
this.serviceImplementation._set(state); this.serviceImplementation._set(state);
} }
public getNumViewportPanes() {
return this.serviceImplementation._getNumViewportPanes();
}
} }
export default ViewportGridService; 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 | | `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
| `getState()` | Gets the states of the viewport (see below) | | `getState()` | Gets the states of the viewport (see below) |
| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | | `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 | | `reset()` | Resets the default states |
| `getNumViewportPanes()` | Gets the number of visible viewport panes |
## Implementations ## 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 | | `setActiveViewportIndex(index)` | Sets the active viewport index in the app |
| `getState()` | Gets the states of the viewport (see below) | | `getState()` | Gets the states of the viewport (see below) |
| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | | `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 | | `reset()` | Resets the default states |
| `getNumViewportPanes()` | Gets the number of visible viewport panes |
## Implementations ## Implementations

View File

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

View File

@ -54,20 +54,35 @@ function ViewerViewportGrid(props) {
return; return;
} }
// Match each viewport individually const gridDisplaySetUIDs = [];
const numViewports = viewportGrid.numRows * viewportGrid.numCols; const blankViewportIndices = [];
// Match each viewport individually.
const numViewports = viewportGridService.getNumViewportPanes();
for ( for (
let viewportIndex = 0; let viewportIndex = 0;
viewportIndex < numViewports; viewportIndex < numViewports;
viewportIndex++ viewportIndex++
) { ) {
const viewportDisplaySetUIDs =
viewports[viewportIndex]?.displaySetInstanceUIDs ?? [];
if (hpAlreadyApplied.get(viewportIndex)) { if (hpAlreadyApplied.get(viewportIndex)) {
gridDisplaySetUIDs.push(...viewportDisplaySetUIDs);
continue; continue;
} }
// if current viewport doesn't have a match // if current viewport doesn't have a match
if (viewportMatchDetails.get(viewportIndex) === undefined) { 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( const { displaySetsInfo, viewportOptions } = viewportMatchDetails.get(
@ -87,6 +102,8 @@ function ViewerViewportGrid(props) {
} }
); );
gridDisplaySetUIDs.push(...displaySetUIDsToHang);
viewportGridService.setDisplaySetsForViewport({ viewportGridService.setDisplaySetsForViewport({
viewportIndex: viewportIndex, viewportIndex: viewportIndex,
displaySetInstanceUIDs: displaySetUIDsToHang, 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] [viewportGrid, numRows, numCols]
); );
@ -178,6 +214,20 @@ function ViewerViewportGrid(props) {
}; };
}, [viewports]); }, [viewports]);
useEffect(() => {
const { unsubscribe } = hangingProtocolService.subscribe(
hangingProtocolService.EVENTS.STAGE_CHANGE,
() => {
const displaySets = DisplaySetService.getActiveDisplaySets();
updateDisplaySetsForViewports(displaySets);
}
);
return () => {
unsubscribe();
};
}, [viewports]);
useEffect(() => { useEffect(() => {
const { unsubscribe } = measurementService.subscribe( const { unsubscribe } = measurementService.subscribe(
measurementService.EVENTS.JUMP_TO_MEASUREMENT, measurementService.EVENTS.JUMP_TO_MEASUREMENT,
@ -314,7 +364,8 @@ function ViewerViewportGrid(props) {
const getViewportPanes = useCallback(() => { const getViewportPanes = useCallback(() => {
const viewportPanes = []; 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 viewportIndex = i;
const isActive = activeViewportIndex === viewportIndex; const isActive = activeViewportIndex === viewportIndex;
const paneMetadata = viewports[i] || {}; const paneMetadata = viewports[i] || {};
@ -388,7 +439,7 @@ function ViewerViewportGrid(props) {
<ViewportComponent <ViewportComponent
displaySets={displaySets} displaySets={displaySets}
viewportIndex={viewportIndex} viewportIndex={viewportIndex}
viewportLabel={viewports.length > 1 ? viewportLabel : ''} viewportLabel={numViewports > 1 ? viewportLabel : ''}
dataSource={dataSource} dataSource={dataSource}
viewportOptions={viewportOptions} viewportOptions={viewportOptions}
displaySetOptions={displaySetOptions} displaySetOptions={displaySetOptions}