Skip to content

Commit

Permalink
fix(viewport-sync): remember synced viewports bw stack and volume and…
Browse files Browse the repository at this point in the history
… RENAME StackImageSync to ImageSliceSync (OHIF#3849)
  • Loading branch information
sedghi authored and thanh-nguyen-dang committed Apr 30, 2024
1 parent dd9ee84 commit a8cd4ba
Show file tree
Hide file tree
Showing 15 changed files with 1,135 additions and 1,056 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ function OHIFCornerstoneRTViewport(props) {
}, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]);

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
const { unsubscribe } = segmentationService.subscribe(
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
Expand Down
66 changes: 65 additions & 1 deletion extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ const OHIFCornerstoneViewport = React.memo(props => {

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) {
onElementEnabled(evt);
}
Expand All @@ -197,13 +204,18 @@ const OHIFCornerstoneViewport = React.memo(props => {
return;
}

cleanUpServices(viewportInfo);
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) {
onElementDisabled(viewportInfo);
}

cornerstoneViewportService.disableElement(viewportId);

eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
};
}, []);
Expand Down Expand Up @@ -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
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';

Expand Down
10 changes: 5 additions & 5 deletions extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { vec3, mat4 } from 'gl-matrix';

import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
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 getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import { CornerstoneServices } from './types';
Expand Down Expand Up @@ -566,8 +566,8 @@ function commandsModule({
viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string);
},

toggleStackImageSync: ({ toggledState }) => {
toggleStackImageSync({
toggleImageSliceSync: ({ toggledState }) => {
toggleImageSliceSync({
servicesManager,
toggledState,
});
Expand Down Expand Up @@ -726,8 +726,8 @@ function commandsModule({
setViewportColormap: {
commandFn: actions.setViewportColormap,
},
toggleStackImageSync: {
commandFn: actions.toggleStackImageSync,
toggleImageSliceSync: {
commandFn: actions.toggleImageSliceSync,
},
setSourceViewportForReferenceLinesTool: {
commandFn: actions.setSourceViewportForReferenceLinesTool,
Expand Down
5 changes: 5 additions & 0 deletions extensions/cornerstone/src/init.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
metaData,
volumeLoader,
imageLoadPoolManager,
getEnabledElement,
Settings,
utilities as csUtilities,
Enums as csEnums,
Expand Down Expand Up @@ -97,6 +98,7 @@ export default async function init({
toolbarService,
viewportGridService,
stateSyncService,
syncGroupService,
} = servicesManager.services as CornerstoneServices;

window.services = servicesManager.services;
Expand Down Expand Up @@ -124,6 +126,9 @@ export default async function init({
// an OHIFCornerstoneViewport can be redisplayed with the same LUT
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
// an OHIFCornerstoneViewport can be redisplayed with the same position
stateSyncService.register('positionPresentationStore', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default class SyncGroupService {
[POSITION]: synchronizers.createCameraPositionSynchronizer,
[VOI]: synchronizers.createVOISynchronizer,
[ZOOMPAN]: synchronizers.createZoomPanSynchronizer,
[STACKIMAGE]: synchronizers.createStackImageSynchronizer,
[STACKIMAGE]: synchronizers.createImageSliceSynchronizer,
};

constructor(serviceManager: ServicesManager) {
Expand Down Expand Up @@ -74,6 +74,10 @@ export default class SyncGroupService {
this.synchronizerCreators[type.toLowerCase()] = creator;
}

public getSynchronizer(id: string): Synchronizer | void {
return SynchronizerManager.getSynchronizer(id);
}

protected _getOrCreateSynchronizer(
type: string,
id: string,
Expand Down Expand Up @@ -121,6 +125,17 @@ export default class SyncGroupService {
SynchronizerManager.destroy();
}

public getSynchronizersForViewport(
viewportId: string,
renderingEngineId: string
): Synchronizer[] {
return SynchronizerManager.getAllSynchronizers().filter(
s =>
s.hasSourceViewport(renderingEngineId, viewportId) ||
s.hasTargetViewport(renderingEngineId, viewportId)
);
}

public removeViewportFromSyncGroup(
viewportId: string,
renderingEngineId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
}

public storePresentation({ viewportId }) {
const stateSyncService = this.servicesManager.services.stateSyncService;
const { stateSyncService, syncGroupService } = this.servicesManager.services;
let presentation;
try {
presentation = this.getPresentation(viewportId);
Expand All @@ -189,7 +189,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
if (!presentation || !presentation.presentationIds) {
return;
}
const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState();

const synchronizers = syncGroupService.getSynchronizersForViewport(
viewportId,
this.renderingEngine.id
);

const { lutPresentationStore, positionPresentationStore, synchronizersStore } =
stateSyncService.getState();
const { presentationIds } = presentation;
const { lutPresentationId, positionPresentationId } = presentationIds || {};
const storeState = {};
Expand All @@ -205,6 +212,22 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
[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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
servicesManager,
viewports: providedViewports,
}) {
if (!toggledState) {
return disableSync(STACK_SYNC_NAME, servicesManager);
return disableSync(IMAGE_SLICE_SYNC_NAME, servicesManager);
}

const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
Expand All @@ -24,7 +24,7 @@ export default function toggleStackImageSync({
}
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
type: 'stackimage',
id: STACK_SYNC_NAME,
id: IMAGE_SLICE_SYNC_NAME,
source: true,
target: true,
});
Expand Down
8 changes: 4 additions & 4 deletions modes/basic-test-mode/src/moreTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,19 @@ const moreTools = [
'Flip Horizontally'
),
ToolbarService._createToggleButton(
'StackImageSync',
'ImageSliceSync',
'link',
'Stack Image Sync',
'Image Slice Sync',
[
{
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
},
],
'Enable position synchronization on stack viewports',
{
listeners: {
[EVENTS.STACK_VIEWPORT_NEW_STACK]: {
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
Expand Down
8 changes: 4 additions & 4 deletions modes/longitudinal/src/moreTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,19 @@ const moreTools = [
'Flip Horizontal'
),
ToolbarService._createToggleButton(
'StackImageSync',
'ImageSliceSync',
'link',
'Stack Image Sync',
'Image Slice Sync',
[
{
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
},
],
'Enable position synchronization on stack viewports',
{
listeners: {
[EVENTS.STACK_VIEWPORT_NEW_STACK]: {
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
Expand Down
8 changes: 4 additions & 4 deletions modes/longitudinal/src/moreToolsMpr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,19 @@ const moreToolsMpr = [
'Reset'
),
ToolbarService._createToggleButton(
'StackImageSync',
'ImageSliceSync',
'link',
'Stack Image Sync',
'Image Slice Sync',
[
{
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
},
],
'Enable position synchronization on stack viewports',
{
listeners: {
[EVENTS.STACK_VIEWPORT_NEW_STACK]: {
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
commandOptions: { toggledState: true },
},
},
Expand Down
4 changes: 2 additions & 2 deletions modes/segmentation/src/toolbarButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ const toolbarButtons = [
],
'Flip Horizontal'
),
_createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [
_createToggleButton('ImageSliceSync', 'link', 'Stack Image Sync', [
{
commandName: 'toggleStackImageSync',
commandName: 'toggleImageSliceSync',
commandOptions: {},
context: 'CORNERSTONE',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ describe('OHIF Cornerstone Toolbar', () => {
it('checks if stack sync is preserved on new display set and uses FOR', () => {
// Active stack image sync and reference lines
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
cy.get('[data-cy="MoreTools-split-button-secondary"]').click();
cy.get('[data-cy="ReferenceLines"]').click();
Expand Down
6 changes: 3 additions & 3 deletions platform/app/cypress/integration/volume/MPR.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ describe('OHIF MPR', () => {
const viewports = cornerstone.getRenderingEngines()[0].getViewports();

// 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();
const imageData1 = viewports[0].getImageData();
const imageData2 = viewports[1].getImageData();
const imageData3 = viewports[2].getImageData();

// for some reason map doesn't work here
cy.wrap(imageData1).should('not.be', undefined);
Expand Down
Loading

0 comments on commit a8cd4ba

Please sign in to comment.