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
@@ -0,0 +1,31 @@
|
||||
describe('OHIF HP', () => {
|
||||
beforeEach(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
|
||||
'&hangingProtocolId=@ohif/hp-extension.mn'
|
||||
);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
});
|
||||
|
||||
it('Should display 3 up', () => {
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', 3);
|
||||
});
|
||||
|
||||
it('Should navigate next/previous stage', () => {
|
||||
cy.get('body').type(',');
|
||||
cy.wait(250);
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', 4);
|
||||
|
||||
cy.get('body').type('..');
|
||||
cy.wait(250);
|
||||
cy.get('[data-cy="viewport-pane"]')
|
||||
.its('length')
|
||||
.should('be.eq', 2);
|
||||
});
|
||||
});
|
||||
@@ -17,8 +17,21 @@ describe('OHIF Study Viewer Page', function() {
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
// Can't use the native drag version as the element should be rerendered
|
||||
// cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)') //element to be dragged
|
||||
// .drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
const dataTransfer = new DataTransfer();
|
||||
|
||||
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)')
|
||||
.first()
|
||||
.trigger('mousedown', { which: 1, button: 0 })
|
||||
.trigger('dragstart', { dataTransfer })
|
||||
.trigger('drag', {});
|
||||
cy.get('.cornerstone-canvas')
|
||||
.trigger('mousemove', 'center')
|
||||
.trigger('dragover', { dataTransfer, force: true })
|
||||
.trigger('drop', { dataTransfer, force: true });
|
||||
|
||||
//const expectedText =
|
||||
// 'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
|
||||
@@ -54,20 +54,29 @@ Cypress.Commands.add('openStudy', PatientName => {
|
||||
.click({ force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add('checkStudyRouteInViewer', StudyInstanceUID => {
|
||||
cy.location('pathname').then($url => {
|
||||
cy.log($url);
|
||||
if ($url == 'blank' || !$url.includes(`/basic-test/${StudyInstanceUID}`)) {
|
||||
cy.openStudyInViewer(StudyInstanceUID);
|
||||
cy.waitDicomImage();
|
||||
cy.wait(2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
Cypress.Commands.add(
|
||||
'checkStudyRouteInViewer',
|
||||
(StudyInstanceUID, otherParams = '') => {
|
||||
cy.location('pathname').then($url => {
|
||||
cy.log($url);
|
||||
if (
|
||||
$url == 'blank' ||
|
||||
!$url.includes(`/basic-test/${StudyInstanceUID}${otherParams}`)
|
||||
) {
|
||||
cy.openStudyInViewer(StudyInstanceUID, otherParams);
|
||||
cy.waitDicomImage();
|
||||
cy.wait(2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Cypress.Commands.add('openStudyInViewer', StudyInstanceUID => {
|
||||
cy.visit(`/basic-test?StudyInstanceUIDs=${StudyInstanceUID}`);
|
||||
});
|
||||
Cypress.Commands.add(
|
||||
'openStudyInViewer',
|
||||
(StudyInstanceUID, otherParams = '') => {
|
||||
cy.visit(`/basic-test?StudyInstanceUIDs=${StudyInstanceUID}${otherParams}`);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Command to search for a Modality and open the study.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
window.config = {
|
||||
// Activate the new HP mode....
|
||||
isNewHP: true,
|
||||
|
||||
routerBasename: '/',
|
||||
customizationService: [
|
||||
'@ohif/extension-default.customizationModule.datasources',
|
||||
@@ -59,25 +62,6 @@ window.config = {
|
||||
singlepart: 'bulkdata,video,pdf',
|
||||
},
|
||||
},
|
||||
{
|
||||
friendlyName: 'dcmjs DICOMWeb Server',
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'shared',
|
||||
configuration: {
|
||||
name: 'shared',
|
||||
qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
||||
wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: 'bulkdata,video,pdf',
|
||||
},
|
||||
},
|
||||
{
|
||||
friendlyName: 'E2E Test Data',
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
@@ -122,92 +106,7 @@ window.config = {
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
defaultDataSourceName: 'default',
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// {
|
||||
// commandName: 'previousViewportDisplaySet',
|
||||
// label: 'Previous Series',
|
||||
// keys: ['pagedown'],
|
||||
// },
|
||||
// {
|
||||
// commandName: 'nextViewportDisplaySet',
|
||||
// label: 'Next Series',
|
||||
// keys: ['pageup'],
|
||||
// },
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
|
||||
// Only list the unique hotkeys
|
||||
hotkeys: [],
|
||||
};
|
||||
+20
-12
@@ -5,7 +5,12 @@ import i18n from '@ohif/i18n';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import Compose from './routes/Mode/Compose';
|
||||
|
||||
import {
|
||||
ServicesManager,
|
||||
ExtensionManager,
|
||||
CommandsManager,
|
||||
HotkeysManager,
|
||||
} from '@ohif/core';
|
||||
import {
|
||||
DialogProvider,
|
||||
Modal,
|
||||
@@ -24,7 +29,10 @@ import createRoutes from './routes';
|
||||
import appInit from './appInit.js';
|
||||
import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes';
|
||||
|
||||
let commandsManager, extensionManager, servicesManager, hotkeysManager;
|
||||
let commandsManager: CommandsManager,
|
||||
extensionManager: ExtensionManager,
|
||||
servicesManager: ServicesManager,
|
||||
hotkeysManager: HotkeysManager;
|
||||
|
||||
function App({ config, defaultExtensions, defaultModes }) {
|
||||
const [init, setInit] = useState(null);
|
||||
@@ -59,12 +67,12 @@ function App({ config, defaultExtensions, defaultModes }) {
|
||||
} = appConfigState;
|
||||
|
||||
const {
|
||||
UIDialogService,
|
||||
uiDialogService,
|
||||
uiModalService,
|
||||
UINotificationService,
|
||||
UIViewportDialogService,
|
||||
ViewportGridService,
|
||||
CineService,
|
||||
uiNotificationService,
|
||||
uiViewportDialogService,
|
||||
viewportGridService,
|
||||
cineService,
|
||||
userAuthenticationService,
|
||||
customizationService,
|
||||
} = servicesManager.services;
|
||||
@@ -74,11 +82,11 @@ function App({ config, defaultExtensions, defaultModes }) {
|
||||
[UserAuthenticationProvider, { service: userAuthenticationService }],
|
||||
[I18nextProvider, { i18n }],
|
||||
[ThemeWrapper],
|
||||
[ViewportGridProvider, { service: ViewportGridService }],
|
||||
[ViewportDialogProvider, { service: UIViewportDialogService }],
|
||||
[CineProvider, { service: CineService }],
|
||||
[SnackbarProvider, { service: UINotificationService }],
|
||||
[DialogProvider, { service: UIDialogService }],
|
||||
[ViewportGridProvider, { service: viewportGridService }],
|
||||
[ViewportDialogProvider, { service: uiViewportDialogService }],
|
||||
[CineProvider, { service: cineService }],
|
||||
[SnackbarProvider, { service: uiNotificationService }],
|
||||
[DialogProvider, { service: uiDialogService }],
|
||||
[ModalProvider, { service: uiModalService, modal: Modal }],
|
||||
];
|
||||
const CombinedProviders = ({ children }) =>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
UIDialogService,
|
||||
UIViewportDialogService,
|
||||
MeasurementService,
|
||||
StateSyncService,
|
||||
DisplaySetService,
|
||||
ToolbarService,
|
||||
ViewportGridService,
|
||||
@@ -60,6 +61,7 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
CineService.REGISTRATION,
|
||||
UserAuthenticationService.REGISTRATION,
|
||||
PanelService.REGISTRATION,
|
||||
StateSyncService.REGISTRATION,
|
||||
]);
|
||||
|
||||
errorHandler.getHTTPErrorHandler = () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ServicesManager } from '@ohif/core';
|
||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||
import { utils } from '@ohif/core';
|
||||
import EmptyViewport from './EmptyViewport';
|
||||
@@ -22,11 +23,28 @@ const ORIENTATION_MAP = {
|
||||
},
|
||||
};
|
||||
|
||||
const createHpInfo = (protocol, stage, activeStudyUID) => {
|
||||
return {
|
||||
hangingProtocolId: protocol.id,
|
||||
stageId: stage.stageId,
|
||||
stageIdx: protocol.stages.findIndex(it => it === stage),
|
||||
activeStudyUID,
|
||||
};
|
||||
};
|
||||
|
||||
const compareViewportOptions = (opts1, opts2) => {
|
||||
if ((opts1.viewportType || 'stack') != opts2.viewportType) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function ViewerViewportGrid(props) {
|
||||
const { servicesManager, viewportComponents, dataSource } = props;
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
const { numCols, numRows, activeViewportIndex, viewports } = viewportGrid;
|
||||
const { layout, activeViewportIndex, viewports } = viewportGrid;
|
||||
const { numCols, numRows } = layout;
|
||||
|
||||
// TODO -> Need some way of selecting which displaySets hit the viewports.
|
||||
const {
|
||||
@@ -34,124 +52,77 @@ function ViewerViewportGrid(props) {
|
||||
measurementService,
|
||||
hangingProtocolService,
|
||||
uiNotificationService,
|
||||
} = servicesManager.services;
|
||||
} = (servicesManager as ServicesManager).services;
|
||||
|
||||
/**
|
||||
* This callback runs only after displaySets have changed (created and added or modified)
|
||||
* This callback runs after the viewports structure has changed in any way.
|
||||
* On initial display, that means if it has changed by applying a HangingProtocol,
|
||||
* while subsequently it may mean by changing the stage or by manually adjusting
|
||||
* the layout.
|
||||
|
||||
*/
|
||||
const updateDisplaySetsForViewports = useCallback(
|
||||
availableDisplaySets => {
|
||||
if (!availableDisplaySets.length) {
|
||||
const updateDisplaySetsFromProtocol = (
|
||||
protocol,
|
||||
stage,
|
||||
activeStudyUID,
|
||||
viewportMatchDetails
|
||||
) => {
|
||||
const availableDisplaySets = displaySetService.getActiveDisplaySets();
|
||||
|
||||
if (!availableDisplaySets.length) {
|
||||
console.log('No available display sets', availableDisplaySets);
|
||||
return;
|
||||
}
|
||||
|
||||
// Match each viewport individually
|
||||
const { layoutType } = stage.viewportStructure;
|
||||
const stageProps = stage.viewportStructure.properties;
|
||||
const { columns: numCols, rows: numRows, layoutOptions = [] } = stageProps;
|
||||
|
||||
/**
|
||||
* This find or create viewport uses the hanging protocol results to
|
||||
* specify the viewport match details, which specifies the size and
|
||||
* setup of the various viewports.
|
||||
*/
|
||||
const findOrCreateViewport = viewportIndex => {
|
||||
const details = viewportMatchDetails.get(viewportIndex);
|
||||
if (!details) {
|
||||
console.log('No match details for viewport', viewportIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
viewportMatchDetails,
|
||||
hpAlreadyApplied,
|
||||
} = hangingProtocolService.getMatchDetails();
|
||||
|
||||
if (!viewportMatchDetails.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
// if the current viewport is empty/blank
|
||||
if (viewportDisplaySetUIDs.length === 0) {
|
||||
blankViewportIndices.push(viewportIndex);
|
||||
} else {
|
||||
gridDisplaySetUIDs.push(...viewportDisplaySetUIDs);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const { displaySetsInfo, viewportOptions } = viewportMatchDetails.get(
|
||||
viewportIndex
|
||||
);
|
||||
|
||||
const displaySetUIDsToHang = [];
|
||||
const displaySetUIDsToHangOptions = [];
|
||||
displaySetsInfo.forEach(
|
||||
({ displaySetInstanceUID, displaySetOptions }) => {
|
||||
if (!displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
const { displaySetsInfo, viewportOptions } = details;
|
||||
const displaySetUIDsToHang = [];
|
||||
const displaySetUIDsToHangOptions = [];
|
||||
|
||||
displaySetsInfo.forEach(
|
||||
({ displaySetInstanceUID, displaySetOptions }) => {
|
||||
if (displaySetInstanceUID) {
|
||||
displaySetUIDsToHang.push(displaySetInstanceUID);
|
||||
displaySetUIDsToHangOptions.push(displaySetOptions);
|
||||
}
|
||||
);
|
||||
|
||||
gridDisplaySetUIDs.push(...displaySetUIDsToHang);
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportIndex: viewportIndex,
|
||||
displaySetInstanceUIDs: displaySetUIDsToHang,
|
||||
viewportOptions,
|
||||
displaySetOptions: displaySetUIDsToHangOptions,
|
||||
});
|
||||
|
||||
// During setting displaySets for viewport, we need to update the hanging protocol
|
||||
// but some viewports contain more than one display set (fusion), and their displaySet
|
||||
// will not be available at the time of setting displaySets for viewport. So we need to
|
||||
// update the hanging protocol after making sure all the matched display sets are available
|
||||
// and set on the viewport
|
||||
if (displaySetUIDsToHang.length === displaySetsInfo.length) {
|
||||
// The following will set the viewportsDisplaySetsMatched state
|
||||
|
||||
const suppressEvent = true;
|
||||
const applied = true;
|
||||
hangingProtocolService.setHangingProtocolAppliedForViewport(
|
||||
viewportIndex,
|
||||
applied,
|
||||
suppressEvent
|
||||
);
|
||||
displaySetUIDsToHangOptions.push(displaySetOptions);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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 &&
|
||||
['SEG', 'SR', 'RTSTRUCT'].indexOf(displaySet.Modality) === -1
|
||||
);
|
||||
return {
|
||||
displaySetInstanceUIDs: displaySetUIDsToHang,
|
||||
displaySetOptions: displaySetUIDsToHangOptions,
|
||||
viewportOptions: {
|
||||
...viewportOptions,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (displaySetsNotInGrid.length > 0) {
|
||||
const displaySetUIDToAdd =
|
||||
displaySetsNotInGrid[0].displaySetInstanceUID;
|
||||
gridDisplaySetUIDs.push(displaySetUIDToAdd);
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportIndex: blankVPIndex,
|
||||
displaySetInstanceUIDs: [displaySetUIDToAdd],
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[viewportGrid, numRows, numCols]
|
||||
);
|
||||
viewportGridService.setLayout({
|
||||
numRows,
|
||||
numCols,
|
||||
layoutType,
|
||||
layoutOptions,
|
||||
hpInfo: createHpInfo(protocol, stage, activeStudyUID),
|
||||
findOrCreateViewport,
|
||||
});
|
||||
};
|
||||
|
||||
const _getUpdatedViewports = useCallback(
|
||||
(viewportIndex, displaySetInstanceUID) => {
|
||||
@@ -177,22 +148,17 @@ function ViewerViewportGrid(props) {
|
||||
[hangingProtocolService, uiNotificationService]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const displaySets = displaySetService.getActiveDisplaySets();
|
||||
updateDisplaySetsForViewports(displaySets);
|
||||
}, [numRows, numCols]);
|
||||
|
||||
// Layout change based on hanging protocols
|
||||
// Using Hanging protocol engine to match the displaySets
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = hangingProtocolService.subscribe(
|
||||
hangingProtocolService.EVENTS.NEW_LAYOUT,
|
||||
({ layoutType, numRows, numCols, layoutOptions }) => {
|
||||
viewportGridService.setLayout({
|
||||
numRows,
|
||||
numCols,
|
||||
layoutType,
|
||||
layoutOptions,
|
||||
});
|
||||
hangingProtocolService.EVENTS.PROTOCOL_CHANGED,
|
||||
({ protocol, stage, activeStudyUID, viewportMatchDetails }) => {
|
||||
updateDisplaySetsFromProtocol(
|
||||
protocol,
|
||||
stage,
|
||||
activeStudyUID,
|
||||
viewportMatchDetails
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -201,35 +167,6 @@ function ViewerViewportGrid(props) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Using Hanging protocol engine to match the displaySets
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = hangingProtocolService.subscribe(
|
||||
hangingProtocolService.EVENTS.PROTOCOL_CHANGED,
|
||||
() => {
|
||||
const displaySets = displaySetService.getActiveDisplaySets();
|
||||
updateDisplaySetsForViewports(displaySets);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [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,
|
||||
@@ -366,11 +303,15 @@ function ViewerViewportGrid(props) {
|
||||
const getViewportPanes = useCallback(() => {
|
||||
const viewportPanes = [];
|
||||
|
||||
const numViewports = viewportGridService.getNumViewportPanes();
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
const numViewportPanes = viewportGridService.getNumViewportPanes();
|
||||
for (let i = 0; i < numViewportPanes; i++) {
|
||||
const viewportIndex = i;
|
||||
const isActive = activeViewportIndex === viewportIndex;
|
||||
const paneMetadata = viewports[i] || {};
|
||||
const viewportId = paneMetadata.viewportId || `viewport-${i}`;
|
||||
if (!paneMetadata.viewportId) {
|
||||
paneMetadata.viewportId = viewportId;
|
||||
}
|
||||
const {
|
||||
displaySetInstanceUIDs,
|
||||
viewportOptions,
|
||||
@@ -420,7 +361,7 @@ function ViewerViewportGrid(props) {
|
||||
|
||||
viewportPanes[i] = (
|
||||
<ViewportPane
|
||||
key={viewportIndex}
|
||||
key={viewportId}
|
||||
acceptDropsFor="displayset"
|
||||
onDrop={onDropHandler.bind(null, viewportIndex)}
|
||||
onInteraction={onInteractionHandler}
|
||||
@@ -434,6 +375,7 @@ function ViewerViewportGrid(props) {
|
||||
isActive={isActive}
|
||||
>
|
||||
<div
|
||||
data-cy="viewport-pane"
|
||||
className={classNames('h-full w-full flex flex-col', {
|
||||
'pointer-events-none': !isActive,
|
||||
})}
|
||||
@@ -441,7 +383,7 @@ function ViewerViewportGrid(props) {
|
||||
<ViewportComponent
|
||||
displaySets={displaySets}
|
||||
viewportIndex={viewportIndex}
|
||||
viewportLabel={numViewports > 1 ? viewportLabel : ''}
|
||||
viewportLabel={viewports.length > 1 ? viewportLabel : ''}
|
||||
dataSource={dataSource}
|
||||
viewportOptions={viewportOptions}
|
||||
displaySetOptions={displaySetOptions}
|
||||
@@ -456,7 +398,7 @@ function ViewerViewportGrid(props) {
|
||||
}, [viewports, activeViewportIndex, viewportComponents, dataSource]);
|
||||
|
||||
/**
|
||||
* Loading indicator until numCols and numRows are gotten from the hangingProtocolService
|
||||
* Loading indicator until numCols and numRows are gotten from the HangingProtocolService
|
||||
*/
|
||||
if (!numRows || !numCols) {
|
||||
return null;
|
||||
@@ -472,6 +414,7 @@ function ViewerViewportGrid(props) {
|
||||
|
||||
ViewerViewportGrid.propTypes = {
|
||||
viewportComponents: PropTypes.array.isRequired,
|
||||
servicesManager: PropTypes.instanceOf(ServicesManager),
|
||||
};
|
||||
|
||||
ViewerViewportGrid.defaultProps = {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
|
||||
import { useQuery, useSearchParams } from '@hooks';
|
||||
import ViewportGrid from '@components/ViewportGrid';
|
||||
import Compose from './Compose';
|
||||
import getStudies from './studiesList';
|
||||
|
||||
/**
|
||||
* Initialize the route.
|
||||
@@ -64,21 +65,9 @@ function defaultRouteInit(
|
||||
return;
|
||||
}
|
||||
|
||||
const studyMap = {};
|
||||
// Gets the studies list to use
|
||||
const studies = getStudies(studyInstanceUIDs, displaySets);
|
||||
|
||||
// Prior studies don't quite work properly yet, but the studies list
|
||||
// is at least being generated and passed in.
|
||||
const studies = displaySets.reduce((prev, curr) => {
|
||||
const { StudyInstanceUID } = curr;
|
||||
if (!studyMap[StudyInstanceUID]) {
|
||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
studyMap[StudyInstanceUID] = study;
|
||||
prev.push(study);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
// The assumption is that the display set at position 0 is the first
|
||||
// study being displayed, and is thus the "active" study.
|
||||
const activeStudy = studies[0];
|
||||
|
||||
@@ -133,10 +122,8 @@ export default function ModeRoute({
|
||||
|
||||
extensionManager.setActiveDataSource(dataSourceName);
|
||||
|
||||
const dataSources = extensionManager.getActiveDataSource();
|
||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||
|
||||
// Only handling one instance of the datasource type (E.g. one DICOMWeb server)
|
||||
const dataSource = dataSources[0];
|
||||
// Only handling one route per mode for now
|
||||
const route = mode.routes[0];
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { DicomMetadataStore, Types } from '@ohif/core';
|
||||
|
||||
type StudyMetadata = Types.StudyMetadata;
|
||||
|
||||
/**
|
||||
* Compare function for sorting
|
||||
*
|
||||
* @param a - some simple value (string, number, timestamp)
|
||||
* @param b - some simple value
|
||||
* @param defaultCompare - default return value as a fallback when a===b
|
||||
* @returns - compare a and b, returning 1 if a<b -1 if a>b and defaultCompare otherwise
|
||||
*/
|
||||
const compare = (a, b, defaultCompare = 0): number => {
|
||||
if (a === b) return defaultCompare;
|
||||
if (a < b) return 1;
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* The studies from display sets gets the studies in study date
|
||||
* order or in study instance UID order - not very useful, but
|
||||
* if not specifically specified then at least making it consistent is useful.
|
||||
*/
|
||||
const getStudiesfromDisplaySets = (displaysets): StudyMetadata[] => {
|
||||
const studyMap = {};
|
||||
|
||||
const ret = displaySets.reduce((prev, curr) => {
|
||||
const { StudyInstanceUID } = curr;
|
||||
if (!studyMap[StudyInstanceUID]) {
|
||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||
studyMap[StudyInstanceUID] = study;
|
||||
prev.push(study);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
// Return the sorted studies, first on study date and second on study instance UID
|
||||
ret.sort((a, b) => {
|
||||
return compare(
|
||||
a.StudyDate,
|
||||
b.StudyDate,
|
||||
compare(a.StudyInstanceUID, b.StudyInstanceUID)
|
||||
);
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* The studies retrieve from the Uids is faster and gets the studies
|
||||
* in the original order, as specified.
|
||||
*/
|
||||
const getStudiesFromUIDs = (studyUids: string[]): StudyMetadata[] => {
|
||||
if (!studyUids?.length) return;
|
||||
return studyUids.map(uid => DicomMetadataStore.getStudy(uid));
|
||||
};
|
||||
|
||||
/** Gets the array of studies */
|
||||
const getStudies = (studyUids?: string[], displaySets): StudyMetadata[] => {
|
||||
return (
|
||||
getStudiesFromUIDs(studyUids) || getStudiesfromDisplaySets(displaySets)
|
||||
);
|
||||
};
|
||||
|
||||
export default getStudies;
|
||||
|
||||
export { getStudies, getStudiesFromUIDs, getStudiesfromDisplaySets, compare };
|
||||
@@ -501,11 +501,9 @@ const defaultFilterValues = {
|
||||
|
||||
function _tryParseInt(str, defaultValue) {
|
||||
let retValue = defaultValue;
|
||||
if (str != null) {
|
||||
if (str.length > 0) {
|
||||
if (!isNaN(str)) {
|
||||
retValue = parseInt(str);
|
||||
}
|
||||
if (str && str.length > 0) {
|
||||
if (!isNaN(str)) {
|
||||
retValue = parseInt(str);
|
||||
}
|
||||
}
|
||||
return retValue;
|
||||
|
||||
Reference in new issue
Block a user