fix: remaining bugs for MPR and toolbar icon colors (#3026)

* feat: warn for overlapping segments

* fix: typo

* refactor mpr toggle and stack sync

* fix: wip for more general mpr

* fix: tool groups for mpr

* fix: changing of viewport id

* fix: viewport Id for the seg viewports

* fix

* update cornerstone version
This commit is contained in:
Alireza authored and GitHub committed 2022-11-21 10:44:19 -05:00
1 parent 6db4d7a917
commit 87eab39768
27 files changed
+726 -384

No files matched your search

@@ -36,6 +36,7 @@ function OHIFCornerstoneSEGViewport(props) {
DisplaySetService,
ToolGroupService,
SegmentationService,
UINotificationService,
} = servicesManager.services;
const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`;
@@ -105,7 +106,6 @@ function OHIFCornerstoneSEGViewport(props) {
} = referencedDisplaySetRef.current;
// Todo: jump to the center of the first segment
return (
<Component
{...props}
@@ -114,6 +114,7 @@ function OHIFCornerstoneSEGViewport(props) {
viewportType: 'volume',
toolGroupId: toolGroupId,
orientation: viewportOptions.orientation,
viewportId: viewportOptions.viewportId,
}}
onElementEnabled={onElementEnabled}
onElementDisabled={onElementDisabled}
@@ -176,6 +177,15 @@ function OHIFCornerstoneSEGViewport(props) {
) {
setSegIsLoading(false);
}
if (evt.overlappingSegments) {
UINotificationService.show({
title: 'Overlapping Segments',
message:
'Overlapping segments detected which is not currently supported',
type: 'warning',
});
}
}
);
+2 -2
View File
@@ -45,7 +45,7 @@
"dependencies": {
"@babel/runtime": "7.16.3",
"classnames": "^2.2.6",
"@cornerstonejs/core": "^0.21.0",
"@cornerstonejs/tools": "^0.29.2"
"@cornerstonejs/core": "^0.21.4",
"@cornerstonejs/tools": "^0.29.6"
}
}
+3 -3
View File
@@ -43,9 +43,9 @@
},
"dependencies": {
"@babel/runtime": "7.17.9",
"@cornerstonejs/core": "^0.21.0",
"@cornerstonejs/streaming-image-volume-loader": "^0.6.1",
"@cornerstonejs/tools": "^0.29.2",
"@cornerstonejs/core": "^0.21.4",
"@cornerstonejs/streaming-image-volume-loader": "^0.6.5",
"@cornerstonejs/tools": "^0.29.6",
"@kitware/vtk.js": "25.9.0",
"html2canvas": "^1.4.1",
"lodash.debounce": "4.0.8",
@@ -1,7 +1,6 @@
import React, { useEffect, useRef, useCallback, useState } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import PropTypes from 'prop-types';
import { useViewportGrid } from '@ohif/ui';
import * as cs3DTools from '@cornerstonejs/tools';
import {
Enums,
@@ -40,6 +39,13 @@ function areEqual(prevProps, nextProps) {
return false;
}
if (
prevProps.viewportOptions.toolGroupId !==
nextProps.viewportOptions.toolGroupId
) {
return false;
}
if (
prevProps.viewportOptions.viewportType !==
nextProps.viewportOptions.viewportType
@@ -134,6 +140,28 @@ const OHIFCornerstoneViewport = React.memo(props => {
}
}, [elementRef]);
const cleanUpServices = useCallback(() => {
const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);
if (!viewportInfo) {
return;
}
const viewportId = viewportInfo.getViewportId();
const renderingEngineId = viewportInfo.getRenderingEngineId();
const syncGroups = viewportInfo.getSyncGroups();
ToolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId);
SyncGroupService.removeViewportFromSyncGroup(
viewportId,
renderingEngineId,
syncGroups
);
}, [viewportIndex, viewportOptions.viewportId]);
const elementEnabledHandler = useCallback(
evt => {
// check this is this element reference and return early if doesn't match
@@ -188,28 +216,15 @@ const OHIFCornerstoneViewport = React.memo(props => {
setImageScrollBarHeight();
return () => {
const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);
const viewportId = viewportInfo.getViewportId();
const renderingEngineId = viewportInfo.getRenderingEngineId();
const syncGroups = viewportInfo.getSyncGroups();
ToolGroupService.removeViewportFromToolGroup(
viewportId,
renderingEngineId
);
SyncGroupService.removeViewportFromSyncGroup(
viewportId,
renderingEngineId,
syncGroups
);
cleanUpServices();
CornerstoneViewportService.disableElement(viewportIndex);
if (onElementDisabled) {
const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex(
viewportIndex
);
onElementDisabled(viewportInfo);
}
@@ -326,8 +341,8 @@ const OHIFCornerstoneViewport = React.memo(props => {
handleWidth
handleHeight
skipOnMount={true} // Todo: make these configurable
refreshMode={'throttle'}
refreshRate={100}
refreshMode={'debounce'}
refreshRate={200} // transition amount in side panel
onResize={onResize}
targetRef={elementRef.current}
/>
@@ -355,7 +370,7 @@ function _subscribeToJumpToMeasurementEvents(
elementRef,
viewportIndex,
displaySets,
viewportGridService,
ViewportGridService,
CornerstoneViewportService
) {
const displaysUIDs = displaySets.map(
+49 -277
View File
@@ -7,34 +7,20 @@ import {
ToolGroupManager,
Enums,
utilities as cstUtils,
segmentation as cstSegmentation,
ReferenceLinesTool,
synchronizers as cstSynchronizers,
} from '@cornerstonejs/tools';
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
import { getEnabledElement as OHIFgetEnabledElement } from './state';
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
import callInputDialog from './utils/callInputDialog';
import { setColormap } from './utils/colormap/transferFunctionHelpers';
import getProtocolViewportStructureFromGridViewports from './utils/getProtocolViewportStructureFromGridViewports';
import removeToolGroupSegmentationRepresentations from './utils/removeToolGroupSegmentationRepresentations';
import calculateViewportRegistrations from './utils/calculateViewportRegistrations';
const MPR_TOOLGROUP_ID = 'mpr';
// [ {
// synchronizerId: string,
// viewports: [ { viewportId: number, renderingEngineId: string, index: number } , ...]
// ]}
let STACK_IMAGE_SYNC_GROUPS_INFO = [];
import toggleMPRHangingProtocol from './utils/mpr/toggleMPRHangingProtocol';
import toggleStackImageSync from './utils/stackSync/toggleStackImageSync';
const commandsModule = ({ servicesManager }) => {
const {
ViewportGridService,
ToolGroupService,
DisplaySetService,
SyncGroupService,
CineService,
ToolBarService,
UIDialogService,
@@ -160,48 +146,51 @@ const commandsModule = ({ servicesManager }) => {
}
}
const toolGroup = _getToolGroup(toolGroupId);
if (!toolGroup) {
console.warn('No tool group found for toolGroupId:', toolGroupId);
return;
}
// Todo: we need to check if the viewports of the toolGroup is actually
// parts of the ViewportGrid's viewports, if not we return
const { viewports } = ViewportGridService.getState() || {
viewports: [],
};
// iterate over all viewports and set the tool active for the
// viewports that belong to the toolGroup
for (let index = 0; index < viewports.length; index++) {
const ohifEnabledElement = OHIFgetEnabledElement(index);
if (!ohifEnabledElement) {
continue;
}
const viewport = getEnabledElement(ohifEnabledElement.element);
if (!viewport) {
continue;
}
// Find the current active tool and set it to be passive
const activeTool = toolGroup.getActivePrimaryMouseButtonTool();
if (activeTool) {
toolGroup.setToolPassive(activeTool);
}
// Set the new toolName to be active
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: Enums.MouseBindings.Primary }],
});
const toolGroup = _getToolGroup(toolGroupId);
const toolGroupViewportIds = toolGroup.getViewportIds();
// if toolGroup has been destroyed, or its viewports have been removed
if (!toolGroupViewportIds || !toolGroupViewportIds.length) {
return;
}
const filteredViewports = viewports.filter(viewport => {
if (!viewport.viewportOptions) {
return false;
}
return toolGroupViewportIds.includes(
viewport.viewportOptions.viewportId
);
});
if (!filteredViewports.length) {
return;
}
const activeToolName = toolGroup.getActivePrimaryMouseButtonTool();
if (activeToolName) {
// Todo: this is a hack to prevent the crosshairs to stick around
// after another tool is selected. We should find a better way to do this
if (activeToolName === 'Crosshairs') {
toolGroup.setToolDisabled(activeToolName);
} else {
toolGroup.setToolPassive(activeToolName);
}
}
// Set the new toolName to be active
toolGroup.setToolActive(toolName, {
bindings: [
{
mouseButton: Enums.MouseBindings.Primary,
},
],
});
},
showDownloadViewportModal: () => {
const { activeViewportIndex } = ViewportGridService.getState();
@@ -375,229 +364,17 @@ const commandsModule = ({ servicesManager }) => {
HangingProtocolService.setProtocol(protocolId);
},
toggleMPR: ({ toggledState }) => {
const { activeViewportIndex, viewports } = ViewportGridService.getState();
const viewportDisplaySetInstanceUIDs =
viewports[activeViewportIndex].displaySetInstanceUIDs;
const errorCallback = error => {
UINotificationService.show({
title: 'Multiplanar reconstruction (MPR) ',
message:
'Cannot create MPR for this DisplaySet since it is not reconstructable.',
type: 'info',
duration: 3000,
});
};
const cacheId = 'beforeMPR';
if (toggledState) {
ViewportGridService.setCachedLayout({
cacheId,
cachedLayout: ViewportGridService.getState(),
});
const matchDetails = {
displaySetInstanceUIDs: viewportDisplaySetInstanceUIDs,
};
HangingProtocolService.setProtocol(
MPR_TOOLGROUP_ID,
matchDetails,
errorCallback
);
return;
}
const { cachedLayout } = ViewportGridService.getState();
if (!cachedLayout || !cachedLayout[cacheId]) {
return;
}
const { viewports: cachedViewports, numRows, numCols } = cachedLayout[
cacheId
];
// Todo: The following assumes that when turning off MPR we are applying the default
// protocol which might not be the one that was used before MPR was turned on
// In order to properly implement this logic, we should modify the hanging protocol
// upon layout change with layout selector, and cache and restore it when turning
// MPR on and off
const viewportStructure = getProtocolViewportStructureFromGridViewports({
viewports: cachedViewports,
numRows,
numCols,
toggleMPRHangingProtocol({
toggledState,
servicesManager,
getToolGroup: _getToolGroup,
});
const viewportSpecificMatch = cachedViewports.reduce(
(acc, viewport, index) => {
const {
displaySetInstanceUIDs,
viewportOptions,
displaySetOptions,
} = viewport;
acc[index] = {
displaySetInstanceUIDs,
viewportOptions,
displaySetOptions,
};
return acc;
},
{}
);
const defaultProtocol = HangingProtocolService.getProtocolById('default');
// Todo: this assumes there is only one stage in the default protocol
const defaultProtocolStage = defaultProtocol.stages[0];
defaultProtocolStage.viewportStructure = viewportStructure;
const { primaryToolId } = ToolBarService.state;
const mprToolGroup = _getToolGroup(MPR_TOOLGROUP_ID);
// turn off crosshairs if it is on
if (
primaryToolId === 'Crosshairs' ||
mprToolGroup.getToolInstance('Crosshairs')?.mode ===
Enums.ToolModes.Active
) {
const toolGroup = _getToolGroup(MPR_TOOLGROUP_ID);
toolGroup.setToolDisabled('Crosshairs');
ToolBarService.recordInteraction({
groupId: 'WindowLevel',
itemId: 'WindowLevel',
interactionType: 'tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: {
toolName: 'WindowLevel',
},
context: 'CORNERSTONE',
},
],
});
}
// clear segmentations if they exist
removeToolGroupSegmentationRepresentations(MPR_TOOLGROUP_ID);
HangingProtocolService.setProtocol(
'default',
viewportSpecificMatch,
error => {
UINotificationService.show({
title: 'Multiplanar reconstruction (MPR) ',
message:
'Something went wrong while trying to restore the previous layout.',
type: 'info',
duration: 3000,
});
}
);
},
toggleStackImageSync: ({ toggledState }) => {
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
let { viewports } = ViewportGridService.getState();
// filter empty viewports
viewports = viewports.filter(
viewport =>
viewport.displaySetInstanceUIDs &&
viewport.displaySetInstanceUIDs.length
);
// filter reconstructable viewports
viewports = viewports.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = DisplaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
if (displaySet && displaySet.isReconstructable) {
return true;
}
return false;
}
});
const viewportsByOrientation = viewports.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(',');
if (!acc[orientation]) {
acc[orientation] = [];
}
acc[orientation].push({ viewportId, renderingEngineId });
return acc;
}, {});
// create synchronizer for each group
Object.values(viewportsByOrientation).map(viewports => {
let synchronizerId = viewports
.map(({ viewportId }) => viewportId)
.join(',');
synchronizerId = `imageSync_${synchronizerId}`;
calculateViewportRegistrations(viewports);
viewports.forEach(({ viewportId, renderingEngineId }) => {
SyncGroupService.addViewportToSyncGroup(
viewportId,
renderingEngineId,
{
type: 'stackimage',
id: synchronizerId,
source: true,
target: true,
}
);
});
STACK_IMAGE_SYNC_GROUPS_INFO.push({
synchronizerId,
viewports,
});
toggleStackImageSync({
getEnabledElement,
servicesManager,
toggledState,
});
},
toggleReferenceLines: ({ toggledState }) => {
@@ -635,11 +412,6 @@ const commandsModule = ({ servicesManager }) => {
storeContexts: [],
options: {},
},
toggleCrosshairs: {
commandFn: actions.toggleCrosshairs,
storeContexts: [],
options: {},
},
rotateViewportCW: {
commandFn: actions.rotateViewport,
storeContexts: [],
+2 -2
View File
@@ -362,13 +362,13 @@ export default async function init({
const viewportId = `viewport-${viewportIndex}`;
const toolGroup = ToolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup) {
if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) {
return;
}
// check if reference lines are active
const referenceLinesEnabled =
toolGroup._toolInstances?.['ReferenceLines'].mode ===
toolGroup._toolInstances['ReferenceLines'].mode ===
Enums.ToolModes.Enabled;
if (!referenceLinesEnabled) {
@@ -590,6 +590,8 @@ class SegmentationService {
// Note: ideally we could use the TypedArray set method, but since each
// slice can have multiple segments, we need to loop over each slice and
// set the segment value for each segment.
let overlappingSegments = false;
const _segmentInfoUpdate = (segmentInfo, segmentIndex) => {
const { pixelData: segPixelData } = segmentInfo;
@@ -636,6 +638,10 @@ class SegmentationService {
i++, j++
) {
if (functionGroupPixelData[j] !== 0) {
if (derivedVolumeScalarData[i] !== 0) {
overlappingSegments = true;
}
derivedVolumeScalarData[i] = segmentIndex;
// centroid calculations
@@ -716,6 +722,7 @@ class SegmentationService {
this._broadcastEvent(EVENTS.SEGMENTATION_PIXEL_DATA_CREATED, {
segmentationId,
segDisplaySet,
overlappingSegments,
});
return this.addOrUpdateSegmentation(segmentationSchema, suppressEvents);
@@ -77,17 +77,14 @@ class CornerstoneViewportService implements IViewportService {
viewportOptions: PublicViewportOptions,
elementRef: HTMLDivElement
) {
const viewportId =
viewportOptions.viewportId || this.getViewportId(viewportIndex);
const viewportInfo = new ViewportInfo(viewportIndex, viewportId);
const viewportInfo = new ViewportInfo(
viewportIndex,
viewportOptions.viewportId
);
viewportInfo.setElement(elementRef);
this.viewportsInfo.set(viewportIndex, viewportInfo);
}
public getViewportId(viewportIndex: number): string {
return `viewport-${viewportIndex}`;
}
public getViewportIds(): string[] {
const viewportIds = [];
@@ -176,6 +173,22 @@ class CornerstoneViewportService implements IViewportService {
): void {
const renderingEngine = this.getRenderingEngine();
const viewportInfo = this.viewportsInfo.get(viewportIndex);
let viewportId = viewportInfo.getViewportId();
// if currently there is a viewport with the viewportId, but it is not the same
// as the one we are trying to set, we need to disable the old one
// and enable the new one, we could ideally change the name of the viewportId
// but the viewportId is an integral part in renderers map, tools svg cache
// etc. which would require a lot of refactoring, for now we will just disable
// the old one and enable the new one at the end of this function
let newViewportId = null;
if (publicViewportOptions?.viewportId !== viewportId) {
newViewportId = publicViewportOptions.viewportId;
viewportInfo.setViewportId(newViewportId);
renderingEngine.disableElement(viewportId);
}
viewportInfo.setRenderingEngineId(renderingEngine.id);
const {
@@ -196,7 +209,7 @@ class CornerstoneViewportService implements IViewportService {
viewportIndex,
});
const viewportId = viewportInfo.getViewportId();
viewportId = viewportInfo.getViewportId();
const element = viewportInfo.getElement();
const type = viewportInfo.getViewportType();
const background = viewportInfo.getBackground();
@@ -218,7 +231,8 @@ class CornerstoneViewportService implements IViewportService {
// ENABLED_ELEMENT again and again, which will run onEnableElement callbacks
renderingEngine.enableElement(viewportInput);
this._setDisplaySets(viewportId, viewportData, viewportInfo);
const viewport = renderingEngine.getViewport(viewportId);
this._setDisplaySets(viewport, viewportData, viewportInfo);
}
public getCornerstoneViewport(
@@ -616,12 +630,10 @@ class CornerstoneViewportService implements IViewportService {
}
_setDisplaySets(
viewportId: string,
viewport: StackViewport | VolumeViewport,
viewportData: StackViewportData | VolumeViewportData,
viewportInfo: ViewportInfo
): void {
const viewport = this.getCornerstoneViewport(viewportId);
if (viewport instanceof StackViewport) {
this._setStackViewport(
viewport,
@@ -687,10 +699,6 @@ class CornerstoneViewportService implements IViewportService {
} {
const viewportIndex = viewportInfo.getViewportIndex();
if (!publicViewportOptions.viewportId) {
publicViewportOptions.viewportId = this.getViewportId(viewportIndex);
}
// Creating a temporary viewportInfo to handle defaults
const newViewportInfo = new ViewportInfo(
viewportIndex,
@@ -0,0 +1,285 @@
import { Enums } from '@cornerstonejs/tools';
import removeToolGroupSegmentationRepresentations from '../removeToolGroupSegmentationRepresentations';
const MPR_TOOLGROUP_ID = 'mpr';
const cachedState = {
protocol: null,
stage: null,
viewportMatchDetails: null,
viewportStructure: null,
toolOptions: null,
};
const setCachedState = (
protocol,
stage,
viewportMatchDetails,
viewportStructure,
toolOptions
) => {
cachedState.protocol = protocol;
cachedState.stage = stage;
cachedState.viewportMatchDetails = viewportMatchDetails;
cachedState.viewportStructure = viewportStructure;
cachedState.toolOptions = JSON.parse(JSON.stringify(toolOptions));
};
const resetCachedState = () => {
cachedState.protocol = null;
cachedState.stage = null;
cachedState.viewportMatchDetails = null;
cachedState.viewportStructure = null;
cachedState.toolOptions = null;
};
export default function toggleMPRHangingProtocol({
toggledState,
servicesManager,
getToolGroup,
}) {
const {
UINotificationService,
HangingProtocolService,
ViewportGridService,
ToolBarService,
} = servicesManager.services;
const {
activeViewportIndex,
viewports,
numRows,
numCols,
} = ViewportGridService.getState();
const viewportDisplaySetInstanceUIDs =
viewports[activeViewportIndex].displaySetInstanceUIDs;
const errorCallback = error => {
UINotificationService.show({
title: 'Multiplanar reconstruction (MPR) ',
message:
'Cannot create MPR for this DisplaySet since it is not reconstructable.',
type: 'info',
duration: 3000,
});
};
// What is the current active protocol and stage number to restore later
const { protocol, stage } = HangingProtocolService.getActiveProtocol();
if (toggledState) {
resetCachedState();
const {
viewportMatchDetails,
viewportStructure,
toolOptions,
} = _getViewportsInfo({
protocol,
stage,
viewports,
servicesManager,
});
setCachedState(
protocol,
stage,
viewportMatchDetails,
viewportStructure,
toolOptions
);
const matchDetails = {
displaySetInstanceUIDs: viewportDisplaySetInstanceUIDs,
};
_disableCrosshairs(
toolOptions.map(({ toolGroupId }) => toolGroupId),
getToolGroup
);
HangingProtocolService.setProtocol(
MPR_TOOLGROUP_ID,
matchDetails,
errorCallback
);
return;
}
const restoreErrorCallback = error => {
UINotificationService.show({
title: 'Multiplanar reconstruction (MPR) ',
message:
'Something went wrong while trying to restore the previous layout.',
type: 'info',
duration: 3000,
});
};
_disableCrosshairs([MPR_TOOLGROUP_ID], getToolGroup);
const { layoutType, properties } = cachedState.viewportStructure;
const { viewportMatchDetails } = cachedState;
// The reason we split the flow here is that we don't allow viewport grid
// change in the non default hanging protocol, so we can just apply the
// cached protocol and stage. However, for the default protocol, we need
// to also apply the layout type and properties.
if (cachedState.protocol.id !== 'default') {
HangingProtocolService.setProtocol(
cachedState.protocol.id,
viewportMatchDetails,
restoreErrorCallback
);
return;
}
HangingProtocolService.setProtocol(
'default',
viewportMatchDetails,
restoreErrorCallback
);
if (numRows !== properties.rows || numCols !== properties.columns) {
ViewportGridService.setLayout({
numRows: properties.rows,
numCols: properties.columns,
layoutType,
layoutOptions: properties.layoutOptions,
});
}
const numViewports =
properties.layoutOptions.length || properties.rows * properties.columns;
// loop inside viewportMatchDetails map
// and set the viewportOptions for each viewport
[...Array(numViewports).keys()].forEach(viewportIndex => {
const viewportMatchDetailsForViewport = viewportMatchDetails.get(
viewportIndex
);
if (viewportMatchDetailsForViewport) {
const {
viewportOptions,
displaySetsInfo,
} = viewportMatchDetailsForViewport;
ViewportGridService.setDisplaySetsForViewport({
viewportIndex,
displaySetInstanceUIDs: displaySetsInfo.map(
displaySetInfo => displaySetInfo.displaySetInstanceUID
),
viewportOptions,
});
} else {
ViewportGridService.setDisplaySetsForViewport({
viewportIndex,
displaySetInstanceUIDs: [],
viewportOptions: {},
});
}
});
ToolBarService.recordInteraction({
groupId: 'WindowLevel',
itemId: 'WindowLevel',
interactionType: 'tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: {
toolName: 'WindowLevel',
},
context: 'CORNERSTONE',
},
],
});
//clear segmentations if they exist
removeToolGroupSegmentationRepresentations(MPR_TOOLGROUP_ID);
}
function _disableCrosshairs(toolGroupIds, getToolGroup) {
toolGroupIds.forEach(toolGroupId => {
const toolGroup = getToolGroup(toolGroupId);
if (
toolGroup.getToolInstance('Crosshairs')?.mode === Enums.ToolModes.Active
) {
toolGroup.setToolDisabled('Crosshairs');
}
});
}
function _getViewportsInfo({ protocol, stage, viewports, servicesManager }) {
// here we need to use the viewports and try to map it into the
// viewportMatchDetails and displaySetMatch that HangingProtocolService
// expects
const {
ViewportGridService,
HangingProtocolService,
ToolGroupService,
} = servicesManager.services;
const { numRows, numCols } = ViewportGridService.getState();
let viewportMatchDetails = new Map();
const viewportStructure = {
layoutType: 'grid',
properties: {
rows: numRows,
columns: numCols,
layoutOptions: [],
},
};
viewports.forEach((viewport, viewportIndex) => {
viewportStructure.properties.layoutOptions.push({
x: viewport.x,
y: viewport.y,
width: viewport.width,
height: viewport.height,
});
});
if (protocol.id === 'default') {
viewports.forEach((viewport, viewportIndex) => {
if (viewport.displaySetInstanceUIDs) {
viewportMatchDetails.set(viewportIndex, {
displaySetsInfo: viewport.displaySetInstanceUIDs.map(
displaySetInstanceUID => {
return { displaySetInstanceUID };
}
),
viewportOptions: viewport.viewportOptions,
});
}
});
} else {
({ viewportMatchDetails } = HangingProtocolService.getMatchDetails());
}
// get the toolGroup state for viewports
let toolOptions = [];
const viewportIds = viewports
.map(
viewport =>
viewport.displaySetInstanceUIDs &&
viewport.displaySetInstanceUIDs.length > 0 &&
viewport.viewportOptions?.viewportId
)
.filter(Boolean);
if (viewportIds.length) {
toolOptions = viewportIds.map(viewportId => {
const toolGroup = ToolGroupService.getToolGroupForViewport(viewportId);
return {
toolGroupId: toolGroup.id,
toolOptions: toolGroup.toolOptions,
};
});
}
return { viewportMatchDetails, viewportStructure, toolOptions };
}
@@ -0,0 +1,115 @@
import calculateViewportRegistrations from './calculateViewportRegistrations';
// [ {
// synchronizerId: string,
// viewports: [ { viewportId: number, renderingEngineId: string, index: number } , ...]
// ]}
let STACK_IMAGE_SYNC_GROUPS_INFO = [];
export default function toggleStackImageSync({
toggledState,
servicesManager,
getEnabledElement,
}) {
const {
SyncGroupService,
ViewportGridService,
DisplaySetService,
CornerstoneViewportService,
} = servicesManager.services;
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
let { viewports } = ViewportGridService.getState();
// filter empty viewports
viewports = viewports.filter(
viewport =>
viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length
);
// filter reconstructable viewports
viewports = viewports.filter(viewport => {
const { displaySetInstanceUIDs } = viewport;
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
const displaySet = DisplaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
if (displaySet && displaySet.isReconstructable) {
return true;
}
return false;
}
});
const viewportsByOrientation = viewports.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(',');
if (!acc[orientation]) {
acc[orientation] = [];
}
acc[orientation].push({ viewportId, renderingEngineId });
return acc;
}, {});
// create synchronizer for each group
Object.values(viewportsByOrientation).map(viewports => {
let synchronizerId = viewports
.map(({ viewportId }) => viewportId)
.join(',');
synchronizerId = `imageSync_${synchronizerId}`;
calculateViewportRegistrations(viewports);
viewports.forEach(({ viewportId, renderingEngineId }) => {
SyncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, {
type: 'stackimage',
id: synchronizerId,
source: true,
target: true,
});
});
STACK_IMAGE_SYNC_GROUPS_INFO.push({
synchronizerId,
viewports,
});
});
}
@@ -92,7 +92,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs');
const StudyInstanceUIDs =
queryStudyInstanceUIDs.length && queryStudyInstanceUIDs ||
(queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) ||
paramsStudyInstanceUIDs;
const StudyInstanceUIDsAsArray =
StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs)
+7 -10
View File
@@ -7,7 +7,7 @@ const commandsModule = ({ servicesManager, commandsManager }) => {
HangingProtocolService,
UINotificationService,
ViewportGridService,
DisplaySetService
DisplaySetService,
} = servicesManager.services;
const actions = {
@@ -30,16 +30,13 @@ const commandsModule = ({ servicesManager, commandsManager }) => {
},
openDICOMTagViewer() {
const { activeViewportIndex, viewports } = ViewportGridService.getState();
const activeViewportSpecificData =
viewports[activeViewportIndex];
const {
displaySetInstanceUIDs,
} = activeViewportSpecificData;
const activeViewportSpecificData = viewports[activeViewportIndex];
const { displaySetInstanceUIDs } = activeViewportSpecificData;
const displaySets = DisplaySetService.activeDisplaySets;
const { UIModalService } = servicesManager.services;
const displaySetInstanceUID = displaySetInstanceUIDs[0]
const displaySetInstanceUID = displaySetInstanceUIDs[0];
UIModalService.show({
content: DicomTagBrowser,
contentProps: {
@@ -47,9 +44,9 @@ const commandsModule = ({ servicesManager, commandsManager }) => {
displaySetInstanceUID,
onClose: UIModalService.hide,
},
title: 'DICOM Tag Browser'
title: 'DICOM Tag Browser',
});
}
},
};
const definitions = {
@@ -75,7 +72,7 @@ const commandsModule = ({ servicesManager, commandsManager }) => {
},
openDICOMTagViewer: {
commandFn: actions.openDICOMTagViewer,
}
},
};
return {
+2 -2
View File
@@ -32,8 +32,8 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"classnames": "^2.2.6",
"@cornerstonejs/core": "^0.21.0",
"@cornerstonejs/tools": "^0.29.2",
"@cornerstonejs/core": "^0.21.4",
"@cornerstonejs/tools": "^0.29.6",
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
"dcmjs": "^0.28.3",
"prop-types": "^15.6.2",
@@ -29,6 +29,7 @@ function TrackedCornerstoneViewport(props) {
servicesManager,
extensionManager,
commandsManager,
viewportOptions,
} = props;
const { t } = useTranslation('TrackedViewport');
@@ -50,8 +51,7 @@ function TrackedCornerstoneViewport(props) {
const [element, setElement] = useState(null);
const { trackedSeries } = trackedMeasurements.context;
const viewportId = CornerstoneViewportService.getViewportId(viewportIndex);
const viewportId = viewportOptions.viewportId;
const {
Modality,
@@ -86,8 +86,6 @@ function TrackedCornerstoneViewport(props) {
cineService.playClip(element, {
framesPerSecond: validFrameRate,
});
} else {
cineService.stopClip(element);
}
};