fix(viewport-sync): remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync (#3849)

This commit is contained in:
Alireza 2024-01-22 09:44:35 -05:00 committed by GitHub
parent 2460d28afc
commit e4a116b074
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1110 additions and 1031 deletions

View File

@ -168,7 +168,7 @@ function OHIFCornerstoneRTViewport(props) {
}, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]); }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);
useEffect(() => { useEffect(() => {
// I'm not sure what is this, since in RT we support Overlapping segmnets // I'm not sure what is this, since in RT we support Overlapping segments
// via contours // via contours
const { unsubscribe } = segmentationService.subscribe( const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,

View File

@ -175,6 +175,13 @@ const OHIFCornerstoneViewport = React.memo(props => {
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups); syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups);
const synchronizersStore = stateSyncService.getState().synchronizersStore;
if (synchronizersStore?.[viewportId]?.length) {
// If the viewport used to have a synchronizer, re apply it again
_rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService);
}
if (onElementEnabled) { if (onElementEnabled) {
onElementEnabled(evt); onElementEnabled(evt);
} }
@ -197,13 +204,18 @@ const OHIFCornerstoneViewport = React.memo(props => {
return; return;
} }
cleanUpServices(viewportInfo);
cornerstoneViewportService.storePresentation({ viewportId }); cornerstoneViewportService.storePresentation({ viewportId });
// This should be done after the store presentation since synchronizers
// will get cleaned up and they need the viewportInfo to be present
cleanUpServices(viewportInfo);
if (onElementDisabled) { if (onElementDisabled) {
onElementDisabled(viewportInfo); onElementDisabled(viewportInfo);
} }
cornerstoneViewportService.disableElement(viewportId);
eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
}; };
}, []); }, []);
@ -547,6 +559,58 @@ function _jumpToMeasurement(
} }
} }
function _rehydrateSynchronizers(
synchronizersStore: { [key: string]: unknown },
viewportId: string,
syncGroupService: any
) {
synchronizersStore[viewportId].forEach(synchronizerObj => {
if (!synchronizerObj.id) {
return;
}
const { id, sourceViewports, targetViewports } = synchronizerObj;
const synchronizer = syncGroupService.getSynchronizer(id);
if (!synchronizer) {
return;
}
const sourceViewportInfo = sourceViewports.find(
sourceViewport => sourceViewport.viewportId === viewportId
);
const targetViewportInfo = targetViewports.find(
targetViewport => targetViewport.viewportId === viewportId
);
const isSourceViewportInSynchronizer = synchronizer
.getSourceViewports()
.find(sourceViewport => sourceViewport.viewportId === viewportId);
const isTargetViewportInSynchronizer = synchronizer
.getTargetViewports()
.find(targetViewport => targetViewport.viewportId === viewportId);
// if the viewport was previously a source viewport, add it again
if (sourceViewportInfo && !isSourceViewportInSynchronizer) {
synchronizer.addSource({
viewportId: sourceViewportInfo.viewportId,
renderingEngineId: sourceViewportInfo.renderingEngineId,
});
}
// if the viewport was previously a target viewport, add it again
if (targetViewportInfo && !isTargetViewportInSynchronizer) {
synchronizer.addTarget({
viewportId: targetViewportInfo.viewportId,
renderingEngineId: targetViewportInfo.renderingEngineId,
});
}
});
}
// Component displayName // Component displayName
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport'; OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';

View File

@ -17,7 +17,7 @@ import { vec3, mat4 } from 'gl-matrix';
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm'; import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
import callInputDialog from './utils/callInputDialog'; import callInputDialog from './utils/callInputDialog';
import toggleStackImageSync from './utils/stackSync/toggleStackImageSync'; import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection'; import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement'; import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import { CornerstoneServices } from './types'; import { CornerstoneServices } from './types';
@ -566,8 +566,8 @@ function commandsModule({
viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string); viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string);
}, },
toggleStackImageSync: ({ toggledState }) => { toggleImageSliceSync: ({ toggledState }) => {
toggleStackImageSync({ toggleImageSliceSync({
servicesManager, servicesManager,
toggledState, toggledState,
}); });
@ -726,8 +726,8 @@ function commandsModule({
setViewportColormap: { setViewportColormap: {
commandFn: actions.setViewportColormap, commandFn: actions.setViewportColormap,
}, },
toggleStackImageSync: { toggleImageSliceSync: {
commandFn: actions.toggleStackImageSync, commandFn: actions.toggleImageSliceSync,
}, },
setSourceViewportForReferenceLinesTool: { setSourceViewportForReferenceLinesTool: {
commandFn: actions.setSourceViewportForReferenceLinesTool, commandFn: actions.setSourceViewportForReferenceLinesTool,

View File

@ -10,6 +10,7 @@ import {
metaData, metaData,
volumeLoader, volumeLoader,
imageLoadPoolManager, imageLoadPoolManager,
getEnabledElement,
Settings, Settings,
utilities as csUtilities, utilities as csUtilities,
Enums as csEnums, Enums as csEnums,
@ -97,6 +98,7 @@ export default async function init({
toolbarService, toolbarService,
viewportGridService, viewportGridService,
stateSyncService, stateSyncService,
syncGroupService,
} = servicesManager.services as CornerstoneServices; } = servicesManager.services as CornerstoneServices;
window.services = servicesManager.services; window.services = servicesManager.services;
@ -124,6 +126,9 @@ export default async function init({
// an OHIFCornerstoneViewport can be redisplayed with the same LUT // an OHIFCornerstoneViewport can be redisplayed with the same LUT
stateSyncService.register('lutPresentationStore', { clearOnModeExit: true }); stateSyncService.register('lutPresentationStore', { clearOnModeExit: true });
// Stores synchronizers state to be restored
stateSyncService.register('synchronizersStore', { clearOnModeExit: true });
// Stores a map from `positionPresentationId` to a Presentation object so that // Stores a map from `positionPresentationId` to a Presentation object so that
// an OHIFCornerstoneViewport can be redisplayed with the same position // an OHIFCornerstoneViewport can be redisplayed with the same position
stateSyncService.register('positionPresentationStore', { stateSyncService.register('positionPresentationStore', {

View File

@ -45,7 +45,7 @@ export default class SyncGroupService {
[POSITION]: synchronizers.createCameraPositionSynchronizer, [POSITION]: synchronizers.createCameraPositionSynchronizer,
[VOI]: synchronizers.createVOISynchronizer, [VOI]: synchronizers.createVOISynchronizer,
[ZOOMPAN]: synchronizers.createZoomPanSynchronizer, [ZOOMPAN]: synchronizers.createZoomPanSynchronizer,
[STACKIMAGE]: synchronizers.createStackImageSynchronizer, [STACKIMAGE]: synchronizers.createImageSliceSynchronizer,
}; };
constructor(serviceManager: ServicesManager) { constructor(serviceManager: ServicesManager) {
@ -74,6 +74,10 @@ export default class SyncGroupService {
this.synchronizerCreators[type.toLowerCase()] = creator; this.synchronizerCreators[type.toLowerCase()] = creator;
} }
public getSynchronizer(id: string): Synchronizer | void {
return SynchronizerManager.getSynchronizer(id);
}
protected _getOrCreateSynchronizer( protected _getOrCreateSynchronizer(
type: string, type: string,
id: string, id: string,
@ -121,6 +125,17 @@ export default class SyncGroupService {
SynchronizerManager.destroy(); SynchronizerManager.destroy();
} }
public getSynchronizersForViewport(
viewportId: string,
renderingEngineId: string
): Synchronizer[] {
return SynchronizerManager.getAllSynchronizers().filter(
s =>
s.hasSourceViewport(renderingEngineId, viewportId) ||
s.hasTargetViewport(renderingEngineId, viewportId)
);
}
public removeViewportFromSyncGroup( public removeViewportFromSyncGroup(
viewportId: string, viewportId: string,
renderingEngineId: string, renderingEngineId: string,

View File

@ -178,7 +178,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
} }
public storePresentation({ viewportId }) { public storePresentation({ viewportId }) {
const stateSyncService = this.servicesManager.services.stateSyncService; const { stateSyncService, syncGroupService } = this.servicesManager.services;
let presentation; let presentation;
try { try {
presentation = this.getPresentation(viewportId); presentation = this.getPresentation(viewportId);
@ -189,7 +189,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
if (!presentation || !presentation.presentationIds) { if (!presentation || !presentation.presentationIds) {
return; return;
} }
const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState();
const synchronizers = syncGroupService.getSynchronizersForViewport(
viewportId,
this.renderingEngine.id
);
const { lutPresentationStore, positionPresentationStore, synchronizersStore } =
stateSyncService.getState();
const { presentationIds } = presentation; const { presentationIds } = presentation;
const { lutPresentationId, positionPresentationId } = presentationIds || {}; const { lutPresentationId, positionPresentationId } = presentationIds || {};
const storeState = {}; const storeState = {};
@ -205,6 +212,22 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
[positionPresentationId]: presentation, [positionPresentationId]: presentation,
}; };
} }
if (synchronizers?.length) {
storeState.synchronizersStore = {
...synchronizersStore,
[viewportId]: synchronizers.map(synchronizer => {
const sourceViewports = synchronizer.getSourceViewports();
const targetViewports = synchronizer.getTargetViewports();
return {
id: synchronizer.id,
sourceViewports: [...sourceViewports],
targetViewports: [...targetViewports],
};
}),
};
}
stateSyncService.store(storeState); stateSyncService.store(storeState);
} }

View File

@ -1,12 +1,12 @@
const STACK_SYNC_NAME = 'stackImageSync'; const IMAGE_SLICE_SYNC_NAME = 'IMAGE_SLICE_SYNC';
export default function toggleStackImageSync({ export default function toggleImageSliceSync({
toggledState, toggledState,
servicesManager, servicesManager,
viewports: providedViewports, viewports: providedViewports,
}) { }) {
if (!toggledState) { if (!toggledState) {
return disableSync(STACK_SYNC_NAME, servicesManager); return disableSync(IMAGE_SLICE_SYNC_NAME, servicesManager);
} }
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } = const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
@ -24,7 +24,7 @@ export default function toggleStackImageSync({
} }
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, { syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
type: 'stackimage', type: 'stackimage',
id: STACK_SYNC_NAME, id: IMAGE_SLICE_SYNC_NAME,
source: true, source: true,
target: true, target: true,
}); });

View File

@ -79,19 +79,19 @@ const moreTools = [
'Flip Horizontally' 'Flip Horizontally'
), ),
ToolbarService._createToggleButton( ToolbarService._createToggleButton(
'StackImageSync', 'ImageSliceSync',
'link', 'link',
'Stack Image Sync', 'Image Slice Sync',
[ [
{ {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
}, },
], ],
'Enable position synchronization on stack viewports', 'Enable position synchronization on stack viewports',
{ {
listeners: { listeners: {
[EVENTS.STACK_VIEWPORT_NEW_STACK]: { [EVENTS.STACK_VIEWPORT_NEW_STACK]: {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true }, commandOptions: { toggledState: true },
}, },
}, },

View File

@ -83,19 +83,19 @@ const moreTools = [
'Flip Horizontal' 'Flip Horizontal'
), ),
ToolbarService._createToggleButton( ToolbarService._createToggleButton(
'StackImageSync', 'ImageSliceSync',
'link', 'link',
'Stack Image Sync', 'Image Slice Sync',
[ [
{ {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
}, },
], ],
'Enable position synchronization on stack viewports', 'Enable position synchronization on stack viewports',
{ {
listeners: { listeners: {
[EVENTS.STACK_VIEWPORT_NEW_STACK]: { [EVENTS.STACK_VIEWPORT_NEW_STACK]: {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true }, commandOptions: { toggledState: true },
}, },
}, },

View File

@ -57,19 +57,19 @@ const moreToolsMpr = [
'Reset' 'Reset'
), ),
ToolbarService._createToggleButton( ToolbarService._createToggleButton(
'StackImageSync', 'ImageSliceSync',
'link', 'link',
'Stack Image Sync', 'Image Slice Sync',
[ [
{ {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
}, },
], ],
'Enable position synchronization on stack viewports', 'Enable position synchronization on stack viewports',
{ {
listeners: { listeners: {
[EVENTS.STACK_VIEWPORT_NEW_STACK]: { [EVENTS.STACK_VIEWPORT_NEW_STACK]: {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true }, commandOptions: { toggledState: true },
}, },
}, },

View File

@ -261,9 +261,9 @@ const toolbarButtons = [
], ],
'Flip Horizontal' 'Flip Horizontal'
), ),
_createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [ _createToggleButton('ImageSliceSync', 'link', 'Stack Image Sync', [
{ {
commandName: 'toggleStackImageSync', commandName: 'toggleImageSliceSync',
commandOptions: {}, commandOptions: {},
context: 'CORNERSTONE', context: 'CORNERSTONE',
}, },

View File

@ -427,7 +427,7 @@ describe('OHIF Cornerstone Toolbar', () => {
it('checks if stack sync is preserved on new display set and uses FOR', () => { it('checks if stack sync is preserved on new display set and uses FOR', () => {
// Active stack image sync and reference lines // Active stack image sync and reference lines
cy.get('[data-cy="MoreTools-split-button-secondary"]').click(); cy.get('[data-cy="MoreTools-split-button-secondary"]').click();
cy.get('[data-cy="StackImageSync"]').click(); cy.get('[data-cy="ImageSliceSync"]').click();
// Add reference lines as that sometimes throws an exception // Add reference lines as that sometimes throws an exception
cy.get('[data-cy="MoreTools-split-button-secondary"]').click(); cy.get('[data-cy="MoreTools-split-button-secondary"]').click();
cy.get('[data-cy="ReferenceLines"]').click(); cy.get('[data-cy="ReferenceLines"]').click();

View File

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

1970
yarn.lock

File diff suppressed because it is too large Load Diff