feat(grid): remove viewportIndex and only rely on viewportId (#3591)

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
Alireza 2023-08-30 16:46:55 -04:00 committed by GitHub
parent 5dafac7c92
commit 4c6ff873e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1309 additions and 1123 deletions

View File

@ -121,7 +121,7 @@ const getSopClassHandlerModule = (/* ... */) => {
const getToolbarModule = () => {}; const getToolbarModule = () => {};
// displaySet, viewportIndex, dataSource // displaySet, dataSource
const getViewportModule = () => { const getViewportModule = () => {
const wrappedViewport = props => { const wrappedViewport = props => {
return ( return (

View File

@ -1,6 +1,6 @@
async function _hydrateRTDisplaySet({ async function _hydrateRTDisplaySet({
rtDisplaySet, rtDisplaySet,
viewportIndex, viewportId,
servicesManager, servicesManager,
}) { }) {
const { const {
@ -27,7 +27,7 @@ async function _hydrateRTDisplaySet({
const { viewports } = viewportGridService.getState(); const { viewports } = viewportGridService.getState();
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex, viewportId,
displaySetInstanceUID displaySetInstanceUID
); );
@ -39,8 +39,8 @@ async function _hydrateRTDisplaySet({
// is being displayed, if so we need to update the viewport to use volume viewport // is being displayed, if so we need to update the viewport to use volume viewport
// (if already is not using it) since Cornerstone3D currently only supports // (if already is not using it) since Cornerstone3D currently only supports
// volume viewport for segmentation // volume viewport for segmentation
viewports.forEach((viewport, index) => { viewports.forEach(viewport => {
if (index === viewportIndex) { if (viewport.viewportId === viewportId) {
return; return;
} }
@ -51,9 +51,16 @@ async function _hydrateRTDisplaySet({
if (shouldDisplaySeg) { if (shouldDisplaySeg) {
updatedViewports.push({ updatedViewports.push({
viewportIndex: index, viewportId: viewport.viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
viewportOptions: { viewportOptions: {
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
// so we need to re-render (force update via change of the needsRerendering) so that React
// does the diffing and decides we should render this again (although the id and element has not changed)
// so that the CornerstoneViewportService can decide whether to render the segmentation or not.
needsRerendering: true,
initialImageOptions: { initialImageOptions: {
preset: 'middle', preset: 'middle',
}, },

View File

@ -10,17 +10,14 @@ const RESPONSE = {
function promptHydrateRT({ function promptHydrateRT({
servicesManager, servicesManager,
rtDisplaySet, rtDisplaySet,
viewportIndex, viewportId,
toolGroupId = 'default', toolGroupId = 'default',
preHydrateCallbacks, preHydrateCallbacks,
}) { }) {
const { uiViewportDialogService } = servicesManager.services; const { uiViewportDialogService } = servicesManager.services;
return new Promise(async function(resolve, reject) { return new Promise(async function(resolve, reject) {
const promptResult = await _askHydrate( const promptResult = await _askHydrate(uiViewportDialogService, viewportId);
uiViewportDialogService,
viewportIndex
);
if (promptResult === RESPONSE.HYDRATE_SEG) { if (promptResult === RESPONSE.HYDRATE_SEG) {
preHydrateCallbacks?.forEach(callback => { preHydrateCallbacks?.forEach(callback => {
@ -29,7 +26,7 @@ function promptHydrateRT({
const isHydrated = await hydrateRTDisplaySet({ const isHydrated = await hydrateRTDisplaySet({
rtDisplaySet, rtDisplaySet,
viewportIndex, viewportId,
toolGroupId, toolGroupId,
servicesManager, servicesManager,
}); });
@ -39,7 +36,7 @@ function promptHydrateRT({
}); });
} }
function _askHydrate(uiViewportDialogService, viewportIndex) { function _askHydrate(uiViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = 'Do you want to open this Segmentation?'; const message = 'Do you want to open this Segmentation?';
const actions = [ const actions = [
@ -60,7 +57,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) {
}; };
uiViewportDialogService.show({ uiViewportDialogService.show({
viewportIndex, viewportId,
type: 'info', type: 'info',
message, message,
actions, actions,

View File

@ -21,7 +21,6 @@ function OHIFCornerstoneRTViewport(props) {
children, children,
displaySets, displaySets,
viewportOptions, viewportOptions,
viewportIndex,
viewportLabel, viewportLabel,
servicesManager, servicesManager,
extensionManager, extensionManager,
@ -36,7 +35,9 @@ function OHIFCornerstoneRTViewport(props) {
customizationService, customizationService,
} = servicesManager.services; } = servicesManager.services;
const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportIndex}`; const viewportId = viewportOptions.viewportId;
const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`;
// RT viewport will always have a single display set // RT viewport will always have a single display set
if (displaySets.length > 1) { if (displaySets.length > 1) {
@ -67,7 +68,7 @@ function OHIFCornerstoneRTViewport(props) {
// refs // refs
const referencedDisplaySetRef = useRef(null); const referencedDisplaySetRef = useRef(null);
const { viewports, activeViewportIndex } = viewportGrid; const { viewports, activeViewportId } = viewportGrid;
const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet(); const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet();
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
@ -93,9 +94,9 @@ function OHIFCornerstoneRTViewport(props) {
}; };
const storePresentationState = useCallback(() => { const storePresentationState = useCallback(() => {
viewportGrid?.viewports.forEach(({ viewportIndex }) => { viewportGrid?.viewports.forEach(({ viewportId }) => {
commandsManager.runCommand('storePresentation', { commandsManager.runCommand('storePresentation', {
viewportIndex, viewportId,
}); });
}); });
}, [viewportGrid]); }, [viewportGrid]);
@ -124,7 +125,7 @@ function OHIFCornerstoneRTViewport(props) {
onElementDisabled={onElementDisabled} onElementDisabled={onElementDisabled}
></Component> ></Component>
); );
}, [viewportIndex, rtDisplaySet, toolGroupId]); }, [viewportId, rtDisplaySet, toolGroupId]);
const onSegmentChange = useCallback( const onSegmentChange = useCallback(
direction => { direction => {
@ -162,7 +163,7 @@ function OHIFCornerstoneRTViewport(props) {
promptHydrateRT({ promptHydrateRT({
servicesManager, servicesManager,
viewportIndex, viewportId,
rtDisplaySet, rtDisplaySet,
preHydrateCallbacks: [storePresentationState], preHydrateCallbacks: [storePresentationState],
}).then(isHydrated => { }).then(isHydrated => {
@ -170,7 +171,7 @@ function OHIFCornerstoneRTViewport(props) {
setIsHydrated(true); setIsHydrated(true);
} }
}); });
}, [servicesManager, viewportIndex, rtDisplaySet, rtIsLoading]); }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);
useEffect(() => { useEffect(() => {
const { unsubscribe } = segmentationService.subscribe( const { unsubscribe } = segmentationService.subscribe(
@ -222,12 +223,12 @@ function OHIFCornerstoneRTViewport(props) {
const onDisplaySetsRemovedSubscription = displaySetService.subscribe( const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_REMOVED, displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
({ displaySetInstanceUIDs }) => { ({ displaySetInstanceUIDs }) => {
const activeViewport = viewports[activeViewportIndex]; const activeViewport = viewports.get(activeViewportId);
if ( if (
displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)
) { ) {
viewportGridService.setDisplaySetsForViewport({ viewportGridService.setDisplaySetsForViewport({
viewportIndex: activeViewportIndex, viewportId: activeViewportId,
displaySetInstanceUIDs: [], displaySetInstanceUIDs: [],
}); });
} }
@ -292,7 +293,7 @@ function OHIFCornerstoneRTViewport(props) {
return ( return (
child && child &&
React.cloneElement(child, { React.cloneElement(child, {
viewportIndex, viewportId,
key: index, key: index,
}) })
); );
@ -322,7 +323,7 @@ function OHIFCornerstoneRTViewport(props) {
storePresentationState(); storePresentationState();
const isHydrated = await _hydrateRTDisplaySet({ const isHydrated = await _hydrateRTDisplaySet({
rtDisplaySet, rtDisplaySet,
viewportIndex, viewportId,
servicesManager, servicesManager,
}); });
@ -384,7 +385,7 @@ function OHIFCornerstoneRTViewport(props) {
OHIFCornerstoneRTViewport.propTypes = { OHIFCornerstoneRTViewport.propTypes = {
displaySets: PropTypes.arrayOf(PropTypes.object), displaySets: PropTypes.arrayOf(PropTypes.object),
viewportIndex: PropTypes.number.isRequired, viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object, dataSource: PropTypes.object,
children: PropTypes.node, children: PropTypes.node,
customProps: PropTypes.object, customProps: PropTypes.object,

View File

@ -271,7 +271,6 @@ PanelSegmentation.propTypes = {
commandsManager: PropTypes.shape({ commandsManager: PropTypes.shape({
runCommand: PropTypes.func.isRequired, runCommand: PropTypes.func.isRequired,
}), }),
appConfig: PropTypes.object.isRequired,
servicesManager: PropTypes.shape({ servicesManager: PropTypes.shape({
services: PropTypes.shape({ services: PropTypes.shape({
segmentationService: PropTypes.shape({ segmentationService: PropTypes.shape({

View File

@ -1,6 +1,6 @@
async function _hydrateSEGDisplaySet({ async function _hydrateSEGDisplaySet({
segDisplaySet, segDisplaySet,
viewportIndex, viewportId: targetViewportId,
servicesManager, servicesManager,
}) { }) {
const { const {
@ -27,7 +27,7 @@ async function _hydrateSEGDisplaySet({
const { viewports } = viewportGridService.getState(); const { viewports } = viewportGridService.getState();
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex, targetViewportId,
displaySetInstanceUID displaySetInstanceUID
); );
@ -37,8 +37,8 @@ async function _hydrateSEGDisplaySet({
// is being displayed, if so we need to update the viewport to use volume viewport // is being displayed, if so we need to update the viewport to use volume viewport
// (if already is not using it) since Cornerstone3D currently only supports // (if already is not using it) since Cornerstone3D currently only supports
// volume viewport for segmentation // volume viewport for segmentation
viewports.forEach((viewport, index) => { viewports.forEach((viewport, viewportId) => {
if (index === viewportIndex) { if (targetViewportId === viewportId) {
return; return;
} }
@ -49,9 +49,16 @@ async function _hydrateSEGDisplaySet({
if (shouldDisplaySeg) { if (shouldDisplaySeg) {
updatedViewports.push({ updatedViewports.push({
viewportIndex: index, viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
viewportOptions: { viewportOptions: {
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
// so we need to re-render (force update via change of the needsRerendering) so that React
// does the diffing and decides we should render this again (although the id and element has not changed)
// so that the CornerstoneViewportService can decide whether to render the segmentation or not.
needsRerendering: true,
initialImageOptions: { initialImageOptions: {
preset: 'middle', preset: 'middle',
}, },

View File

@ -10,16 +10,13 @@ const RESPONSE = {
function promptHydrateSEG({ function promptHydrateSEG({
servicesManager, servicesManager,
segDisplaySet, segDisplaySet,
viewportIndex, viewportId,
preHydrateCallbacks, preHydrateCallbacks,
}) { }) {
const { uiViewportDialogService } = servicesManager.services; const { uiViewportDialogService } = servicesManager.services;
return new Promise(async function(resolve, reject) { return new Promise(async function(resolve, reject) {
const promptResult = await _askHydrate( const promptResult = await _askHydrate(uiViewportDialogService, viewportId);
uiViewportDialogService,
viewportIndex
);
if (promptResult === RESPONSE.HYDRATE_SEG) { if (promptResult === RESPONSE.HYDRATE_SEG) {
preHydrateCallbacks?.forEach(callback => { preHydrateCallbacks?.forEach(callback => {
@ -28,7 +25,7 @@ function promptHydrateSEG({
const isHydrated = await hydrateSEGDisplaySet({ const isHydrated = await hydrateSEGDisplaySet({
segDisplaySet, segDisplaySet,
viewportIndex, viewportId,
servicesManager, servicesManager,
}); });
@ -37,7 +34,7 @@ function promptHydrateSEG({
}); });
} }
function _askHydrate(uiViewportDialogService, viewportIndex) { function _askHydrate(uiViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = 'Do you want to open this Segmentation?'; const message = 'Do you want to open this Segmentation?';
const actions = [ const actions = [
@ -58,7 +55,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) {
}; };
uiViewportDialogService.show({ uiViewportDialogService.show({
viewportIndex, viewportId,
type: 'info', type: 'info',
message, message,
actions, actions,

View File

@ -20,7 +20,6 @@ function OHIFCornerstoneSEGViewport(props) {
children, children,
displaySets, displaySets,
viewportOptions, viewportOptions,
viewportIndex,
viewportLabel, viewportLabel,
servicesManager, servicesManager,
extensionManager, extensionManager,
@ -28,6 +27,7 @@ function OHIFCornerstoneSEGViewport(props) {
} = props; } = props;
const { t } = useTranslation('SEGViewport'); const { t } = useTranslation('SEGViewport');
const viewportId = viewportOptions.viewportId;
const { const {
displaySetService, displaySetService,
@ -37,7 +37,7 @@ function OHIFCornerstoneSEGViewport(props) {
customizationService, customizationService,
} = servicesManager.services; } = servicesManager.services;
const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`; const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`;
// SEG viewport will always have a single display set // SEG viewport will always have a single display set
if (displaySets.length > 1) { if (displaySets.length > 1) {
@ -67,7 +67,7 @@ function OHIFCornerstoneSEGViewport(props) {
// refs // refs
const referencedDisplaySetRef = useRef(null); const referencedDisplaySetRef = useRef(null);
const { viewports, activeViewportIndex } = viewportGrid; const { viewports, activeViewportId } = viewportGrid;
const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet(); const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet();
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
@ -94,9 +94,9 @@ function OHIFCornerstoneSEGViewport(props) {
}; };
const storePresentationState = useCallback(() => { const storePresentationState = useCallback(() => {
viewportGrid?.viewports.forEach(({ viewportIndex }) => { viewportGrid?.viewports.forEach(({ viewportId }) => {
commandsManager.runCommand('storePresentation', { commandsManager.runCommand('storePresentation', {
viewportIndex, viewportId,
}); });
}); });
}, [viewportGrid]); }, [viewportGrid]);
@ -126,7 +126,7 @@ function OHIFCornerstoneSEGViewport(props) {
// initialImageIndex={initialImageIndex} // initialImageIndex={initialImageIndex}
></Component> ></Component>
); );
}, [viewportIndex, segDisplaySet, toolGroupId]); }, [viewportId, segDisplaySet, toolGroupId]);
const onSegmentChange = useCallback( const onSegmentChange = useCallback(
direction => { direction => {
@ -165,7 +165,7 @@ function OHIFCornerstoneSEGViewport(props) {
promptHydrateSEG({ promptHydrateSEG({
servicesManager, servicesManager,
viewportIndex, viewportId,
segDisplaySet, segDisplaySet,
preHydrateCallbacks: [storePresentationState], preHydrateCallbacks: [storePresentationState],
}).then(isHydrated => { }).then(isHydrated => {
@ -173,7 +173,7 @@ function OHIFCornerstoneSEGViewport(props) {
setIsHydrated(true); setIsHydrated(true);
} }
}); });
}, [servicesManager, viewportIndex, segDisplaySet, segIsLoading]); }, [servicesManager, viewportId, segDisplaySet, segIsLoading]);
useEffect(() => { useEffect(() => {
const { unsubscribe } = segmentationService.subscribe( const { unsubscribe } = segmentationService.subscribe(
@ -225,12 +225,12 @@ function OHIFCornerstoneSEGViewport(props) {
const onDisplaySetsRemovedSubscription = displaySetService.subscribe( const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_REMOVED, displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
({ displaySetInstanceUIDs }) => { ({ displaySetInstanceUIDs }) => {
const activeViewport = viewports[activeViewportIndex]; const activeViewport = viewports.get(activeViewportId);
if ( if (
displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)
) { ) {
viewportGridService.setDisplaySetsForViewport({ viewportGridService.setDisplaySetsForViewport({
viewportIndex: activeViewportIndex, viewportId: activeViewportId,
displaySetInstanceUIDs: [], displaySetInstanceUIDs: [],
}); });
} }
@ -296,7 +296,7 @@ function OHIFCornerstoneSEGViewport(props) {
return ( return (
child && child &&
React.cloneElement(child, { React.cloneElement(child, {
viewportIndex, viewportId,
key: index, key: index,
}) })
); );
@ -325,7 +325,7 @@ function OHIFCornerstoneSEGViewport(props) {
storePresentationState(); storePresentationState();
const isHydrated = await hydrateSEGDisplaySet({ const isHydrated = await hydrateSEGDisplaySet({
segDisplaySet, segDisplaySet,
viewportIndex, viewportId,
servicesManager, servicesManager,
}); });
@ -388,7 +388,7 @@ function OHIFCornerstoneSEGViewport(props) {
OHIFCornerstoneSEGViewport.propTypes = { OHIFCornerstoneSEGViewport.propTypes = {
displaySets: PropTypes.arrayOf(PropTypes.object), displaySets: PropTypes.arrayOf(PropTypes.object),
viewportIndex: PropTypes.number.isRequired, viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object, dataSource: PropTypes.object,
children: PropTypes.node, children: PropTypes.node,
customProps: PropTypes.object, customProps: PropTypes.object,

View File

@ -21,7 +21,6 @@ function OHIFCornerstoneSRViewport(props) {
children, children,
dataSource, dataSource,
displaySets, displaySets,
viewportIndex,
viewportLabel, viewportLabel,
viewportOptions, viewportOptions,
servicesManager, servicesManager,
@ -36,6 +35,8 @@ function OHIFCornerstoneSRViewport(props) {
measurementService, measurementService,
} = servicesManager.services; } = servicesManager.services;
const viewportId = viewportOptions.viewportId;
// SR viewport will always have a single display set // SR viewport will always have a single display set
if (displaySets.length > 1) { if (displaySets.length > 1) {
throw new Error('SR viewport should only have a single display set'); throw new Error('SR viewport should only have a single display set');
@ -54,7 +55,7 @@ function OHIFCornerstoneSRViewport(props) {
setReferencedDisplaySetMetadata, setReferencedDisplaySetMetadata,
] = useState(null); ] = useState(null);
const [element, setElement] = useState(null); const [element, setElement] = useState(null);
const { viewports, activeViewportIndex } = viewportGrid; const { viewports, activeViewportId } = viewportGrid;
// Optional hook into tracking extension, if present. // Optional hook into tracking extension, if present.
let trackedMeasurements; let trackedMeasurements;
@ -88,7 +89,7 @@ function OHIFCornerstoneSRViewport(props) {
if (displaySets.length) { if (displaySets.length) {
viewportGridService.setDisplaySetsForViewports([ viewportGridService.setDisplaySetsForViewports([
{ {
viewportIndex: activeViewportIndex, viewportId: activeViewportId,
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID], displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
}, },
]); ]);
@ -163,12 +164,8 @@ function OHIFCornerstoneSRViewport(props) {
// imageIdIndex will handle it by updating the viewport, but if they // imageIdIndex will handle it by updating the viewport, but if they
// are the same we just need to use measurementService to jump to the // are the same we just need to use measurementService to jump to the
// new measurement // new measurement
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);
const csViewport = cornerstoneViewportService.getCornerstoneViewport( const csViewport = cornerstoneViewportService.getCornerstoneViewport(
viewportInfo.getViewportId() viewportId
); );
const imageIds = csViewport.getImageIds(); const imageIds = csViewport.getImageIds();
@ -183,7 +180,7 @@ function OHIFCornerstoneSRViewport(props) {
} }
}); });
}, },
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex] [dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
); );
const getCornerstoneViewport = useCallback(() => { const getCornerstoneViewport = useCallback(() => {
@ -231,7 +228,7 @@ function OHIFCornerstoneSRViewport(props) {
isJumpToMeasurementDisabled={true} isJumpToMeasurementDisabled={true}
></Component> ></Component>
); );
}, [activeImageDisplaySetData, viewportIndex, measurementSelected]); }, [activeImageDisplaySetData, viewportId, measurementSelected]);
const onMeasurementChange = useCallback( const onMeasurementChange = useCallback(
direction => { direction => {
@ -269,12 +266,12 @@ function OHIFCornerstoneSRViewport(props) {
const onDisplaySetsRemovedSubscription = displaySetService.subscribe( const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_REMOVED, displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
({ displaySetInstanceUIDs }) => { ({ displaySetInstanceUIDs }) => {
const activeViewport = viewports[activeViewportIndex]; const activeViewport = viewports[activeViewportId];
if ( if (
displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)
) { ) {
viewportGridService.setDisplaySetsForViewport({ viewportGridService.setDisplaySetsForViewport({
viewportIndex: activeViewportIndex, viewportId: activeViewportId,
displaySetInstanceUIDs: [], displaySetInstanceUIDs: [],
}); });
} }
@ -341,7 +338,7 @@ function OHIFCornerstoneSRViewport(props) {
return ( return (
child && child &&
React.cloneElement(child, { React.cloneElement(child, {
viewportIndex, viewportId,
key: index, key: index,
}) })
); );
@ -373,7 +370,7 @@ function OHIFCornerstoneSRViewport(props) {
getStatusComponent={() => getStatusComponent={() =>
_getStatusComponent({ _getStatusComponent({
srDisplaySet, srDisplaySet,
viewportIndex, viewportId,
isTracked: false, isTracked: false,
isRehydratable: srDisplaySet.isRehydratable, isRehydratable: srDisplaySet.isRehydratable,
isLocked, isLocked,
@ -413,7 +410,7 @@ function OHIFCornerstoneSRViewport(props) {
OHIFCornerstoneSRViewport.propTypes = { OHIFCornerstoneSRViewport.propTypes = {
displaySets: PropTypes.arrayOf(PropTypes.object), displaySets: PropTypes.arrayOf(PropTypes.object),
viewportIndex: PropTypes.number.isRequired, viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object, dataSource: PropTypes.object,
children: PropTypes.node, children: PropTypes.node,
viewportLabel: PropTypes.string, viewportLabel: PropTypes.string,
@ -462,7 +459,7 @@ async function _getViewportReferencedDisplaySetData(
function _getStatusComponent({ function _getStatusComponent({
srDisplaySet, srDisplaySet,
viewportIndex, viewportId,
isRehydratable, isRehydratable,
isLocked, isLocked,
sendTrackedMeasurementsEvent, sendTrackedMeasurementsEvent,
@ -470,7 +467,7 @@ function _getStatusComponent({
const handleMouseUp = () => { const handleMouseUp = () => {
sendTrackedMeasurementsEvent('HYDRATE_SR', { sendTrackedMeasurementsEvent('HYDRATE_SR', {
displaySetInstanceUID: srDisplaySet.displaySetInstanceUID, displaySetInstanceUID: srDisplaySet.displaySetInstanceUID,
viewportIndex, viewportId,
}); });
}; };

View File

@ -63,6 +63,10 @@ function areEqual(prevProps, nextProps) {
return false; return false;
} }
if (nextProps.viewportOptions.needsRerendering) {
return false;
}
const prevDisplaySets = prevProps.displaySets; const prevDisplaySets = prevProps.displaySets;
const nextDisplaySets = nextProps.displaySets; const nextDisplaySets = nextProps.displaySets;
@ -107,7 +111,6 @@ function areEqual(prevProps, nextProps) {
// Then we don't need to worry about the re-renders if the props change. // Then we don't need to worry about the re-renders if the props change.
const OHIFCornerstoneViewport = React.memo(props => { const OHIFCornerstoneViewport = React.memo(props => {
const { const {
viewportIndex,
displaySets, displaySets,
dataSource, dataSource,
viewportOptions, viewportOptions,
@ -123,6 +126,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
initialImageIndex, initialImageIndex,
} = props; } = props;
const viewportId = viewportOptions.viewportId;
const [scrollbarHeight, setScrollbarHeight] = useState('100px'); const [scrollbarHeight, setScrollbarHeight] = useState('100px');
const [enabledVPElement, setEnabledVPElement] = useState(null); const [enabledVPElement, setEnabledVPElement] = useState(null);
const elementRef = useRef(); const elementRef = useRef();
@ -154,27 +158,24 @@ const OHIFCornerstoneViewport = React.memo(props => {
} }
}, [elementRef]); }, [elementRef]);
const cleanUpServices = useCallback(() => { const cleanUpServices = useCallback(
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( viewportInfo => {
viewportIndex const renderingEngineId = viewportInfo.getRenderingEngineId();
); const syncGroups = viewportInfo.getSyncGroups();
if (!viewportInfo) { toolGroupService.removeViewportFromToolGroup(
return; viewportId,
} renderingEngineId
);
const viewportId = viewportInfo.getViewportId(); syncGroupService.removeViewportFromSyncGroup(
const renderingEngineId = viewportInfo.getRenderingEngineId(); viewportId,
const syncGroups = viewportInfo.getSyncGroups(); renderingEngineId,
syncGroups
toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); );
},
syncGroupService.removeViewportFromSyncGroup( [viewportId]
viewportId, );
renderingEngineId,
syncGroups
);
}, [viewportIndex, viewportOptions.viewportId]);
const elementEnabledHandler = useCallback( const elementEnabledHandler = useCallback(
evt => { evt => {
@ -187,9 +188,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
const viewportInfo = cornerstoneViewportService.getViewportInfo( const viewportInfo = cornerstoneViewportService.getViewportInfo(
viewportId viewportId
); );
const viewportIndex = viewportInfo.getViewportIndex(); setEnabledElement(viewportId, element);
setEnabledElement(viewportIndex, element);
setEnabledVPElement(element); setEnabledVPElement(element);
const renderingEngineId = viewportInfo.getRenderingEngineId(); const renderingEngineId = viewportInfo.getRenderingEngineId();
@ -212,16 +211,12 @@ const OHIFCornerstoneViewport = React.memo(props => {
onElementEnabled(evt); onElementEnabled(evt);
} }
}, },
[viewportIndex, onElementEnabled, toolGroupService] [viewportId, onElementEnabled, toolGroupService]
); );
// disable the element upon unmounting // disable the element upon unmounting
useEffect(() => { useEffect(() => {
cornerstoneViewportService.enableViewport( cornerstoneViewportService.enableViewport(viewportId, elementRef.current);
viewportIndex,
viewportOptions,
elementRef.current
);
eventTarget.addEventListener( eventTarget.addEventListener(
Enums.Events.ELEMENT_ENABLED, Enums.Events.ELEMENT_ENABLED,
@ -231,17 +226,16 @@ const OHIFCornerstoneViewport = React.memo(props => {
setImageScrollBarHeight(); setImageScrollBarHeight();
return () => { return () => {
commandsManager.runCommand('storePresentation', { const viewportInfo = cornerstoneViewportService.getViewportInfo(
viewportIndex, viewportId
});
cleanUpServices();
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
); );
cornerstoneViewportService.disableElement(viewportIndex); if (!viewportInfo) {
return;
}
cleanUpServices(viewportInfo);
cornerstoneViewportService.storePresentation({ viewportId });
if (onElementDisabled) { if (onElementDisabled) {
onElementDisabled(viewportInfo); onElementDisabled(viewportInfo);
@ -273,8 +267,8 @@ const OHIFCornerstoneViewport = React.memo(props => {
return; return;
} }
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( const viewportInfo = cornerstoneViewportService.getViewportInfo(
viewportIndex viewportId
); );
if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) { if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) {
@ -288,7 +282,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
const keepCamera = true; const keepCamera = true;
cornerstoneViewportService.updateViewport( cornerstoneViewportService.updateViewport(
viewportIndex, viewportId,
newViewportData, newViewportData,
keepCamera keepCamera
); );
@ -298,7 +292,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
return () => { return () => {
unsubscribe(); unsubscribe();
}; };
}, [viewportIndex]); }, [viewportId]);
useEffect(() => { useEffect(() => {
// handle the default viewportType to be stack // handle the default viewportType to be stack
@ -329,15 +323,25 @@ const OHIFCornerstoneViewport = React.memo(props => {
lutPresentationStore[presentationIds?.lutPresentationId], lutPresentationStore[presentationIds?.lutPresentationId],
}; };
let measurement; let measurement;
if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) { if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
measurement = cacheJumpToMeasurementEvent.measurement; measurement = cacheJumpToMeasurementEvent.measurement;
// Delete the position presentation so that viewport navigates direct // Delete the position presentation so that viewport navigates direct
presentations.positionPresentation = null; presentations.positionPresentation = null;
cacheJumpToMeasurementEvent = null; cacheJumpToMeasurementEvent = null;
} }
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
// so we need to re-render (force update via change of the needsRerendering) so that React
// does the diffing and decides we should render this again (although the id and element has not changed)
// so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off.
if (viewportOptions.needsRerendering) {
viewportOptions.needsRerendering = false;
}
cornerstoneViewportService.setViewportData( cornerstoneViewportService.setViewportData(
viewportIndex, viewportId,
viewportData, viewportData,
viewportOptions, viewportOptions,
displaySetOptions, displaySetOptions,
@ -370,7 +374,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
measurementService, measurementService,
displaySetService, displaySetService,
elementRef, elementRef,
viewportIndex, viewportId,
displaySets, displaySets,
viewportGridService, viewportGridService,
cornerstoneViewportService cornerstoneViewportService
@ -380,7 +384,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
measurementService, measurementService,
displaySetService, displaySetService,
elementRef, elementRef,
viewportIndex, viewportId,
displaySets, displaySets,
viewportGridService, viewportGridService,
cornerstoneViewportService cornerstoneViewportService
@ -389,7 +393,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
return () => { return () => {
unsubscribeFromJumpToMeasurementEvents(); unsubscribeFromJumpToMeasurementEvents();
}; };
}, [displaySets, elementRef, viewportIndex]); }, [displaySets, elementRef, viewportId]);
return ( return (
<React.Fragment> <React.Fragment>
@ -408,7 +412,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
ref={elementRef} ref={elementRef}
></div> ></div>
<CornerstoneOverlays <CornerstoneOverlays
viewportIndex={viewportIndex} viewportId={viewportId}
toolBarService={toolbarService} toolBarService={toolbarService}
element={elementRef.current} element={elementRef.current}
scrollbarHeight={scrollbarHeight} scrollbarHeight={scrollbarHeight}
@ -416,12 +420,12 @@ const OHIFCornerstoneViewport = React.memo(props => {
/> />
<CinePlayer <CinePlayer
enabledVPElement={enabledVPElement} enabledVPElement={enabledVPElement}
viewportIndex={viewportIndex} viewportId={viewportId}
servicesManager={servicesManager} servicesManager={servicesManager}
/> />
</div> </div>
<div className="absolute w-full"> <div className="absolute w-full">
{viewportDialogState.viewportIndex === viewportIndex && ( {viewportDialogState.viewportId === viewportId && (
<Notification <Notification
id="viewport-notification" id="viewport-notification"
message={viewportDialogState.message} message={viewportDialogState.message}
@ -440,37 +444,34 @@ function _subscribeToJumpToMeasurementEvents(
measurementService, measurementService,
displaySetService, displaySetService,
elementRef, elementRef,
viewportIndex, viewportId,
displaySets, displaySets,
viewportGridService, viewportGridService,
cornerstoneViewportService cornerstoneViewportService
) { ) {
const displaysUIDs = displaySets.map(
displaySet => displaySet.displaySetInstanceUID
);
const { unsubscribe } = measurementService.subscribe( const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
props => { props => {
cacheJumpToMeasurementEvent = props; cacheJumpToMeasurementEvent = props;
const { viewportIndex: jumpIndex, measurement, isConsumed } = props; const { viewportId: jumpId, measurement, isConsumed } = props;
if (!measurement || isConsumed) { if (!measurement || isConsumed) {
return; return;
} }
if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) { if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
// Decide on which viewport should handle this // Decide on which viewport should handle this
cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIndexToJump( cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIdToJump(
jumpIndex, jumpId,
measurement.displaySetInstanceUID, measurement.displaySetInstanceUID,
{ referencedImageId: measurement.referencedImageId } { referencedImageId: measurement.referencedImageId }
); );
} }
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) { if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
return; return;
} }
_jumpToMeasurement( _jumpToMeasurement(
measurement, measurement,
elementRef, elementRef,
viewportIndex, viewportId,
measurementService, measurementService,
displaySetService, displaySetService,
viewportGridService, viewportGridService,
@ -487,7 +488,7 @@ function _checkForCachedJumpToMeasurementEvents(
measurementService, measurementService,
displaySetService, displaySetService,
elementRef, elementRef,
viewportIndex, viewportId,
displaySets, displaySets,
viewportGridService, viewportGridService,
cornerstoneViewportService cornerstoneViewportService
@ -513,7 +514,7 @@ function _checkForCachedJumpToMeasurementEvents(
_jumpToMeasurement( _jumpToMeasurement(
measurement, measurement,
elementRef, elementRef,
viewportIndex, viewportId,
measurementService, measurementService,
displaySetService, displaySetService,
viewportGridService, viewportGridService,
@ -526,7 +527,7 @@ function _checkForCachedJumpToMeasurementEvents(
function _jumpToMeasurement( function _jumpToMeasurement(
measurement, measurement,
targetElementRef, targetElementRef,
viewportIndex, viewportId,
measurementService, measurementService,
displaySetService, displaySetService,
viewportGridService, viewportGridService,
@ -548,13 +549,10 @@ function _jumpToMeasurement(
// to set it properly // to set it properly
// setCornerstoneMeasurementActive(measurement); // setCornerstoneMeasurementActive(measurement);
viewportGridService.setActiveViewportIndex(viewportIndex); viewportGridService.setActiveViewportId(viewportId);
const enabledElement = getEnabledElement(targetElement); const enabledElement = getEnabledElement(targetElement);
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);
if (enabledElement) { if (enabledElement) {
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param. // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
const viewport = enabledElement.viewport as const viewport = enabledElement.viewport as
@ -621,7 +619,6 @@ OHIFCornerstoneViewport.defaultProps = {
}; };
OHIFCornerstoneViewport.propTypes = { OHIFCornerstoneViewport.propTypes = {
viewportIndex: PropTypes.number.isRequired,
displaySets: PropTypes.array.isRequired, displaySets: PropTypes.array.isRequired,
dataSource: PropTypes.object.isRequired, dataSource: PropTypes.object.isRequired,
viewportOptions: PropTypes.object, viewportOptions: PropTypes.object,

View File

@ -6,7 +6,7 @@ import ViewportOrientationMarkers from './ViewportOrientationMarkers';
import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator'; import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator';
function CornerstoneOverlays(props) { function CornerstoneOverlays(props) {
const { viewportIndex, element, scrollbarHeight, servicesManager } = props; const { viewportId, element, scrollbarHeight, servicesManager } = props;
const { cornerstoneViewportService } = servicesManager.services; const { cornerstoneViewportService } = servicesManager.services;
const [imageSliceData, setImageSliceData] = useState({ const [imageSliceData, setImageSliceData] = useState({
imageIndex: 0, imageIndex: 0,
@ -18,7 +18,7 @@ function CornerstoneOverlays(props) {
const { unsubscribe } = cornerstoneViewportService.subscribe( const { unsubscribe } = cornerstoneViewportService.subscribe(
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
props => { props => {
if (props.viewportIndex !== viewportIndex) { if (props.viewportId !== viewportId) {
return; return;
} }
@ -29,16 +29,14 @@ function CornerstoneOverlays(props) {
return () => { return () => {
unsubscribe(); unsubscribe();
}; };
}, [viewportIndex]); }, [viewportId]);
if (!element) { if (!element) {
return null; return null;
} }
if (viewportData) { if (viewportData) {
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
viewportIndex
);
if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) { if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
return null; return null;
@ -48,7 +46,7 @@ function CornerstoneOverlays(props) {
return ( return (
<div className="noselect"> <div className="noselect">
<ViewportImageScrollbar <ViewportImageScrollbar
viewportIndex={viewportIndex} viewportId={viewportId}
viewportData={viewportData} viewportData={viewportData}
element={element} element={element}
imageSliceData={imageSliceData} imageSliceData={imageSliceData}
@ -60,7 +58,7 @@ function CornerstoneOverlays(props) {
<CustomizableViewportOverlay <CustomizableViewportOverlay
imageSliceData={imageSliceData} imageSliceData={imageSliceData}
viewportData={viewportData} viewportData={viewportData}
viewportIndex={viewportIndex} viewportId={viewportId}
servicesManager={servicesManager} servicesManager={servicesManager}
element={element} element={element}
/> />
@ -75,7 +73,7 @@ function CornerstoneOverlays(props) {
element={element} element={element}
viewportData={viewportData} viewportData={viewportData}
servicesManager={servicesManager} servicesManager={servicesManager}
viewportIndex={viewportIndex} viewportId={viewportId}
/> />
</div> </div>
); );

View File

@ -21,7 +21,6 @@ interface OverlayItemProps {
element: any; element: any;
viewportData: any; viewportData: any;
imageSliceData: ImageSliceData; imageSliceData: ImageSliceData;
viewportIndex: number | null;
servicesManager: ServicesManager; servicesManager: ServicesManager;
instance: InstanceMetadata; instance: InstanceMetadata;
customization: any; customization: any;
@ -114,7 +113,7 @@ function CustomizableViewportOverlay({
element, element,
viewportData, viewportData,
imageSliceData, imageSliceData,
viewportIndex, viewportId,
servicesManager, servicesManager,
}) { }) {
const { const {
@ -152,13 +151,13 @@ function CustomizableViewportOverlay({
if (viewportData != null) { if (viewportData != null) {
return _getInstanceNumber( return _getInstanceNumber(
viewportData, viewportData,
viewportIndex, viewportId,
imageIndex, imageIndex,
cornerstoneViewportService cornerstoneViewportService
); );
} }
return null; return null;
}, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]); }, [viewportData, viewportId, imageIndex, cornerstoneViewportService]);
/** /**
* Initial toolbar state * Initial toolbar state
@ -192,7 +191,7 @@ function CustomizableViewportOverlay({
return () => { return () => {
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
}; };
}, [viewportIndex, viewportData, voi, element]); }, [viewportId, viewportData, voi, element]);
/** /**
* Updating the scale when the viewport changes its zoom * Updating the scale when the viewport changes its zoom
@ -205,8 +204,8 @@ function CustomizableViewportOverlay({
previousCamera.parallelScale !== camera.parallelScale || previousCamera.parallelScale !== camera.parallelScale ||
previousCamera.scale !== camera.scale previousCamera.scale !== camera.scale
) { ) {
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const viewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
if (!viewport) { if (!viewport) {
@ -237,7 +236,7 @@ function CustomizableViewportOverlay({
return () => { return () => {
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
}; };
}, [viewportIndex, viewportData, cornerstoneViewportService, element]); }, [viewportId, viewportData, cornerstoneViewportService, element]);
/** /**
* Updating the active tools when the toolbar changes * Updating the active tools when the toolbar changes
@ -262,7 +261,7 @@ function CustomizableViewportOverlay({
element, element,
viewportData, viewportData,
imageSliceData, imageSliceData,
viewportIndex, viewportId,
servicesManager, servicesManager,
customization: item, customization: item,
formatters: { formatters: {
@ -296,7 +295,7 @@ function CustomizableViewportOverlay({
element, element,
viewportData, viewportData,
imageSliceData, imageSliceData,
viewportIndex, viewportId,
servicesManager, servicesManager,
customizationService, customizationService,
instance, instance,
@ -390,7 +389,7 @@ function _getViewportInstance(viewportData, imageIndex) {
function _getInstanceNumber( function _getInstanceNumber(
viewportData, viewportData,
viewportIndex, viewportId,
imageIndex, imageIndex,
cornerstoneViewportService cornerstoneViewportService
) { ) {
@ -406,7 +405,7 @@ function _getInstanceNumber(
instanceNumber = _getInstanceNumberFromVolume( instanceNumber = _getInstanceNumberFromVolume(
viewportData, viewportData,
imageIndex, imageIndex,
viewportIndex, viewportId,
cornerstoneViewportService cornerstoneViewportService
); );
} }
@ -438,8 +437,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) {
// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber // Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
function _getInstanceNumberFromVolume( function _getInstanceNumberFromVolume(
viewportData, viewportData,
imageIndex, viewportId,
viewportIndex,
cornerstoneViewportService cornerstoneViewportService
) { ) {
const volumes = viewportData.volumes; const volumes = viewportData.volumes;
@ -452,8 +450,8 @@ function _getInstanceNumberFromVolume(
const volume = volumes[0]; const volume = volumes[0];
const { direction, imageIds } = volume; const { direction, imageIds } = volume;
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
if (!cornerstoneViewport) { if (!cornerstoneViewport) {
@ -486,7 +484,7 @@ function _getInstanceNumberFromVolume(
CustomizableViewportOverlay.propTypes = { CustomizableViewportOverlay.propTypes = {
viewportData: PropTypes.object, viewportData: PropTypes.object,
imageIndex: PropTypes.number, imageIndex: PropTypes.number,
viewportIndex: PropTypes.number, viewportId: PropTypes.string,
}; };
export default CustomizableViewportOverlay; export default CustomizableViewportOverlay;

View File

@ -7,7 +7,7 @@ import { ServicesManger } from '@ohif/core';
function CornerstoneImageScrollbar({ function CornerstoneImageScrollbar({
viewportData, viewportData,
viewportIndex, viewportId,
element, element,
imageSliceData, imageSliceData,
setImageSliceData, setImageSliceData,
@ -19,12 +19,7 @@ function CornerstoneImageScrollbar({
cornerstoneViewportService, cornerstoneViewportService,
} = (servicesManager as ServicesManger).services; } = (servicesManager as ServicesManger).services;
const onImageScrollbarChange = (imageIndex, viewportIndex) => { const onImageScrollbarChange = (imageIndex, viewportId) => {
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);
const viewportId = viewportInfo.getViewportId();
const viewport = cornerstoneViewportService.getCornerstoneViewport( const viewport = cornerstoneViewportService.getCornerstoneViewport(
viewportId viewportId
); );
@ -34,7 +29,7 @@ function CornerstoneImageScrollbar({
if (isCineEnabled) { if (isCineEnabled) {
// on image scrollbar change, stop the CINE if it is playing // on image scrollbar change, stop the CINE if it is playing
cineService.stopClip(element); cineService.stopClip(element);
cineService.setCine({ id: viewportIndex, isPlaying: false }); cineService.setCine({ id: viewportId, isPlaying: false });
} }
csToolsUtils.jumpToSlice(viewport.element, { csToolsUtils.jumpToSlice(viewport.element, {
@ -48,8 +43,8 @@ function CornerstoneImageScrollbar({
return; return;
} }
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const viewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
if (!viewport) { if (!viewport) {
@ -79,7 +74,7 @@ function CornerstoneImageScrollbar({
const { imageIndex, numberOfSlices } = sliceData; const { imageIndex, numberOfSlices } = sliceData;
setImageSliceData({ imageIndex, numberOfSlices }); setImageSliceData({ imageIndex, numberOfSlices });
} }
}, [viewportIndex, viewportData]); }, [viewportId, viewportData]);
useEffect(() => { useEffect(() => {
if (viewportData?.viewportType !== Enums.ViewportType.STACK) { if (viewportData?.viewportType !== Enums.ViewportType.STACK) {
@ -131,7 +126,7 @@ function CornerstoneImageScrollbar({
return ( return (
<ImageScrollbar <ImageScrollbar
onChange={evt => onImageScrollbarChange(evt, viewportIndex)} onChange={evt => onImageScrollbarChange(evt, viewportId)}
max={ max={
imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0 imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0
} }
@ -143,7 +138,7 @@ function CornerstoneImageScrollbar({
CornerstoneImageScrollbar.propTypes = { CornerstoneImageScrollbar.propTypes = {
viewportData: PropTypes.object, viewportData: PropTypes.object,
viewportIndex: PropTypes.number.isRequired, viewportId: PropTypes.string.isRequired,
element: PropTypes.instanceOf(Element), element: PropTypes.instanceOf(Element),
scrollbarHeight: PropTypes.string, scrollbarHeight: PropTypes.string,
imageSliceData: PropTypes.object.isRequired, imageSliceData: PropTypes.object.isRequired,

View File

@ -22,7 +22,7 @@ function ViewportOrientationMarkers({
element, element,
viewportData, viewportData,
imageSliceData, imageSliceData,
viewportIndex, viewportId,
servicesManager, servicesManager,
orientationMarkers = ['top', 'left'], orientationMarkers = ['top', 'left'],
}) { }) {
@ -114,9 +114,7 @@ function ViewportOrientationMarkers({
flipHorizontal flipHorizontal
); );
const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
viewportIndex
);
if (!ohifViewport) { if (!ohifViewport) {
console.log('ViewportOrientationMarkers::No viewport'); console.log('ViewportOrientationMarkers::No viewport');

View File

@ -11,7 +11,7 @@ function CornerstoneViewportOverlay({
element, element,
viewportData, viewportData,
imageSliceData, imageSliceData,
viewportIndex, viewportId,
servicesManager, servicesManager,
}) { }) {
const { const {
@ -73,7 +73,7 @@ function CornerstoneViewportOverlay({
return () => { return () => {
element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI);
}; };
}, [viewportIndex, viewportData, voi, element]); }, [viewportId, viewportData, voi, element]);
/** /**
* Updating the scale when the viewport changes its zoom * Updating the scale when the viewport changes its zoom
@ -86,8 +86,8 @@ function CornerstoneViewportOverlay({
previousCamera.parallelScale !== camera.parallelScale || previousCamera.parallelScale !== camera.parallelScale ||
previousCamera.scale !== camera.scale previousCamera.scale !== camera.scale
) { ) {
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const viewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
if (!viewport) { if (!viewport) {
@ -118,7 +118,7 @@ function CornerstoneViewportOverlay({
return () => { return () => {
element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale);
}; };
}, [viewportIndex, viewportData]); }, [viewportId, viewportData]);
const getTopLeftContent = useCallback(() => { const getTopLeftContent = useCallback(() => {
const { windowWidth, windowCenter } = voi; const { windowWidth, windowCenter } = voi;
@ -168,7 +168,7 @@ function CornerstoneViewportOverlay({
instanceNumber = _getInstanceNumberFromVolume( instanceNumber = _getInstanceNumberFromVolume(
viewportData, viewportData,
imageIndex, imageIndex,
viewportIndex, viewportId,
cornerstoneViewportService cornerstoneViewportService
); );
} }
@ -183,15 +183,13 @@ function CornerstoneViewportOverlay({
</span> </span>
</div> </div>
); );
}, [imageSliceData, viewportData, viewportIndex]); }, [imageSliceData, viewportData, viewportId]);
if (!viewportData) { if (!viewportData) {
return null; return null;
} }
const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
viewportIndex
);
if (!ohifViewport) { if (!ohifViewport) {
return null; return null;
@ -240,7 +238,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) {
function _getInstanceNumberFromVolume( function _getInstanceNumberFromVolume(
viewportData, viewportData,
imageIndex, imageIndex,
viewportIndex, viewportId,
cornerstoneViewportService cornerstoneViewportService
) { ) {
const volumes = viewportData.volumes; const volumes = viewportData.volumes;
@ -253,8 +251,8 @@ function _getInstanceNumberFromVolume(
const volume = volumes[0]; const volume = volumes[0];
const { direction, imageIds } = volume; const { direction, imageIds } = volume;
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
if (!cornerstoneViewport) { if (!cornerstoneViewport) {
@ -287,7 +285,7 @@ function _getInstanceNumberFromVolume(
CornerstoneViewportOverlay.propTypes = { CornerstoneViewportOverlay.propTypes = {
viewportData: PropTypes.object, viewportData: PropTypes.object,
imageIndex: PropTypes.number, imageIndex: PropTypes.number,
viewportIndex: PropTypes.number, viewportId: PropTypes.string,
servicesManager: PropTypes.instanceOf(ServicesManager), servicesManager: PropTypes.instanceOf(ServicesManager),
}; };

View File

@ -239,8 +239,7 @@ function commandsModule({
return; return;
} }
const viewportIndex = viewportInfo.getViewportIndex(); viewportGridService.setActiveViewportId(viewportId);
viewportGridService.setActiveViewportIndex(viewportIndex);
}, },
arrowTextCallback: ({ callback, data }) => { arrowTextCallback: ({ callback, data }) => {
callInputDialog(uiDialogService, data, callback); callInputDialog(uiDialogService, data, callback);
@ -310,9 +309,11 @@ function commandsModule({
} }
} }
const { viewports } = viewportGridService.getState() || { const { viewports } = viewportGridService.getState();
viewports: [],
}; if (!viewports.size) {
return;
}
const toolGroup = toolGroupService.getToolGroup(toolGroupId); const toolGroup = toolGroupService.getToolGroup(toolGroupId);
const toolGroupViewportIds = toolGroup?.getViewportIds?.(); const toolGroupViewportIds = toolGroup?.getViewportIds?.();
@ -322,15 +323,11 @@ function commandsModule({
return; return;
} }
const filteredViewports = viewports.filter(viewport => { const filteredViewports = Array.from(viewports.values()).filter(
if (!viewport.viewportOptions) { viewport => {
return false; return toolGroupViewportIds.includes(viewport.viewportId);
} }
);
return toolGroupViewportIds.includes(
viewport.viewportOptions.viewportId
);
});
if (!filteredViewports.length) { if (!filteredViewports.length) {
return; return;
@ -368,12 +365,10 @@ function commandsModule({
}); });
}, },
showDownloadViewportModal: () => { showDownloadViewportModal: () => {
const { activeViewportIndex } = viewportGridService.getState(); const { activeViewportId } = viewportGridService.getState();
if ( if (
!cornerstoneViewportService.getCornerstoneViewportByIndex( !cornerstoneViewportService.getCornerstoneViewport(activeViewportId)
activeViewportIndex
)
) { ) {
// Cannot download a non-cornerstone viewport (image). // Cannot download a non-cornerstone viewport (image).
uiNotificationService.show({ uiNotificationService.show({
@ -391,7 +386,7 @@ function commandsModule({
content: CornerstoneViewportDownloadForm, content: CornerstoneViewportDownloadForm,
title: 'Download High Quality Image', title: 'Download High Quality Image',
contentProps: { contentProps: {
activeViewportIndex, activeViewportId,
onClose: uiModalService.hide, onClose: uiModalService.hide,
cornerstoneViewportService, cornerstoneViewportService,
}, },
@ -554,13 +549,13 @@ function commandsModule({
cstUtils.scroll(viewport, options); cstUtils.scroll(viewport, options);
}, },
setViewportColormap: ({ setViewportColormap: ({
viewportIndex, viewportId,
displaySetInstanceUID, displaySetInstanceUID,
colormap, colormap,
immediate = false, immediate = false,
}) => { }) => {
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const viewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
const actorEntries = viewport.getActors(); const actorEntries = viewport.getActors();
@ -577,17 +572,17 @@ function commandsModule({
viewport.render(); viewport.render();
} }
}, },
incrementActiveViewport: () => { changeActiveViewport: ({ direction = 1 }) => {
const { activeViewportIndex, viewports } = viewportGridService.getState(); const { activeViewportId, viewports } = viewportGridService.getState();
const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; const viewportIds = Array.from(viewports.keys());
viewportGridService.setActiveViewportIndex(nextViewportIndex); const currentIndex = viewportIds.indexOf(activeViewportId);
},
decrementActiveViewport: () => {
const { activeViewportIndex, viewports } = viewportGridService.getState();
const nextViewportIndex = const nextViewportIndex =
(activeViewportIndex - 1 + viewports.length) % viewports.length; (currentIndex + direction + viewportIds.length) % viewportIds.length;
viewportGridService.setActiveViewportIndex(nextViewportIndex); viewportGridService.setActiveViewportId(
viewportIds[nextViewportIndex] as string
);
}, },
toggleStackImageSync: ({ toggledState }) => { toggleStackImageSync: ({ toggledState }) => {
toggleStackImageSync({ toggleStackImageSync({
getEnabledElement, getEnabledElement,
@ -596,9 +591,9 @@ function commandsModule({
}); });
}, },
toggleReferenceLines: ({ toggledState }) => { toggleReferenceLines: ({ toggledState }) => {
const { activeViewportIndex } = viewportGridService.getState(); const { activeViewportId } = viewportGridService.getState();
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( const viewportInfo = cornerstoneViewportService.getViewportInfo(
activeViewportIndex activeViewportId
); );
const viewportId = viewportInfo.getViewportId(); const viewportId = viewportInfo.getViewportId();
@ -617,34 +612,8 @@ function commandsModule({
); );
toolGroup.setToolEnabled(ReferenceLinesTool.toolName); toolGroup.setToolEnabled(ReferenceLinesTool.toolName);
}, },
storePresentation: ({ viewportIndex }) => { storePresentation: ({ viewportId }) => {
const presentation = cornerstoneViewportService.getPresentation( cornerstoneViewportService.storePresentation({ viewportId });
viewportIndex
);
if (!presentation || !presentation.presentationIds) {
return;
}
const {
lutPresentationStore,
positionPresentationStore,
} = stateSyncService.getState();
const { presentationIds } = presentation;
const { lutPresentationId, positionPresentationId } =
presentationIds || {};
const storeState = {};
if (lutPresentationId) {
storeState.lutPresentationStore = {
...lutPresentationStore,
[lutPresentationId]: presentation,
};
}
if (positionPresentationId) {
storeState.positionPresentationStore = {
...positionPresentationStore,
[positionPresentationId]: presentation,
};
}
stateSyncService.store(storeState);
}, },
}; };
@ -701,10 +670,11 @@ function commandsModule({
options: { rotation: -90 }, options: { rotation: -90 },
}, },
incrementActiveViewport: { incrementActiveViewport: {
commandFn: actions.incrementActiveViewport, commandFn: actions.changeActiveViewport,
}, },
decrementActiveViewport: { decrementActiveViewport: {
commandFn: actions.decrementActiveViewport, commandFn: actions.changeActiveViewport,
options: { direction: -1 },
}, },
flipViewportHorizontal: { flipViewportHorizontal: {
commandFn: actions.flipViewportHorizontal, commandFn: actions.flipViewportHorizontal,

View File

@ -2,14 +2,10 @@ import React, { useEffect } from 'react';
import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui'; import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui';
import { Enums, eventTarget } from '@cornerstonejs/core'; import { Enums, eventTarget } from '@cornerstonejs/core';
function WrappedCinePlayer({ function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) {
enabledVPElement,
viewportIndex,
servicesManager,
}) {
const { toolbarService, customizationService } = servicesManager.services; const { toolbarService, customizationService } = servicesManager.services;
const [{ isCineEnabled, cines }, cineService] = useCine(); const [{ isCineEnabled, cines }, cineService] = useCine();
const [{ activeViewportIndex }] = useViewportGrid(); const [{ activeViewportId }] = useViewportGrid();
const { component: CinePlayerComponent = CinePlayer } = const { component: CinePlayerComponent = CinePlayer } =
customizationService.get('cinePlayer') ?? {}; customizationService.get('cinePlayer') ?? {};
@ -30,11 +26,11 @@ function WrappedCinePlayer({
}; };
const cineHandler = () => { const cineHandler = () => {
if (!cines || !cines[viewportIndex] || !enabledVPElement) { if (!cines || !cines[viewportId] || !enabledVPElement) {
return; return;
} }
const cine = cines[viewportIndex]; const cine = cines[viewportId];
const isPlaying = cine.isPlaying || false; const isPlaying = cine.isPlaying || false;
const frameRate = cine.frameRate || 24; const frameRate = cine.frameRate || 24;
@ -56,7 +52,7 @@ function WrappedCinePlayer({
); );
return () => { return () => {
cineService.setCine({ id: viewportIndex, isPlaying: false }); cineService.setCine({ id: viewportId, isPlaying: false });
eventTarget.removeEventListener( eventTarget.removeEventListener(
Enums.Events.STACK_VIEWPORT_NEW_STACK, Enums.Events.STACK_VIEWPORT_NEW_STACK,
cineHandler cineHandler
@ -65,20 +61,20 @@ function WrappedCinePlayer({
}, [enabledVPElement]); }, [enabledVPElement]);
useEffect(() => { useEffect(() => {
if (!cines || !cines[viewportIndex] || !enabledVPElement) { if (!cines || !cines[viewportId] || !enabledVPElement) {
return; return;
} }
cineHandler(); cineHandler();
return () => { return () => {
if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) { if (enabledVPElement && cines?.[viewportId]?.isPlaying) {
cineService.stopClip(enabledVPElement); cineService.stopClip(enabledVPElement);
} }
}; };
}, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]); }, [cines, viewportId, cineService, enabledVPElement, cineHandler]);
const cine = cines[viewportIndex]; const cine = cines[viewportId];
const isPlaying = (cine && cine.isPlaying) || false; const isPlaying = (cine && cine.isPlaying) || false;
return ( return (
@ -89,13 +85,13 @@ function WrappedCinePlayer({
onClose={handleCineClose} onClose={handleCineClose}
onPlayPauseChange={isPlaying => onPlayPauseChange={isPlaying =>
cineService.setCine({ cineService.setCine({
id: activeViewportIndex, id: activeViewportId,
isPlaying, isPlaying,
}) })
} }
onFrameRateChange={frameRate => onFrameRateChange={frameRate =>
cineService.setCine({ cineService.setCine({
id: activeViewportIndex, id: activeViewportId,
frameRate, frameRate,
}) })
} }

View File

@ -88,6 +88,7 @@ const mpr: Types.HangingProtocol.Protocol = {
viewports: [ viewports: [
{ {
viewportOptions: { viewportOptions: {
viewportId: 'mpr-axial',
toolGroupId: 'mpr', toolGroupId: 'mpr',
viewportType: 'volume', viewportType: 'volume',
orientation: 'axial', orientation: 'axial',
@ -111,6 +112,7 @@ const mpr: Types.HangingProtocol.Protocol = {
}, },
{ {
viewportOptions: { viewportOptions: {
viewportId: 'mpr-sagittal',
toolGroupId: 'mpr', toolGroupId: 'mpr',
viewportType: 'volume', viewportType: 'volume',
orientation: 'sagittal', orientation: 'sagittal',
@ -134,6 +136,7 @@ const mpr: Types.HangingProtocol.Protocol = {
}, },
{ {
viewportOptions: { viewportOptions: {
viewportId: 'mpr-coronal',
toolGroupId: 'mpr', toolGroupId: 'mpr',
viewportType: 'volume', viewportType: 'volume',
orientation: 'coronal', orientation: 'coronal',

View File

@ -282,9 +282,8 @@ export default async function init({
); );
viewportGridService.subscribe( viewportGridService.subscribe(
viewportGridService.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
({ viewportIndex, viewportId }) => { ({ viewportId }) => {
viewportId = viewportId || `viewport-${viewportIndex}`;
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) { if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) {

View File

@ -63,7 +63,6 @@ function initContextMenu({
customizationService.get('cornerstoneViewportClickCommands') || customizationService.get('cornerstoneViewportClickCommands') ||
DEFAULT_CONTEXT_MENU_CLICKS; DEFAULT_CONTEXT_MENU_CLICKS;
const toRun = customizations[name]; const toRun = customizations[name];
console.log('initContextMenu::cornerstoneViewportHandleEvent', name, toRun);
const options = { const options = {
nearbyToolData: findNearbyToolData(commandsManager, evt), nearbyToolData: findNearbyToolData(commandsManager, evt),
event: evt, event: evt,
@ -82,9 +81,8 @@ function initContextMenu({
if (!viewportInfo) { if (!viewportInfo) {
return; return;
} }
const viewportIndex = viewportInfo.getViewportIndex();
// TODO check update upstream // TODO check update upstream
setEnabledElement(viewportIndex, element); setEnabledElement(viewportId, element);
element.addEventListener( element.addEventListener(
cs3DToolsEvents.MOUSE_CLICK, cs3DToolsEvents.MOUSE_CLICK,

View File

@ -9,8 +9,6 @@ import {
VolumeViewport, VolumeViewport,
VolumeViewport3D, VolumeViewport3D,
cache, cache,
utilities,
CONSTANTS,
Enums as csEnums, Enums as csEnums,
} from '@cornerstonejs/core'; } from '@cornerstonejs/core';
@ -55,7 +53,6 @@ class CornerstoneViewportService extends PubSubService
}; };
renderingEngine: Types.IRenderingEngine | null; renderingEngine: Types.IRenderingEngine | null;
viewportsInfo: Map<number, ViewportInfo> = new Map();
viewportsById: Map<string, ViewportInfo> = new Map(); viewportsById: Map<string, ViewportInfo> = new Map();
viewportGridResizeObserver: ResizeObserver | null; viewportGridResizeObserver: ResizeObserver | null;
viewportsDisplaySets: Map<string, string[]> = new Map(); viewportsDisplaySets: Map<string, string[]> = new Map();
@ -75,45 +72,17 @@ class CornerstoneViewportService extends PubSubService
/** /**
* Adds the HTML element to the viewportService * Adds the HTML element to the viewportService
* @param {*} viewportIndex * @param {*} viewportId
* @param {*} elementRef * @param {*} elementRef
*/ */
public enableViewport( public enableViewport(viewportId: string, elementRef: HTMLDivElement): void {
viewportIndex: number, const viewportInfo = new ViewportInfo(viewportId);
viewportOptions: PublicViewportOptions,
elementRef: HTMLDivElement
): void {
// Use the provided viewportId
// Not providing a viewportId is frowned upon because it does weird things
// on moving them around, but it does mostly work.
if (!viewportOptions.viewportId) {
console.warn('Should provide viewport id externally', viewportOptions);
viewportOptions.viewportId =
this.getViewportId(viewportIndex) || `viewport-${viewportIndex}`;
}
const { viewportId } = viewportOptions;
const viewportInfo = new ViewportInfo(viewportIndex, viewportId);
if (!viewportInfo.viewportId) {
throw new Error('Should have viewport ID afterwards');
}
viewportInfo.setElement(elementRef); viewportInfo.setElement(elementRef);
this.viewportsInfo.set(viewportIndex, viewportInfo);
this.viewportsById.set(viewportId, viewportInfo); this.viewportsById.set(viewportId, viewportInfo);
} }
public getViewportIds(): string[] { public getViewportIds(): string[] {
const viewportIds = []; return Array.from(this.viewportsById.keys());
this.viewportsInfo.forEach(viewportInfo => {
viewportIds.push(viewportInfo.getViewportId());
});
return viewportIds;
}
public getViewportId(viewportIndex: number): string {
return this.viewportsInfo[viewportIndex]?.viewportId;
} }
/** /**
@ -171,21 +140,14 @@ class CornerstoneViewportService extends PubSubService
* created for every new viewport, this will be called whenever the set of * created for every new viewport, this will be called whenever the set of
* viewports is changed, but NOT when the viewport position changes only. * viewports is changed, but NOT when the viewport position changes only.
* *
* @param viewportIndex * @param viewportId - The viewportId to disable
*/ */
public disableElement(viewportIndex: number): void { public disableElement(viewportId: string): void {
const viewportInfo = this.viewportsInfo.get(viewportIndex); this.renderingEngine?.disableElement(viewportId);
if (!viewportInfo) {
return;
}
const viewportId = viewportInfo.getViewportId(); // clean up
this.renderingEngine && this.renderingEngine.disableElement(viewportId);
this.viewportsInfo.get(viewportIndex).destroy();
this.viewportsInfo.delete(viewportIndex);
this.viewportsById.delete(viewportId); this.viewportsById.delete(viewportId);
this.viewportsDisplaySets.delete(viewportId);
} }
public setPresentations(viewport, presentations?: Presentations): void { public setPresentations(viewport, presentations?: Presentations): void {
@ -199,14 +161,14 @@ class CornerstoneViewportService extends PubSubService
} }
} }
public getPresentation(viewportIndex: number): Presentation { public getPresentation(viewportId: string): Presentation {
const viewportInfo = this.viewportsInfo.get(viewportIndex); const viewportInfo = this.viewportsById.get(viewportId);
if (!viewportInfo) { if (!viewportInfo) {
return; return;
} }
const { viewportType, presentationIds } = viewportInfo.getViewportOptions(); const { viewportType, presentationIds } = viewportInfo.getViewportOptions();
const csViewport = this.getCornerstoneViewportByIndex(viewportIndex); const csViewport = this.getCornerstoneViewport(viewportId);
if (!csViewport) { if (!csViewport) {
return; return;
} }
@ -228,55 +190,80 @@ class CornerstoneViewportService extends PubSubService
}; };
} }
public storePresentation({ viewportId }) {
const stateSyncService = this.servicesManager.services.stateSyncService;
let presentation;
try {
presentation = this.getPresentation(viewportId);
} catch (error) {
console.warn(error);
}
if (!presentation || !presentation.presentationIds) {
return;
}
const {
lutPresentationStore,
positionPresentationStore,
} = stateSyncService.getState();
const { presentationIds } = presentation;
const { lutPresentationId, positionPresentationId } = presentationIds || {};
const storeState = {};
if (lutPresentationId) {
storeState.lutPresentationStore = {
...lutPresentationStore,
[lutPresentationId]: presentation,
};
}
if (positionPresentationId) {
storeState.positionPresentationStore = {
...positionPresentationStore,
[positionPresentationId]: presentation,
};
}
stateSyncService.store(storeState);
}
/** /**
* Uses the renderingEngine to enable the element for the given viewport index * Sets the viewport data for a viewport.
* and sets the displaySet data to the viewport * @param viewportId - The ID of the viewport to set the data for.
* @param {*} viewportIndex * @param viewportData - The viewport data to set.
* @param {*} displaySet * @param publicViewportOptions - The public viewport options.
* @param {*} dataSource * @param publicDisplaySetOptions - The public display set options.
* @returns * @param presentations - The presentations to set.
*/ */
public setViewportData( public setViewportData(
viewportIndex: number, viewportId: string,
viewportData: StackViewportData | VolumeViewportData, viewportData: StackViewportData | VolumeViewportData,
publicViewportOptions: PublicViewportOptions, publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[], publicDisplaySetOptions: DisplaySetOptions[],
presentations?: Presentations presentations?: Presentations
): void { ): void {
const renderingEngine = this.getRenderingEngine(); const renderingEngine = this.getRenderingEngine();
const viewportId =
publicViewportOptions.viewportId || this.getViewportId(viewportIndex);
if (!viewportId) {
throw new Error('Must define viewportId externally');
}
// This is the old viewportInfo, which may have old options but we might be
// using its viewport (same viewportId as the new viewportInfo)
const viewportInfo = this.viewportsById.get(viewportId); const viewportInfo = this.viewportsById.get(viewportId);
// We should store the presentation for the current viewport since we can't only
// rely to store it WHEN the viewport is disabled since we might keep around the
// same viewport/element and just change the viewportData for it (drag and drop etc.)
// the disableElement storePresentation handle would not be called in this case
// and we would lose the presentation.
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
if (!viewportInfo) { if (!viewportInfo) {
throw new Error('Viewport info not defined'); throw new Error('element is not enabled for the given viewportId');
} }
// If the viewport has moved index, then record the new index // override the viewportOptions and displaySetOptions with the public ones
if (viewportInfo.viewportIndex !== viewportIndex) { // since those are the newly set ones, we set them here so that it handles defaults
this.viewportsInfo.delete(viewportInfo.viewportIndex); const displaySetOptions = viewportInfo.setPublicDisplaySetOptions(
this.viewportsInfo.set(viewportIndex, viewportInfo); publicDisplaySetOptions
viewportInfo.viewportIndex = viewportIndex; );
} const viewportOptions = viewportInfo.setPublicViewportOptions(
publicViewportOptions
viewportInfo.setRenderingEngineId(renderingEngine.id);
const {
viewportOptions,
displaySetOptions,
} = this._getViewportAndDisplaySetOptions(
publicViewportOptions,
publicDisplaySetOptions,
viewportInfo
); );
viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions);
viewportInfo.setViewportData(viewportData);
const element = viewportInfo.getElement(); const element = viewportInfo.getElement();
const type = viewportInfo.getViewportType(); const type = viewportInfo.getViewportType();
@ -293,12 +280,26 @@ class CornerstoneViewportService extends PubSubService
}, },
}; };
// Rendering Engine Id set should happen before enabling the element
// since there are callbacks that depend on the renderingEngine id
// Todo: however, this is a limitation which means that we can't change
// the rendering engine id for a given viewport which might be a super edge
// case
viewportInfo.setRenderingEngineId(renderingEngine.id);
// Todo: this is not optimal at all, we are re-enabling the already enabled // Todo: this is not optimal at all, we are re-enabling the already enabled
// element which is not what we want. But enabledElement as part of the // element which is not what we want. But enabledElement as part of the
// renderingEngine is designed to be used like this. This will trigger // renderingEngine is designed to be used like this. This will trigger
// ENABLED_ELEMENT again and again, which will run onEnableElement callbacks // ENABLED_ELEMENT again and again, which will run onEnableElement callbacks
renderingEngine.enableElement(viewportInput); renderingEngine.enableElement(viewportInput);
viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions);
viewportInfo.setViewportData(viewportData);
viewportInfo.setViewportId(viewportId);
this.viewportsById.set(viewportId, viewportInfo);
const viewport = renderingEngine.getViewport(viewportId); const viewport = renderingEngine.getViewport(viewportId);
this._setDisplaySets(viewport, viewportData, viewportInfo, presentations); this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
@ -307,7 +308,6 @@ class CornerstoneViewportService extends PubSubService
// invalid data. // invalid data.
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
viewportData, viewportData,
viewportIndex,
viewportId, viewportId,
}); });
} }
@ -330,43 +330,8 @@ class CornerstoneViewportService extends PubSubService
return viewport; return viewport;
} }
public getCornerstoneViewportByIndex(
viewportIndex: number
): Types.IStackViewport | Types.IVolumeViewport | null {
const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
if (
!viewportInfo ||
!this.renderingEngine ||
this.renderingEngine.hasBeenDestroyed
) {
return null;
}
const viewport = this.renderingEngine.getViewport(
viewportInfo.getViewportId()
);
return viewport;
}
/**
* Returns the viewportIndex for the provided viewportId
* @param {string} viewportId - the viewportId
* @returns {number} - the viewportIndex
*/
public getViewportInfoByIndex(viewportIndex: number): ViewportInfo {
return this.viewportsInfo.get(viewportIndex);
}
public getViewportInfo(viewportId: string): ViewportInfo { public getViewportInfo(viewportId: string): ViewportInfo {
// @ts-ignore return this.viewportsById.get(viewportId);
for (const [index, viewport] of this.viewportsInfo.entries()) {
if (viewport.getViewportId() === viewportId) {
return viewport;
}
}
return null;
} }
_setStackViewport( _setStackViewport(
@ -414,7 +379,7 @@ class CornerstoneViewportService extends PubSubService
} }
viewport.setStack(imageIds, initialImageIndexToUse).then(() => { viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
viewport.setProperties(properties); viewport.setProperties({ ...properties });
const camera = presentations.positionPresentation?.camera; const camera = presentations.positionPresentation?.camera;
if (camera) { if (camera) {
viewport.setCamera(camera); viewport.setCamera(camera);
@ -541,10 +506,7 @@ class CornerstoneViewportService extends PubSubService
this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs); this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs);
if ( if (hangingProtocolService.getShouldPerformCustomImageLoad()) {
hangingProtocolService.hasCustomImageLoadStrategy() &&
!hangingProtocolService.customImageLoadPerformed
) {
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy // delegate the volume loading to the hanging protocol service if it has a custom image load strategy
return hangingProtocolService.runImageLoadStrategy({ return hangingProtocolService.runImageLoadStrategy({
viewportId: viewport.id, viewportId: viewport.id,
@ -744,14 +706,8 @@ class CornerstoneViewportService extends PubSubService
// Todo: keepCamera is an interim solution until we have a better solution for // Todo: keepCamera is an interim solution until we have a better solution for
// keeping the camera position when the viewport data is changed // keeping the camera position when the viewport data is changed
public updateViewport( public updateViewport(viewportId: string, viewportData, keepCamera = false) {
viewportIndex: number, const viewportInfo = this.getViewportInfo(viewportId);
viewportData,
keepCamera = false
) {
const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
const viewportId = viewportInfo.getViewportId();
const viewport = this.getCornerstoneViewport(viewportId); const viewport = this.getCornerstoneViewport(viewportId);
const viewportCamera = viewport.getCamera(); const viewportCamera = viewport.getCamera();
@ -841,36 +797,6 @@ class CornerstoneViewportService extends PubSubService
} }
} }
_getViewportAndDisplaySetOptions(
publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[],
viewportInfo: ViewportInfo
): {
viewportOptions: ViewportOptions;
displaySetOptions: DisplaySetOptions[];
} {
const viewportIndex = viewportInfo.getViewportIndex();
// Creating a temporary viewportInfo to handle defaults
const newViewportInfo = new ViewportInfo(
viewportIndex,
viewportInfo.getViewportId()
);
// To handle setting the default values if missing for the viewportOptions and
// displaySetOptions
newViewportInfo.setPublicViewportOptions(publicViewportOptions);
newViewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions);
const newViewportOptions = newViewportInfo.getViewportOptions();
const newDisplaySetOptions = newViewportInfo.getDisplaySetOptions();
return {
viewportOptions: newViewportOptions,
displaySetOptions: newDisplaySetOptions,
};
}
_getFrameOfReferenceUID(displaySetInstanceUID) { _getFrameOfReferenceUID(displaySetInstanceUID) {
const { displaySetService } = this.servicesManager.services; const { displaySetService } = this.servicesManager.services;
const displaySet = displaySetService.getDisplaySetByUID( const displaySet = displaySetService.getDisplaySetByUID(
@ -907,25 +833,25 @@ class CornerstoneViewportService extends PubSubService
* *
* @param measurement * @param measurement
* The measurement that is desired to view. * The measurement that is desired to view.
* @param activeViewportIndex - the index that was active at the time the jump * @param activeViewportId - the index that was active at the time the jump
* was initiated. * was initiated.
* @return the viewportIndex to display the given measurement * @return the viewportId that the measurement should be displayed in.
*/ */
public getViewportIndexToJump( public getViewportIdToJump(
activeViewportIndex: number, activeViewportId: string,
displaySetInstanceUID: string, displaySetInstanceUID: string,
cameraProps: unknown cameraProps: unknown
): number { ): string {
const viewportInfo = this.viewportsInfo.get(activeViewportIndex); const viewportInfo = this.getViewportInfo(activeViewportId);
const { referencedImageId } = cameraProps; const { referencedImageId } = cameraProps;
if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) { if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) {
return activeViewportIndex; return activeViewportId;
} }
return ( return (
[...this.viewportsById.values()].find(viewportInfo => [...this.viewportsById.values()].find(viewportInfo =>
viewportInfo.contains(displaySetInstanceUID, referencedImageId) viewportInfo.contains(displaySetInstanceUID, referencedImageId)
)?.viewportIndex ?? -1 )?.viewportId ?? null
); );
} }
} }

View File

@ -17,7 +17,6 @@ export interface IViewportService {
viewportGridResizeObserver: unknown; viewportGridResizeObserver: unknown;
viewportsInfo: unknown; viewportsInfo: unknown;
sceneVolumeInputs: unknown; sceneVolumeInputs: unknown;
viewportIndexUIDMap: unknown;
viewportDivElements: unknown; viewportDivElements: unknown;
ViewportPropertiesMap: unknown; ViewportPropertiesMap: unknown;
volumeUIDs: unknown; volumeUIDs: unknown;
@ -28,14 +27,9 @@ export interface IViewportService {
_broadcastEvent: unknown; _broadcastEvent: unknown;
/** /**
* Adds the HTML element to the viewportService * Adds the HTML element to the viewportService
* @param {*} viewportIndex
* @param {*} elementRef * @param {*} elementRef
*/ */
enableViewport( enableViewport(viewportId: string, elementRef: HTMLDivElement): void;
viewportIndex: number,
viewportOptions: ViewportOptions,
elementRef: HTMLDivElement
): void;
/** /**
* It retrieves the renderingEngine if it does exist, or creates one otherwise * It retrieves the renderingEngine if it does exist, or creates one otherwise
* @returns {RenderingEngine} rendering engine * @returns {RenderingEngine} rendering engine
@ -54,19 +48,17 @@ export interface IViewportService {
/** /**
* Disables the viewport inside the renderingEngine, if no viewport is left * Disables the viewport inside the renderingEngine, if no viewport is left
* it destroys the renderingEngine. * it destroys the renderingEngine.
* @param viewportIndex * @param viewportId
*/ */
disableElement(viewportIndex: number): void; disableElement(viewportId: string): void;
/** /**
* Uses the renderingEngine to enable the element for the given viewport index * Uses the renderingEngine to enable the element for the given viewport index
* and sets the displaySet data to the viewport * and sets the displaySet data to the viewport
* @param {*} viewportIndex
* @param {*} displaySet * @param {*} displaySet
* @param {*} dataSource * @param {*} dataSource
* @returns * @returns
*/ */
setViewportData( setViewportData(
viewportIndex: number,
viewportData: StackData | VolumeData, viewportData: StackData | VolumeData,
publicViewportOptions: PublicViewportOptions, publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[] publicDisplaySetOptions: DisplaySetOptions[]

View File

@ -106,15 +106,13 @@ const dataContains = (
class ViewportInfo { class ViewportInfo {
private viewportId = ''; private viewportId = '';
private viewportIndex: number;
private element: HTMLDivElement; private element: HTMLDivElement;
private viewportOptions: ViewportOptions; private viewportOptions: ViewportOptions;
private displaySetOptions: Array<DisplaySetOptions>; private displaySetOptions: Array<DisplaySetOptions>;
private viewportData: StackViewportData | VolumeViewportData; private viewportData: StackViewportData | VolumeViewportData;
private renderingEngineId: string; private renderingEngineId: string;
constructor(viewportIndex: number, viewportId: string) { constructor(viewportId: string) {
this.viewportIndex = viewportIndex;
this.viewportId = viewportId; this.viewportId = viewportId;
this.setPublicViewportOptions({}); this.setPublicViewportOptions({});
this.setPublicDisplaySetOptions([{}]); this.setPublicDisplaySetOptions([{}]);
@ -155,9 +153,6 @@ class ViewportInfo {
public setViewportId(viewportId: string): void { public setViewportId(viewportId: string): void {
this.viewportId = viewportId; this.viewportId = viewportId;
} }
public setViewportIndex(viewportIndex: number): void {
this.viewportIndex = viewportIndex;
}
public setElement(element: HTMLDivElement): void { public setElement(element: HTMLDivElement): void {
this.element = element; this.element = element;
@ -173,9 +168,6 @@ class ViewportInfo {
return this.viewportData; return this.viewportData;
} }
public getViewportIndex(): number {
return this.viewportIndex;
}
public getElement(): HTMLDivElement { public getElement(): HTMLDivElement {
return this.element; return this.element;
@ -187,13 +179,15 @@ class ViewportInfo {
public setPublicDisplaySetOptions( public setPublicDisplaySetOptions(
publicDisplaySetOptions: PublicDisplaySetOptions[] | DisplaySetSelector[] publicDisplaySetOptions: PublicDisplaySetOptions[] | DisplaySetSelector[]
): void { ): Array<DisplaySetOptions> {
// map the displaySetOptions and check if they are undefined then set them to default values // map the displaySetOptions and check if they are undefined then set them to default values
const displaySetOptions = this.mapDisplaySetOptions( const displaySetOptions = this.mapDisplaySetOptions(
publicDisplaySetOptions publicDisplaySetOptions
); );
this.setDisplaySetOptions(displaySetOptions); this.setDisplaySetOptions(displaySetOptions);
return this.displaySetOptions
} }
public hasDisplaySet(displaySetInstanceUID: string): boolean { public hasDisplaySet(displaySetInstanceUID: string): boolean {
@ -217,7 +211,7 @@ class ViewportInfo {
public setPublicViewportOptions( public setPublicViewportOptions(
viewportOptionsEntry: PublicViewportOptions viewportOptionsEntry: PublicViewportOptions
): void { ): ViewportOptions {
let viewportType = viewportOptionsEntry.viewportType; let viewportType = viewportOptionsEntry.viewportType;
const { const {
toolGroupId = DEFAULT_TOOLGROUP_ID, toolGroupId = DEFAULT_TOOLGROUP_ID,
@ -250,6 +244,8 @@ class ViewportInfo {
toolGroupId, toolGroupId,
presentationIds, presentationIds,
}); });
return this.viewportOptions;
} }
public setViewportOptions(viewportOptions: ViewportOptions): void { public setViewportOptions(viewportOptions: ViewportOptions): void {

View File

@ -10,13 +10,13 @@ const state = {
* @return void * @return void
*/ */
const setEnabledElement = ( const setEnabledElement = (
viewportIndex: number, viewportId: string,
element: HTMLElement, element: HTMLElement,
context?: string context?: string
): void => { ): void => {
const targetContext = context || state.DEFAULT_CONTEXT; const targetContext = context || state.DEFAULT_CONTEXT;
state.enabledElements[viewportIndex] = { state.enabledElements[viewportId] = {
element, element,
context: targetContext, context: targetContext,
}; };
@ -27,8 +27,8 @@ const setEnabledElement = (
* *
* @return {HTMLElement} Active viewport element. * @return {HTMLElement} Active viewport element.
*/ */
const getEnabledElement = viewportIndex => { const getEnabledElement = viewportId => {
return state.enabledElements[viewportIndex]; return state.enabledElements[viewportId];
}; };
const reset = () => { const reset = () => {

View File

@ -20,10 +20,10 @@ const VIEWPORT_ID = 'cornerstone-viewport-download-form';
const CornerstoneViewportDownloadForm = ({ const CornerstoneViewportDownloadForm = ({
onClose, onClose,
activeViewportIndex, activeViewportId: activeViewportIdProp,
cornerstoneViewportService, cornerstoneViewportService,
}) => { }) => {
const enabledElement = OHIFgetEnabledElement(activeViewportIndex); const enabledElement = OHIFgetEnabledElement(activeViewportIdProp);
const activeViewportElement = enabledElement?.element; const activeViewportElement = enabledElement?.element;
const activeViewportEnabledElement = getEnabledElement(activeViewportElement); const activeViewportEnabledElement = getEnabledElement(activeViewportElement);
@ -268,7 +268,7 @@ const CornerstoneViewportDownloadForm = ({
CornerstoneViewportDownloadForm.propTypes = { CornerstoneViewportDownloadForm.propTypes = {
onClose: PropTypes.func, onClose: PropTypes.func,
activeViewportIndex: PropTypes.number.isRequired, activeViewportId: PropTypes.string.isRequired,
}; };
export default CornerstoneViewportDownloadForm; export default CornerstoneViewportDownloadForm;

View File

@ -6,8 +6,8 @@ import { getEnabledElement as OHIFgetEnabledElement } from '../state';
export default function getActiveViewportEnabledElement( export default function getActiveViewportEnabledElement(
viewportGridService viewportGridService
): IEnabledElement { ): IEnabledElement {
const { activeViewportIndex } = viewportGridService.getState(); const { activeViewportId } = viewportGridService.getState();
const { element } = OHIFgetEnabledElement(activeViewportIndex) || {}; const { element } = OHIFgetEnabledElement(activeViewportId) || {};
const enabledElement = getEnabledElement(element); const enabledElement = getEnabledElement(element);
return enabledElement; return enabledElement;
} }

View File

@ -2,7 +2,7 @@ import calculateViewportRegistrations from './calculateViewportRegistrations';
// [ { // [ {
// synchronizerId: string, // synchronizerId: string,
// viewports: [ { viewportId: number, renderingEngineId: string, index: number } , ...] // viewports: [ { viewportId: string, renderingEngineId: string, index: number } , ...]
// ]} // ]}
let STACK_IMAGE_SYNC_GROUPS_INFO = []; let STACK_IMAGE_SYNC_GROUPS_INFO = [];
@ -37,32 +37,25 @@ export default function toggleStackImageSync({
STACK_IMAGE_SYNC_GROUPS_INFO = []; STACK_IMAGE_SYNC_GROUPS_INFO = [];
// create synchronization groups and add viewports // create synchronization groups and add viewports
let { viewports } = viewportGridService.getState(); const { viewports } = viewportGridService.getState();
// filter empty viewports // filter empty viewports
viewports = viewports.filter( const viewportsArray = Array.from(viewports.values())
viewport => .filter(viewport => viewport.displaySetInstanceUIDs?.length)
viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length // filter reconstructable viewports
); .filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
// filter reconstructable viewports for (const displaySetInstanceUID of displaySetInstanceUIDs) {
viewports = viewports.filter(viewport => { const displaySet = displaySetService.getDisplaySetByUID(
const { displaySetInstanceUIDs } = viewport; displaySetInstanceUID
);
for (const displaySetInstanceUID of displaySetInstanceUIDs) { return !!displaySet?.isReconstructable;
const displaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
if (displaySet && displaySet.isReconstructable) {
return true;
} }
});
return false; const viewportsByOrientation = viewportsArray.reduce((acc, viewport) => {
}
});
const viewportsByOrientation = viewports.reduce((acc, viewport) => {
const { viewportId, viewportType } = viewport.viewportOptions; const { viewportId, viewportType } = viewport.viewportOptions;
if (viewportType !== 'stack') { if (viewportType !== 'stack') {
@ -71,9 +64,8 @@ export default function toggleStackImageSync({
} }
const { element } = cornerstoneViewportService.getViewportInfo(viewportId); const { element } = cornerstoneViewportService.getViewportInfo(viewportId);
const { viewport: csViewport, renderingEngineId } = getEnabledElement( const { viewport: csViewport, renderingEngineId } =
element getEnabledElement(element);
);
const { viewPlaneNormal } = csViewport.getCamera(); const { viewPlaneNormal } = csViewport.getCamera();
// Should we round here? I guess so, but not sure how much precision we need // Should we round here? I guess so, but not sure how much precision we need

View File

@ -25,7 +25,7 @@ export default function PanelMeasurementTable({
extensionManager, extensionManager,
}): React.FunctionComponent { }): React.FunctionComponent {
const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportGrid, viewportGridService] = useViewportGrid();
const { activeViewportIndex, viewports } = viewportGrid; const { activeViewportId, viewports } = viewportGrid;
const { const {
measurementService, measurementService,
uiDialogService, uiDialogService,
@ -80,7 +80,7 @@ export default function PanelMeasurementTable({
async function createReport(): Promise<any> { async function createReport(): Promise<any> {
// filter measurements that are added to the active study // filter measurements that are added to the active study
const activeViewport = viewports[activeViewportIndex]; const activeViewport = viewports.get(activeViewportId);
const measurements = measurementService.getMeasurements(); const measurements = measurementService.getMeasurements();
const displaySet = displaySetService.getDisplaySetByUID( const displaySet = displaySetService.getDisplaySetByUID(
activeViewport.displaySetInstanceUIDs[0] activeViewport.displaySetInstanceUIDs[0]
@ -133,7 +133,7 @@ export default function PanelMeasurementTable({
} }
const jumpToImage = ({ uid, isActive }) => { const jumpToImage = ({ uid, isActive }) => {
measurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
onMeasurementItemClickHandler({ uid, isActive }); onMeasurementItemClickHandler({ uid, isActive });
}; };

View File

@ -29,7 +29,7 @@ function PanelStudyBrowser({
// Tabs --> Studies --> DisplaySets --> Thumbnails // Tabs --> Studies --> DisplaySets --> Thumbnails
const { StudyInstanceUIDs } = useImageViewer(); const { StudyInstanceUIDs } = useImageViewer();
const [ const [
{ activeViewportIndex, viewports }, { activeViewportId, viewports },
viewportGridService, viewportGridService,
] = useViewportGrid(); ] = useViewportGrid();
const [activeTabName, setActiveTabName] = useState('primary'); const [activeTabName, setActiveTabName] = useState('primary');
@ -43,10 +43,10 @@ function PanelStudyBrowser({
const onDoubleClickThumbnailHandler = displaySetInstanceUID => { const onDoubleClickThumbnailHandler = displaySetInstanceUID => {
let updatedViewports = []; let updatedViewports = [];
const viewportIndex = activeViewportIndex; const viewportId = activeViewportId;
try { try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate( updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex, viewportId,
displaySetInstanceUID displaySetInstanceUID
); );
} catch (error) { } catch (error) {
@ -262,8 +262,8 @@ function PanelStudyBrowser({
} }
} }
const activeDisplaySetInstanceUIDs = const activeDisplaySetInstanceUIDs = viewports.get(activeViewportId)
viewports[activeViewportIndex]?.displaySetInstanceUIDs; ?.displaySetInstanceUIDs;
return ( return (
<StudyBrowser <StudyBrowser

View File

@ -261,17 +261,17 @@ const commandsModule = ({
]; ];
stateSyncService.store(stateSyncReduce); stateSyncService.store(stateSyncReduce);
// This is a default action applied // This is a default action applied
actions.toggleHpTools(hangingProtocolService.getActiveProtocol()); const { protocol } = hangingProtocolService.getActiveProtocol();
actions.toggleHpTools(protocol);
// Send the notification about updating the state // Send the notification about updating the state
if (protocolId !== hpInfo.protocolId) { if (protocolId !== hpInfo.protocolId) {
const { protocol } = hangingProtocolService.getActiveProtocol();
// The old protocol callbacks are used for turning off things // The old protocol callbacks are used for turning off things
// like crosshairs when moving to the new HP // like crosshairs when moving to the new HP
commandsManager.run(oldProtocol.callbacks?.onProtocolExit); commandsManager.run(oldProtocol.callbacks?.onProtocolExit);
// The new protocol callback is used for things like // The new protocol callback is used for things like
// activating modes etc. // activating modes etc.
commandsManager.run(protocol.callbacks?.onProtocolEnter);
} }
commandsManager.run(protocol.callbacks?.onProtocolEnter);
return true; return true;
} catch (e) { } catch (e) {
actions.toggleHpTools(hangingProtocolService.getActiveProtocol()); actions.toggleHpTools(hangingProtocolService.getActiveProtocol());
@ -394,12 +394,12 @@ const commandsModule = ({
toggleOneUp() { toggleOneUp() {
const viewportGridState = viewportGridService.getState(); const viewportGridState = viewportGridService.getState();
const { activeViewportIndex, viewports, layout } = viewportGridState; const { activeViewportId, viewports, layout } = viewportGridState;
const { const {
displaySetInstanceUIDs, displaySetInstanceUIDs,
displaySetOptions, displaySetOptions,
viewportOptions, viewportOptions,
} = viewports[activeViewportIndex]; } = viewports.get(activeViewportId);
if (layout.numCols === 1 && layout.numRows === 1) { if (layout.numCols === 1 && layout.numRows === 1) {
// The viewer is in one-up. Check if there is a state to restore/toggle back to. // The viewer is in one-up. Check if there is a state to restore/toggle back to.
@ -410,20 +410,16 @@ const commandsModule = ({
} }
// There is a state to toggle back to. The viewport that was // There is a state to toggle back to. The viewport that was
// originally toggled to one up was the former active viewport. // originally toggled to one up was the former active viewport.
const viewportIndexToUpdate = const viewportIdToUpdate =
toggleOneUpViewportGridStore.activeViewportIndex; toggleOneUpViewportGridStore.activeViewportId;
// Determine which viewports need to be updated. This is particularly const updatedViewportsViaHP =
// important when MPR is toggled to one up and a different reconstructable
// is swapped in. Note that currently HangingProtocolService.getViewportsRequireUpdate
// does not support viewport with multiple display sets.
const updatedViewports =
displaySetInstanceUIDs.length > 1 displaySetInstanceUIDs.length > 1
? [] ? []
: displaySetInstanceUIDs : displaySetInstanceUIDs
.map(displaySetInstanceUID => .map(displaySetInstanceUID =>
hangingProtocolService.getViewportsRequireUpdate( hangingProtocolService.getViewportsRequireUpdate(
viewportIndexToUpdate, viewportIdToUpdate,
displaySetInstanceUID displaySetInstanceUID
) )
) )
@ -432,14 +428,37 @@ const commandsModule = ({
// This findOrCreateViewport returns either one of the updatedViewports // This findOrCreateViewport returns either one of the updatedViewports
// returned from the HP service OR if there is not one from the HP service then // returned from the HP service OR if there is not one from the HP service then
// simply returns what was in the previous state. // simply returns what was in the previous state.
const findOrCreateViewport = (viewportIndex: number) => { const findOrCreateViewport = (position: number) => {
const viewport = updatedViewports.find( // get the viewportId in the current state (since we are in the one-up layout)
viewport => viewport.viewportIndex === viewportIndex const currentOneUpViewport = Array.from(viewports.values())[0];
);
return viewport // we should restore the previous layout but take into the account the fact that
? { viewportOptions, displaySetOptions, ...viewport } // the current one up viewport might have a new displaySet dragged and dropped on it
: toggleOneUpViewportGridStore.viewports[viewportIndex]; // so we should prioritize the current one in the old grid store layout viewports
const newViewports = Array.from(
toggleOneUpViewportGridStore.viewports.values()
).map(viewport => {
if (viewport.viewportId === currentOneUpViewport.viewportId) {
return {
...currentOneUpViewport,
};
}
return viewport;
});
// However, we also need to take into account that the current one up viewport
// might have been part of a bigger hanging protocol layout, so going back
// from one up we should apply those viewports as well.
return updatedViewportsViaHP.length > 1 &&
updatedViewportsViaHP[position]
? {
viewportOptions,
displaySetOptions,
...updatedViewportsViaHP[position],
}
: newViewports[position];
}; };
const layoutOptions = viewportGridService.getLayoutOptionsFromState( const layoutOptions = viewportGridService.getLayoutOptionsFromState(
@ -450,7 +469,7 @@ const commandsModule = ({
viewportGridService.setLayout({ viewportGridService.setLayout({
numRows: toggleOneUpViewportGridStore.layout.numRows, numRows: toggleOneUpViewportGridStore.layout.numRows,
numCols: toggleOneUpViewportGridStore.layout.numCols, numCols: toggleOneUpViewportGridStore.layout.numCols,
activeViewportIndex: viewportIndexToUpdate, activeViewportId: viewportIdToUpdate,
layoutOptions, layoutOptions,
findOrCreateViewport, findOrCreateViewport,
}); });
@ -520,8 +539,8 @@ const commandsModule = ({
}, },
openDICOMTagViewer() { openDICOMTagViewer() {
const { activeViewportIndex, viewports } = viewportGridService.getState(); const { activeViewportId, viewports } = viewportGridService.getState();
const activeViewportSpecificData = viewports[activeViewportIndex]; const activeViewportSpecificData = viewports.get(activeViewportId);
const { displaySetInstanceUIDs } = activeViewportSpecificData; const { displaySetInstanceUIDs } = activeViewportSpecificData;
const displaySets = displaySetService.activeDisplaySets; const displaySets = displaySetService.activeDisplaySets;
@ -552,17 +571,9 @@ const commandsModule = ({
}, },
scrollActiveThumbnailIntoView: () => { scrollActiveThumbnailIntoView: () => {
const { activeViewportIndex, viewports } = viewportGridService.getState(); const { activeViewportId, viewports } = viewportGridService.getState();
if ( const activeViewport = viewports.get(activeViewportId);
!viewports ||
activeViewportIndex < 0 ||
activeViewportIndex > viewports.length - 1
) {
return;
}
const activeViewport = viewports[activeViewportIndex];
const activeDisplaySetInstanceUID = const activeDisplaySetInstanceUID =
activeViewport.displaySetInstanceUIDs[0]; activeViewport.displaySetInstanceUIDs[0];
@ -615,9 +626,9 @@ const commandsModule = ({
currentDisplaySets.sort(dsSortFn); currentDisplaySets.sort(dsSortFn);
const { activeViewportIndex, viewports } = viewportGridService.getState(); const { activeViewportId, viewports } = viewportGridService.getState();
const { displaySetInstanceUIDs } = viewports[activeViewportIndex]; const { displaySetInstanceUIDs } = viewports.get(activeViewportId);
const activeDisplaySetIndex = currentDisplaySets.findIndex(displaySet => const activeDisplaySetIndex = currentDisplaySets.findIndex(displaySet =>
displaySetInstanceUIDs.includes(displaySet.displaySetInstanceUID) displaySetInstanceUIDs.includes(displaySet.displaySetInstanceUID)
@ -656,7 +667,7 @@ const commandsModule = ({
try { try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate( updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
activeViewportIndex, activeViewportId,
displaySetInstanceUID displaySetInstanceUID
); );
} catch (error) { } catch (error) {

View File

@ -1,4 +1,4 @@
import { StateSyncService, Types } from '@ohif/core'; import { StateSyncService } from '@ohif/core';
/** /**
* This find or create viewport is paired with the reduce results from * This find or create viewport is paired with the reduce results from
@ -7,7 +7,7 @@ import { StateSyncService, Types } from '@ohif/core';
* then one can be re-used from the display set if it isn't going to be displayed. * then one can be re-used from the display set if it isn't going to be displayed.
* @param hangingProtocolService - bound parameter supplied before using this * @param hangingProtocolService - bound parameter supplied before using this
* @param viewportsByPosition - bound parameter supplied before using this * @param viewportsByPosition - bound parameter supplied before using this
* @param viewportIndex - the index to retrieve * @param position - the position in the grid to retrieve
* @param positionId - the current position on screen to retrieve * @param positionId - the current position on screen to retrieve
* @param options - the set of options used, so that subsequent calls can * @param options - the set of options used, so that subsequent calls can
* store state that is reset by the setLayout. * store state that is reset by the setLayout.
@ -17,7 +17,7 @@ import { StateSyncService, Types } from '@ohif/core';
export const findOrCreateViewport = ( export const findOrCreateViewport = (
hangingProtocolService, hangingProtocolService,
viewportsByPosition, viewportsByPosition,
viewportIndex: number, position: number,
positionId: string, positionId: string,
options: Record<string, unknown> options: Record<string, unknown>
) => { ) => {
@ -74,24 +74,19 @@ const findViewportsByPosition = (
const viewportsByPosition = { ...syncState.viewportsByPosition }; const viewportsByPosition = { ...syncState.viewportsByPosition };
const initialInDisplay = []; const initialInDisplay = [];
for (const viewport of viewports) { viewports.forEach(viewport => {
if (viewport.positionId) { if (viewport.positionId) {
const storedViewport = { const storedViewport = {
...viewport, ...viewport,
viewportOptions: { ...viewport.viewportOptions }, viewportOptions: { ...viewport.viewportOptions },
}; };
viewportsByPosition[viewport.positionId] = storedViewport; viewportsByPosition[viewport.positionId] = storedViewport;
// The cache doesn't store the viewport options - it is only useful
// for remembering the type of viewport and UIDs
delete storedViewport.viewportId;
delete storedViewport.viewportOptions.viewportId;
} }
} });
for (let row = 0; row < numRows; row++) { for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) { for (let col = 0; col < numCols; col++) {
const pos = col + row * numCols; const positionId = `${col}-${row}`;
const positionId = viewports?.[pos]?.positionId || `${col}-${row}`;
const viewport = viewportsByPosition[positionId]; const viewport = viewportsByPosition[positionId];
if (viewport?.displaySetInstanceUIDs) { if (viewport?.displaySetInstanceUIDs) {
initialInDisplay.push(...viewport.displaySetInstanceUIDs); initialInDisplay.push(...viewport.displaySetInstanceUIDs);

View File

@ -73,6 +73,7 @@ const defaultProtocol = {
{ {
viewportOptions: { viewportOptions: {
viewportType: 'stack', viewportType: 'stack',
viewportId: 'default',
toolGroupId: 'default', toolGroupId: 'default',
// This will specify the initial image options index if it matches in the URL // This will specify the initial image options index if it matches in the URL
// and will otherwise not specify anything. // and will otherwise not specify anything.

View File

@ -19,22 +19,24 @@ const reuseCachedLayout = (
hangingProtocolService: HangingProtocolService, hangingProtocolService: HangingProtocolService,
syncService: StateSyncService syncService: StateSyncService
): ReturnType => { ): ReturnType => {
const { activeViewportIndex, viewports, layout } = state; const { activeViewportId } = state;
const { protocol } = hangingProtocolService.getActiveProtocol();
const hpInfo = hangingProtocolService.getState(); const hpInfo = hangingProtocolService.getState();
const { protocolId, stageIndex, activeStudyUID } = hpInfo; const { protocolId, stageIndex, activeStudyUID } = hpInfo;
const { protocol } = hangingProtocolService.getActiveProtocol();
const syncState = syncService.getState();
const viewportGridStore = { ...syncState.viewportGridStore };
const displaySetSelectorMap = { ...syncState.displaySetSelectorMap };
const stage = protocol.stages[stageIndex]; const stage = protocol.stages[stageIndex];
const storeId = `${activeStudyUID}:${protocolId}:${stageIndex}`; const storeId = `${activeStudyUID}:${protocolId}:${stageIndex}`;
const syncState = syncService.getState();
const cacheId = `${activeStudyUID}:${protocolId}`; const cacheId = `${activeStudyUID}:${protocolId}`;
const viewportGridStore = { ...syncState.viewportGridStore };
const hangingProtocolStageIndexMap = { const hangingProtocolStageIndexMap = {
...syncState.hangingProtocolStageIndexMap, ...syncState.hangingProtocolStageIndexMap,
}; };
const displaySetSelectorMap = { ...syncState.displaySetSelectorMap };
const { rows, columns } = stage.viewportStructure.properties; const { rows, columns } = stage.viewportStructure.properties;
const custom = const custom =
stage.viewports.length !== state.viewports.length || stage.viewports.length !== state.viewports.size ||
state.layout.numRows !== rows || state.layout.numRows !== rows ||
state.layout.numCols !== columns; state.layout.numCols !== columns;
@ -44,18 +46,17 @@ const reuseCachedLayout = (
viewportGridStore[storeId] = { ...state }; viewportGridStore[storeId] = { ...state };
} }
for (let idx = 0; idx < state.viewports.length; idx++) { state.viewports.forEach((viewport, viewportId) => {
const viewport = state.viewports[idx];
const { displaySetOptions, displaySetInstanceUIDs } = viewport; const { displaySetOptions, displaySetInstanceUIDs } = viewport;
if (!displaySetOptions) { if (!displaySetOptions) {
continue; return;
} }
for (let i = 0; i < displaySetOptions.length; i++) { for (let i = 0; i < displaySetOptions.length; i++) {
const displaySetUID = displaySetInstanceUIDs[i]; const displaySetUID = displaySetInstanceUIDs[i];
if (!displaySetUID) { if (!displaySetUID) {
continue; continue;
} }
if (idx === activeViewportIndex && i === 0) { if (viewportId === activeViewportId && i === 0) {
displaySetSelectorMap[ displaySetSelectorMap[
`${activeStudyUID}:activeDisplaySet:0` `${activeStudyUID}:activeDisplaySet:0`
] = displaySetUID; ] = displaySetUID;
@ -67,7 +68,7 @@ const reuseCachedLayout = (
] = displaySetUID; ] = displaySetUID;
} }
} }
} });
return { return {
hangingProtocolStageIndexMap, hangingProtocolStageIndexMap,

View File

@ -49,12 +49,12 @@ class DicomMicroscopyViewport extends Component {
static propTypes = { static propTypes = {
viewportData: PropTypes.object, viewportData: PropTypes.object,
activeViewportIndex: PropTypes.number, activeViewportId: PropTypes.string,
setViewportActive: PropTypes.func, setViewportActive: PropTypes.func,
// props from OHIF Viewport Grid // props from OHIF Viewport Grid
displaySets: PropTypes.array, displaySets: PropTypes.array,
viewportIndex: PropTypes.number, viewportId: PropTypes.string,
viewportLabel: PropTypes.string, viewportLabel: PropTypes.string,
dataSource: PropTypes.object, dataSource: PropTypes.object,
viewportOptions: PropTypes.object, viewportOptions: PropTypes.object,
@ -230,7 +230,7 @@ class DicomMicroscopyViewport extends Component {
this.managedViewer = this.microscopyService.addViewer( this.managedViewer = this.microscopyService.addViewer(
this.viewer, this.viewer,
this.props.viewportIndex, this.props.viewportId,
container, container,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUID SeriesInstanceUID
@ -259,8 +259,10 @@ class DicomMicroscopyViewport extends Component {
} }
componentDidMount() { componentDidMount() {
const { displaySets, viewportIndex } = this.props; const { displaySets, viewportOptions } = this.props;
const displaySet = displaySets[viewportIndex]; const { viewportId } = viewportOptions;
// Todo-rename: this is always getting the 0
const displaySet = displaySets[0];
this.installOpenLayersRenderer(this.container.current, displaySet).then( this.installOpenLayersRenderer(this.container.current, displaySet).then(
() => { () => {
this.setState({ isLoaded: true }); this.setState({ isLoaded: true });
@ -295,14 +297,10 @@ class DicomMicroscopyViewport extends Component {
} }
setViewportActiveHandler = () => { setViewportActiveHandler = () => {
const { const { setViewportActive, viewportId, activeViewportId } = this.props;
setViewportActive,
viewportIndex,
activeViewportIndex,
} = this.props;
if (viewportIndex !== activeViewportIndex) { if (viewportId !== activeViewportId) {
setViewportActive(viewportIndex); setViewportActive(viewportId);
} }
}; };

View File

@ -52,7 +52,7 @@ const formatLength = (length, unit) => {
interface IMicroscopyPanelProps extends WithTranslation { interface IMicroscopyPanelProps extends WithTranslation {
viewports: PropTypes.array; viewports: PropTypes.array;
activeViewportIndex: PropTypes.number; activeViewportId: PropTypes.string;
// //
onSaveComplete?: PropTypes.func; // callback when successfully saved annotations onSaveComplete?: PropTypes.func; // callback when successfully saved annotations
@ -83,7 +83,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const { uiDialogService, displaySetService } = servicesManager.services; const { uiDialogService, displaySetService } = servicesManager.services;
useEffect(() => { useEffect(() => {
const viewport = props.viewports[props.activeViewportIndex]; const viewport = props.viewports[props.activeViewportId];
if (viewport.displaySetInstanceUIDs[0]) { if (viewport.displaySetInstanceUIDs[0]) {
const displaySet = displaySetService.getDisplaySetByUID( const displaySet = displaySetService.getDisplaySetByUID(
viewport.displaySetInstanceUIDs[0] viewport.displaySetInstanceUIDs[0]
@ -92,7 +92,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
setStudyInstanceUID(displaySet.StudyInstanceUID); setStudyInstanceUID(displaySet.StudyInstanceUID);
} }
} }
}, [props.viewports, props.activeViewportIndex]); }, [props.viewports, props.activeViewportId]);
useEffect(() => { useEffect(() => {
const onAnnotationUpdated = () => { const onAnnotationUpdated = () => {
@ -265,7 +265,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
*/ */
const onDeleteCurrentSRHandler = async () => { const onDeleteCurrentSRHandler = async () => {
try { try {
const activeViewport = props.viewports[props.activeViewportIndex]; const activeViewport = props.viewports[props.activeViewportId];
const { StudyInstanceUID } = activeViewport; const { StudyInstanceUID } = activeViewport;
// TODO: studies? // TODO: studies?
@ -307,7 +307,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => { const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => {
const roiAnnotation = microscopyService.getAnnotation(uid); const roiAnnotation = microscopyService.getAnnotation(uid);
microscopyService.selectAnnotation(roiAnnotation); microscopyService.selectAnnotation(roiAnnotation);
microscopyService.focusAnnotation(roiAnnotation, props.activeViewportIndex); microscopyService.focusAnnotation(roiAnnotation, props.activeViewportId);
}; };
/** /**

View File

@ -122,19 +122,6 @@ export default function getCommandsModule({
]); ]);
} }
}, },
incrementActiveViewport: () => {
const { activeViewportIndex, viewports } = viewportGridService.getState();
const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
viewportGridService.setActiveViewportIndex(nextViewportIndex);
},
decrementActiveViewport: () => {
const { activeViewportIndex, viewports } = viewportGridService.getState();
const nextViewportIndex =
(activeViewportIndex - 1 + viewports.length) % viewports.length;
viewportGridService.setActiveViewportIndex(nextViewportIndex);
},
toggleOverlays: () => { toggleOverlays: () => {
// overlay // overlay
const overlays = document.getElementsByClassName( const overlays = document.getElementsByClassName(
@ -149,8 +136,8 @@ export default function getCommandsModule({
} }
// overview // overview
const { activeViewportIndex, viewports } = viewportGridService.getState(); const { activeViewportId, viewports } = viewportGridService.getState();
microscopyService.toggleOverviewMap(activeViewportIndex); microscopyService.toggleOverviewMap(activeViewportId);
}, },
toggleAnnotations: () => { toggleAnnotations: () => {
microscopyService.toggleROIsVisibility(); microscopyService.toggleROIsVisibility();
@ -173,14 +160,6 @@ export default function getCommandsModule({
storeContexts: [] as any[], storeContexts: [] as any[],
options: {}, options: {},
}, },
incrementActiveViewport: {
commandFn: actions.incrementActiveViewport,
storeContexts: [] as any[],
},
decrementActiveViewport: {
commandFn: actions.decrementActiveViewport,
storeContexts: [] as any[],
},
toggleOverlays: { toggleOverlays: {
commandFn: actions.toggleOverlays, commandFn: actions.toggleOverlays,
storeContexts: [] as any[], storeContexts: [] as any[],

View File

@ -18,15 +18,12 @@ export default function getPanelModule({
extensionManager: ExtensionManager; extensionManager: ExtensionManager;
}) { }) {
const wrappedMeasurementPanel = () => { const wrappedMeasurementPanel = () => {
const [ const [{ activeViewportId, viewports }] = useViewportGrid();
{ activeViewportIndex, viewports },
viewportGridService,
] = useViewportGrid();
return ( return (
<MicroscopyPanel <MicroscopyPanel
viewports={viewports} viewports={viewports}
activeViewportIndex={activeViewportIndex} activeViewportId={activeViewportId}
onSaveComplete={() => {}} onSaveComplete={() => {}}
onRejectComplete={() => {}} onRejectComplete={() => {}}
commandsManager={commandsManager} commandsManager={commandsManager}

View File

@ -52,7 +52,7 @@ export default {
* *
* @param props {*} * @param props {*}
* @param props.displaySets * @param props.displaySets
* @param props.viewportIndex * @param props.viewportId
* @param props.viewportLabel * @param props.viewportLabel
* @param props.dataSource * @param props.dataSource
* @param props.viewportOptions * @param props.viewportOptions
@ -63,16 +63,16 @@ export default {
const { viewportOptions } = props; const { viewportOptions } = props;
const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportGrid, viewportGridService] = useViewportGrid();
const { viewports, activeViewportIndex } = viewportGrid; const { activeViewportId } = viewportGrid;
return ( return (
<MicroscopyViewport <MicroscopyViewport
servicesManager={servicesManager} servicesManager={servicesManager}
extensionManager={extensionManager} extensionManager={extensionManager}
commandsManager={commandsManager} commandsManager={commandsManager}
activeViewportIndex={activeViewportIndex} activeViewportId={activeViewportId}
setViewportActive={(viewportIndex: number) => { setViewportActive={(viewportId: string) => {
viewportGridService.setActiveViewportIndex(viewportIndex); viewportGridService.setActiveViewportId(viewportId);
}} }}
viewportData={viewportOptions} viewportData={viewportOptions}
{...props} {...props}

View File

@ -286,31 +286,31 @@ export default class MicroscopyService extends PubSubService {
} }
/** /**
* Creates a managed viewer instance for the given thrid-party API's viewer. * Creates a managed viewer instance for the given third-party API's viewer.
* Restores existing annotations for the given study/series. * Restores existing annotations for the given study/series.
* Adds event subscriptions for the viewer being added. * Adds event subscriptions for the viewer being added.
* Focuses the selected annotation when the viewer is being loaded into the * Focuses the selected annotation when the viewer is being loaded into the
* active viewport. * active viewport.
* *
* @param {Object} viewer Third-party viewer API's object to be managed * @param viewer - Third-party viewer API's object to be managed
* @param {Number} viewportIndex The index of the viewport to load the viewer * @param viewportId - The viewport Id where the viewer will be loaded
* @param {HTMLElement} container The DOM element where it will be renderd * @param container - The DOM element where it will be rendered
* @param {String} studyInstanceUID The study UID of the loaded image * @param studyInstanceUID - The study UID of the loaded image
* @param {String} seriesInstanceUID The series UID of the loaded image * @param seriesInstanceUID - The series UID of the loaded image
* @param {Array} displaySets All displaySets related to the same StudyInstanceUID * @param displaySets - All displaySets related to the same StudyInstanceUID
* *
* @returns {ViewerManager} managed viewer * @returns {ViewerManager} managed viewer
*/ */
addViewer( addViewer(
viewer, viewer,
viewportIndex, viewportId,
container, container,
studyInstanceUID, studyInstanceUID,
seriesInstanceUID seriesInstanceUID
) { ) {
const managedViewer = new ViewerManager( const managedViewer = new ViewerManager(
viewer, viewer,
viewportIndex, viewportId,
container, container,
studyInstanceUID, studyInstanceUID,
seriesInstanceUID seriesInstanceUID
@ -325,7 +325,7 @@ export default class MicroscopyService extends PubSubService {
if (this.pendingFocus) { if (this.pendingFocus) {
this.pendingFocus = false; this.pendingFocus = false;
this.focusAnnotation(this.selectedAnnotation, viewportIndex); this.focusAnnotation(this.selectedAnnotation, viewportId);
} }
return managedViewer; return managedViewer;
@ -502,13 +502,13 @@ export default class MicroscopyService extends PubSubService {
/** /**
* Toggles overview map * Toggles overview map
* *
* @param viewportIndex The active viewport index * @param viewportId The active viewport index
* @returns {void} * @returns {void}
*/ */
toggleOverviewMap(viewportIndex) { toggleOverviewMap(viewportId) {
const managedViewers = Array.from(this.managedViewers); const managedViewers = Array.from(this.managedViewers);
const managedViewer = managedViewers.find( const managedViewer = managedViewers.find(
mv => mv.viewportIndex === viewportIndex mv => mv.viewportId === viewportId
); );
if (managedViewer) { if (managedViewer) {
managedViewer.toggleOverviewMap(); managedViewer.toggleOverviewMap();
@ -550,10 +550,10 @@ export default class MicroscopyService extends PubSubService {
* the managed viewer instance is created. * the managed viewer instance is created.
* *
* @param {RoiAnnotation} roiAnnotation RoiAnnotation instance to be focused * @param {RoiAnnotation} roiAnnotation RoiAnnotation instance to be focused
* @param {Number} viewportIndex Index of the viewport to focus * @param {string} viewportId Index of the viewport to focus
*/ */
focusAnnotation(roiAnnotation, viewportIndex) { focusAnnotation(roiAnnotation, viewportId) {
const filter = mv => mv.viewportIndex === viewportIndex; const filter = mv => mv.viewportId === viewportId;
const managedViewer = Array.from(this.managedViewers).find(filter); const managedViewer = Array.from(this.managedViewers).find(filter);
if (managedViewer) { if (managedViewer) {
managedViewer.setViewStateByExtent(roiAnnotation); managedViewer.setViewStateByExtent(roiAnnotation);

View File

@ -48,14 +48,14 @@ const EVENTS = {
class ViewerManager extends PubSubService { class ViewerManager extends PubSubService {
constructor( constructor(
viewer, viewer,
viewportIndex, viewportId,
container, container,
studyInstanceUID, studyInstanceUID,
seriesInstanceUID seriesInstanceUID
) { ) {
super(EVENTS); super(EVENTS);
this.viewer = viewer; this.viewer = viewer;
this.viewportIndex = viewportIndex; this.viewportId = viewportId;
this.container = container; this.container = container;
this.studyInstanceUID = studyInstanceUID; this.studyInstanceUID = studyInstanceUID;
this.seriesInstanceUID = seriesInstanceUID; this.seriesInstanceUID = seriesInstanceUID;

View File

@ -33,7 +33,7 @@ function TrackedMeasurementsContextProvider(
const [appConfig] = useAppConfig(); const [appConfig] = useAppConfig();
const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportGrid, viewportGridService] = useViewportGrid();
const { activeViewportIndex, viewports } = viewportGrid; const { activeViewportId, viewports } = viewportGrid;
const { measurementService, displaySetService } = servicesManager.services; const { measurementService, displaySetService } = servicesManager.services;
const machineOptions = Object.assign({}, defaultOptions); const machineOptions = Object.assign({}, defaultOptions);
@ -49,7 +49,7 @@ function TrackedMeasurementsContextProvider(
console.log( console.log(
'jumping to measurement reset viewport', 'jumping to measurement reset viewport',
viewportGrid.activeViewportIndex, viewportGrid.activeViewportId,
trackedMeasurements[0] trackedMeasurements[0]
); );
@ -84,7 +84,7 @@ function TrackedMeasurementsContextProvider(
} }
viewportGridService.setDisplaySetsForViewport({ viewportGridService.setDisplaySetsForViewport({
viewportIndex: viewportGrid.activeViewportIndex, viewportId: viewportGrid.activeViewportId,
displaySetInstanceUIDs: [referencedDisplaySetUID], displaySetInstanceUIDs: [referencedDisplaySetUID],
viewportOptions: { viewportOptions: {
initialImageOptions: { initialImageOptions: {
@ -99,7 +99,7 @@ function TrackedMeasurementsContextProvider(
evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID; evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID;
viewportGridService.setDisplaySetsForViewport({ viewportGridService.setDisplaySetsForViewport({
viewportIndex: evt.data.viewportIndex, viewportId: evt.data.viewportId,
displaySetInstanceUIDs: [StructuredReportDisplaySetInstanceUID], displaySetInstanceUIDs: [StructuredReportDisplaySetInstanceUID],
}); });
} }
@ -172,16 +172,14 @@ function TrackedMeasurementsContextProvider(
machineOptions machineOptions
); );
const [ const [trackedMeasurements, sendTrackedMeasurementsEvent] = useMachine(
trackedMeasurements, measurementTrackingMachine
sendTrackedMeasurementsEvent, );
trackedMeasurementsService,
] = useMachine(measurementTrackingMachine);
// ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle // ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle
useEffect(() => { useEffect(() => {
if (viewports.length > 0) { if (viewports.size > 0) {
const activeViewport = viewports[activeViewportIndex]; const activeViewport = viewports.get(activeViewportId);
if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) { if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) {
return; return;
@ -229,12 +227,12 @@ function TrackedMeasurementsContextProvider(
sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', { sendTrackedMeasurementsEvent('PROMPT_HYDRATE_SR', {
displaySetInstanceUID: displaySet.displaySetInstanceUID, displaySetInstanceUID: displaySet.displaySetInstanceUID,
SeriesInstanceUID: displaySet.SeriesInstanceUID, SeriesInstanceUID: displaySet.SeriesInstanceUID,
viewportIndex: activeViewportIndex, viewportId: activeViewportId,
}); });
} }
} }
}, [ }, [
activeViewportIndex, activeViewportId,
sendTrackedMeasurementsEvent, sendTrackedMeasurementsEvent,
servicesManager.services, servicesManager.services,
viewports, viewports,

View File

@ -6,7 +6,7 @@ function hydrateStructuredReport(
evt evt
) { ) {
const { displaySetService } = servicesManager.services; const { displaySetService } = servicesManager.services;
const { viewportIndex, displaySetInstanceUID } = evt; const { viewportId, displaySetInstanceUID } = evt;
const srDisplaySet = displaySetService.getDisplaySetByUID( const srDisplaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID displaySetInstanceUID
); );
@ -23,7 +23,7 @@ function hydrateStructuredReport(
resolve({ resolve({
displaySetInstanceUID: evt.displaySetInstanceUID, displaySetInstanceUID: evt.displaySetInstanceUID,
srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID, srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
viewportIndex, viewportId,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUIDs, SeriesInstanceUIDs,
}); });

View File

@ -10,24 +10,24 @@ const RESPONSE = {
function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) { function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) {
const { uiViewportDialogService } = servicesManager.services; const { uiViewportDialogService } = servicesManager.services;
const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt;
return new Promise(async function(resolve, reject) { return new Promise(async function(resolve, reject) {
let promptResult = await _askTrackMeasurements( let promptResult = await _askTrackMeasurements(
uiViewportDialogService, uiViewportDialogService,
viewportIndex viewportId
); );
resolve({ resolve({
userResponse: promptResult, userResponse: promptResult,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUID, SeriesInstanceUID,
viewportIndex, viewportId,
}); });
}); });
} }
function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { function _askTrackMeasurements(uiViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = 'Track measurements for this series?'; const message = 'Track measurements for this series?';
const actions = [ const actions = [
@ -56,7 +56,7 @@ function _askTrackMeasurements(uiViewportDialogService, viewportIndex) {
}; };
uiViewportDialogService.show({ uiViewportDialogService.show({
viewportIndex, viewportId,
id: 'measurement-tracking-prompt-begin-tracking', id: 'measurement-tracking-prompt-begin-tracking',
type: 'info', type: 'info',
message, message,

View File

@ -20,15 +20,15 @@ function promptHydrateStructuredReport(
uiViewportDialogService, uiViewportDialogService,
displaySetService, displaySetService,
} = servicesManager.services; } = servicesManager.services;
const { viewportIndex, displaySetInstanceUID } = evt; const { viewportId, displaySetInstanceUID } = evt;
const srDisplaySet = displaySetService.getDisplaySetByUID( const srDisplaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID displaySetInstanceUID
); );
return new Promise(async function (resolve, reject) { return new Promise(async function(resolve, reject) {
const promptResult = await _askTrackMeasurements( const promptResult = await _askTrackMeasurements(
uiViewportDialogService, uiViewportDialogService,
viewportIndex viewportId
); );
// Need to do action here... So we can set state... // Need to do action here... So we can set state...
@ -49,15 +49,15 @@ function promptHydrateStructuredReport(
userResponse: promptResult, userResponse: promptResult,
displaySetInstanceUID: evt.displaySetInstanceUID, displaySetInstanceUID: evt.displaySetInstanceUID,
srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID, srSeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
viewportIndex, viewportId,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUIDs, SeriesInstanceUIDs,
}); });
}); });
} }
function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { function _askTrackMeasurements(uiViewportDialogService, viewportId) {
return new Promise(function (resolve, reject) { return new Promise(function(resolve, reject) {
const message = const message =
'Do you want to continue tracking measurements for this study?'; 'Do you want to continue tracking measurements for this study?';
const actions = [ const actions = [
@ -78,7 +78,7 @@ function _askTrackMeasurements(uiViewportDialogService, viewportIndex) {
}; };
uiViewportDialogService.show({ uiViewportDialogService.show({
viewportIndex, viewportId,
type: 'info', type: 'info',
message, message,
actions, actions,

View File

@ -13,10 +13,8 @@ function promptSaveReport(
measurementService, measurementService,
displaySetService, displaySetService,
} = servicesManager.services; } = servicesManager.services;
const viewportIndex = const viewportId =
evt.viewportIndex === undefined evt.viewportId === undefined ? evt.data.viewportId : evt.viewportId;
? evt.data.viewportIndex
: evt.viewportIndex;
const isBackupSave = const isBackupSave =
evt.isBackupSave === undefined ? evt.data.isBackupSave : evt.isBackupSave; evt.isBackupSave === undefined ? evt.data.isBackupSave : evt.isBackupSave;
const StudyInstanceUID = evt?.data?.StudyInstanceUID; const StudyInstanceUID = evt?.data?.StudyInstanceUID;
@ -66,7 +64,7 @@ function promptSaveReport(
createdDisplaySetInstanceUIDs: displaySetInstanceUIDs, createdDisplaySetInstanceUIDs: displaySetInstanceUIDs,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUID, SeriesInstanceUID,
viewportIndex, viewportId,
isBackupSave, isBackupSave,
}); });
}); });

View File

@ -11,17 +11,17 @@ const RESPONSE = {
function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) { function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) {
const { UIViewportDialogService } = servicesManager.services; const { UIViewportDialogService } = servicesManager.services;
const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt;
return new Promise(async function(resolve, reject) { return new Promise(async function(resolve, reject) {
let promptResult = await _askShouldAddMeasurements( let promptResult = await _askShouldAddMeasurements(
UIViewportDialogService, UIViewportDialogService,
viewportIndex viewportId
); );
if (promptResult === RESPONSE.CREATE_REPORT) { if (promptResult === RESPONSE.CREATE_REPORT) {
promptResult = ctx.isDirty promptResult = ctx.isDirty
? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) ? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportId)
: RESPONSE.SET_STUDY_AND_SERIES; : RESPONSE.SET_STUDY_AND_SERIES;
} }
@ -29,13 +29,13 @@ function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) {
userResponse: promptResult, userResponse: promptResult,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUID, SeriesInstanceUID,
viewportIndex, viewportId,
isBackupSave: false, isBackupSave: false,
}); });
}); });
} }
function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) { function _askShouldAddMeasurements(uiViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = const message =
'Do you want to add this measurement to the existing report?'; 'Do you want to add this measurement to the existing report?';
@ -62,7 +62,7 @@ function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) {
}; };
uiViewportDialogService.show({ uiViewportDialogService.show({
viewportIndex, viewportId,
type: 'info', type: 'info',
message, message,
actions, actions,
@ -75,7 +75,7 @@ function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) {
}); });
} }
function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { function _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = const message =
'You have existing tracked measurements. What would you like to do with your existing tracked measurements?'; 'You have existing tracked measurements. What would you like to do with your existing tracked measurements?';
@ -98,7 +98,7 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) {
}; };
UIViewportDialogService.show({ UIViewportDialogService.show({
viewportIndex, viewportId,
type: 'warning', type: 'warning',
message, message,
actions, actions,

View File

@ -9,17 +9,17 @@ const RESPONSE = {
function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) {
const { UIViewportDialogService } = servicesManager.services; const { UIViewportDialogService } = servicesManager.services;
const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; const { viewportId, StudyInstanceUID, SeriesInstanceUID } = evt;
return new Promise(async function(resolve, reject) { return new Promise(async function(resolve, reject) {
let promptResult = await _askTrackMeasurements( let promptResult = await _askTrackMeasurements(
UIViewportDialogService, UIViewportDialogService,
viewportIndex viewportId
); );
if (promptResult === RESPONSE.SET_STUDY_AND_SERIES) { if (promptResult === RESPONSE.SET_STUDY_AND_SERIES) {
promptResult = ctx.isDirty promptResult = ctx.isDirty
? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) ? await _askSaveDiscardOrCancel(UIViewportDialogService, viewportId)
: RESPONSE.SET_STUDY_AND_SERIES; : RESPONSE.SET_STUDY_AND_SERIES;
} }
@ -27,13 +27,13 @@ function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) {
userResponse: promptResult, userResponse: promptResult,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUID, SeriesInstanceUID,
viewportIndex, viewportId,
isBackupSave: false, isBackupSave: false,
}); });
}); });
} }
function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { function _askTrackMeasurements(UIViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = 'Track measurements for this series?'; const message = 'Track measurements for this series?';
const actions = [ const actions = [
@ -55,7 +55,7 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) {
}; };
UIViewportDialogService.show({ UIViewportDialogService.show({
viewportIndex, viewportId,
type: 'info', type: 'info',
message, message,
actions, actions,
@ -68,7 +68,7 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) {
}); });
} }
function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { function _askSaveDiscardOrCancel(UIViewportDialogService, viewportId) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
const message = const message =
'Measurements cannot span across multiple studies. Do you want to save your tracked measurements?'; 'Measurements cannot span across multiple studies. Do you want to save your tracked measurements?';
@ -91,7 +91,7 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) {
}; };
UIViewportDialogService.show({ UIViewportDialogService.show({
viewportIndex, viewportId,
type: 'warning', type: 'warning',
message, message,
actions, actions,

View File

@ -25,7 +25,7 @@ const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = {
}; };
function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportGrid] = useViewportGrid();
const [measurementChangeTimestamp, setMeasurementsUpdated] = useState( const [measurementChangeTimestamp, setMeasurementsUpdated] = useState(
Date.now().toString() Date.now().toString()
); );
@ -157,7 +157,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
} }
const jumpToImage = ({ uid, isActive }) => { const jumpToImage = ({ uid, isActive }) => {
measurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
onMeasurementItemClickHandler({ uid, isActive }); onMeasurementItemClickHandler({ uid, isActive });
}; };
@ -280,7 +280,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
onExportClick={exportReport} onExportClick={exportReport}
onCreateReportClick={() => { onCreateReportClick={() => {
sendTrackedMeasurementsEvent('SAVE_REPORT', { sendTrackedMeasurementsEvent('SAVE_REPORT', {
viewportIndex: viewportGrid.activeViewportIndex, viewportId: viewportGrid.activeViewportId,
isBackupSave: true, isBackupSave: true,
}); });
}} }}

View File

@ -40,7 +40,7 @@ function PanelStudyBrowserTracking({
// Tabs --> Studies --> DisplaySets --> Thumbnails // Tabs --> Studies --> DisplaySets --> Thumbnails
const { StudyInstanceUIDs } = useImageViewer(); const { StudyInstanceUIDs } = useImageViewer();
const [ const [
{ activeViewportIndex, viewports }, { activeViewportId, viewports },
viewportGridService, viewportGridService,
] = useViewportGrid(); ] = useViewportGrid();
const [ const [
@ -58,10 +58,10 @@ function PanelStudyBrowserTracking({
const onDoubleClickThumbnailHandler = displaySetInstanceUID => { const onDoubleClickThumbnailHandler = displaySetInstanceUID => {
let updatedViewports = []; let updatedViewports = [];
const viewportIndex = activeViewportIndex; const viewportId = activeViewportId;
try { try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate( updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex, viewportId,
displaySetInstanceUID displaySetInstanceUID
); );
} catch (error) { } catch (error) {
@ -78,8 +78,8 @@ function PanelStudyBrowserTracking({
viewportGridService.setDisplaySetsForViewports(updatedViewports); viewportGridService.setDisplaySetsForViewports(updatedViewports);
}; };
const activeViewportDisplaySetInstanceUIDs = const activeViewportDisplaySetInstanceUIDs = viewports.get(activeViewportId)
viewports[activeViewportIndex]?.displaySetInstanceUIDs; ?.displaySetInstanceUIDs;
const { trackedSeries } = trackedMeasurements.context; const { trackedSeries } = trackedMeasurements.context;
@ -447,20 +447,19 @@ function _mapDisplaySets(
const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID];
const componentType = _getComponentType(ds); const componentType = _getComponentType(ds);
const numPanes = viewportGridService.getNumViewportPanes(); const numPanes = viewportGridService.getNumViewportPanes();
const viewportIdentificator = const viewportIdentificator = [];
numPanes === 1
? [] if (numPanes !== 1) {
: viewports.reduce((acc, viewportData, index) => { viewports.forEach(viewportData => {
if ( if (
index < numPanes && viewportData?.displaySetInstanceUIDs?.includes(
viewportData?.displaySetInstanceUIDs?.includes( ds.displaySetInstanceUID
ds.displaySetInstanceUID )
) ) {
) { viewportIdentificator.push(viewportData.viewportLabel);
acc.push(viewportData.viewportLabel); }
} });
return acc; }
}, []);
const array = const array =
componentType === 'thumbnailTracked' componentType === 'thumbnailTracked'

View File

@ -15,11 +15,10 @@ const { formatDate } = utils;
function TrackedCornerstoneViewport(props) { function TrackedCornerstoneViewport(props) {
const { const {
displaySets, displaySets,
viewportIndex, viewportId,
viewportLabel, viewportLabel,
servicesManager, servicesManager,
extensionManager, extensionManager,
viewportOptions,
} = props; } = props;
const { t } = useTranslation('Common'); const { t } = useTranslation('Common');
@ -43,7 +42,6 @@ function TrackedCornerstoneViewport(props) {
const [viewportElem, setViewportElem] = useState(null); const [viewportElem, setViewportElem] = useState(null);
const { trackedSeries } = trackedMeasurements.context; const { trackedSeries } = trackedMeasurements.context;
const viewportId = viewportOptions.viewportId;
const { const {
SeriesDate, SeriesDate,
@ -64,8 +62,8 @@ function TrackedCornerstoneViewport(props) {
} = displaySet.images[0]; } = displaySet.images[0];
const updateIsTracked = useCallback(() => { const updateIsTracked = useCallback(() => {
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( const viewport = cornerstoneViewportService.getCornerstoneViewport(
viewportIndex viewportId
); );
if (viewport instanceof BaseVolumeViewport) { if (viewport instanceof BaseVolumeViewport) {
@ -84,7 +82,7 @@ function TrackedCornerstoneViewport(props) {
if (trackedSeries.includes(SeriesInstanceUID) !== isTracked) { if (trackedSeries.includes(SeriesInstanceUID) !== isTracked) {
setIsTracked(!isTracked); setIsTracked(!isTracked);
} }
}, [isTracked, trackedMeasurements, viewportIndex, SeriesInstanceUID]); }, [isTracked, trackedMeasurements, viewportId, SeriesInstanceUID]);
const onElementEnabled = useCallback( const onElementEnabled = useCallback(
evt => { evt => {
@ -113,7 +111,7 @@ function TrackedCornerstoneViewport(props) {
const { unsubscribe } = cornerstoneViewportService.subscribe( const { unsubscribe } = cornerstoneViewportService.subscribe(
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
props => { props => {
if (props.viewportIndex !== viewportIndex) { if (props.viewportId !== viewportId) {
return; return;
} }
@ -124,7 +122,7 @@ function TrackedCornerstoneViewport(props) {
return () => { return () => {
unsubscribe(); unsubscribe();
}; };
}, [updateIsTracked, viewportIndex]); }, [updateIsTracked, viewportId]);
useEffect(() => { useEffect(() => {
if (isTracked) { if (isTracked) {
@ -169,12 +167,12 @@ function TrackedCornerstoneViewport(props) {
[added, addedRaw].forEach(evt => { [added, addedRaw].forEach(evt => {
subscriptions.push( subscriptions.push(
measurementService.subscribe(evt, ({ source, measurement }) => { measurementService.subscribe(evt, ({ source, measurement }) => {
const { activeViewportIndex } = viewportGridService.getState(); const { activeViewportId } = viewportGridService.getState();
// Each TrackedCornerstoneViewport receives the MeasurementService's events. // Each TrackedCornerstoneViewport receives the MeasurementService's events.
// Only send the tracked measurements event for the active viewport to avoid // Only send the tracked measurements event for the active viewport to avoid
// sending it more than once. // sending it more than once.
if (viewportIndex === activeViewportIndex) { if (viewportId === activeViewportId) {
const { const {
referenceStudyUID: StudyInstanceUID, referenceStudyUID: StudyInstanceUID,
referenceSeriesUID: SeriesInstanceUID, referenceSeriesUID: SeriesInstanceUID,
@ -182,7 +180,7 @@ function TrackedCornerstoneViewport(props) {
sendTrackedMeasurementsEvent('SET_DIRTY', { SeriesInstanceUID }); sendTrackedMeasurementsEvent('SET_DIRTY', { SeriesInstanceUID });
sendTrackedMeasurementsEvent('TRACK_SERIES', { sendTrackedMeasurementsEvent('TRACK_SERIES', {
viewportIndex, viewportId,
StudyInstanceUID, StudyInstanceUID,
SeriesInstanceUID, SeriesInstanceUID,
}); });
@ -199,7 +197,7 @@ function TrackedCornerstoneViewport(props) {
}, [ }, [
measurementService, measurementService,
sendTrackedMeasurementsEvent, sendTrackedMeasurementsEvent,
viewportIndex, viewportId,
viewportGridService, viewportGridService,
]); ]);
@ -217,10 +215,7 @@ function TrackedCornerstoneViewport(props) {
setTrackedMeasurementUID(newTrackedMeasurementUID); setTrackedMeasurementUID(newTrackedMeasurementUID);
measurementService.jumpToMeasurement( measurementService.jumpToMeasurement(viewportId, newTrackedMeasurementUID);
viewportIndex,
newTrackedMeasurementUID
);
} }
const getCornerstoneViewport = () => { const getCornerstoneViewport = () => {
@ -280,7 +275,7 @@ function TrackedCornerstoneViewport(props) {
TrackedCornerstoneViewport.propTypes = { TrackedCornerstoneViewport.propTypes = {
displaySets: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired, displaySets: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired,
viewportIndex: PropTypes.number.isRequired, viewportId: PropTypes.string.isRequired,
dataSource: PropTypes.object, dataSource: PropTypes.object,
children: PropTypes.node, children: PropTypes.node,
customProps: PropTypes.object, customProps: PropTypes.object,
@ -299,10 +294,10 @@ function _getNextMeasurementUID(
const { measurementService, viewportGridService } = servicesManager.services; const { measurementService, viewportGridService } = servicesManager.services;
const measurements = measurementService.getMeasurements(); const measurements = measurementService.getMeasurements();
const { activeViewportIndex, viewports } = viewportGridService.getState(); const { activeViewportId, viewports } = viewportGridService.getState();
const { const {
displaySetInstanceUIDs: activeViewportDisplaySetInstanceUIDs, displaySetInstanceUIDs: activeViewportDisplaySetInstanceUIDs,
} = viewports[activeViewportIndex]; } = viewports.get(activeViewportId);
const { trackedSeries } = trackedMeasurements.context; const { trackedSeries } = trackedMeasurements.context;

View File

@ -0,0 +1,246 @@
import { Types } from '@ohif/core';
const viewport0a = {
viewportOptions: {
viewportId: 'viewportA',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
id: 'defaultDisplaySetId',
},
],
};
const viewport1b = {
viewportOptions: {
viewportId: 'viewportB',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 1,
id: 'defaultDisplaySetId',
},
],
};
const viewport2c = {
viewportOptions: {
viewportId: 'viewportC',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 2,
id: 'defaultDisplaySetId',
},
],
};
const viewport3d = {
viewportOptions: {
viewportId: 'viewportD',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 3,
id: 'defaultDisplaySetId',
},
],
};
const viewport4e = {
viewportOptions: {
viewportId: 'viewportE',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 4,
id: 'defaultDisplaySetId',
},
],
};
const viewport5f = {
viewportOptions: {
viewportId: 'viewportF',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 5,
id: 'defaultDisplaySetId',
},
],
};
const viewport3a = {
viewportOptions: {
viewportId: 'viewportA',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 3,
id: 'defaultDisplaySetId',
},
],
};
const viewport2b = {
viewportOptions: {
viewportId: 'viewportB',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 2,
id: 'defaultDisplaySetId',
},
],
};
const viewport1c = {
viewportOptions: {
viewportId: 'viewportC',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 1,
id: 'defaultDisplaySetId',
},
],
};
const viewport0d = {
viewportOptions: {
viewportId: 'viewportD',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
matchedDisplaySetsIndex: 0,
id: 'defaultDisplaySetId',
},
],
};
const viewportStructure = {
layoutType: 'grid',
properties: {
rows: 2,
columns: 2,
},
};
const viewportStructure32 = {
layoutType: 'grid',
properties: {
rows: 2,
columns: 3,
},
};
/**
* This hanging protocol is a test hanging protocol used to apply various
* layouts in different positions for display, re-using earlier names in
* various orders.
*/
const hpTestSwitch: Types.HangingProtocol.Protocol = {
hasUpdatedPriorsInformation: false,
id: '@ohif/mnTestSwitch',
description: 'Has various hanging protocol grid layouts',
name: 'Test Switch',
protocolMatchingRules: [
{
id: 'OneOrMoreSeries',
weight: 25,
attribute: 'numberOfDisplaySetsWithImages',
constraint: {
greaterThan: 0,
},
},
],
toolGroupIds: ['default'],
displaySetSelectors: {
defaultDisplaySetId: {
seriesMatchingRules: [
{
attribute: 'numImageFrames',
constraint: {
greaterThan: { value: 0 },
},
},
// This display set will select the specified items by preference
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
constraint: {
equals: true,
},
},
],
},
},
defaultViewport: {
viewportOptions: {
viewportType: 'stack',
toolGroupId: 'default',
allowUnmatchedView: true,
},
displaySets: [
{
id: 'defaultDisplaySetId',
matchedDisplaySetsIndex: -1,
},
],
},
stages: [
{
name: '2x2 0a1b2c3d',
viewportStructure,
viewports: [viewport0a, viewport1b, viewport2c, viewport3d],
},
{
name: '3x2 0a1b4e2c3d5f',
viewportStructure: viewportStructure32,
// Note the following structure simply preserves the viewportId for
// a given screen position
viewports: [
viewport0a,
viewport1b,
viewport4e,
viewport2c,
viewport3d,
viewport5f,
],
},
{
name: '2x2 1c0d3a2b',
viewportStructure,
viewports: [viewport1c, viewport0d, viewport3a, viewport2b],
},
{
name: '2x2 3a2b1c0d',
viewportStructure,
viewports: [viewport3a, viewport2b, viewport1c, viewport0d],
},
],
numberOfPriorsReferenced: -1,
};
export default hpTestSwitch;

View File

@ -2,6 +2,8 @@ import { Types } from '@ohif/core';
import { id } from './id'; import { id } from './id';
import hpTestSwitch from './hpTestSwitch';
import getCustomizationModule from './getCustomizationModule'; import getCustomizationModule from './getCustomizationModule';
// import {setViewportZoomPan, storeViewportZoomPan } from './custom-viewport/setViewportZoomPan'; // import {setViewportZoomPan, storeViewportZoomPan } from './custom-viewport/setViewportZoomPan';
import sameAs from './custom-attribute/sameAs'; import sameAs from './custom-attribute/sameAs';
@ -47,6 +49,16 @@ const testExtension: Types.Extensions.Extension = {
/** Registers some customizations */ /** Registers some customizations */
getCustomizationModule, getCustomizationModule,
getHangingProtocolModule: () => {
return [
// Create a MxN hanging protocol available by default
{
name: hpTestSwitch.id,
protocol: hpTestSwitch,
},
];
},
}; };
export default testExtension; export default testExtension;

View File

@ -36,8 +36,8 @@ const commandsModule = ({
const { getEnabledElement } = utilityModule.exports; const { getEnabledElement } = utilityModule.exports;
function _getActiveViewportsEnabledElement() { function _getActiveViewportsEnabledElement() {
const { activeViewportIndex } = viewportGridService.getState(); const { activeViewportId } = viewportGridService.getState();
const { element } = getEnabledElement(activeViewportIndex) || {}; const { element } = getEnabledElement(activeViewportId) || {};
const enabledElement = cs.getEnabledElement(element); const enabledElement = cs.getEnabledElement(element);
return enabledElement; return enabledElement;
} }
@ -45,8 +45,8 @@ const commandsModule = ({
function _getMatchedViewportsToolGroupIds() { function _getMatchedViewportsToolGroupIds() {
const { viewportMatchDetails } = hangingProtocolService.getMatchDetails(); const { viewportMatchDetails } = hangingProtocolService.getMatchDetails();
const toolGroupIds = []; const toolGroupIds = [];
viewportMatchDetails.forEach((value, key) => { viewportMatchDetails.forEach(viewport => {
const { viewportOptions } = value; const { viewportOptions } = viewport;
const { toolGroupId } = viewportOptions; const { toolGroupId } = viewportOptions;
if (toolGroupIds.indexOf(toolGroupId) === -1) { if (toolGroupIds.indexOf(toolGroupId) === -1) {
toolGroupIds.push(toolGroupId); toolGroupIds.push(toolGroupId);
@ -64,7 +64,7 @@ const commandsModule = ({
// corrected PT vs the non-attenuation correct PT) // corrected PT vs the non-attenuation correct PT)
let ptDisplaySet = null; let ptDisplaySet = null;
for (const [viewportIndex, viewportDetails] of viewportMatchDetails) { for (const [viewportId, viewportDetails] of viewportMatchDetails) {
const { displaySetsInfo } = viewportDetails; const { displaySetsInfo } = viewportDetails;
const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) => const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) =>
displaySetService.getDisplaySetByUID(displaySetInstanceUID) displaySetService.getDisplaySetByUID(displaySetInstanceUID)
@ -137,7 +137,6 @@ const commandsModule = ({
// Add Segmentation to all toolGroupIds in the viewer // Add Segmentation to all toolGroupIds in the viewer
const toolGroupIds = _getMatchedViewportsToolGroupIds(); const toolGroupIds = _getMatchedViewportsToolGroupIds();
const representationType = LABELMAP; const representationType = LABELMAP;
for (const toolGroupId of toolGroupIds) { for (const toolGroupId of toolGroupIds) {
@ -545,13 +544,8 @@ const commandsModule = ({
let viewports = []; let viewports = [];
fusionViewportIds.forEach(viewportId => { fusionViewportIds.forEach(viewportId => {
const viewportInfo = cornerstoneViewportService.getViewportInfo(
viewportId
);
const viewportIndex = viewportInfo.getViewportIndex();
commandsManager.runCommand('setViewportColormap', { commandsManager.runCommand('setViewportColormap', {
viewportIndex, viewportId,
displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID, displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID,
colormap: { colormap: {
name: colormap, name: colormap,

View File

@ -114,6 +114,9 @@ const ptAXIAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: true, source: true,
target: false, target: false,
options: {
syncInvertState: false,
},
}, },
], ],
}, },
@ -155,6 +158,9 @@ const ptSAGITTAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: true, source: true,
target: false, target: false,
options: {
syncInvertState: false,
},
}, },
], ],
}, },
@ -196,6 +202,9 @@ const ptCORONAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: true, source: true,
target: false, target: false,
options: {
syncInvertState: false,
},
}, },
], ],
}, },
@ -246,6 +255,9 @@ const fusionAXIAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: false, source: false,
target: true, target: true,
options: {
syncInvertState: false,
},
}, },
], ],
}, },
@ -306,6 +318,9 @@ const fusionSAGITTAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: false, source: false,
target: true, target: true,
options: {
syncInvertState: false,
},
}, },
], ],
}, },
@ -366,6 +381,9 @@ const fusionCORONAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: false, source: false,
target: true, target: true,
options: {
syncInvertState: false,
},
}, },
], ],
}, },
@ -411,6 +429,9 @@ const mipSAGITTAL = {
id: 'ptFusionWLSync', id: 'ptFusionWLSync',
source: true, source: true,
target: false, target: false,
options: {
syncInvertState: false,
},
}, },
], ],

View File

@ -120,7 +120,7 @@
"postcss-import": "^14.0.2", "postcss-import": "^14.0.2",
"postcss-loader": "^6.1.1", "postcss-loader": "^6.1.1",
"postcss-preset-env": "^7.4.3", "postcss-preset-env": "^7.4.3",
"prettier": "^1.18.2", "prettier": "^3.0.3",
"react-hot-loader": "^4.13.0", "react-hot-loader": "^4.13.0",
"semver": "^7.5.1", "semver": "^7.5.1",
"serve": "^14.2.0", "serve": "^14.2.0",

View File

@ -16,12 +16,18 @@ describe('OHIF Double Click', () => {
.should('be.eq', numExpectedViewports); .should('be.eq', numExpectedViewports);
for (let i = 0; i < numExpectedViewports; i += 1) { for (let i = 0; i < numExpectedViewports; i += 1) {
cy.wait(2000);
// For whatever reason, with Cypress tests, we have to activate the // For whatever reason, with Cypress tests, we have to activate the
// viewport we are double clicking first. // viewport we are double clicking first.
cy.get('[data-cy="viewport-pane"]') cy.get('[data-cy="viewport-pane"]')
.eq(i) .eq(i)
.trigger('mousedown', 'center', { force: true }) .trigger('mousedown', 'center', {
.trigger('mouseup', 'center', { force: true }); force: true,
})
.trigger('mouseup', 'center', {
force: true,
});
// Wait for the viewport to be 'active'. // Wait for the viewport to be 'active'.
// TODO Is there a better way to do this? // TODO Is there a better way to do this?
@ -41,12 +47,16 @@ describe('OHIF Double Click', () => {
.should('be.eq', 1); .should('be.eq', 1);
cy.get('[data-cy="viewport-pane"]') cy.get('[data-cy="viewport-pane"]')
.eq(0) .trigger('mousedown', 'center', {
.trigger('dblclick', 'center'); force: true,
})
.trigger('mouseup', 'center', {
force: true,
});
cy.get('[data-cy="viewport-pane"]') cy.get('[data-cy="viewport-pane"]')
.its('length') .eq(0)
.should('be.eq', numExpectedViewports); .trigger('dblclick', 'center');
} }
}); });
}); });

View File

@ -66,9 +66,10 @@ describe('OHIF MPR', () => {
.then(cornerstone => { .then(cornerstone => {
const viewports = cornerstone.getRenderingEngines()[0].getViewports(); const viewports = cornerstone.getRenderingEngines()[0].getViewports();
const imageData1 = viewports[0].getImageData(); // The stack viewport still exists after the changes to viewportId and inde
const imageData2 = viewports[1].getImageData(); const imageData1 = viewports[1].getImageData();
const imageData3 = viewports[2].getImageData(); const imageData2 = viewports[2].getImageData();
const imageData3 = viewports[3].getImageData();
// for some reason map doesn't work here // for some reason map doesn't work here
cy.wrap(imageData1).should('not.be', undefined); cy.wrap(imageData1).should('not.be', undefined);

View File

@ -10,7 +10,7 @@ function ViewerViewportGrid(props) {
const { servicesManager, viewportComponents, dataSource } = props; const { servicesManager, viewportComponents, dataSource } = props;
const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportGrid, viewportGridService] = useViewportGrid();
const { layout, activeViewportIndex, viewports } = viewportGrid; const { layout, activeViewportId, viewports } = viewportGrid;
const { numCols, numRows } = layout; const { numCols, numRows } = layout;
// TODO -> Need some way of selecting which displaySets hit the viewports. // TODO -> Need some way of selecting which displaySets hit the viewports.
@ -51,10 +51,11 @@ function ViewerViewportGrid(props) {
* specify the viewport match details, which specifies the size and * specify the viewport match details, which specifies the size and
* setup of the various viewports. * setup of the various viewports.
*/ */
const findOrCreateViewport = viewportIndex => { const findOrCreateViewport = pos => {
const details = viewportMatchDetails.get(viewportIndex); const viewportId = Array.from(viewportMatchDetails.keys())[pos];
const details = viewportMatchDetails.get(viewportId);
if (!details) { if (!details) {
console.log('No match details for viewport', viewportIndex); console.log('No match details for viewport', viewportId);
return; return;
} }
@ -99,11 +100,11 @@ function ViewerViewportGrid(props) {
}; };
const _getUpdatedViewports = useCallback( const _getUpdatedViewports = useCallback(
(viewportIndex, displaySetInstanceUID) => { (viewportId, displaySetInstanceUID) => {
let updatedViewports = []; let updatedViewports = [];
try { try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate( updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex, viewportId,
displaySetInstanceUID displaySetInstanceUID
); );
} catch (error) { } catch (error) {
@ -144,7 +145,7 @@ function ViewerViewportGrid(props) {
useEffect(() => { useEffect(() => {
const { unsubscribe } = measurementService.subscribe( const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT, MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
({ viewportIndex, measurement, isConsumed }) => { ({ viewportId, measurement, isConsumed }) => {
if (isConsumed) { if (isConsumed) {
return; return;
} }
@ -155,7 +156,7 @@ function ViewerViewportGrid(props) {
measurement; measurement;
const updatedViewports = _getUpdatedViewports( const updatedViewports = _getUpdatedViewports(
viewportIndex, viewportId,
referencedDisplaySetInstanceUID referencedDisplaySetInstanceUID
); );
// Arbitrarily assign the viewport to element 0 // Arbitrarily assign the viewport to element 0
@ -197,7 +198,7 @@ function ViewerViewportGrid(props) {
}, [viewports]); }, [viewports]);
/** /**
const onDoubleClick = viewportIndex => { const onDoubleClick = viewportId => {
// TODO -> Disabled for now. // TODO -> Disabled for now.
// onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport. // onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport.
// Which it really really shouldn't. We need a larger fix for jump to // Which it really really shouldn't. We need a larger fix for jump to
@ -206,7 +207,7 @@ function ViewerViewportGrid(props) {
viewportGridService.set({ viewportGridService.set({
numCols: cachedLayout.numCols, numCols: cachedLayout.numCols,
numRows: cachedLayout.numRows, numRows: cachedLayout.numRows,
activeViewportIndex: cachedLayout.activeViewportIndex, activeViewportId: cachedLayout.activeViewportId,
viewports: cachedLayout.viewports, viewports: cachedLayout.viewports,
cachedLayout: null, cachedLayout: null,
}); });
@ -223,10 +224,10 @@ function ViewerViewportGrid(props) {
viewportGridService.set({ viewportGridService.set({
numCols: 1, numCols: 1,
numRows: 1, numRows: 1,
activeViewportIndex: 0, activeViewportId: 0,
viewports: [ viewports: [
{ {
displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID, displaySetInstanceUID: viewports[viewportId].displaySetInstanceUID,
imageIndex: undefined, imageIndex: undefined,
}, },
], ],
@ -234,15 +235,15 @@ function ViewerViewportGrid(props) {
numCols, numCols,
numRows, numRows,
viewports: cachedViewports, viewports: cachedViewports,
activeViewportIndex: viewportIndex, activeViewportId: viewportId,
}, },
}); });
}; };
*/ */
const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => { const onDropHandler = (viewportId, { displaySetInstanceUID }) => {
const updatedViewports = _getUpdatedViewports( const updatedViewports = _getUpdatedViewports(
viewportIndex, viewportId,
displaySetInstanceUID displaySetInstanceUID
); );
viewportGridService.setDisplaySetsForViewports(updatedViewports); viewportGridService.setDisplaySetsForViewports(updatedViewports);
@ -253,13 +254,7 @@ function ViewerViewportGrid(props) {
const numViewportPanes = viewportGridService.getNumViewportPanes(); const numViewportPanes = viewportGridService.getNumViewportPanes();
for (let i = 0; i < numViewportPanes; i++) { for (let i = 0; i < numViewportPanes; i++) {
const viewportIndex = i; const paneMetadata = Array.from(viewports.values())[i] || {};
const isActive = activeViewportIndex === viewportIndex;
const paneMetadata = viewports[i] || {};
const viewportId = paneMetadata.viewportId || `viewport-${i}`;
if (!paneMetadata.viewportId) {
paneMetadata.viewportId = viewportId;
}
const { const {
displaySetInstanceUIDs, displaySetInstanceUIDs,
viewportOptions, viewportOptions,
@ -271,9 +266,13 @@ function ViewerViewportGrid(props) {
viewportLabel, viewportLabel,
} = paneMetadata; } = paneMetadata;
const viewportId = viewportOptions.viewportId
const isActive = activeViewportId === viewportId;
const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || []; const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || [];
// This is causing the viewport components re-render when the activeViewportIndex changes // This is causing the viewport components re-render when the activeViewportId changes
const displaySets = displaySetInstanceUIDsToUse.map( const displaySets = displaySetInstanceUIDsToUse.map(
displaySetInstanceUID => { displaySetInstanceUID => {
return ( return (
@ -307,17 +306,23 @@ function ViewerViewportGrid(props) {
event.stopPropagation(); event.stopPropagation();
} }
viewportGridService.setActiveViewportIndex(viewportIndex); viewportGridService.setActiveViewportId(viewportId);
}; };
// TEMP -> Double click disabled for now
// onDoubleClick={() => onDoubleClick(viewportIndex)}
viewportPanes[i] = ( viewportPanes[i] = (
<ViewportPane <ViewportPane
// Note: It is highly important that the key is the viewportId here,
// since it is used to determine if the component should be re-rendered
// by React, and also in the hanging protocol and stage changes if the
// same viewportId is used, React, by default, will only move (not re-render)
// those components. For instance, if we have a 2x3 layout, and we move
// from 2x3 to 1x1 (second viewport), if the key is the viewportIndex,
// React will RE-RENDER the resulting viewport as the key will be different.
// however, if the key is the viewportId, React will only move the component
// and not re-render it.
key={viewportId} key={viewportId}
acceptDropsFor="displayset" acceptDropsFor="displayset"
onDrop={onDropHandler.bind(null, viewportIndex)} onDrop={onDropHandler.bind(null, viewportId)}
onInteraction={onInteractionHandler} onInteraction={onInteractionHandler}
customStyle={{ customStyle={{
position: 'absolute', position: 'absolute',
@ -336,8 +341,8 @@ function ViewerViewportGrid(props) {
> >
<ViewportComponent <ViewportComponent
displaySets={displaySets} displaySets={displaySets}
viewportIndex={viewportIndex} viewportLabel={viewports.size > 1 ? viewportLabel : ''}
viewportLabel={viewports.length > 1 ? viewportLabel : ''} viewportId={viewportId}
dataSource={dataSource} dataSource={dataSource}
viewportOptions={viewportOptions} viewportOptions={viewportOptions}
displaySetOptions={displaySetOptions} displaySetOptions={displaySetOptions}
@ -349,7 +354,7 @@ function ViewerViewportGrid(props) {
} }
return viewportPanes; return viewportPanes;
}, [viewports, activeViewportIndex, viewportComponents, dataSource]); }, [viewports, activeViewportId, viewportComponents, dataSource]);
/** /**
* Loading indicator until numCols and numRows are gotten from the HangingProtocolService * Loading indicator until numCols and numRows are gotten from the HangingProtocolService

View File

@ -74,13 +74,14 @@ const match = (
errorMessages = ['Something went wrong during validation.', e]; errorMessages = ['Something went wrong during validation.', e];
} }
console.log( // TODO: move to a logger
'Test', // console.log(
`${from}.${attribute}`, // 'Test',
readValues[attribute], // `${from}.${attribute}`,
JSON.stringify(rule.constraint), // readValues[attribute],
!errorMessages // JSON.stringify(rule.constraint),
); // !errorMessages
// );
if (!errorMessages) { if (!errorMessages) {
// If no errorMessages were returned, then validation passed. // If no errorMessages were returned, then validation passed.

View File

@ -118,7 +118,7 @@ function checkHpsBestMatch(hps) {
hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets }); hps.run({ studies: [studyMatch], displaySets: studyMatchDisplaySets });
const { viewportMatchDetails } = hps.getMatchDetails(); const { viewportMatchDetails } = hps.getMatchDetails();
expect(viewportMatchDetails.size).toBe(1); expect(viewportMatchDetails.size).toBe(1);
expect(viewportMatchDetails.get(0)).toMatchObject({ expect(viewportMatchDetails.get('ctAXIAL')).toMatchObject({
viewportOptions: { viewportOptions: {
viewportId: 'ctAXIAL', viewportId: 'ctAXIAL',
viewportType: 'volume', viewportType: 'volume',

View File

@ -14,6 +14,7 @@ import {
} from './custom-attribute/isDisplaySetFromUrl'; } from './custom-attribute/isDisplaySetFromUrl';
import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages'; import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages';
import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets'; import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets';
import uuidv4 from '../../utils/uuidv4';
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator; type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
@ -132,11 +133,11 @@ export default class HangingProtocolService extends PubSubService {
> = new Map(); > = new Map();
/** /**
* An array that contains for each viewport (viewportIndex) specified in the * An array that contains for each viewport (viewportId) specified in the
* hanging protocol, an object of the form * hanging protocol, an object of the form
*/ */
viewportMatchDetails: Map< viewportMatchDetails: Map<
number, // viewportIndex string, // viewportId
HangingProtocol.ViewportMatchDetails HangingProtocol.ViewportMatchDetails
> = new Map(); > = new Map();
@ -185,7 +186,7 @@ export default class HangingProtocolService extends PubSubService {
stage: HangingProtocol.ProtocolStage; stage: HangingProtocol.ProtocolStage;
stageIndex: number; stageIndex: number;
activeStudy?: StudyMetadata; activeStudy?: StudyMetadata;
viewportMatchDetails: Map<number, HangingProtocol.ViewportMatchDetails>; viewportMatchDetails: Map<string, HangingProtocol.ViewportMatchDetails>;
displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>; displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>;
activeImageLoadStrategyName: string; activeImageLoadStrategyName: string;
} { } {
@ -404,7 +405,7 @@ export default class HangingProtocolService extends PubSubService {
/** /**
* Returns true, if the hangingProtocol has a custom loading strategy for the images * Returns true, if the hangingProtocol has a custom loading strategy for the images
* and its callback has been added to the HangingProtocolService * and its callback has been added to the HangingProtocolService
* @returns {boolean} true * @returns A boolean indicating whether a custom image load strategy has been added or not.
*/ */
public hasCustomImageLoadStrategy(): boolean { public hasCustomImageLoadStrategy(): boolean {
return ( return (
@ -415,10 +416,31 @@ export default class HangingProtocolService extends PubSubService {
); );
} }
/**
* Returns a boolean indicating whether a custom image load has been performed or not.
* A custom image load is performed when a custom image load strategy is used to load images.
* This method is used internally by the HangingProtocolService to determine whether to perform
* a custom image load or not.
*
* @returns A boolean indicating whether a custom image load has been performed or not.
*/
public getCustomImageLoadPerformed(): boolean { public getCustomImageLoadPerformed(): boolean {
return this.customImageLoadPerformed; return this.customImageLoadPerformed;
} }
/**
* Returns a boolean indicating whether a custom image load should be performed or not.
* A custom image load should be performed if a custom image load strategy has been added to the HangingProtocolService
* and it has not been performed yet.
*
* @returns A boolean indicating whether a custom image load should be performed or not.
*/
public getShouldPerformCustomImageLoad(): boolean {
return (
this.hasCustomImageLoadStrategy() && !this.getCustomImageLoadPerformed()
);
}
/** /**
* Set the strategy callback for loading images to the HangingProtocolService * Set the strategy callback for loading images to the HangingProtocolService
* @param {string} name strategy name * @param {string} name strategy name
@ -519,21 +541,28 @@ export default class HangingProtocolService extends PubSubService {
for (let i = 0; i < rows * columns; i++) { for (let i = 0; i < rows * columns; i++) {
stage.viewports.push({ stage.viewports.push({
viewportOptions: defaultViewportOptions, viewportOptions: {
...defaultViewportOptions,
viewportId: uuidv4(),
},
displaySets: [], displaySets: [],
}); });
} }
} else { } else {
// Clone each viewport to ensure independent objects
stage.viewports = stage.viewports.map(viewport => ({
...viewport,
viewportOptions: {
...(viewport.viewportOptions || defaultViewportOptions),
viewportId: viewport.viewportOptions?.viewportId || uuidv4(),
},
displaySets: viewport.displaySets || [],
}));
stage.viewports.forEach(viewport => { stage.viewports.forEach(viewport => {
viewport.viewportOptions = viewport.displaySets.forEach(displaySet => {
viewport.viewportOptions || defaultViewportOptions; displaySet.options = displaySet.options || {};
if (!viewport.displaySets) { });
viewport.displaySets = [];
} else {
viewport.displaySets.forEach(displaySet => {
displaySet.options = displaySet.options || {};
});
}
}); });
} }
}); });
@ -558,7 +587,7 @@ export default class HangingProtocolService extends PubSubService {
}; };
} }
getViewportsRequireUpdate(viewportIndex, displaySetInstanceUID) { getViewportsRequireUpdate(viewportId, displaySetInstanceUID) {
const { displaySetService } = this._servicesManager.services; const { displaySetService } = this._servicesManager.services;
const displaySet = displaySetService.getDisplaySetByUID( const displaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID displaySetInstanceUID
@ -570,15 +599,22 @@ export default class HangingProtocolService extends PubSubService {
const protocol = this.protocol; const protocol = this.protocol;
const protocolStage = protocol.stages[this.stageIndex]; const protocolStage = protocol.stages[this.stageIndex];
const protocolViewports = protocolStage.viewports; const protocolViewports = protocolStage.viewports;
const protocolViewport = protocolViewports[viewportIndex];
const defaultReturn = [ const defaultReturn = [
{ {
viewportIndex, viewportId,
displaySetInstanceUIDs: [newDisplaySetInstanceUID], displaySetInstanceUIDs: [newDisplaySetInstanceUID],
}, },
]; ];
if (!protocolViewports) {
return defaultReturn;
}
const protocolViewport = protocolViewports.find(
pv => pv.viewportOptions.viewportId === viewportId
);
// if no viewport, then we can assume there is no predefined set of // if no viewport, then we can assume there is no predefined set of
// rules that should be applied to this viewport while matching // rules that should be applied to this viewport while matching
if (!protocolViewport) { if (!protocolViewport) {
@ -644,13 +680,13 @@ export default class HangingProtocolService extends PubSubService {
} }
// if we reach here, it means that the displaySetInstanceUIDs to be dropped // if we reach here, it means that the displaySetInstanceUIDs to be dropped
// in the viewportIndex are valid, and we can proceed with the update. However // for the desired viewportId are valid, and we can proceed with the update. However
// we need to check if the displaySets that the viewport were showing // we need to check if the displaySets that the viewport were showing
// was also referenced by other viewports, and if so, we need to update those // was also referenced by other viewports, and if so, we need to update those
// viewports as well // viewports as well
// check if displaySetSelectors are used by other viewports, and // check if displaySetSelectors are used by other viewports, and
// store the viewportIndex and displaySetInstanceUIDs that need to be updated // store the viewportId and displaySetInstanceUIDs that need to be updated
const viewportsToUpdate = []; const viewportsToUpdate = [];
protocolViewports.forEach((viewport, index) => { protocolViewports.forEach((viewport, index) => {
const viewportNeedsUpdate = viewport.displaySets.some( const viewportNeedsUpdate = viewport.displaySets.some(
@ -661,7 +697,7 @@ export default class HangingProtocolService extends PubSubService {
if (viewportNeedsUpdate) { if (viewportNeedsUpdate) {
// Try to recompute the viewport options based on the current // Try to recompute the viewport options based on the current
// viewportIndex that needs update but from its old/original un-computed // viewportId that needs update but from its old/original un-computed
// viewport & displaySet options // viewport & displaySet options
if (originalProtocolStage) { if (originalProtocolStage) {
const originalViewport = originalProtocolStage.viewports[index]; const originalViewport = originalProtocolStage.viewports[index];
@ -692,7 +728,7 @@ export default class HangingProtocolService extends PubSubService {
); );
viewportsToUpdate.push({ viewportsToUpdate.push({
viewportIndex: index, viewportId: viewport.viewportOptions.viewportId,
displaySetInstanceUIDs, displaySetInstanceUIDs,
viewportOptions: viewport.viewportOptions, viewportOptions: viewport.viewportOptions,
displaySetOptions, displaySetOptions,
@ -799,7 +835,7 @@ export default class HangingProtocolService extends PubSubService {
* @param protocolId - name of the registered protocol to be set * @param protocolId - name of the registered protocol to be set
* @param options - options to be passed to the protocol, this is either an array * @param options - options to be passed to the protocol, this is either an array
* of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object * of the displaySetInstanceUIDs to be set on ALL VIEWPORTS OF THE PROTOCOL or an object
* that contains viewportIndex as the key and displaySetInstanceUIDs as the value * that contains viewportId as the key and displaySetInstanceUIDs as the value
* for each viewport that needs to be set. * for each viewport that needs to be set.
* @param errorCallback - callback to be called if there is an error * @param errorCallback - callback to be called if there is an error
* during the protocol application * during the protocol application
@ -918,7 +954,7 @@ export default class HangingProtocolService extends PubSubService {
const stage = stages[i]; const stage = stages[i];
if (stage.id === stageId && stage.status !== 'disabled') { if (stage.id === stageId && stage.status !== 'disabled') {
return i; return i;
} }
} }
return; return;
} }
@ -1098,7 +1134,6 @@ export default class HangingProtocolService extends PubSubService {
// matching applied // matching applied
this.viewportMatchDetails = new Map(); this.viewportMatchDetails = new Map();
this.displaySetMatchDetails = new Map(); this.displaySetMatchDetails = new Map();
this.customImageLoadPerformed = false;
// Retrieve the current stage // Retrieve the current stage
const stageModel = this._getCurrentStageModel(); const stageModel = this._getCurrentStageModel();
@ -1148,7 +1183,11 @@ export default class HangingProtocolService extends PubSubService {
displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>; displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>;
} { } {
let matchedViewports = 0; let matchedViewports = 0;
stageModel.viewports.forEach((viewport, viewportIndex) => { stageModel.viewports.forEach(viewport => {
// Todo: we should probably assign a random viewportId if not defined
// below, but it feels odd since viewportGrid should handle this kind
// of thing
const viewportId = viewport.viewportOptions.viewportId;
const matchDetails = this._matchViewport( const matchDetails = this._matchViewport(
viewport, viewport,
options, options,
@ -1169,7 +1208,7 @@ export default class HangingProtocolService extends PubSubService {
displaySetOptions: it, displaySetOptions: it,
})); }));
} }
viewportMatchDetails.set(viewportIndex, matchDetails); viewportMatchDetails.set(viewportId, matchDetails);
} }
}); });
return { return {
@ -1231,7 +1270,7 @@ export default class HangingProtocolService extends PubSubService {
for (const subMatch of match.matchingScores) { for (const subMatch of match.matchingScores) {
if (subMatch.displaySetInstanceUID === displaySetUID) { if (subMatch.displaySetInstanceUID === displaySetUID) {
return; return;
} }
} }
throw new Error( throw new Error(
`Reused viewport details ${id} with ds ${displaySetUID} not valid` `Reused viewport details ${id} with ds ${displaySetUID} not valid`
@ -1442,15 +1481,15 @@ export default class HangingProtocolService extends PubSubService {
); );
const studyMatchDetails = this.protocolEngine.findMatch( const studyMatchDetails = this.protocolEngine.findMatch(
study, study,
studyMatchingRules, studyMatchingRules,
{ {
studies: this.studies, studies: this.studies,
displaySets: studyDisplaySets, displaySets: studyDisplaySets,
allDisplaySets: this.displaySets, allDisplaySets: this.displaySets,
displaySetMatchDetails: this.displaySetMatchDetails, displaySetMatchDetails: this.displaySetMatchDetails,
studyInstanceUIDsIndex, studyInstanceUIDsIndex,
} }
); );
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
@ -1459,10 +1498,10 @@ export default class HangingProtocolService extends PubSubService {
} }
this.debug( this.debug(
'study', 'study',
study.StudyInstanceUID, study.StudyInstanceUID,
'display sets #', 'display sets #',
studyDisplaySets.length studyDisplaySets.length
); );
studyDisplaySets.forEach(displaySet => { studyDisplaySets.forEach(displaySet => {
const { const {
@ -1471,15 +1510,15 @@ export default class HangingProtocolService extends PubSubService {
displaySetInstanceUID, displaySetInstanceUID,
} = displaySet; } = displaySet;
const seriesMatchDetails = this.protocolEngine.findMatch( const seriesMatchDetails = this.protocolEngine.findMatch(
displaySet, displaySet,
seriesMatchingRules, seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it // Todo: why we have images here since the matching type does not have it
{ {
studies: this.studies, studies: this.studies,
instance: displaySet.images?.[0], instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails, displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets, displaySets: studyDisplaySets,
} }
); );
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed // Prevent bestMatch from being updated if the matchDetails' required attribute check has failed

View File

@ -649,10 +649,7 @@ class MeasurementService extends PubSubService {
* merely causes it to fire the event with the isConsumed set to true. * merely causes it to fire the event with the isConsumed set to true.
*/ */
public jumpToMeasurement( public jumpToMeasurement(viewportId: string, measurementUID: string): void {
viewportIndex: number,
measurementUID: string
): void {
const measurement = this.measurements.get(measurementUID); const measurement = this.measurements.get(measurementUID);
if (!measurement) { if (!measurement) {
@ -660,7 +657,7 @@ class MeasurementService extends PubSubService {
return; return;
} }
const consumableEvent = this.createConsumableEvent({ const consumableEvent = this.createConsumableEvent({
viewportIndex, viewportId,
measurement, measurement,
}); });

View File

@ -4,7 +4,7 @@
* @typedef {Object} ViewportDialogProps * @typedef {Object} ViewportDialogProps
* @property {ReactElement|HTMLElement} [content=null] Modal content. * @property {ReactElement|HTMLElement} [content=null] Modal content.
* @property {Object} [contentProps=null] Modal content props. * @property {Object} [contentProps=null] Modal content props.
* @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key. * @property {boolean} [viewportId=false] Modal is dismissible via the esc key.
*/ */
const name = 'uiViewportDialogService'; const name = 'uiViewportDialogService';
@ -22,12 +22,12 @@ const serviceImplementation = {
}; };
/** /**
* Show a new UI viewport dialog on the specified viewportIndex; * Show a new UI viewport dialog on the specified viewportId;
* *
* @param {ViewportDialogProps} props { content, contentProps, viewportIndex } * @param {ViewportDialogProps} props { content, contentProps, viewportId }
*/ */
function _show({ function _show({
viewportIndex, viewportId,
id, id,
type, type,
message, message,
@ -36,7 +36,7 @@ function _show({
onOutsideClick, onOutsideClick,
}) { }) {
return serviceImplementation._show({ return serviceImplementation._show({
viewportIndex, viewportId,
id, id,
type, type,
message, message,
@ -59,7 +59,7 @@ function _hide() {
* @param {*} { * @param {*} {
* hide: hideImplementation, * hide: hideImplementation,
* show: showImplementation, * show: showImplementation,
* viewportIndex, * viewportId,
* } * }
*/ */
function setServiceImplementation({ function setServiceImplementation({

View File

@ -3,7 +3,7 @@ import { getPresentationIds, PresentationIds } from './getPresentationIds';
class ViewportGridService extends PubSubService { class ViewportGridService extends PubSubService {
public static readonly EVENTS = { public static readonly EVENTS = {
ACTIVE_VIEWPORT_INDEX_CHANGED: 'event::activeviewportindexchanged', ACTIVE_VIEWPORT_ID_CHANGED: 'event::activeviewportidchanged',
LAYOUT_CHANGED: 'event::layoutChanged', LAYOUT_CHANGED: 'event::layoutChanged',
GRID_STATE_CHANGED: 'event::gridStateChanged', GRID_STATE_CHANGED: 'event::gridStateChanged',
}; };
@ -27,7 +27,7 @@ class ViewportGridService extends PubSubService {
public setServiceImplementation({ public setServiceImplementation({
getState: getStateImplementation, getState: getStateImplementation,
setActiveViewportIndex: setActiveViewportIndexImplementation, setActiveViewportId: setActiveViewportIdImplementation,
setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation, setDisplaySetsForViewports: setDisplaySetsForViewportsImplementation,
setLayout: setLayoutImplementation, setLayout: setLayoutImplementation,
reset: resetImplementation, reset: resetImplementation,
@ -38,9 +38,9 @@ class ViewportGridService extends PubSubService {
if (getStateImplementation) { if (getStateImplementation) {
this.serviceImplementation._getState = getStateImplementation; this.serviceImplementation._getState = getStateImplementation;
} }
if (setActiveViewportIndexImplementation) { if (setActiveViewportIdImplementation) {
this.serviceImplementation._setActiveViewportIndex = this.serviceImplementation._setActiveViewport =
setActiveViewportIndexImplementation; setActiveViewportIdImplementation;
} }
if (setDisplaySetsForViewportsImplementation) { if (setDisplaySetsForViewportsImplementation) {
this.serviceImplementation._setDisplaySetsForViewports = this.serviceImplementation._setDisplaySetsForViewports =
@ -64,13 +64,11 @@ class ViewportGridService extends PubSubService {
} }
} }
public setActiveViewportIndex(index) { public setActiveViewportId(id: string) {
this.serviceImplementation._setActiveViewportIndex(index); this.serviceImplementation._setActiveViewport(id);
const state = this.getState(); const state = this.getState();
const viewportId = state.viewports[index]?.viewportOptions?.viewportId; this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, {
this._broadcastEvent(this.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, { viewportId: id,
viewportIndex: index,
viewportId,
}); });
} }
@ -89,7 +87,7 @@ class ViewportGridService extends PubSubService {
const viewports = []; const viewports = [];
for (const viewport of props) { for (const viewport of props) {
const updatedViewport = state.viewports[viewport.viewportIndex]; const updatedViewport = state.viewports.get(viewport.viewportId);
if (updatedViewport) { if (updatedViewport) {
viewports.push(updatedViewport); viewports.push(updatedViewport);
} else { } else {
@ -118,7 +116,7 @@ class ViewportGridService extends PubSubService {
numRows, numRows,
layoutOptions, layoutOptions,
layoutType = 'grid', layoutType = 'grid',
activeViewportIndex = undefined, activeViewportId = undefined,
findOrCreateViewport = undefined, findOrCreateViewport = undefined,
}) { }) {
this.serviceImplementation._setLayout({ this.serviceImplementation._setLayout({
@ -126,7 +124,7 @@ class ViewportGridService extends PubSubService {
numRows, numRows,
layoutOptions, layoutOptions,
layoutType, layoutType,
activeViewportIndex, activeViewportId,
findOrCreateViewport, findOrCreateViewport,
}); });
this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, { this._broadcastEvent(this.EVENTS.LAYOUT_CHANGED, {
@ -160,8 +158,8 @@ class ViewportGridService extends PubSubService {
return this.serviceImplementation._getNumViewportPanes(); return this.serviceImplementation._getNumViewportPanes();
} }
public getLayoutOptionsFromState(state) { public getLayoutOptionsFromState(state: any): { x: number; y: number; width: number; height: number }[] {
return state.viewports.map(viewport => { return Array.from(state.viewports.entries()).map(([_, viewport]) => {
return { return {
x: viewport.x, x: viewport.x,
y: viewport.y, y: viewport.y,

View File

@ -25,7 +25,7 @@ const addUniqueIndex = (arr, key, viewports, isUpdatingSameViewport) => {
arr[arr.length - 1] = displayInstance; arr[arr.length - 1] = displayInstance;
const testId = arr.join(JOIN_STR); const testId = arr.join(JOIN_STR);
if ( if (
!viewports.find( !Array.from(viewports.values()).find(
viewport => viewport.viewportOptions?.presentationIds?.[key] === testId viewport => viewport.viewportOptions?.presentationIds?.[key] === testId
) )
) { ) {
@ -126,11 +126,11 @@ const getPresentationIds = (viewport, viewports): PresentationIds => {
} }
// only add unique index if the viewport is getting inserted and not updated // only add unique index if the viewport is getting inserted and not updated
const isUpdatingSameViewport = viewports.some(v => { const isUpdatingSameViewport = Array.from(viewports.values()).some(v => {
return ( return (
v.displaySetInstanceUIDs.toString() === v.displaySetInstanceUIDs.toString() ===
viewport.displaySetInstanceUIDs.toString() && viewport.displaySetInstanceUIDs.toString() &&
v.viewportIndex === viewport.viewportIndex v.viewportId === viewport.viewportId
); );
}); });

View File

@ -61,7 +61,7 @@ export type SetProtocolOptions = {
export type HangingProtocolMatchDetails = { export type HangingProtocolMatchDetails = {
displaySetMatchDetails: Map<string, DisplaySetMatchDetails>; displaySetMatchDetails: Map<string, DisplaySetMatchDetails>;
viewportMatchDetails: Map<number, ViewportMatchDetails>; viewportMatchDetails: Map<string, ViewportMatchDetails>;
}; };
export type ConstraintValue = export type ConstraintValue =
@ -263,6 +263,8 @@ export type ProtocolNotifications = {
export type Protocol = { export type Protocol = {
// Mandatory // Mandatory
id: string; id: string;
/** A description of this protocol. Used as a tool tip for the user. */
description?: string;
/** Maps ids to display set selectors to choose display sets */ /** Maps ids to display set selectors to choose display sets */
displaySetSelectors: Record<string, DisplaySetSelector>; displaySetSelectors: Record<string, DisplaySetSelector>;
/** A default viewport to use for any stage to select new viewport layouts. */ /** A default viewport to use for any stage to select new viewport layouts. */

View File

@ -1,6 +1,7 @@
import ObjectPath from './objectPath'; import ObjectPath from './objectPath';
import absoluteUrl from './absoluteUrl'; import absoluteUrl from './absoluteUrl';
import guid from './guid'; import guid from './guid';
import uuidv4 from './uuidv4';
import sortBy from './sortBy.js'; import sortBy from './sortBy.js';
import writeScript from './writeScript.js'; import writeScript from './writeScript.js';
import b64toBlob from './b64toBlob.js'; import b64toBlob from './b64toBlob.js';
@ -41,6 +42,7 @@ import { splitComma, getSplitParam } from './splitComma';
const utils = { const utils = {
guid, guid,
uuidv4,
ObjectPath, ObjectPath,
absoluteUrl, absoluteUrl,
sortBy, sortBy,

View File

@ -39,6 +39,7 @@ describe('Top level exports', () => {
'hierarchicalListUtils', 'hierarchicalListUtils',
'progressTrackingUtils', 'progressTrackingUtils',
'subscribeToNextViewportGridChange', 'subscribeToNextViewportGridChange',
'uuidv4',
].sort(); ].sort();
const exports = Object.keys(utils.default).sort(); const exports = Object.keys(utils.default).sort();

View File

@ -1,7 +1,7 @@
import { ViewportGridService } from '../services'; import { ViewportGridService } from '../services';
/** /**
* Subscribes to the very next LAYOUT_CHANGED or GRID_STATE_CHANGED event that * Subscribes to the very next LAYOUT_CHANGED event that
* is not currently on the event queue. The subscriptions are made on a 'zero' * is not currently on the event queue. The subscriptions are made on a 'zero'
* timeout so as to avoid responding to any of those events currently on the event queue. * timeout so as to avoid responding to any of those events currently on the event queue.
* The subscription persists only for a single invocation of either event. * The subscription persists only for a single invocation of either event.
@ -24,10 +24,6 @@ function subscribeToNextViewportGridChange(
viewportGridService.EVENTS.LAYOUT_CHANGED, viewportGridService.EVENTS.LAYOUT_CHANGED,
callback callback
), ),
viewportGridService.subscribe(
viewportGridService.EVENTS.GRID_STATE_CHANGED,
callback
),
]; ];
}; };

View File

@ -43,20 +43,13 @@ const getViewportModule = () => {
A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which
creates a cornerstone viewport: creates a cornerstone viewport:
:::note Tip
Not in OHIF version 3.1 we use `displaySets` in the props which is new compared to
the previous version (3.0) which uses `displaySet`. This is due to the fact that
we are moving to a new data model that can render fused images in a single viewport.
:::
```jsx ```jsx
function TrackedCornerstoneViewport({ function TrackedCornerstoneViewport({
children, children,
dataSource, dataSource,
displaySets, displaySets,
viewportIndex, viewportId,
servicesManager, servicesManager,
extensionManager, extensionManager,
commandsManager, commandsManager,
@ -87,6 +80,45 @@ function TrackedCornerstoneViewport({
} }
``` ```
### Viewport re-rendering optimizations
We make use of the React memoization pattern to prevent unnecessary re-renders
for the viewport unless certain aspects of the Viewport props change. You can take
a look into the `areEqual` function in the `OHIFCornerstoneViewport` component to
see how this is done.
```js
function areEqual(prevProps, nextProps) {
if (prevProps.displaySets.length !== nextProps.displaySets.length) {
return false;
}
if (
prevProps.viewportOptions.orientation !==
nextProps.viewportOptions.orientation
) {
return false;
}
// rest of the code
```
as you see, we check if the `needsRerendering` prop is true, and if so, we will
re-render the viewport if the `displaySets` prop changes or the orientation
changes.
We use viewportId to identify a viewport and we use it as a key in React
rendering. This is important because it allows us to keep track of the viewport
and its state, and also let React optimize and move the viewport around in the
grid without re-rendering it. However, there are some cases where we need to
force re-render the viewport, for example, when the viewport is hydrated
with a new Segmentation. For these cases, we use the `needsRerendering` prop
to force re-render the viewport. You can add it to the `viewportOptions`
### `@ohif/app` ### `@ohif/app`

View File

@ -68,7 +68,7 @@ There are seven events that get publish in `MeasurementService`:
- `toMeasurementSchema`: A function to get the `data` into the same shape as - `toMeasurementSchema`: A function to get the `data` into the same shape as
the source definition. the source definition.
- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have - `jumpToMeasurement(viewportId, id)`: calls the listeners who have
subscribed to `JUMP_TO_MEASUREMENT`. subscribed to `JUMP_TO_MEASUREMENT`.
## Source / Mappers ## Source / Mappers

View File

@ -48,7 +48,7 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
```js ```js
const DEFAULT_STATE = { const DEFAULT_STATE = {
viewportIndex: null, viewportId: null,
message: undefined, message: undefined,
type: 'info', // "error" | "warning" | "info" | "success" type: 'info', // "error" | "warning" | "info" | "success"
actions: undefined, // array of { type, text, value } actions: undefined, // array of { type, text, value }

View File

@ -15,7 +15,7 @@ There are seven events that get publish in `ViewportGridService `:
| Event | Description | | Event | Description |
| ----------------------------- | --------------------------------------------------| | ----------------------------- | --------------------------------------------------|
| ACTIVE_VIEWPORT_INDEX_CHANGED | Fires the index of the active viewport is changed | | ACTIVE_VIEWPORT_ID_CHANGED | Fires the Id of the active viewport is changed |
| LAYOUT_CHANGED | Fires the layout is changed | | LAYOUT_CHANGED | Fires the layout is changed |
| GRID_STATE_CHANGED | Fires when the entire grid state is changed | | GRID_STATE_CHANGED | Fires when the entire grid state is changed |
## Interface ## Interface
@ -25,9 +25,9 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
| API Member | Description | | API Member | Description |
| --------------------------------------------------------------------- | --------------------------------------------------- | | --------------------------------------------------------------------- | --------------------------------------------------- |
| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | | `setActiveViewportId(viewportId)` | Sets the active viewport Id 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({ viewportId, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id |
| `setLayout({numCols, numRows, keepExtraViewports})` | Sets rows and columns. When the total number of viewports decreases, optionally keep the extra/offscreen viewports. | | `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 | | `getNumViewportPanes()` | Gets the number of visible viewport panes |
@ -56,6 +56,6 @@ const DEFAULT_STATE = {
* } * }
*/ */
], ],
activeViewportIndex: 0, activeViewportId: null,
}; };
``` ```

View File

@ -25,7 +25,7 @@ Each `ViewportComponent` will receive the following props:
```html ```html
<viewportComponent <viewportComponent
viewportData="{viewportData}" viewportData="{viewportData}"
viewportIndex="{viewportIndex}" viewportId="{viewportId}"
children="{[children]}" children="{[children]}"
/> />
``` ```
@ -34,7 +34,7 @@ Each `ViewportComponent` will receive the following props:
| --------------- | --------------- | --------------------------------- | | --------------- | --------------- | --------------------------------- |
| `children` | React.element[] | | | `children` | React.element[] | |
| `viewportData` | object | `viewportSpecificData` (probably) | | `viewportData` | object | `viewportSpecificData` (probably) |
| `viewportIndex` | number | | | `viewportId` | string | |
### `@ohif/viewer` ### `@ohif/viewer`

View File

@ -10,7 +10,7 @@ const Footer = ({ actions, className, onSubmit, value }) => {
return ( return (
<div className={classNames(flex, padding, className)}> <div className={classNames(flex, padding, className)}>
{actions.map((action, index) => { {actions?.map((action, index) => {
const isFirst = index === 0; const isFirst = index === 0;
const onClickHandler = event => onSubmit({ action, value, event }); const onClickHandler = event => onSubmit({ action, value, event });

View File

@ -73,9 +73,7 @@ const Notification = ({
<span className="ml-2 text-[13px] text-black">{message}</span> <span className="ml-2 text-[13px] text-black">{message}</span>
</div> </div>
<div className="flex flex-wrap gap-2 justify-end mt-2"> <div className="flex flex-wrap gap-2 justify-end mt-2">
{actions.map((action, index) => { {actions?.map((action, index) => {
const isFirst = index === 0;
return ( return (
<Button <Button
name={action.id} name={action.id}

View File

@ -1,18 +1,40 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import LegacyViewportActionBar from '../LegacyViewportActionBar'; import LegacyViewportActionBar from '../LegacyViewportActionBar';
import Notification from '../Notification'; import Notification from '../Notification';
const Viewport = ({ interface StudyData {
label: string;
isTracked: boolean;
isLocked: boolean;
isRehydratable: boolean;
studyDate: string;
currentSeries: number;
seriesDescription: string;
modality: string;
patientInformation: {
patientName: string;
patientSex: string;
patientAge: string;
MRN: string;
thickness: string;
spacing: string;
scanner: string;
};
}
interface ViewportProps {
viewportId: string;
onArrowsClick: () => void;
studyData: StudyData;
children: React.ReactNode;
}
const Viewport: React.FC<ViewportProps> = ({
viewportId, viewportId,
viewportIndex,
onArrowsClick, onArrowsClick,
studyData, studyData,
children, children,
}) => { }) => {
if (!viewportId) {
viewportId = `viewport-${viewportIndex}`;
}
return ( return (
<div className="relative flex flex-col h-full"> <div className="relative flex flex-col h-full">
<div className="absolute top-0 left-0 w-full"> <div className="absolute top-0 left-0 w-full">
@ -58,30 +80,5 @@ const Viewport = ({
); );
}; };
Viewport.propTypes = {
viewportIndex: PropTypes.number.isRequired,
onArrowsClick: PropTypes.func.isRequired,
studyData: PropTypes.shape({
label: PropTypes.string.isRequired,
isTracked: PropTypes.bool.isRequired,
/* Specific to SR Viewports only... */
isLocked: PropTypes.bool.isRequired,
isRehydratable: PropTypes.bool.isRequired,
studyDate: PropTypes.string.isRequired,
currentSeries: PropTypes.number.isRequired,
seriesDescription: PropTypes.string.isRequired,
modality: PropTypes.string.isRequired,
patientInformation: PropTypes.shape({
patientName: PropTypes.string.isRequired,
patientSex: PropTypes.string.isRequired,
patientAge: PropTypes.string.isRequired,
MRN: PropTypes.string.isRequired,
thickness: PropTypes.string.isRequired,
spacing: PropTypes.string.isRequired,
scanner: PropTypes.string.isRequired,
}),
}).isRequired,
children: PropTypes.node.isRequired,
};
export default Viewport; export default Viewport;
export type { ViewportProps, StudyData };

View File

@ -1,2 +0,0 @@
import Viewport from './Viewport';
export default Viewport;

View File

@ -0,0 +1,5 @@
import Viewport from './Viewport';
import type { StudyData, ViewportProps } from './Viewport';
export default Viewport;
export type { StudyData, ViewportProps };

View File

@ -27,7 +27,6 @@ const ViewportActionBar = ({
getStatusComponent, getStatusComponent,
}: ViewportActionBarProps): JSX.Element => { }: ViewportActionBarProps): JSX.Element => {
const { label, studyDate, seriesDescription, patientInformation } = studyData; const { label, studyDate, seriesDescription, patientInformation } = studyData;
const { const {
patientName, patientName,
patientSex, patientSex,

View File

@ -8,7 +8,6 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const DEFAULT_STATE = { const DEFAULT_STATE = {
viewportIndex: null,
message: undefined, message: undefined,
type: 'info', // "error" | "warning" | "info" | "success" type: 'info', // "error" | "warning" | "info" | "success"
actions: undefined, // array of { type, text, value } actions: undefined, // array of { type, text, value }

View File

@ -5,121 +5,130 @@ import React, {
useEffect, useEffect,
useReducer, useReducer,
} from 'react'; } from 'react';
import merge from 'lodash.merge';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal'; import { ViewportGridService, utils } from '@ohif/core';
import { ViewportGridService } from '@ohif/core';
import viewportLabels from '../utils/viewportLabels'; import viewportLabels from '../utils/viewportLabels';
const DEFAULT_STATE = { interface Viewport {
activeViewportIndex: 0, viewportId: string;
displaySetInstanceUIDs: string[];
viewportOptions: any;
displaySetSelectors: any[];
displaySetOptions: any[];
x: number;
y: number;
width: number;
height: number;
viewportLabel: any;
}
interface Layout {
numRows: number;
numCols: number;
layoutType: string;
}
interface DefaultState {
activeViewportId: string | null;
layout: Layout;
viewports: Map<string, Viewport>;
}
const DEFAULT_STATE: DefaultState = {
activeViewportId: null,
layout: { layout: {
numRows: 0, numRows: 0,
numCols: 0, numCols: 0,
layoutType: 'grid', layoutType: 'grid',
}, },
viewports: [ // Viewports structure has been changed to Map (previously it was
{ // tied to the viewportIndex which caused multiple issues. Now we have
displaySetInstanceUIDs: [], // moved completely to viewportId which is unique for each viewport.
viewportOptions: {}, viewports: new Map(
displaySetSelectors: [], Object.entries({
displaySetOptions: [{}], default: {
x: 0, // left viewportId: 'default',
y: 0, // top displaySetInstanceUIDs: [],
width: 100, viewportOptions: {
height: 100, viewportId: 'default',
viewportLabel: null, },
}, displaySetSelectors: [],
], displaySetOptions: [{}],
x: 0, // left
y: 0, // top
width: 100,
height: 100,
viewportLabel: null,
},
})
),
};
const getViewportLabel = (viewports, viewportId) => {
const viewportIds = Array.from(viewports.keys());
return viewportLabels[viewportIds.indexOf(viewportId)];
};
const determineActiveViewportId = (state: DefaultState, newViewports: Map) => {
const { activeViewportId } = state;
const currentActiveViewport = state.viewports.get(activeViewportId);
if (!currentActiveViewport) {
return null; // Return an appropriate value if the current active viewport is not found
}
// for the new viewports, we should rank them by the displaySetInstanceUIDs
// they are displaying and the orientation then we can find the active viewport
const currentActiveDisplaySetInstanceUIDs =
currentActiveViewport.displaySetInstanceUIDs;
// This doesn't take into account where stack viewport is converting to volumeViewport
// since in stack viewport we don't have a concept of "orientation" as a string
// maybe we should calculate the orientation based on the active imageId
// so that we can compare it with the new viewports (which might be volume viewports)
// and find the best match
const currentOrientation = currentActiveViewport.viewportOptions.orientation;
const sortedViewports = Array.from(newViewports.values()).sort((a, b) => {
// Compare orientations
const aOrientationMatch =
a.viewportOptions.orientation === currentOrientation;
const bOrientationMatch =
b.viewportOptions.orientation === currentOrientation;
if (aOrientationMatch !== bOrientationMatch) {
return bOrientationMatch - aOrientationMatch;
}
// Compare displaySetInstanceUIDs
const aMatch = a.displaySetInstanceUIDs.some(uid =>
currentActiveDisplaySetInstanceUIDs.includes(uid)
);
const bMatch = b.displaySetInstanceUIDs.some(uid =>
currentActiveDisplaySetInstanceUIDs.includes(uid)
);
if (aMatch !== bMatch) {
return bMatch - aMatch;
}
return 0; // Return 0 if no differences found
});
if (!sortedViewports?.length) {
return null;
}
return sortedViewports[0].viewportId;
}; };
export const ViewportGridContext = createContext(DEFAULT_STATE); export const ViewportGridContext = createContext(DEFAULT_STATE);
/** A viewport is reuseable if it is the same size as the old
* one and has the same display sets, in the same position.
* It SHOULD be possible to re-use them at different positions, but
* this causes problems with segmentation.
*/
const isReuseableViewport = (oldViewport, newViewport) => {
const sameDiplaySets = isEqual(
oldViewport.displaySetInstanceUIDs,
newViewport.displaySetInstanceUIDs
);
return (
oldViewport.viewportIndex === newViewport.viewportIndex &&
sameDiplaySets &&
oldViewport.height === newViewport.height &&
oldViewport.width === newViewport.width
);
};
// Holds a global viewport counter - used to assign new id's to viewports
// Starts at a value above zero so that any of the old viewport id's are
// immediately obvious and if we get any index generated viewports, they are
// definitely distinct from these ones.
let viewportCounter = 5000;
/**
* Find a viewportId to re-use if possible, preserving the existing
* viewport information, OR create a new one if the viewport given isn't
* compatible with what was there before.
*
* @param viewportIdSet
* @param viewport
* @param stateViewports
* @returns
*/
const reuseViewportId = (viewportIdSet: Set, viewport, stateViewports) => {
for (const oldViewport of stateViewports) {
const { viewportId: oldId } = oldViewport;
if (!oldId) {
// This occurs on startup, so skip re-using it
continue;
}
if (viewportIdSet.has(oldId)) {
// oldId is already used - we can't reuse it
continue;
}
if (isReuseableViewport(oldViewport, viewport)) {
viewportIdSet.add(oldId);
// This means the old and the new viewport are compatible, and
// since we have gotten here, the viewport ID isn't used, so we
// are good to reuse it.
// This will remember the old viewport options, assuming they are unchanging.
return {
...oldViewport,
...viewport,
id: oldId,
viewportId: oldId,
viewportOptions: {
// Update any viewport options from new
...viewport.viewportOptions,
viewportId: oldId,
},
};
}
}
// There wasn't an old id found to be reused, so create a new one
// Find a viewport instance number different from earlier viewports
const viewportId = 'viewport-' + viewportCounter;
viewportIdSet.add(viewportId);
// Loop over viewport counters in case of a really long lived display
viewportCounter = (viewportCounter + 1) % 100000;
// viewportOptions is already a copy, so can just update direct
viewport.viewportOptions.viewportId = viewportId;
return {
...viewport,
id: viewportId,
viewportId,
};
};
export function ViewportGridProvider({ children, service }) { export function ViewportGridProvider({ children, service }) {
const viewportGridReducer = (state, action) => { const viewportGridReducer = (state: DefaultState, action) => {
switch (action.type) { switch (action.type) {
case 'SET_ACTIVE_VIEWPORT_INDEX': { case 'SET_ACTIVE_VIEWPORT_ID': {
return { ...state, ...{ activeViewportIndex: action.payload } }; return { ...state, ...{ activeViewportId: action.payload } };
} }
/** /**
@ -130,65 +139,49 @@ export function ViewportGridProvider({ children, service }) {
*/ */
case 'SET_DISPLAYSETS_FOR_VIEWPORTS': { case 'SET_DISPLAYSETS_FOR_VIEWPORTS': {
const { payload } = action; const { payload } = action;
const viewports = state.viewports.slice(); const viewports = new Map(state.viewports);
// Have the initial id set contain all viewports not updated here payload.forEach(updatedViewport => {
const viewportIdSet = new Set(); const { viewportId, displaySetInstanceUIDs } = updatedViewport;
viewports.forEach((viewport, index) => {
if (!viewport.viewportId) { const previousViewport = viewports.get(viewportId);
return;
}
const isUpdated = payload.find(
newViewport => newViewport.viewportIndex === index
);
if (isUpdated) {
return;
}
viewportIdSet.add(viewport.viewportId);
});
for (const updatedViewport of payload) {
// Use the newly provide viewportOptions and display set options // Use the newly provide viewportOptions and display set options
// when provided, and otherwise fall back to the previous ones. // when provided, and otherwise fall back to the previous ones.
// That allows for easy updates of just the display set. // That allows for easy updates of just the display set.
const { viewportIndex, displaySetInstanceUIDs } = updatedViewport; const viewportOptions = merge(
const previousViewport = viewports[viewportIndex] || {}; {},
const viewportOptions = { updatedViewport?.viewportOptions,
...(updatedViewport.viewportOptions || previousViewport?.viewportOptions
previousViewport.viewportOptions), );
};
const displaySetOptions = updatedViewport.displaySetOptions || []; const displaySetOptions = updatedViewport.displaySetOptions || [];
if (!displaySetOptions.length) { if (!displaySetOptions.length) {
// Copy all the display set options, assuming a full set of displa // Copy all the display set options, assuming a full set of displaySet UID's is provided.
// set UID's is provided.
displaySetOptions.push(...previousViewport.displaySetOptions); displaySetOptions.push(...previousViewport.displaySetOptions);
if (!displaySetOptions.length) { if (!displaySetOptions.length) {
displaySetOptions.push({}); displaySetOptions.push({});
} }
} }
let newViewport = { const newViewport = {
...previousViewport, ...previousViewport,
displaySetInstanceUIDs, displaySetInstanceUIDs,
viewportOptions, viewportOptions,
displaySetOptions, displaySetOptions,
viewportLabel: viewportLabels[viewportIndex], viewportLabel: getViewportLabel(viewports, viewportId),
}; };
viewportOptions.presentationIds = ViewportGridService.getPresentationIds( viewportOptions.presentationIds = ViewportGridService.getPresentationIds(
newViewport, newViewport,
viewports viewports
); );
newViewport = reuseViewportId( viewports.set(viewportId, {
viewportIdSet, ...viewports.get(viewportId),
newViewport, ...newViewport,
state.viewports });
); });
newViewport.viewportIndex = previousViewport.viewportIndex;
viewports[viewportIndex] = newViewport;
}
return { ...state, viewports }; return { ...state, viewports };
} }
@ -198,13 +191,13 @@ export function ViewportGridProvider({ children, service }) {
numRows, numRows,
layoutOptions, layoutOptions,
layoutType = 'grid', layoutType = 'grid',
activeViewportIndex, activeViewportId,
findOrCreateViewport, findOrCreateViewport,
} = 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
const hasOptions = layoutOptions?.length; const hasOptions = layoutOptions?.length;
const viewports = []; const viewports = new Map<string, Viewport>();
// Options is a temporary state store which can be used by the // Options is a temporary state store which can be used by the
// findOrCreate to store state about already found viewports. Typically, // findOrCreate to store state about already found viewports. Typically,
@ -213,34 +206,46 @@ export function ViewportGridProvider({ children, service }) {
// haven't been viewed yet, and add them in the appropriate order. // haven't been viewed yet, and add them in the appropriate order.
const options = {}; const options = {};
let activeViewportIndexToSet = activeViewportIndex; let activeViewportIdToSet = activeViewportId;
for (let row = 0; row < numRows; row++) { for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) { for (let col = 0; col < numCols; col++) {
const pos = col + row * numCols; const position = col + row * numCols;
const layoutOption = layoutOptions[pos]; const layoutOption = layoutOptions[position];
const positionId = layoutOption?.positionId || `${col}-${row}`; const positionId = layoutOption?.positionId || `${col}-${row}`;
if (hasOptions && pos >= layoutOptions.length) {
if (hasOptions && position >= layoutOptions.length) {
continue; continue;
} }
if (
activeViewportIndexToSet == null && const viewport = findOrCreateViewport(
state.viewports[state.activeViewportIndex]?.positionId === position,
positionId positionId,
) { options
activeViewportIndexToSet = pos; );
}
const viewport = findOrCreateViewport(pos, positionId, options);
if (!viewport) { if (!viewport) {
continue; continue;
} }
viewport.positionId = positionId; viewport.positionId = positionId;
// If the viewport doesn't have a viewportId, we create one
if (!viewport.viewportOptions?.viewportId) {
const randomUID = utils.uuidv4().substring(0, 8);
viewport.viewportOptions.viewportId = `viewport-${randomUID}`;
}
viewport.viewportId = viewport.viewportOptions.viewportId;
// Create a new viewport object as it is getting updated here // Create a new viewport object as it is getting updated here
// and it is part of the read only state // and it is part of the read only state
viewports.push(viewport); viewports.set(viewport.viewportId, viewport);
let xPos, yPos, w, h;
if (layoutOptions && layoutOptions[pos]) { let xPos, yPos, w, h;
({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[pos]); if (layoutOptions && layoutOptions[position]) {
({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[
position
]);
} else { } else {
w = 1 / numCols; w = 1 / numCols;
h = 1 / numRows; h = 1 / numRows;
@ -248,40 +253,35 @@ export function ViewportGridProvider({ children, service }) {
yPos = row * h; yPos = row * h;
} }
viewport.width = w; Object.assign(viewport, {
viewport.height = h; width: w,
viewport.x = xPos; height: h,
viewport.y = yPos; x: xPos,
} y: yPos,
} });
activeViewportIndexToSet = activeViewportIndexToSet ?? 0; viewport.viewportLabel = getViewportLabel(
viewports,
const viewportIdSet = new Set(); viewport.viewportId
for (
let viewportIndex = 0;
viewportIndex < viewports.length;
viewportIndex++
) {
const viewport = reuseViewportId(
viewportIdSet,
viewports[viewportIndex],
state.viewports
);
if (!viewport.viewportOptions.presentationIds) {
viewport.viewportOptions.presentationIds = ViewportGridService.getPresentationIds(
viewport,
viewports
); );
if (!viewport.viewportOptions.presentationIds) {
viewport.viewportOptions.presentationIds = ViewportGridService.getPresentationIds(
viewport,
viewports
);
}
} }
viewport.viewportIndex = viewportIndex;
viewport.viewportLabel = viewportLabels[viewportIndex];
viewports[viewportIndex] = viewport;
} }
activeViewportIdToSet =
activeViewportIdToSet ??
determineActiveViewportId(state, viewports) ??
'default';
const ret = { const ret = {
...state, ...state,
activeViewportIndex: activeViewportIndexToSet, activeViewportId: activeViewportIdToSet,
layout: { layout: {
...state.layout, ...state.layout,
numCols, numCols,
@ -317,8 +317,8 @@ export function ViewportGridProvider({ children, service }) {
return viewportGridState; return viewportGridState;
}, [viewportGridState]); }, [viewportGridState]);
const setActiveViewportIndex = useCallback( const setActiveViewportId = useCallback(
index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index }), index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_ID', payload: index }),
[dispatch] [dispatch]
); );
@ -337,7 +337,7 @@ export function ViewportGridProvider({ children, service }) {
numRows, numRows,
numCols, numCols,
layoutOptions = [], layoutOptions = [],
activeViewportIndex, activeViewportId,
findOrCreateViewport, findOrCreateViewport,
}) => }) =>
dispatch({ dispatch({
@ -347,7 +347,7 @@ export function ViewportGridProvider({ children, service }) {
numRows, numRows,
numCols, numCols,
layoutOptions, layoutOptions,
activeViewportIndex, activeViewportId,
findOrCreateViewport, findOrCreateViewport,
}, },
}), }),
@ -375,7 +375,7 @@ export function ViewportGridProvider({ children, service }) {
const getNumViewportPanes = useCallback(() => { const getNumViewportPanes = useCallback(() => {
const { layout, viewports } = viewportGridState; const { layout, viewports } = viewportGridState;
const { numRows, numCols } = layout; const { numRows, numCols } = layout;
return Math.min(viewports.length, numCols * numRows); return Math.min(viewports.size, numCols * numRows);
}, [viewportGridState]); }, [viewportGridState]);
/** /**
@ -387,7 +387,7 @@ export function ViewportGridProvider({ children, service }) {
if (service) { if (service) {
service.setServiceImplementation({ service.setServiceImplementation({
getState, getState,
setActiveViewportIndex, setActiveViewportId,
setDisplaySetsForViewports, setDisplaySetsForViewports,
setLayout, setLayout,
reset, reset,
@ -399,7 +399,7 @@ export function ViewportGridProvider({ children, service }) {
}, [ }, [
getState, getState,
service, service,
setActiveViewportIndex, setActiveViewportId,
setDisplaySetsForViewports, setDisplaySetsForViewports,
setLayout, setLayout,
reset, reset,
@ -410,7 +410,7 @@ export function ViewportGridProvider({ children, service }) {
// run many of the calls through the service itself since we want to publish events // run many of the calls through the service itself since we want to publish events
const api = { const api = {
getState, getState,
setActiveViewportIndex: index => service.setActiveViewportIndex(index), setActiveViewportId: index => service.setActiveViewportId(index),
setDisplaySetsForViewport: props => setDisplaySetsForViewport: props =>
service.setDisplaySetsForViewports([props]), service.setDisplaySetsForViewports([props]),
setDisplaySetsForViewports: props => setDisplaySetsForViewports: props =>

View File

@ -16451,16 +16451,16 @@ prettier-linter-helpers@^1.0.0:
dependencies: dependencies:
fast-diff "^1.1.2" fast-diff "^1.1.2"
prettier@^1.18.2:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
prettier@^2.8.0: prettier@^2.8.0:
version "2.8.8" version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prettier@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643"
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0:
version "5.6.0" version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"