fix(viewport-sync): remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync (#3849)
This commit is contained in:
parent
2460d28afc
commit
e4a116b074
@ -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,
|
||||||
|
|||||||
@ -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';
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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', {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
@ -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 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user