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:
parent
6db4d7a917
commit
87eab39768
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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: [],
|
||||
|
||||
@ -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,
|
||||
|
||||
285
extensions/cornerstone/src/utils/mpr/toggleMPRHangingProtocol.ts
Normal file
285
extensions/cornerstone/src/utils/mpr/toggleMPRHangingProtocol.ts
Normal file
@ -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,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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -91,8 +91,6 @@ function modeFactory({ modeConfiguration }) {
|
||||
});
|
||||
};
|
||||
|
||||
// Since we only have one viewport for the basic cs3d mode and it has
|
||||
// only one hanging protocol, we can just use the first viewport
|
||||
const { unsubscribe } = ToolGroupService.subscribe(
|
||||
ToolGroupService.EVENTS.VIEWPORT_ADDED,
|
||||
() => {
|
||||
|
||||
@ -4,9 +4,10 @@ export const toolGroupIds = {
|
||||
Fusion: 'fusionToolGroup',
|
||||
MIP: 'mipToolGroup',
|
||||
default: 'default',
|
||||
// MPR: 'mpr',
|
||||
};
|
||||
|
||||
function initToolGroups(toolNames, Enums, ToolGroupService, commandsManager) {
|
||||
function _initToolGroups(toolNames, Enums, ToolGroupService, commandsManager) {
|
||||
const tools = {
|
||||
active: [
|
||||
{
|
||||
@ -43,7 +44,7 @@ function initToolGroups(toolNames, Enums, ToolGroupService, commandsManager) {
|
||||
[toolNames.Crosshairs]: {
|
||||
viewportIndicators: false,
|
||||
autoPan: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
panSize: 10,
|
||||
},
|
||||
},
|
||||
@ -122,4 +123,70 @@ function initToolGroups(toolNames, Enums, ToolGroupService, commandsManager) {
|
||||
);
|
||||
}
|
||||
|
||||
function initMPRToolGroup(toolNames, Enums, ToolGroupService, commandsManager) {
|
||||
const tools = {
|
||||
active: [
|
||||
{
|
||||
toolName: toolNames.WindowLevel,
|
||||
bindings: [{ mouseButton: Enums.MouseBindings.Primary }],
|
||||
},
|
||||
{
|
||||
toolName: toolNames.Pan,
|
||||
bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }],
|
||||
},
|
||||
{
|
||||
toolName: toolNames.Zoom,
|
||||
bindings: [{ mouseButton: Enums.MouseBindings.Secondary }],
|
||||
},
|
||||
{ toolName: toolNames.StackScrollMouseWheel, bindings: [] },
|
||||
],
|
||||
passive: [
|
||||
{ toolName: toolNames.Length },
|
||||
{ toolName: toolNames.ArrowAnnotate },
|
||||
{ toolName: toolNames.Bidirectional },
|
||||
{ toolName: toolNames.DragProbe },
|
||||
{ toolName: toolNames.EllipticalROI },
|
||||
{ toolName: toolNames.RectangleROI },
|
||||
{ toolName: toolNames.StackScroll },
|
||||
{ toolName: toolNames.Angle },
|
||||
{ toolName: toolNames.SegmentationDisplay },
|
||||
],
|
||||
disabled: [{ toolName: toolNames.Crosshairs }],
|
||||
|
||||
// enabled
|
||||
// disabled
|
||||
};
|
||||
|
||||
const toolsConfig = {
|
||||
[toolNames.Crosshairs]: {
|
||||
viewportIndicators: false,
|
||||
autoPan: {
|
||||
enabled: false,
|
||||
panSize: 10,
|
||||
},
|
||||
},
|
||||
[toolNames.ArrowAnnotate]: {
|
||||
getTextCallback: (callback, eventDetails) =>
|
||||
commandsManager.runCommand('arrowTextCallback', {
|
||||
callback,
|
||||
eventDetails,
|
||||
}),
|
||||
|
||||
changeTextCallback: (data, eventDetails, callback) =>
|
||||
commandsManager.runCommand('arrowTextCallback', {
|
||||
callback,
|
||||
data,
|
||||
eventDetails,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
ToolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig);
|
||||
}
|
||||
|
||||
function initToolGroups(toolNames, Enums, ToolGroupService, commandsManager) {
|
||||
_initToolGroups(toolNames, Enums, ToolGroupService, commandsManager);
|
||||
// initMPRToolGroup(toolNames, Enums, ToolGroupService, commandsManager);
|
||||
}
|
||||
|
||||
export default initToolGroups;
|
||||
|
||||
@ -105,6 +105,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
'Length'
|
||||
@ -125,6 +126,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
'Length Tool'
|
||||
@ -138,6 +140,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
'Bidirectional Tool'
|
||||
@ -151,6 +154,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
'Arrow Annotate'
|
||||
@ -164,6 +168,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
'Ellipse Tool'
|
||||
@ -184,10 +189,27 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'MPR',
|
||||
type: 'ohif.action',
|
||||
props: {
|
||||
type: 'toggle',
|
||||
icon: 'icon-mpr',
|
||||
label: 'MPR',
|
||||
commands: [
|
||||
{
|
||||
commandName: 'toggleMPR',
|
||||
commandOptions: {},
|
||||
context: 'CORNERSTONE',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Window Level + Presets...
|
||||
{
|
||||
id: 'WindowLevel',
|
||||
@ -203,6 +225,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
'Window Level'
|
||||
@ -236,6 +259,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
},
|
||||
@ -253,6 +277,7 @@ const toolbarButtons = [
|
||||
toolGroupIds.CT,
|
||||
toolGroupIds.PT,
|
||||
toolGroupIds.Fusion,
|
||||
// toolGroupIds.MPR,
|
||||
]),
|
||||
],
|
||||
},
|
||||
|
||||
@ -122,6 +122,13 @@ class HangingProtocolService {
|
||||
this.displaySetMatchDetails = new Map();
|
||||
}
|
||||
|
||||
public getActiveProtocol(): {
|
||||
protocol: HangingProtocol.Protocol;
|
||||
stage: number;
|
||||
} {
|
||||
return { protocol: this.protocol, stage: this.stage };
|
||||
}
|
||||
|
||||
public getDefaultProtocol(): HangingProtocol.Protocol {
|
||||
return this.getProtocolById('default');
|
||||
}
|
||||
|
||||
@ -20,6 +20,10 @@ export default function sortInstances(instances: Array<any>) {
|
||||
ImageOrientationPatient,
|
||||
} = instances[Math.floor(instances.length / 2)]; // this prevents getting scout image as test image
|
||||
|
||||
if (!referenceImagePositionPatient || !ImageOrientationPatient) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
const rowCosineVec = vec3.fromValues(
|
||||
ImageOrientationPatient[0],
|
||||
ImageOrientationPatient[1],
|
||||
|
||||
@ -52,5 +52,4 @@ const utilityModule = extensionManager.getModuleEntry(
|
||||
);
|
||||
|
||||
const { CornerstoneViewportService } = utilityModule.exports;
|
||||
const viewportId = CornerstoneViewportService.getViewportId(viewportIndex);
|
||||
```
|
||||
|
||||
@ -33,27 +33,47 @@ const variantClasses = {
|
||||
},
|
||||
outlined: {
|
||||
default:
|
||||
'border bg-trasparent border-primary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
'border border-primary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
primary:
|
||||
'border bg-transparent border-primary-main text-primary-main hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
'border border-primary-main text-primary-main hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
secondary:
|
||||
'border bg-transparent border-secondary-light text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
'border border-secondary-light text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
white:
|
||||
'border bg-transparent border-white text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
'border border-white text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
black:
|
||||
'border bg-black border-primary-main text-white hover:bg-primary-main focus:bg-primary-main hover:border-black focus:border-black',
|
||||
'border border-primary-main text-white hover:bg-primary-main focus:bg-primary-main hover:border-black focus:border-black',
|
||||
},
|
||||
contained: {
|
||||
default:
|
||||
'bg-primary-light text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
primary:
|
||||
'bg-primary-main text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
default: 'text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
primary: 'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
secondary:
|
||||
'bg-secondary-light text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
white:
|
||||
'bg-white text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
black:
|
||||
'bg-black text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
white: 'text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
black: 'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
},
|
||||
};
|
||||
|
||||
const backgroundClasses = {
|
||||
text: {
|
||||
default: '',
|
||||
primary: '',
|
||||
secondary: '',
|
||||
white: '',
|
||||
black: '',
|
||||
},
|
||||
outlined: {
|
||||
default: 'bg-transparent',
|
||||
primary: 'bg-transparent',
|
||||
secondary: 'bg-transparent',
|
||||
white: 'bg-transparent',
|
||||
black: 'bg-black',
|
||||
},
|
||||
contained: {
|
||||
default: 'bg-primary-light',
|
||||
primary: 'bg-primary-main',
|
||||
secondary: 'bg-secondary-light',
|
||||
white: 'bg-white',
|
||||
black: 'bg-black',
|
||||
},
|
||||
};
|
||||
|
||||
@ -90,6 +110,7 @@ const IconButton = ({
|
||||
className,
|
||||
name,
|
||||
id,
|
||||
bgColor,
|
||||
...rest
|
||||
}) => {
|
||||
const buttonElement = useRef(null);
|
||||
@ -98,6 +119,9 @@ const IconButton = ({
|
||||
buttonElement.current.blur();
|
||||
onClick(e);
|
||||
};
|
||||
|
||||
const bgColorToUse = bgColor ? bgColor : backgroundClasses[variant][color];
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classnames(
|
||||
@ -107,6 +131,7 @@ const IconButton = ({
|
||||
sizeClasses[size],
|
||||
fullWidthClasses[fullWidth],
|
||||
disabledClasses[disabled],
|
||||
bgColorToUse,
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
|
||||
@ -5,7 +5,7 @@ import classnames from 'classnames';
|
||||
import { IconButton, Icon, Tooltip } from '../';
|
||||
|
||||
const ToolbarButton = ({
|
||||
type,
|
||||
type = 'tool',
|
||||
id,
|
||||
icon,
|
||||
label,
|
||||
@ -20,14 +20,19 @@ const ToolbarButton = ({
|
||||
const { primaryToolId } = bState;
|
||||
const isActive = _isActive || (type === 'tool' && id === primaryToolId);
|
||||
const classes = {
|
||||
type: {
|
||||
primary: isActive
|
||||
? 'text-black'
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light',
|
||||
secondary: isActive
|
||||
? 'text-black'
|
||||
: 'text-white hover:bg-secondary-dark hover:text-white focus:bg-secondary-dark focus:text-white',
|
||||
},
|
||||
tool: isActive
|
||||
? 'text-black'
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light',
|
||||
toggle: isActive
|
||||
? 'text-[#348CFD]'
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light',
|
||||
action: isActive
|
||||
? 'text-black'
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light',
|
||||
};
|
||||
|
||||
const bgClasses = {
|
||||
toggle: isActive && 'bg-transparent',
|
||||
};
|
||||
|
||||
const activeClass = isActive ? 'active' : '';
|
||||
@ -42,8 +47,9 @@ const ToolbarButton = ({
|
||||
>
|
||||
<IconButton
|
||||
variant={isActive ? 'contained' : 'text'}
|
||||
bgColor={bgClasses[type]}
|
||||
size="toolbar"
|
||||
className={classnames('mx-1', activeClass, classes.type[type])}
|
||||
className={classnames('mx-1', activeClass, classes[type])}
|
||||
onClick={() => {
|
||||
onInteraction({
|
||||
itemId: id,
|
||||
|
||||
@ -64,6 +64,10 @@ export function ViewportGridProvider({ children, service }) {
|
||||
|
||||
const viewports = state.viewports.slice();
|
||||
|
||||
if (!viewportOptions.viewportId) {
|
||||
viewportOptions.viewportId = `viewport-${viewportIndex}`;
|
||||
}
|
||||
|
||||
// merge the displaySetOptions and viewportOptions and displaySetInstanceUIDs
|
||||
// into the viewport object at the given index
|
||||
viewports[viewportIndex] = {
|
||||
|
||||
@ -4,7 +4,7 @@ window.config = {
|
||||
extensions: [],
|
||||
modes: [],
|
||||
showStudyList: true,
|
||||
// maxNumberOfWebWorkers: 3,
|
||||
maxNumberOfWebWorkers: 4,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showLoadingIndicator: true,
|
||||
|
||||
28
yarn.lock
28
yarn.lock
@ -2383,28 +2383,28 @@
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-0.1.1.tgz#5bd1c52a33a425299299e970312731fa0cc2711b"
|
||||
integrity sha512-HOMMOLV6xy8O/agNGGvrl0a8DwShpBvWxAzEzv2pqq12d3r5z/3MyIgNA3Oj/8bIBVvvVXxh9RX7rMDRHJdowg==
|
||||
|
||||
"@cornerstonejs/core@^0.21.0":
|
||||
version "0.21.0"
|
||||
resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.21.0.tgz#12f58f7552a5664d8c9085df2a8c12558c89d70e"
|
||||
integrity sha512-p1aRTJxu1ZWV5uM5EctA870g37Okjpat4xDGfcqTupI2DgwVbCRNvJmuf44gRmBjVeHImDCybMMXXi9nWsCmLw==
|
||||
"@cornerstonejs/core@^0.21.4":
|
||||
version "0.21.4"
|
||||
resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.21.4.tgz#d1dab2c985c382495a8ec91a28f2d62872cdb350"
|
||||
integrity sha512-8D8QyK5bjqxefSVr3vLzsp2MUprmABAbX4qpvpp1XRmC76i1dKKR8Yo9G9jdreR5xwKvJN7pY6/n1F2ZYSN48w==
|
||||
dependencies:
|
||||
detect-gpu "^4.0.45"
|
||||
lodash.clonedeep "4.5.0"
|
||||
|
||||
"@cornerstonejs/streaming-image-volume-loader@^0.6.1":
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.6.1.tgz#a8240c38607648dbc9983b7998e4de2d0ba916fa"
|
||||
integrity sha512-QBuT22dojEH5A0UKgbuYleMEBjbVTJH7bunL6ePcgxI0DY82Lq87qVOMtODfFcHUomBvUlBjP08U/6uP7VJYtA==
|
||||
"@cornerstonejs/streaming-image-volume-loader@^0.6.5":
|
||||
version "0.6.5"
|
||||
resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.6.5.tgz#f3f2ab362bf494e4072a146b45acedce0197fb36"
|
||||
integrity sha512-dowoZYz0tMVtuUQl2WIZjOC/2TC0zMgas8KtpVcxJcGEu9HWA9oatgPlY0WqtFniDU7Oxnyy7OqYawtEoSSwdw==
|
||||
dependencies:
|
||||
"@cornerstonejs/core" "^0.21.0"
|
||||
"@cornerstonejs/core" "^0.21.4"
|
||||
cornerstone-wado-image-loader "^4.2.1"
|
||||
|
||||
"@cornerstonejs/tools@^0.29.2":
|
||||
version "0.29.2"
|
||||
resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.29.2.tgz#6655a91ab82681bd585ad7469ec3a2d372b45521"
|
||||
integrity sha512-sy1pK6YG+vWhJvESDNkiVDnLLpEwgou3LG1VAXIYXvA5JBUVG98LVXBWeAFY8TqdseSrnc/nwW+VQPk899wG5g==
|
||||
"@cornerstonejs/tools@^0.29.6":
|
||||
version "0.29.6"
|
||||
resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.29.6.tgz#6cfcb6b56284c4d1265330993ecafa3f48c429b8"
|
||||
integrity sha512-Rmz4Nx6IfH+31C7fsGxLWwQXQ+w1nN5WmILb9/N7WB4M5mYUZvYiopXdWII4mzh91nHXcEfEiJimaihyuCVLJQ==
|
||||
dependencies:
|
||||
"@cornerstonejs/core" "^0.21.0"
|
||||
"@cornerstonejs/core" "^0.21.4"
|
||||
lodash.clonedeep "4.5.0"
|
||||
lodash.get "^4.4.2"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user