Skip to content

Commit

Permalink
fix: Miscellaneous fixes for stack image sync
Browse files Browse the repository at this point in the history
  • Loading branch information
wayfarer3130 committed Sep 19, 2023
1 parent c49b833 commit 71b4827
Show file tree
Hide file tree
Showing 13 changed files with 451 additions and 307 deletions.
51 changes: 48 additions & 3 deletions extensions/cornerstone/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@ function commandsModule({
toolbarService.recordInteraction(props);
},

// Enable or disable a toggleable command, without calling the activation
// Used to setup already active tools from hanging protocols
toolbarSetActive: props => {
toolbarService.setActive(props.toolId, props.isActive ?? true);
},

// Enable stack prefetch on the given element
stackPrefetch: props => {
const { element } = props;
cstUtils.stackPrefetch.enable(element);
},

setToolActive: ({ toolName, toolGroupId = null, toggledState }) => {
if (toolName === 'Crosshairs') {
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
Expand Down Expand Up @@ -345,6 +357,28 @@ function commandsModule({
],
});
},
toggleReferenceLines: ({ toggledState }) => {
try {
const { viewportId } = _getActiveViewportEnabledElement();
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);

if (!toggledState) {
toolGroup.setToolDisabled(ReferenceLinesTool.toolName);
return;
}

toolGroup.setToolConfiguration(
ReferenceLinesTool.toolName,
{
sourceViewportId: viewportId,
},
true // overwrite
);
toolGroup.setToolEnabled(ReferenceLinesTool.toolName);
} catch (e) {
console.log("Couldn't activate reference lines");
}
},
showDownloadViewportModal: () => {
const { activeViewportId } = viewportGridService.getState();

Expand Down Expand Up @@ -551,7 +585,6 @@ function commandsModule({

toggleStackImageSync: ({ toggledState }) => {
toggleStackImageSync({
getEnabledElement,
servicesManager,
toggledState,
});
Expand All @@ -563,6 +596,11 @@ function commandsModule({
const viewportId = viewportInfo.getViewportId();
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);

if (!toggledState) {
toolGroup.setToolDisabled(ReferenceLinesTool.toolName);
return;
}

toolGroup.setToolConfiguration(
ReferenceLinesTool.toolName,
{
Expand Down Expand Up @@ -701,8 +739,15 @@ function commandsModule({
},
storePresentation: {
commandFn: actions.storePresentation,
storeContexts: [],
options: {},
},
stackPrefetch: {
commandFn: actions.stackPrefetch,
},
toolbarSetActive: {
commandFn: actions.toolbarSetActive,
},
toggleReferenceLines: {
commandFn: actions.toggleReferenceLines,
},
};

Expand Down
69 changes: 40 additions & 29 deletions extensions/cornerstone/src/init.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Settings,
utilities as csUtilities,
} from '@cornerstonejs/core';
import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools';
import { Enums, } from '@cornerstonejs/tools';
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';

import initWADOImageLoader from './initWADOImageLoader';
Expand Down Expand Up @@ -72,6 +72,7 @@ export default async function init({
cornerstoneViewportService,
hangingProtocolService,
toolGroupService,
toolbarService,
viewportGridService,
stateSyncService,
} = servicesManager.services as CornerstoneServices;
Expand Down Expand Up @@ -182,9 +183,45 @@ export default async function init({
commandsManager,
});

/**
* When a viewport gets a new display set, this call will go through all the
* active tools in the toolbar, and call any commands registered in the
* toolbar service with a callback to re-enable on displaying the viewport.
*/
const newStackCallback = evt => {
const { element } = evt.detail;
utilities.stackPrefetch.enable(element);
const activeTools = toolbarService.getActiveTools();

activeTools.forEach(tool => {
const toolData = toolbarService.getButton(tool);
const commands = toolData?.listeners?.newStack;
console.log('Running', tool, commands);
commandsManager.run(commands, { element, evt });
});
};

const activeViewportIdChangedListener = evt => {
const { viewportId } = evt;
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);

const activeTools = toolbarService.getActiveTools();

activeTools.forEach(tool => {
if (!toolGroup?._toolInstances?.[tool]) {
return;
}

// check if tool is active on the new viewport
const toolEnabled = toolGroup._toolInstances[tool].mode === Enums.ToolModes.Enabled;

if (!toolEnabled) {
return;
}

const toolData = toolbarService.getButton(tool);
const commands = toolData?.listeners?.activeViewportIdChanged;
commandsManager.run(commands, { viewportId, evt });
});
};

const resetCrosshairs = evt => {
Expand Down Expand Up @@ -236,33 +273,7 @@ export default async function init({

viewportGridService.subscribe(
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
({ viewportId }) => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);

if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) {
return;
}

// check if reference lines are active
const referenceLinesEnabled =
toolGroup._toolInstances['ReferenceLines'].mode === Enums.ToolModes.Enabled;

if (!referenceLinesEnabled) {
return;
}

toolGroup.setToolConfiguration(
ReferenceLinesTool.toolName,
{
sourceViewportId: viewportId,
},
true // overwrite
);

// make sure to set it to enabled again since we want to recalculate
// the source-target lines
toolGroup.setToolEnabled(ReferenceLinesTool.toolName);
}
activeViewportIdChangedListener
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,16 @@ const CornerstoneViewportDownloadForm = ({
const properties = viewport.getProperties();

downloadViewport.setStack([imageId]).then(() => {
downloadViewport.setProperties(properties);

const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE);
const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE);

resolve({ width: newWidth, height: newHeight });
try {
downloadViewport.setProperties(properties);
const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE);
const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE);

resolve({ width: newWidth, height: newHeight });
} catch (e) {
// Happens on clicking the cancel button
console.warn('Unable to set properties', e);
}
});
} else if (downloadViewport instanceof VolumeViewport) {
const actors = viewport.getActors();
Expand Down
131 changes: 61 additions & 70 deletions extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,81 @@
import calculateViewportRegistrations from './calculateViewportRegistrations';

// [ {
// synchronizerId: string,
// viewports: [ { viewportId: string, renderingEngineId: string, index: number } , ...]
// ]}
let STACK_IMAGE_SYNC_GROUPS_INFO = [];

export default function toggleStackImageSync({ toggledState, servicesManager, getEnabledElement }) {
const disableSync = (syncName, servicesManager) => {
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
servicesManager.services;
const viewports = getViewports(viewportGridService, displaySetService);
viewports.forEach(gridViewport => {
const { viewportId } = gridViewport.viewportOptions;
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return;
}
syncGroupService.removeViewportFromSyncGroup(
viewport.id,
viewport.getRenderingEngine().id,
syncName
);
});
};

if (!toggledState) {
STACK_IMAGE_SYNC_GROUPS_INFO.forEach(syncGroupInfo => {
const { viewports, synchronizerId } = syncGroupInfo;

viewports.forEach(({ viewportId, renderingEngineId }) => {
syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, synchronizerId);
});
});

return;
}

STACK_IMAGE_SYNC_GROUPS_INFO = [];

// create synchronization groups and add viewports
const { viewports } = viewportGridService.getState();
const getViewports = (viewportGridService, displaySetService) => {
let { viewports } = viewportGridService.getState();

viewports = [...viewports.values()];
// filter empty viewports
const viewportsArray = Array.from(viewports.values())
.filter(viewport => viewport.displaySetInstanceUIDs?.length)
// filter reconstructable viewports
.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
viewports = viewports.filter(
viewport => viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length
);

for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
// filter reconstructable viewports
viewports = viewports.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;

return !!displaySet?.isReconstructable;
}
});
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);

const viewportsByOrientation = viewportsArray.reduce((acc, viewport) => {
const { viewportId, viewportType } = viewport.viewportOptions;

if (viewportType !== 'stack') {
console.warn('Viewport is not a stack, cannot sync images yet');
return acc;
}

const { element } = cornerstoneViewportService.getViewportInfo(viewportId);
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
const orientation = viewPlaneNormal.map(v => Math.round(v)).join(',');
// TODO - add a better test than isReconstructable
if (displaySet && displaySet.isReconstructable) {
return true;
}

if (!acc[orientation]) {
acc[orientation] = [];
return false;
}
});

acc[orientation].push({ viewportId, renderingEngineId });
// viewports = viewports.filter(viewport => viewport.viewportOptions.viewportType === 'stack');

return acc;
}, {});
console.log('viewports=', viewports);
return viewports;
};

// create synchronizer for each group
Object.values(viewportsByOrientation).map(viewports => {
let synchronizerId = viewports.map(({ viewportId }) => viewportId).join(',');
const STACK_SYNC_NAME = 'stackImageSync';

synchronizerId = `imageSync_${synchronizerId}`;
export default function toggleStackImageSync({
toggledState,
servicesManager,
viewports: providedViewports,
}) {
if (!toggledState) {
return disableSync(STACK_SYNC_NAME, servicesManager);
}

calculateViewportRegistrations(viewports);
console.log('Toggling stack image sync');
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
servicesManager.services;

viewports.forEach(({ viewportId, renderingEngineId }) => {
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, {
type: 'stackimage',
id: synchronizerId,
source: true,
target: true,
});
});
const viewports = providedViewports || getViewports(viewportGridService, displaySetService);

STACK_IMAGE_SYNC_GROUPS_INFO.push({
synchronizerId,
viewports,
// create synchronization group and add the viewports to it.
viewports.forEach(gridViewport => {
const { viewportId } = gridViewport.viewportOptions;
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
if (!viewport) {
return;
}
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
type: 'stackimage',
id: STACK_SYNC_NAME,
source: true,
target: true,
});
});
}
Loading

0 comments on commit 71b4827

Please sign in to comment.