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 = () => {};
// displaySet, viewportIndex, dataSource
// displaySet, dataSource
const getViewportModule = () => {
const wrappedViewport = props => {
return (

View File

@ -1,6 +1,6 @@
async function _hydrateRTDisplaySet({
rtDisplaySet,
viewportIndex,
viewportId,
servicesManager,
}) {
const {
@ -27,7 +27,7 @@ async function _hydrateRTDisplaySet({
const { viewports } = viewportGridService.getState();
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex,
viewportId,
displaySetInstanceUID
);
@ -39,8 +39,8 @@ async function _hydrateRTDisplaySet({
// 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
// volume viewport for segmentation
viewports.forEach((viewport, index) => {
if (index === viewportIndex) {
viewports.forEach(viewport => {
if (viewport.viewportId === viewportId) {
return;
}
@ -51,9 +51,16 @@ async function _hydrateRTDisplaySet({
if (shouldDisplaySeg) {
updatedViewports.push({
viewportIndex: index,
viewportId: viewport.viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
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: {
preset: 'middle',
},

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
async function _hydrateSEGDisplaySet({
segDisplaySet,
viewportIndex,
viewportId: targetViewportId,
servicesManager,
}) {
const {
@ -27,7 +27,7 @@ async function _hydrateSEGDisplaySet({
const { viewports } = viewportGridService.getState();
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex,
targetViewportId,
displaySetInstanceUID
);
@ -37,8 +37,8 @@ async function _hydrateSEGDisplaySet({
// 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
// volume viewport for segmentation
viewports.forEach((viewport, index) => {
if (index === viewportIndex) {
viewports.forEach((viewport, viewportId) => {
if (targetViewportId === viewportId) {
return;
}
@ -49,9 +49,16 @@ async function _hydrateSEGDisplaySet({
if (shouldDisplaySeg) {
updatedViewports.push({
viewportIndex: index,
viewportId,
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
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: {
preset: 'middle',
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,8 +9,6 @@ import {
VolumeViewport,
VolumeViewport3D,
cache,
utilities,
CONSTANTS,
Enums as csEnums,
} from '@cornerstonejs/core';
@ -55,7 +53,6 @@ class CornerstoneViewportService extends PubSubService
};
renderingEngine: Types.IRenderingEngine | null;
viewportsInfo: Map<number, ViewportInfo> = new Map();
viewportsById: Map<string, ViewportInfo> = new Map();
viewportGridResizeObserver: ResizeObserver | null;
viewportsDisplaySets: Map<string, string[]> = new Map();
@ -75,45 +72,17 @@ class CornerstoneViewportService extends PubSubService
/**
* Adds the HTML element to the viewportService
* @param {*} viewportIndex
* @param {*} viewportId
* @param {*} elementRef
*/
public enableViewport(
viewportIndex: number,
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');
}
public enableViewport(viewportId: string, elementRef: HTMLDivElement): void {
const viewportInfo = new ViewportInfo(viewportId);
viewportInfo.setElement(elementRef);
this.viewportsInfo.set(viewportIndex, viewportInfo);
this.viewportsById.set(viewportId, viewportInfo);
}
public getViewportIds(): string[] {
const viewportIds = [];
this.viewportsInfo.forEach(viewportInfo => {
viewportIds.push(viewportInfo.getViewportId());
});
return viewportIds;
}
public getViewportId(viewportIndex: number): string {
return this.viewportsInfo[viewportIndex]?.viewportId;
return Array.from(this.viewportsById.keys());
}
/**
@ -171,21 +140,14 @@ class CornerstoneViewportService extends PubSubService
* created for every new viewport, this will be called whenever the set of
* viewports is changed, but NOT when the viewport position changes only.
*
* @param viewportIndex
* @param viewportId - The viewportId to disable
*/
public disableElement(viewportIndex: number): void {
const viewportInfo = this.viewportsInfo.get(viewportIndex);
if (!viewportInfo) {
return;
}
public disableElement(viewportId: string): void {
this.renderingEngine?.disableElement(viewportId);
const viewportId = viewportInfo.getViewportId();
this.renderingEngine && this.renderingEngine.disableElement(viewportId);
this.viewportsInfo.get(viewportIndex).destroy();
this.viewportsInfo.delete(viewportIndex);
// clean up
this.viewportsById.delete(viewportId);
this.viewportsDisplaySets.delete(viewportId);
}
public setPresentations(viewport, presentations?: Presentations): void {
@ -199,14 +161,14 @@ class CornerstoneViewportService extends PubSubService
}
}
public getPresentation(viewportIndex: number): Presentation {
const viewportInfo = this.viewportsInfo.get(viewportIndex);
public getPresentation(viewportId: string): Presentation {
const viewportInfo = this.viewportsById.get(viewportId);
if (!viewportInfo) {
return;
}
const { viewportType, presentationIds } = viewportInfo.getViewportOptions();
const csViewport = this.getCornerstoneViewportByIndex(viewportIndex);
const csViewport = this.getCornerstoneViewport(viewportId);
if (!csViewport) {
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
* and sets the displaySet data to the viewport
* @param {*} viewportIndex
* @param {*} displaySet
* @param {*} dataSource
* @returns
* Sets the viewport data for a viewport.
* @param viewportId - The ID of the viewport to set the data for.
* @param viewportData - The viewport data to set.
* @param publicViewportOptions - The public viewport options.
* @param publicDisplaySetOptions - The public display set options.
* @param presentations - The presentations to set.
*/
public setViewportData(
viewportIndex: number,
viewportId: string,
viewportData: StackViewportData | VolumeViewportData,
publicViewportOptions: PublicViewportOptions,
publicDisplaySetOptions: DisplaySetOptions[],
presentations?: Presentations
): void {
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);
// 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) {
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
if (viewportInfo.viewportIndex !== viewportIndex) {
this.viewportsInfo.delete(viewportInfo.viewportIndex);
this.viewportsInfo.set(viewportIndex, viewportInfo);
viewportInfo.viewportIndex = viewportIndex;
}
viewportInfo.setRenderingEngineId(renderingEngine.id);
const {
viewportOptions,
displaySetOptions,
} = this._getViewportAndDisplaySetOptions(
publicViewportOptions,
publicDisplaySetOptions,
viewportInfo
// override the viewportOptions and displaySetOptions with the public ones
// since those are the newly set ones, we set them here so that it handles defaults
const displaySetOptions = viewportInfo.setPublicDisplaySetOptions(
publicDisplaySetOptions
);
const viewportOptions = viewportInfo.setPublicViewportOptions(
publicViewportOptions
);
viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions);
viewportInfo.setViewportData(viewportData);
const element = viewportInfo.getElement();
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
// element which is not what we want. But enabledElement as part of the
// renderingEngine is designed to be used like this. This will trigger
// ENABLED_ELEMENT again and again, which will run onEnableElement callbacks
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);
this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
@ -307,7 +308,6 @@ class CornerstoneViewportService extends PubSubService
// invalid data.
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
viewportData,
viewportIndex,
viewportId,
});
}
@ -330,43 +330,8 @@ class CornerstoneViewportService extends PubSubService
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 {
// @ts-ignore
for (const [index, viewport] of this.viewportsInfo.entries()) {
if (viewport.getViewportId() === viewportId) {
return viewport;
}
}
return null;
return this.viewportsById.get(viewportId);
}
_setStackViewport(
@ -414,7 +379,7 @@ class CornerstoneViewportService extends PubSubService
}
viewport.setStack(imageIds, initialImageIndexToUse).then(() => {
viewport.setProperties(properties);
viewport.setProperties({ ...properties });
const camera = presentations.positionPresentation?.camera;
if (camera) {
viewport.setCamera(camera);
@ -541,10 +506,7 @@ class CornerstoneViewportService extends PubSubService
this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs);
if (
hangingProtocolService.hasCustomImageLoadStrategy() &&
!hangingProtocolService.customImageLoadPerformed
) {
if (hangingProtocolService.getShouldPerformCustomImageLoad()) {
// delegate the volume loading to the hanging protocol service if it has a custom image load strategy
return hangingProtocolService.runImageLoadStrategy({
viewportId: viewport.id,
@ -744,14 +706,8 @@ class CornerstoneViewportService extends PubSubService
// Todo: keepCamera is an interim solution until we have a better solution for
// keeping the camera position when the viewport data is changed
public updateViewport(
viewportIndex: number,
viewportData,
keepCamera = false
) {
const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
const viewportId = viewportInfo.getViewportId();
public updateViewport(viewportId: string, viewportData, keepCamera = false) {
const viewportInfo = this.getViewportInfo(viewportId);
const viewport = this.getCornerstoneViewport(viewportId);
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) {
const { displaySetService } = this.servicesManager.services;
const displaySet = displaySetService.getDisplaySetByUID(
@ -907,25 +833,25 @@ class CornerstoneViewportService extends PubSubService
*
* @param measurement
* 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.
* @return the viewportIndex to display the given measurement
* @return the viewportId that the measurement should be displayed in.
*/
public getViewportIndexToJump(
activeViewportIndex: number,
public getViewportIdToJump(
activeViewportId: string,
displaySetInstanceUID: string,
cameraProps: unknown
): number {
const viewportInfo = this.viewportsInfo.get(activeViewportIndex);
): string {
const viewportInfo = this.getViewportInfo(activeViewportId);
const { referencedImageId } = cameraProps;
if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) {
return activeViewportIndex;
return activeViewportId;
}
return (
[...this.viewportsById.values()].find(viewportInfo =>
viewportInfo.contains(displaySetInstanceUID, referencedImageId)
)?.viewportIndex ?? -1
)?.viewportId ?? null
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import calculateViewportRegistrations from './calculateViewportRegistrations';
// [ {
// synchronizerId: string,
// viewports: [ { viewportId: number, renderingEngineId: string, index: number } , ...]
// viewports: [ { viewportId: string, renderingEngineId: string, index: number } , ...]
// ]}
let STACK_IMAGE_SYNC_GROUPS_INFO = [];
@ -37,32 +37,25 @@ export default function toggleStackImageSync({
STACK_IMAGE_SYNC_GROUPS_INFO = [];
// create synchronization groups and add viewports
let { viewports } = viewportGridService.getState();
const { viewports } = viewportGridService.getState();
// filter empty viewports
viewports = viewports.filter(
viewport =>
viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length
);
const viewportsArray = Array.from(viewports.values())
.filter(viewport => viewport.displaySetInstanceUIDs?.length)
// filter reconstructable viewports
.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
// filter reconstructable viewports
viewports = viewports.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
if (displaySet && displaySet.isReconstructable) {
return true;
return !!displaySet?.isReconstructable;
}
});
return false;
}
});
const viewportsByOrientation = viewports.reduce((acc, viewport) => {
const viewportsByOrientation = viewportsArray.reduce((acc, viewport) => {
const { viewportId, viewportType } = viewport.viewportOptions;
if (viewportType !== 'stack') {
@ -71,9 +64,8 @@ export default function toggleStackImageSync({
}
const { element } = cornerstoneViewportService.getViewportInfo(viewportId);
const { viewport: csViewport, renderingEngineId } = getEnabledElement(
element
);
const { viewport: csViewport, renderingEngineId } =
getEnabledElement(element);
const { viewPlaneNormal } = csViewport.getCamera();
// 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,
}): React.FunctionComponent {
const [viewportGrid, viewportGridService] = useViewportGrid();
const { activeViewportIndex, viewports } = viewportGrid;
const { activeViewportId, viewports } = viewportGrid;
const {
measurementService,
uiDialogService,
@ -80,7 +80,7 @@ export default function PanelMeasurementTable({
async function createReport(): Promise<any> {
// filter measurements that are added to the active study
const activeViewport = viewports[activeViewportIndex];
const activeViewport = viewports.get(activeViewportId);
const measurements = measurementService.getMeasurements();
const displaySet = displaySetService.getDisplaySetByUID(
activeViewport.displaySetInstanceUIDs[0]
@ -133,7 +133,7 @@ export default function PanelMeasurementTable({
}
const jumpToImage = ({ uid, isActive }) => {
measurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid);
measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid);
onMeasurementItemClickHandler({ uid, isActive });
};

View File

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

View File

@ -261,17 +261,17 @@ const commandsModule = ({
];
stateSyncService.store(stateSyncReduce);
// This is a default action applied
actions.toggleHpTools(hangingProtocolService.getActiveProtocol());
const { protocol } = hangingProtocolService.getActiveProtocol();
actions.toggleHpTools(protocol);
// Send the notification about updating the state
if (protocolId !== hpInfo.protocolId) {
const { protocol } = hangingProtocolService.getActiveProtocol();
// The old protocol callbacks are used for turning off things
// like crosshairs when moving to the new HP
commandsManager.run(oldProtocol.callbacks?.onProtocolExit);
// The new protocol callback is used for things like
// activating modes etc.
commandsManager.run(protocol.callbacks?.onProtocolEnter);
}
commandsManager.run(protocol.callbacks?.onProtocolEnter);
return true;
} catch (e) {
actions.toggleHpTools(hangingProtocolService.getActiveProtocol());
@ -394,12 +394,12 @@ const commandsModule = ({
toggleOneUp() {
const viewportGridState = viewportGridService.getState();
const { activeViewportIndex, viewports, layout } = viewportGridState;
const { activeViewportId, viewports, layout } = viewportGridState;
const {
displaySetInstanceUIDs,
displaySetOptions,
viewportOptions,
} = viewports[activeViewportIndex];
} = viewports.get(activeViewportId);
if (layout.numCols === 1 && layout.numRows === 1) {
// 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
// originally toggled to one up was the former active viewport.
const viewportIndexToUpdate =
toggleOneUpViewportGridStore.activeViewportIndex;
const viewportIdToUpdate =
toggleOneUpViewportGridStore.activeViewportId;
// Determine which viewports need to be updated. This is particularly
// 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 =
const updatedViewportsViaHP =
displaySetInstanceUIDs.length > 1
? []
: displaySetInstanceUIDs
.map(displaySetInstanceUID =>
hangingProtocolService.getViewportsRequireUpdate(
viewportIndexToUpdate,
viewportIdToUpdate,
displaySetInstanceUID
)
)
@ -432,14 +428,37 @@ const commandsModule = ({
// This findOrCreateViewport returns either one of the updatedViewports
// returned from the HP service OR if there is not one from the HP service then
// simply returns what was in the previous state.
const findOrCreateViewport = (viewportIndex: number) => {
const viewport = updatedViewports.find(
viewport => viewport.viewportIndex === viewportIndex
);
const findOrCreateViewport = (position: number) => {
// get the viewportId in the current state (since we are in the one-up layout)
const currentOneUpViewport = Array.from(viewports.values())[0];
return viewport
? { viewportOptions, displaySetOptions, ...viewport }
: toggleOneUpViewportGridStore.viewports[viewportIndex];
// we should restore the previous layout but take into the account the fact that
// the current one up viewport might have a new displaySet dragged and dropped on it
// 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(
@ -450,7 +469,7 @@ const commandsModule = ({
viewportGridService.setLayout({
numRows: toggleOneUpViewportGridStore.layout.numRows,
numCols: toggleOneUpViewportGridStore.layout.numCols,
activeViewportIndex: viewportIndexToUpdate,
activeViewportId: viewportIdToUpdate,
layoutOptions,
findOrCreateViewport,
});
@ -520,8 +539,8 @@ const commandsModule = ({
},
openDICOMTagViewer() {
const { activeViewportIndex, viewports } = viewportGridService.getState();
const activeViewportSpecificData = viewports[activeViewportIndex];
const { activeViewportId, viewports } = viewportGridService.getState();
const activeViewportSpecificData = viewports.get(activeViewportId);
const { displaySetInstanceUIDs } = activeViewportSpecificData;
const displaySets = displaySetService.activeDisplaySets;
@ -552,17 +571,9 @@ const commandsModule = ({
},
scrollActiveThumbnailIntoView: () => {
const { activeViewportIndex, viewports } = viewportGridService.getState();
const { activeViewportId, viewports } = viewportGridService.getState();
if (
!viewports ||
activeViewportIndex < 0 ||
activeViewportIndex > viewports.length - 1
) {
return;
}
const activeViewport = viewports[activeViewportIndex];
const activeViewport = viewports.get(activeViewportId);
const activeDisplaySetInstanceUID =
activeViewport.displaySetInstanceUIDs[0];
@ -615,9 +626,9 @@ const commandsModule = ({
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 =>
displaySetInstanceUIDs.includes(displaySet.displaySetInstanceUID)
@ -656,7 +667,7 @@ const commandsModule = ({
try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
activeViewportIndex,
activeViewportId,
displaySetInstanceUID
);
} 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
@ -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.
* @param hangingProtocolService - 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 options - the set of options used, so that subsequent calls can
* store state that is reset by the setLayout.
@ -17,7 +17,7 @@ import { StateSyncService, Types } from '@ohif/core';
export const findOrCreateViewport = (
hangingProtocolService,
viewportsByPosition,
viewportIndex: number,
position: number,
positionId: string,
options: Record<string, unknown>
) => {
@ -74,24 +74,19 @@ const findViewportsByPosition = (
const viewportsByPosition = { ...syncState.viewportsByPosition };
const initialInDisplay = [];
for (const viewport of viewports) {
viewports.forEach(viewport => {
if (viewport.positionId) {
const storedViewport = {
...viewport,
viewportOptions: { ...viewport.viewportOptions },
};
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 col = 0; col < numCols; col++) {
const pos = col + row * numCols;
const positionId = viewports?.[pos]?.positionId || `${col}-${row}`;
const positionId = `${col}-${row}`;
const viewport = viewportsByPosition[positionId];
if (viewport?.displaySetInstanceUIDs) {
initialInDisplay.push(...viewport.displaySetInstanceUIDs);

View File

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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ const formatLength = (length, unit) => {
interface IMicroscopyPanelProps extends WithTranslation {
viewports: PropTypes.array;
activeViewportIndex: PropTypes.number;
activeViewportId: PropTypes.string;
//
onSaveComplete?: PropTypes.func; // callback when successfully saved annotations
@ -83,7 +83,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const { uiDialogService, displaySetService } = servicesManager.services;
useEffect(() => {
const viewport = props.viewports[props.activeViewportIndex];
const viewport = props.viewports[props.activeViewportId];
if (viewport.displaySetInstanceUIDs[0]) {
const displaySet = displaySetService.getDisplaySetByUID(
viewport.displaySetInstanceUIDs[0]
@ -92,7 +92,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
setStudyInstanceUID(displaySet.StudyInstanceUID);
}
}
}, [props.viewports, props.activeViewportIndex]);
}, [props.viewports, props.activeViewportId]);
useEffect(() => {
const onAnnotationUpdated = () => {
@ -265,7 +265,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
*/
const onDeleteCurrentSRHandler = async () => {
try {
const activeViewport = props.viewports[props.activeViewportIndex];
const activeViewport = props.viewports[props.activeViewportId];
const { StudyInstanceUID } = activeViewport;
// TODO: studies?
@ -307,7 +307,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => {
const roiAnnotation = microscopyService.getAnnotation(uid);
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: () => {
// overlay
const overlays = document.getElementsByClassName(
@ -149,8 +136,8 @@ export default function getCommandsModule({
}
// overview
const { activeViewportIndex, viewports } = viewportGridService.getState();
microscopyService.toggleOverviewMap(activeViewportIndex);
const { activeViewportId, viewports } = viewportGridService.getState();
microscopyService.toggleOverviewMap(activeViewportId);
},
toggleAnnotations: () => {
microscopyService.toggleROIsVisibility();
@ -173,14 +160,6 @@ export default function getCommandsModule({
storeContexts: [] as any[],
options: {},
},
incrementActiveViewport: {
commandFn: actions.incrementActiveViewport,
storeContexts: [] as any[],
},
decrementActiveViewport: {
commandFn: actions.decrementActiveViewport,
storeContexts: [] as any[],
},
toggleOverlays: {
commandFn: actions.toggleOverlays,
storeContexts: [] as any[],

View File

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

View File

@ -52,7 +52,7 @@ export default {
*
* @param props {*}
* @param props.displaySets
* @param props.viewportIndex
* @param props.viewportId
* @param props.viewportLabel
* @param props.dataSource
* @param props.viewportOptions
@ -63,16 +63,16 @@ export default {
const { viewportOptions } = props;
const [viewportGrid, viewportGridService] = useViewportGrid();
const { viewports, activeViewportIndex } = viewportGrid;
const { activeViewportId } = viewportGrid;
return (
<MicroscopyViewport
servicesManager={servicesManager}
extensionManager={extensionManager}
commandsManager={commandsManager}
activeViewportIndex={activeViewportIndex}
setViewportActive={(viewportIndex: number) => {
viewportGridService.setActiveViewportIndex(viewportIndex);
activeViewportId={activeViewportId}
setViewportActive={(viewportId: string) => {
viewportGridService.setActiveViewportId(viewportId);
}}
viewportData={viewportOptions}
{...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.
* Adds event subscriptions for the viewer being added.
* Focuses the selected annotation when the viewer is being loaded into the
* active viewport.
*
* @param {Object} viewer Third-party viewer API's object to be managed
* @param {Number} viewportIndex The index of the viewport to load the viewer
* @param {HTMLElement} container The DOM element where it will be renderd
* @param {String} studyInstanceUID The study UID of the loaded image
* @param {String} seriesInstanceUID The series UID of the loaded image
* @param {Array} displaySets All displaySets related to the same StudyInstanceUID
* @param viewer - Third-party viewer API's object to be managed
* @param viewportId - The viewport Id where the viewer will be loaded
* @param container - The DOM element where it will be rendered
* @param studyInstanceUID - The study UID of the loaded image
* @param seriesInstanceUID - The series UID of the loaded image
* @param displaySets - All displaySets related to the same StudyInstanceUID
*
* @returns {ViewerManager} managed viewer
*/
addViewer(
viewer,
viewportIndex,
viewportId,
container,
studyInstanceUID,
seriesInstanceUID
) {
const managedViewer = new ViewerManager(
viewer,
viewportIndex,
viewportId,
container,
studyInstanceUID,
seriesInstanceUID
@ -325,7 +325,7 @@ export default class MicroscopyService extends PubSubService {
if (this.pendingFocus) {
this.pendingFocus = false;
this.focusAnnotation(this.selectedAnnotation, viewportIndex);
this.focusAnnotation(this.selectedAnnotation, viewportId);
}
return managedViewer;
@ -502,13 +502,13 @@ export default class MicroscopyService extends PubSubService {
/**
* Toggles overview map
*
* @param viewportIndex The active viewport index
* @param viewportId The active viewport index
* @returns {void}
*/
toggleOverviewMap(viewportIndex) {
toggleOverviewMap(viewportId) {
const managedViewers = Array.from(this.managedViewers);
const managedViewer = managedViewers.find(
mv => mv.viewportIndex === viewportIndex
mv => mv.viewportId === viewportId
);
if (managedViewer) {
managedViewer.toggleOverviewMap();
@ -550,10 +550,10 @@ export default class MicroscopyService extends PubSubService {
* the managed viewer instance is created.
*
* @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) {
const filter = mv => mv.viewportIndex === viewportIndex;
focusAnnotation(roiAnnotation, viewportId) {
const filter = mv => mv.viewportId === viewportId;
const managedViewer = Array.from(this.managedViewers).find(filter);
if (managedViewer) {
managedViewer.setViewStateByExtent(roiAnnotation);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ function ViewerViewportGrid(props) {
const { servicesManager, viewportComponents, dataSource } = props;
const [viewportGrid, viewportGridService] = useViewportGrid();
const { layout, activeViewportIndex, viewports } = viewportGrid;
const { layout, activeViewportId, viewports } = viewportGrid;
const { numCols, numRows } = layout;
// 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
* setup of the various viewports.
*/
const findOrCreateViewport = viewportIndex => {
const details = viewportMatchDetails.get(viewportIndex);
const findOrCreateViewport = pos => {
const viewportId = Array.from(viewportMatchDetails.keys())[pos];
const details = viewportMatchDetails.get(viewportId);
if (!details) {
console.log('No match details for viewport', viewportIndex);
console.log('No match details for viewport', viewportId);
return;
}
@ -99,11 +100,11 @@ function ViewerViewportGrid(props) {
};
const _getUpdatedViewports = useCallback(
(viewportIndex, displaySetInstanceUID) => {
(viewportId, displaySetInstanceUID) => {
let updatedViewports = [];
try {
updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
viewportIndex,
viewportId,
displaySetInstanceUID
);
} catch (error) {
@ -144,7 +145,7 @@ function ViewerViewportGrid(props) {
useEffect(() => {
const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
({ viewportIndex, measurement, isConsumed }) => {
({ viewportId, measurement, isConsumed }) => {
if (isConsumed) {
return;
}
@ -155,7 +156,7 @@ function ViewerViewportGrid(props) {
measurement;
const updatedViewports = _getUpdatedViewports(
viewportIndex,
viewportId,
referencedDisplaySetInstanceUID
);
// Arbitrarily assign the viewport to element 0
@ -197,7 +198,7 @@ function ViewerViewportGrid(props) {
}, [viewports]);
/**
const onDoubleClick = viewportIndex => {
const onDoubleClick = viewportId => {
// TODO -> Disabled for now.
// onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport.
// Which it really really shouldn't. We need a larger fix for jump to
@ -206,7 +207,7 @@ function ViewerViewportGrid(props) {
viewportGridService.set({
numCols: cachedLayout.numCols,
numRows: cachedLayout.numRows,
activeViewportIndex: cachedLayout.activeViewportIndex,
activeViewportId: cachedLayout.activeViewportId,
viewports: cachedLayout.viewports,
cachedLayout: null,
});
@ -223,10 +224,10 @@ function ViewerViewportGrid(props) {
viewportGridService.set({
numCols: 1,
numRows: 1,
activeViewportIndex: 0,
activeViewportId: 0,
viewports: [
{
displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID,
displaySetInstanceUID: viewports[viewportId].displaySetInstanceUID,
imageIndex: undefined,
},
],
@ -234,15 +235,15 @@ function ViewerViewportGrid(props) {
numCols,
numRows,
viewports: cachedViewports,
activeViewportIndex: viewportIndex,
activeViewportId: viewportId,
},
});
};
*/
const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => {
const onDropHandler = (viewportId, { displaySetInstanceUID }) => {
const updatedViewports = _getUpdatedViewports(
viewportIndex,
viewportId,
displaySetInstanceUID
);
viewportGridService.setDisplaySetsForViewports(updatedViewports);
@ -253,13 +254,7 @@ function ViewerViewportGrid(props) {
const numViewportPanes = viewportGridService.getNumViewportPanes();
for (let i = 0; i < numViewportPanes; i++) {
const viewportIndex = i;
const isActive = activeViewportIndex === viewportIndex;
const paneMetadata = viewports[i] || {};
const viewportId = paneMetadata.viewportId || `viewport-${i}`;
if (!paneMetadata.viewportId) {
paneMetadata.viewportId = viewportId;
}
const paneMetadata = Array.from(viewports.values())[i] || {};
const {
displaySetInstanceUIDs,
viewportOptions,
@ -271,9 +266,13 @@ function ViewerViewportGrid(props) {
viewportLabel,
} = paneMetadata;
const viewportId = viewportOptions.viewportId
const isActive = activeViewportId === viewportId;
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(
displaySetInstanceUID => {
return (
@ -307,17 +306,23 @@ function ViewerViewportGrid(props) {
event.stopPropagation();
}
viewportGridService.setActiveViewportIndex(viewportIndex);
viewportGridService.setActiveViewportId(viewportId);
};
// TEMP -> Double click disabled for now
// onDoubleClick={() => onDoubleClick(viewportIndex)}
viewportPanes[i] = (
<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}
acceptDropsFor="displayset"
onDrop={onDropHandler.bind(null, viewportIndex)}
onDrop={onDropHandler.bind(null, viewportId)}
onInteraction={onInteractionHandler}
customStyle={{
position: 'absolute',
@ -336,8 +341,8 @@ function ViewerViewportGrid(props) {
>
<ViewportComponent
displaySets={displaySets}
viewportIndex={viewportIndex}
viewportLabel={viewports.length > 1 ? viewportLabel : ''}
viewportLabel={viewports.size > 1 ? viewportLabel : ''}
viewportId={viewportId}
dataSource={dataSource}
viewportOptions={viewportOptions}
displaySetOptions={displaySetOptions}
@ -349,7 +354,7 @@ function ViewerViewportGrid(props) {
}
return viewportPanes;
}, [viewports, activeViewportIndex, viewportComponents, dataSource]);
}, [viewports, activeViewportId, viewportComponents, dataSource]);
/**
* 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];
}
console.log(
'Test',
`${from}.${attribute}`,
readValues[attribute],
JSON.stringify(rule.constraint),
!errorMessages
);
// TODO: move to a logger
// console.log(
// 'Test',
// `${from}.${attribute}`,
// readValues[attribute],
// JSON.stringify(rule.constraint),
// !errorMessages
// );
if (!errorMessages) {
// If no errorMessages were returned, then validation passed.

View File

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

View File

@ -14,6 +14,7 @@ import {
} from './custom-attribute/isDisplaySetFromUrl';
import numberOfDisplaySetsWithImages from './custom-attribute/numberOfDisplaySetsWithImages';
import seriesDescriptionsFromDisplaySets from './custom-attribute/seriesDescriptionsFromDisplaySets';
import uuidv4 from '../../utils/uuidv4';
type Protocol = HangingProtocol.Protocol | HangingProtocol.ProtocolGenerator;
@ -132,11 +133,11 @@ export default class HangingProtocolService extends PubSubService {
> = 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
*/
viewportMatchDetails: Map<
number, // viewportIndex
string, // viewportId
HangingProtocol.ViewportMatchDetails
> = new Map();
@ -185,7 +186,7 @@ export default class HangingProtocolService extends PubSubService {
stage: HangingProtocol.ProtocolStage;
stageIndex: number;
activeStudy?: StudyMetadata;
viewportMatchDetails: Map<number, HangingProtocol.ViewportMatchDetails>;
viewportMatchDetails: Map<string, HangingProtocol.ViewportMatchDetails>;
displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>;
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
* 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 {
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 {
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
* @param {string} name strategy name
@ -519,21 +541,28 @@ export default class HangingProtocolService extends PubSubService {
for (let i = 0; i < rows * columns; i++) {
stage.viewports.push({
viewportOptions: defaultViewportOptions,
viewportOptions: {
...defaultViewportOptions,
viewportId: uuidv4(),
},
displaySets: [],
});
}
} 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 => {
viewport.viewportOptions =
viewport.viewportOptions || defaultViewportOptions;
if (!viewport.displaySets) {
viewport.displaySets = [];
} else {
viewport.displaySets.forEach(displaySet => {
displaySet.options = displaySet.options || {};
});
}
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 displaySet = displaySetService.getDisplaySetByUID(
displaySetInstanceUID
@ -570,15 +599,22 @@ export default class HangingProtocolService extends PubSubService {
const protocol = this.protocol;
const protocolStage = protocol.stages[this.stageIndex];
const protocolViewports = protocolStage.viewports;
const protocolViewport = protocolViewports[viewportIndex];
const defaultReturn = [
{
viewportIndex,
viewportId,
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
// rules that should be applied to this viewport while matching
if (!protocolViewport) {
@ -644,13 +680,13 @@ export default class HangingProtocolService extends PubSubService {
}
// 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
// was also referenced by other viewports, and if so, we need to update those
// viewports as well
// 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 = [];
protocolViewports.forEach((viewport, index) => {
const viewportNeedsUpdate = viewport.displaySets.some(
@ -661,7 +697,7 @@ export default class HangingProtocolService extends PubSubService {
if (viewportNeedsUpdate) {
// 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
if (originalProtocolStage) {
const originalViewport = originalProtocolStage.viewports[index];
@ -692,7 +728,7 @@ export default class HangingProtocolService extends PubSubService {
);
viewportsToUpdate.push({
viewportIndex: index,
viewportId: viewport.viewportOptions.viewportId,
displaySetInstanceUIDs,
viewportOptions: viewport.viewportOptions,
displaySetOptions,
@ -799,7 +835,7 @@ export default class HangingProtocolService extends PubSubService {
* @param protocolId - name of the registered protocol to be set
* @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
* 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.
* @param errorCallback - callback to be called if there is an error
* during the protocol application
@ -918,7 +954,7 @@ export default class HangingProtocolService extends PubSubService {
const stage = stages[i];
if (stage.id === stageId && stage.status !== 'disabled') {
return i;
}
}
}
return;
}
@ -1098,7 +1134,6 @@ export default class HangingProtocolService extends PubSubService {
// matching applied
this.viewportMatchDetails = new Map();
this.displaySetMatchDetails = new Map();
this.customImageLoadPerformed = false;
// Retrieve the current stage
const stageModel = this._getCurrentStageModel();
@ -1148,7 +1183,11 @@ export default class HangingProtocolService extends PubSubService {
displaySetMatchDetails: Map<string, HangingProtocol.DisplaySetMatchDetails>;
} {
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(
viewport,
options,
@ -1169,7 +1208,7 @@ export default class HangingProtocolService extends PubSubService {
displaySetOptions: it,
}));
}
viewportMatchDetails.set(viewportIndex, matchDetails);
viewportMatchDetails.set(viewportId, matchDetails);
}
});
return {
@ -1231,7 +1270,7 @@ export default class HangingProtocolService extends PubSubService {
for (const subMatch of match.matchingScores) {
if (subMatch.displaySetInstanceUID === displaySetUID) {
return;
}
}
}
throw new Error(
`Reused viewport details ${id} with ds ${displaySetUID} not valid`
@ -1442,15 +1481,15 @@ export default class HangingProtocolService extends PubSubService {
);
const studyMatchDetails = this.protocolEngine.findMatch(
study,
studyMatchingRules,
{
studies: this.studies,
displaySets: studyDisplaySets,
allDisplaySets: this.displaySets,
displaySetMatchDetails: this.displaySetMatchDetails,
study,
studyMatchingRules,
{
studies: this.studies,
displaySets: studyDisplaySets,
allDisplaySets: this.displaySets,
displaySetMatchDetails: this.displaySetMatchDetails,
studyInstanceUIDsIndex,
}
}
);
// 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(
'study',
study.StudyInstanceUID,
'display sets #',
studyDisplaySets.length
'study',
study.StudyInstanceUID,
'display sets #',
studyDisplaySets.length
);
studyDisplaySets.forEach(displaySet => {
const {
@ -1471,15 +1510,15 @@ export default class HangingProtocolService extends PubSubService {
displaySetInstanceUID,
} = displaySet;
const seriesMatchDetails = this.protocolEngine.findMatch(
displaySet,
seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it
{
studies: this.studies,
instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets,
}
displaySet,
seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it
{
studies: this.studies,
instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets,
}
);
// 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.
*/
public jumpToMeasurement(
viewportIndex: number,
measurementUID: string
): void {
public jumpToMeasurement(viewportId: string, measurementUID: string): void {
const measurement = this.measurements.get(measurementUID);
if (!measurement) {
@ -660,7 +657,7 @@ class MeasurementService extends PubSubService {
return;
}
const consumableEvent = this.createConsumableEvent({
viewportIndex,
viewportId,
measurement,
});

View File

@ -4,7 +4,7 @@
* @typedef {Object} ViewportDialogProps
* @property {ReactElement|HTMLElement} [content=null] Modal content.
* @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';
@ -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({
viewportIndex,
viewportId,
id,
type,
message,
@ -36,7 +36,7 @@ function _show({
onOutsideClick,
}) {
return serviceImplementation._show({
viewportIndex,
viewportId,
id,
type,
message,
@ -59,7 +59,7 @@ function _hide() {
* @param {*} {
* hide: hideImplementation,
* show: showImplementation,
* viewportIndex,
* viewportId,
* }
*/
function setServiceImplementation({

View File

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

View File

@ -25,7 +25,7 @@ const addUniqueIndex = (arr, key, viewports, isUpdatingSameViewport) => {
arr[arr.length - 1] = displayInstance;
const testId = arr.join(JOIN_STR);
if (
!viewports.find(
!Array.from(viewports.values()).find(
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
const isUpdatingSameViewport = viewports.some(v => {
const isUpdatingSameViewport = Array.from(viewports.values()).some(v => {
return (
v.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 = {
displaySetMatchDetails: Map<string, DisplaySetMatchDetails>;
viewportMatchDetails: Map<number, ViewportMatchDetails>;
viewportMatchDetails: Map<string, ViewportMatchDetails>;
};
export type ConstraintValue =
@ -263,6 +263,8 @@ export type ProtocolNotifications = {
export type Protocol = {
// Mandatory
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 */
displaySetSelectors: Record<string, DisplaySetSelector>;
/** A default viewport to use for any stage to select new viewport layouts. */

View File

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

View File

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

View File

@ -1,7 +1,7 @@
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'
* 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.
@ -24,10 +24,6 @@ function subscribeToNextViewportGridChange(
viewportGridService.EVENTS.LAYOUT_CHANGED,
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
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
function TrackedCornerstoneViewport({
children,
dataSource,
displaySets,
viewportIndex,
viewportId,
servicesManager,
extensionManager,
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`

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
the source definition.
- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have
- `jumpToMeasurement(viewportId, id)`: calls the listeners who have
subscribed to `JUMP_TO_MEASUREMENT`.
## Source / Mappers

View File

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

View File

@ -15,7 +15,7 @@ There are seven events that get publish in `ViewportGridService `:
| 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 |
| GRID_STATE_CHANGED | Fires when the entire grid state is changed |
## Interface
@ -25,9 +25,9 @@ is expected to support, [check out it's interface in `@ohif/core`][interface]
| 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) |
| `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. |
| `reset()` | Resets the default states |
| `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
<viewportComponent
viewportData="{viewportData}"
viewportIndex="{viewportIndex}"
viewportId="{viewportId}"
children="{[children]}"
/>
```
@ -34,7 +34,7 @@ Each `ViewportComponent` will receive the following props:
| --------------- | --------------- | --------------------------------- |
| `children` | React.element[] | |
| `viewportData` | object | `viewportSpecificData` (probably) |
| `viewportIndex` | number | |
| `viewportId` | string | |
### `@ohif/viewer`

View File

@ -10,7 +10,7 @@ const Footer = ({ actions, className, onSubmit, value }) => {
return (
<div className={classNames(flex, padding, className)}>
{actions.map((action, index) => {
{actions?.map((action, index) => {
const isFirst = index === 0;
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>
</div>
<div className="flex flex-wrap gap-2 justify-end mt-2">
{actions.map((action, index) => {
const isFirst = index === 0;
{actions?.map((action, index) => {
return (
<Button
name={action.id}

View File

@ -1,18 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import LegacyViewportActionBar from '../LegacyViewportActionBar';
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,
viewportIndex,
onArrowsClick,
studyData,
children,
}) => {
if (!viewportId) {
viewportId = `viewport-${viewportIndex}`;
}
return (
<div className="relative flex flex-col h-full">
<div className="absolute top-0 left-0 w-full">
@ -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 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,
}: ViewportActionBarProps): JSX.Element => {
const { label, studyDate, seriesDescription, patientInformation } = studyData;
const {
patientName,
patientSex,

View File

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

View File

@ -5,121 +5,130 @@ import React, {
useEffect,
useReducer,
} from 'react';
import merge from 'lodash.merge';
import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal';
import { ViewportGridService } from '@ohif/core';
import { ViewportGridService, utils } from '@ohif/core';
import viewportLabels from '../utils/viewportLabels';
const DEFAULT_STATE = {
activeViewportIndex: 0,
interface Viewport {
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: {
numRows: 0,
numCols: 0,
layoutType: 'grid',
},
viewports: [
{
displaySetInstanceUIDs: [],
viewportOptions: {},
displaySetSelectors: [],
displaySetOptions: [{}],
x: 0, // left
y: 0, // top
width: 100,
height: 100,
viewportLabel: null,
},
],
// Viewports structure has been changed to Map (previously it was
// tied to the viewportIndex which caused multiple issues. Now we have
// moved completely to viewportId which is unique for each viewport.
viewports: new Map(
Object.entries({
default: {
viewportId: 'default',
displaySetInstanceUIDs: [],
viewportOptions: {
viewportId: 'default',
},
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);
/** 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 }) {
const viewportGridReducer = (state, action) => {
const viewportGridReducer = (state: DefaultState, action) => {
switch (action.type) {
case 'SET_ACTIVE_VIEWPORT_INDEX': {
return { ...state, ...{ activeViewportIndex: action.payload } };
case 'SET_ACTIVE_VIEWPORT_ID': {
return { ...state, ...{ activeViewportId: action.payload } };
}
/**
@ -130,65 +139,49 @@ export function ViewportGridProvider({ children, service }) {
*/
case 'SET_DISPLAYSETS_FOR_VIEWPORTS': {
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
const viewportIdSet = new Set();
viewports.forEach((viewport, index) => {
if (!viewport.viewportId) {
return;
}
const isUpdated = payload.find(
newViewport => newViewport.viewportIndex === index
);
if (isUpdated) {
return;
}
viewportIdSet.add(viewport.viewportId);
});
payload.forEach(updatedViewport => {
const { viewportId, displaySetInstanceUIDs } = updatedViewport;
const previousViewport = viewports.get(viewportId);
for (const updatedViewport of payload) {
// Use the newly provide viewportOptions and display set options
// when provided, and otherwise fall back to the previous ones.
// That allows for easy updates of just the display set.
const { viewportIndex, displaySetInstanceUIDs } = updatedViewport;
const previousViewport = viewports[viewportIndex] || {};
const viewportOptions = {
...(updatedViewport.viewportOptions ||
previousViewport.viewportOptions),
};
const viewportOptions = merge(
{},
updatedViewport?.viewportOptions,
previousViewport?.viewportOptions
);
const displaySetOptions = updatedViewport.displaySetOptions || [];
if (!displaySetOptions.length) {
// Copy all the display set options, assuming a full set of displa
// set UID's is provided.
// Copy all the display set options, assuming a full set of displaySet UID's is provided.
displaySetOptions.push(...previousViewport.displaySetOptions);
if (!displaySetOptions.length) {
displaySetOptions.push({});
}
}
let newViewport = {
const newViewport = {
...previousViewport,
displaySetInstanceUIDs,
viewportOptions,
displaySetOptions,
viewportLabel: viewportLabels[viewportIndex],
viewportLabel: getViewportLabel(viewports, viewportId),
};
viewportOptions.presentationIds = ViewportGridService.getPresentationIds(
newViewport,
viewports
);
newViewport = reuseViewportId(
viewportIdSet,
newViewport,
state.viewports
);
newViewport.viewportIndex = previousViewport.viewportIndex;
viewports[viewportIndex] = newViewport;
}
viewports.set(viewportId, {
...viewports.get(viewportId),
...newViewport,
});
});
return { ...state, viewports };
}
@ -198,13 +191,13 @@ export function ViewportGridProvider({ children, service }) {
numRows,
layoutOptions,
layoutType = 'grid',
activeViewportIndex,
activeViewportId,
findOrCreateViewport,
} = action.payload;
// If empty viewportOptions, we use numRow and numCols to calculate number of viewports
const hasOptions = layoutOptions?.length;
const viewports = [];
const viewports = new Map<string, Viewport>();
// Options is a temporary state store which can be used by the
// 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.
const options = {};
let activeViewportIndexToSet = activeViewportIndex;
let activeViewportIdToSet = activeViewportId;
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
const pos = col + row * numCols;
const layoutOption = layoutOptions[pos];
const position = col + row * numCols;
const layoutOption = layoutOptions[position];
const positionId = layoutOption?.positionId || `${col}-${row}`;
if (hasOptions && pos >= layoutOptions.length) {
if (hasOptions && position >= layoutOptions.length) {
continue;
}
if (
activeViewportIndexToSet == null &&
state.viewports[state.activeViewportIndex]?.positionId ===
positionId
) {
activeViewportIndexToSet = pos;
}
const viewport = findOrCreateViewport(pos, positionId, options);
const viewport = findOrCreateViewport(
position,
positionId,
options
);
if (!viewport) {
continue;
}
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
// and it is part of the read only state
viewports.push(viewport);
let xPos, yPos, w, h;
viewports.set(viewport.viewportId, viewport);
if (layoutOptions && layoutOptions[pos]) {
({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[pos]);
let xPos, yPos, w, h;
if (layoutOptions && layoutOptions[position]) {
({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[
position
]);
} else {
w = 1 / numCols;
h = 1 / numRows;
@ -248,40 +253,35 @@ export function ViewportGridProvider({ children, service }) {
yPos = row * h;
}
viewport.width = w;
viewport.height = h;
viewport.x = xPos;
viewport.y = yPos;
}
}
Object.assign(viewport, {
width: w,
height: h,
x: xPos,
y: yPos,
});
activeViewportIndexToSet = activeViewportIndexToSet ?? 0;
const viewportIdSet = new Set();
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
viewport.viewportLabel = getViewportLabel(
viewports,
viewport.viewportId
);
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 = {
...state,
activeViewportIndex: activeViewportIndexToSet,
activeViewportId: activeViewportIdToSet,
layout: {
...state.layout,
numCols,
@ -317,8 +317,8 @@ export function ViewportGridProvider({ children, service }) {
return viewportGridState;
}, [viewportGridState]);
const setActiveViewportIndex = useCallback(
index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index }),
const setActiveViewportId = useCallback(
index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_ID', payload: index }),
[dispatch]
);
@ -337,7 +337,7 @@ export function ViewportGridProvider({ children, service }) {
numRows,
numCols,
layoutOptions = [],
activeViewportIndex,
activeViewportId,
findOrCreateViewport,
}) =>
dispatch({
@ -347,7 +347,7 @@ export function ViewportGridProvider({ children, service }) {
numRows,
numCols,
layoutOptions,
activeViewportIndex,
activeViewportId,
findOrCreateViewport,
},
}),
@ -375,7 +375,7 @@ export function ViewportGridProvider({ children, service }) {
const getNumViewportPanes = useCallback(() => {
const { layout, viewports } = viewportGridState;
const { numRows, numCols } = layout;
return Math.min(viewports.length, numCols * numRows);
return Math.min(viewports.size, numCols * numRows);
}, [viewportGridState]);
/**
@ -387,7 +387,7 @@ export function ViewportGridProvider({ children, service }) {
if (service) {
service.setServiceImplementation({
getState,
setActiveViewportIndex,
setActiveViewportId,
setDisplaySetsForViewports,
setLayout,
reset,
@ -399,7 +399,7 @@ export function ViewportGridProvider({ children, service }) {
}, [
getState,
service,
setActiveViewportIndex,
setActiveViewportId,
setDisplaySetsForViewports,
setLayout,
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
const api = {
getState,
setActiveViewportIndex: index => service.setActiveViewportIndex(index),
setActiveViewportId: index => service.setActiveViewportId(index),
setDisplaySetsForViewport: props =>
service.setDisplaySetsForViewports([props]),
setDisplaySetsForViewports: props =>

View File

@ -16451,16 +16451,16 @@ prettier-linter-helpers@^1.0.0:
dependencies:
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:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
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:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"