feat(RT): add dicom RT support via volume viewports (#3310)
* feat: initial RT support
* make the segmentation service work with representation data
* feat: make segmentation service work with representations
* fix rtss vis
* fix: rt hydration
* fix the rendering of rt names
* fix imports
* refactor: Modify status and click handling for hydration of RTStructures
Modify status and click handling for hydration of RTStructures by renaming `onPillClick` to `onStatusClick` in `OHIFCornerstoneRTViewport.tsx` and `_getStatusComponent.tsx` files. Also, update initial segmentation configurations in `PanelSegmentation.tsx` and simplify configuration changes and values for segmentation service in `SegmentationService.ts`. Finally, remove console debug in `CornerstoneViewportService.ts`.
* wip for highlighting contours
* refactor rt displayset code
* review code update
* update cornerstone dependencies
* refactor: Update license year, version number, and minor code cleanup
This commit updates the license year in several files, updates the version number in package.json, and contains minor code cleanup in two files.
* add bulkdataURI retrieve for RT
* fix package version
* apply review comments
* apply review comments
* apply review comments
* feat(panels): refactor and streamline segmentation configuration and inputs
Rewrote state hooks and streamlined the configuration input for `PanelSegmentation` to be more verbose and reusable. Included several new input types, including the `InputRange` component which now shows a fixed floating value based on the step provided. The `SegmentationConfig` component now works with dynamic values controlled by `initialConfig`. These changes should improve function usability and make the code more maintainable going forward.
* fix various bugs
* fix contour delete by upgrade cs3d version
* feat(viewport, inputNumber, segmentationConfig, orthanc): Implement minimum and maximum values for input number components, and useBulkDataURI for Orthanc configuration. Compare measurement view planes with absolute viewport view planes in Cornerstone viewport.
* update yarn lock
2023-04-26 18:21:08 +02:00
|
|
|
import OHIF, { Types } from '@ohif/core';
|
2022-11-16 20:19:52 +01:00
|
|
|
import React from 'react';
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
import * as cornerstone from '@cornerstonejs/core';
|
|
|
|
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
|
|
|
import {
|
|
|
|
|
init as cs3DInit,
|
|
|
|
|
eventTarget,
|
|
|
|
|
EVENTS,
|
|
|
|
|
metaData,
|
|
|
|
|
volumeLoader,
|
|
|
|
|
imageLoadPoolManager,
|
|
|
|
|
Settings,
|
2023-02-23 03:01:36 +01:00
|
|
|
utilities as csUtilities,
|
2022-07-27 18:39:04 +02:00
|
|
|
} from '@cornerstonejs/core';
|
2022-11-16 20:19:52 +01:00
|
|
|
import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools';
|
2023-01-11 13:19:27 +01:00
|
|
|
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
import initWADOImageLoader from './initWADOImageLoader';
|
|
|
|
|
import initCornerstoneTools from './initCornerstoneTools';
|
|
|
|
|
|
|
|
|
|
import { connectToolsToMeasurementService } from './initMeasurementService';
|
|
|
|
|
import initCineService from './initCineService';
|
|
|
|
|
import interleaveCenterLoader from './utils/interleaveCenterLoader';
|
2022-12-29 17:00:13 +01:00
|
|
|
import nthLoader from './utils/nthLoader';
|
2022-07-27 18:39:04 +02:00
|
|
|
import interleaveTopToBottom from './utils/interleaveTopToBottom';
|
2023-03-22 22:45:28 +01:00
|
|
|
import initContextMenu from './initContextMenu';
|
2023-03-29 21:39:51 +02:00
|
|
|
import initDoubleClick from './initDoubleClick';
|
2020-09-15 03:18:46 +02:00
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
// TODO: Cypress tests are currently grabbing this from the window?
|
|
|
|
|
window.cornerstone = cornerstone;
|
|
|
|
|
window.cornerstoneTools = cornerstoneTools;
|
2019-10-09 16:23:14 +02:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
2022-07-27 18:39:04 +02:00
|
|
|
export default async function init({
|
2020-08-05 14:56:02 +02:00
|
|
|
servicesManager,
|
|
|
|
|
commandsManager,
|
2023-05-03 18:30:02 +02:00
|
|
|
extensionManager,
|
2020-08-05 14:56:02 +02:00
|
|
|
configuration,
|
2022-07-27 18:39:04 +02:00
|
|
|
appConfig,
|
2023-03-22 22:45:28 +01:00
|
|
|
}: Types.Extensions.ExtensionParams): Promise<void> {
|
2022-07-27 18:39:04 +02:00
|
|
|
await cs3DInit();
|
|
|
|
|
|
|
|
|
|
// For debugging e2e tests that are failing on CI
|
2022-11-07 16:05:56 +01:00
|
|
|
cornerstone.setUseCPURendering(Boolean(appConfig.useCPURendering));
|
2023-04-01 04:21:16 +02:00
|
|
|
cornerstone.setConfiguration({
|
|
|
|
|
...cornerstone.getConfiguration(),
|
|
|
|
|
rendering: {
|
|
|
|
|
...cornerstone.getConfiguration().rendering,
|
|
|
|
|
strictZSpacingForVolumeViewport:
|
|
|
|
|
appConfig.strictZSpacingForVolumeViewport,
|
|
|
|
|
},
|
|
|
|
|
});
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
// For debugging large datasets
|
2022-11-07 16:05:56 +01:00
|
|
|
const MAX_CACHE_SIZE_1GB = 1073741824;
|
|
|
|
|
const maxCacheSize = appConfig.maxCacheSize;
|
|
|
|
|
cornerstone.cache.setMaxCacheSize(
|
|
|
|
|
maxCacheSize ? maxCacheSize : MAX_CACHE_SIZE_1GB
|
|
|
|
|
);
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
initCornerstoneTools();
|
|
|
|
|
|
2022-11-16 20:19:52 +01:00
|
|
|
Settings.getRuntimeSettings().set(
|
|
|
|
|
'useCursors',
|
|
|
|
|
Boolean(appConfig.useCursors)
|
|
|
|
|
);
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2020-07-22 19:40:40 +02:00
|
|
|
const {
|
2023-02-13 14:55:55 +01:00
|
|
|
userAuthenticationService,
|
2023-02-10 22:46:09 +01:00
|
|
|
measurementService,
|
2023-03-22 22:45:28 +01:00
|
|
|
customizationService,
|
2023-02-10 22:46:09 +01:00
|
|
|
displaySetService,
|
|
|
|
|
uiDialogService,
|
2023-02-13 14:55:55 +01:00
|
|
|
uiModalService,
|
2023-02-10 22:46:09 +01:00
|
|
|
uiNotificationService,
|
2023-02-07 20:31:55 +01:00
|
|
|
cineService,
|
2023-02-10 22:46:09 +01:00
|
|
|
cornerstoneViewportService,
|
|
|
|
|
hangingProtocolService,
|
|
|
|
|
toolGroupService,
|
|
|
|
|
viewportGridService,
|
2023-03-15 17:41:41 +01:00
|
|
|
stateSyncService,
|
2020-07-22 19:40:40 +02:00
|
|
|
} = servicesManager.services;
|
2020-09-15 03:18:46 +02:00
|
|
|
|
2022-11-16 20:19:52 +01:00
|
|
|
window.services = servicesManager.services;
|
2023-05-03 18:30:02 +02:00
|
|
|
window.extensionManager = extensionManager;
|
|
|
|
|
window.commandsManager = commandsManager;
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2023-02-17 21:04:07 +01:00
|
|
|
if (
|
|
|
|
|
appConfig.showWarningMessageForCrossOrigin &&
|
|
|
|
|
!window.crossOriginIsolated
|
|
|
|
|
) {
|
2023-02-10 22:46:09 +01:00
|
|
|
uiNotificationService.show({
|
2022-12-05 15:53:59 +01:00
|
|
|
title: 'Cross Origin Isolation',
|
|
|
|
|
message:
|
|
|
|
|
'Cross Origin Isolation is not enabled, volume rendering will not work (e.g., MPR)',
|
|
|
|
|
type: 'warning',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-17 21:04:07 +01:00
|
|
|
if (
|
|
|
|
|
appConfig.showCPUFallbackMessage &&
|
|
|
|
|
cornerstone.getShouldUseCPURendering()
|
|
|
|
|
) {
|
2023-02-13 14:55:55 +01:00
|
|
|
_showCPURenderingModal(uiModalService, hangingProtocolService);
|
2022-11-16 20:19:52 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-20 20:11:21 +01:00
|
|
|
// Stores a map from `lutPresentationId` to a Presentation object so that
|
|
|
|
|
// an OHIFCornerstoneViewport can be redisplayed with the same LUT
|
|
|
|
|
stateSyncService.register('lutPresentationStore', { clearOnModeExit: true });
|
|
|
|
|
|
|
|
|
|
// Stores a map from `positionPresentationId` to a Presentation object so that
|
|
|
|
|
// an OHIFCornerstoneViewport can be redisplayed with the same position
|
|
|
|
|
stateSyncService.register('positionPresentationStore', {
|
|
|
|
|
clearOnModeExit: true,
|
|
|
|
|
});
|
2023-03-15 17:41:41 +01:00
|
|
|
|
2023-03-29 21:39:51 +02:00
|
|
|
// Stores the entire ViewportGridService getState when toggling to one up
|
|
|
|
|
// (e.g. via a double click) so that it can be restored when toggling back.
|
|
|
|
|
stateSyncService.register('toggleOneUpViewportGridStore', {
|
|
|
|
|
clearOnModeExit: true,
|
|
|
|
|
});
|
|
|
|
|
|
2022-11-16 20:19:52 +01:00
|
|
|
const labelmapRepresentation =
|
|
|
|
|
cornerstoneTools.Enums.SegmentationRepresentations.Labelmap;
|
|
|
|
|
|
|
|
|
|
cornerstoneTools.segmentation.config.setGlobalRepresentationConfig(
|
|
|
|
|
labelmapRepresentation,
|
|
|
|
|
{
|
|
|
|
|
fillAlpha: 0.3,
|
|
|
|
|
fillAlphaInactive: 0.2,
|
|
|
|
|
outlineOpacity: 1,
|
|
|
|
|
outlineOpacityInactive: 0.65,
|
|
|
|
|
}
|
2022-07-27 18:39:04 +02:00
|
|
|
);
|
2022-11-16 20:19:52 +01:00
|
|
|
|
|
|
|
|
const metadataProvider = OHIF.classes.MetadataProvider;
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
volumeLoader.registerVolumeLoader(
|
|
|
|
|
'cornerstoneStreamingImageVolume',
|
|
|
|
|
cornerstoneStreamingImageVolumeLoader
|
|
|
|
|
);
|
|
|
|
|
|
2023-02-10 22:46:09 +01:00
|
|
|
hangingProtocolService.registerImageLoadStrategy(
|
2022-07-27 18:39:04 +02:00
|
|
|
'interleaveCenter',
|
|
|
|
|
interleaveCenterLoader
|
|
|
|
|
);
|
2023-02-10 22:46:09 +01:00
|
|
|
hangingProtocolService.registerImageLoadStrategy(
|
2022-07-27 18:39:04 +02:00
|
|
|
'interleaveTopToBottom',
|
|
|
|
|
interleaveTopToBottom
|
|
|
|
|
);
|
2023-02-10 22:46:09 +01:00
|
|
|
hangingProtocolService.registerImageLoadStrategy('nth', nthLoader);
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2023-02-23 03:01:36 +01:00
|
|
|
// add metadata providers
|
|
|
|
|
metaData.addProvider(
|
|
|
|
|
csUtilities.calibratedPixelSpacingMetadataProvider.get.bind(
|
|
|
|
|
csUtilities.calibratedPixelSpacingMetadataProvider
|
|
|
|
|
)
|
|
|
|
|
); // this provider is required for Calibration tool
|
2022-07-27 18:39:04 +02:00
|
|
|
metaData.addProvider(metadataProvider.get.bind(metadataProvider), 9999);
|
|
|
|
|
|
|
|
|
|
imageLoadPoolManager.maxNumRequests = {
|
|
|
|
|
interaction: appConfig?.maxNumRequests?.interaction || 100,
|
|
|
|
|
thumbnail: appConfig?.maxNumRequests?.thumbnail || 75,
|
|
|
|
|
prefetch: appConfig?.maxNumRequests?.prefetch || 10,
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-01 23:18:54 +02:00
|
|
|
initWADOImageLoader(userAuthenticationService, appConfig, extensionManager);
|
2019-12-09 18:47:23 +01:00
|
|
|
|
2020-08-05 14:56:02 +02:00
|
|
|
/* Measurement Service */
|
2023-03-22 22:45:28 +01:00
|
|
|
this.measurementServiceSource = connectToolsToMeasurementService(
|
2023-02-23 03:01:36 +01:00
|
|
|
servicesManager
|
2020-08-05 14:56:02 +02:00
|
|
|
);
|
|
|
|
|
|
2023-02-07 20:31:55 +01:00
|
|
|
initCineService(cineService);
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
// When a custom image load is performed, update the relevant viewports
|
2023-02-10 22:46:09 +01:00
|
|
|
hangingProtocolService.subscribe(
|
|
|
|
|
hangingProtocolService.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED,
|
2022-07-27 18:39:04 +02:00
|
|
|
volumeInputArrayMap => {
|
|
|
|
|
for (const entry of volumeInputArrayMap.entries()) {
|
|
|
|
|
const [viewportId, volumeInputArray] = entry;
|
2023-02-10 22:46:09 +01:00
|
|
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(
|
2022-07-27 18:39:04 +02:00
|
|
|
viewportId
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-29 15:40:22 +02:00
|
|
|
const ohifViewport = cornerstoneViewportService.getViewportInfo(
|
|
|
|
|
viewportId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
lutPresentationStore,
|
|
|
|
|
positionPresentationStore,
|
|
|
|
|
} = stateSyncService.getState();
|
|
|
|
|
const { presentationIds } = ohifViewport.getViewportOptions();
|
|
|
|
|
const presentations = {
|
|
|
|
|
positionPresentation:
|
|
|
|
|
positionPresentationStore[presentationIds?.positionPresentationId],
|
|
|
|
|
lutPresentation:
|
|
|
|
|
lutPresentationStore[presentationIds?.lutPresentationId],
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-10 22:46:09 +01:00
|
|
|
cornerstoneViewportService.setVolumesForViewport(
|
2022-07-27 18:39:04 +02:00
|
|
|
viewport,
|
2023-03-29 15:40:22 +02:00
|
|
|
volumeInputArray,
|
|
|
|
|
presentations
|
2022-07-27 18:39:04 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-22 22:45:28 +01:00
|
|
|
initContextMenu({
|
|
|
|
|
cornerstoneViewportService,
|
|
|
|
|
customizationService,
|
|
|
|
|
commandsManager,
|
|
|
|
|
});
|
2020-08-05 14:56:02 +02:00
|
|
|
|
2023-03-29 21:39:51 +02:00
|
|
|
initDoubleClick({
|
|
|
|
|
customizationService,
|
|
|
|
|
commandsManager,
|
|
|
|
|
});
|
|
|
|
|
|
2022-09-16 05:23:17 +02:00
|
|
|
const newStackCallback = evt => {
|
|
|
|
|
const { element } = evt.detail;
|
|
|
|
|
utilities.stackPrefetch.enable(element);
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-16 20:19:52 +01:00
|
|
|
const resetCrosshairs = evt => {
|
|
|
|
|
const { element } = evt.detail;
|
|
|
|
|
const { viewportId, renderingEngineId } = cornerstone.getEnabledElement(
|
|
|
|
|
element
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroupForViewport(
|
|
|
|
|
viewportId,
|
|
|
|
|
renderingEngineId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!toolGroup || !toolGroup._toolInstances?.['Crosshairs']) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mode = toolGroup._toolInstances['Crosshairs'].mode;
|
|
|
|
|
|
|
|
|
|
if (mode === Enums.ToolModes.Active) {
|
|
|
|
|
toolGroup.setToolActive('Crosshairs');
|
|
|
|
|
} else if (mode === Enums.ToolModes.Passive) {
|
|
|
|
|
toolGroup.setToolPassive('Crosshairs');
|
|
|
|
|
} else if (mode === Enums.ToolModes.Enabled) {
|
|
|
|
|
toolGroup.setToolEnabled('Crosshairs');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
function elementEnabledHandler(evt) {
|
|
|
|
|
const { element } = evt.detail;
|
2022-11-16 20:19:52 +01:00
|
|
|
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
|
|
|
|
|
2022-09-16 05:23:17 +02:00
|
|
|
eventTarget.addEventListener(
|
|
|
|
|
EVENTS.STACK_VIEWPORT_NEW_STACK,
|
|
|
|
|
newStackCallback
|
|
|
|
|
);
|
2020-08-05 14:56:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function elementDisabledHandler(evt) {
|
2022-11-16 20:19:52 +01:00
|
|
|
const { element } = evt.detail;
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2022-11-16 20:19:52 +01:00
|
|
|
element.removeEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
|
|
|
|
|
2022-09-16 05:23:17 +02:00
|
|
|
// TODO - consider removing the callback when all elements are gone
|
|
|
|
|
// eventTarget.removeEventListener(
|
|
|
|
|
// EVENTS.STACK_VIEWPORT_NEW_STACK,
|
|
|
|
|
// newStackCallback
|
|
|
|
|
// );
|
2022-07-27 18:39:04 +02:00
|
|
|
}
|
2020-07-03 19:18:59 +02:00
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
eventTarget.addEventListener(
|
|
|
|
|
EVENTS.ELEMENT_ENABLED,
|
|
|
|
|
elementEnabledHandler.bind(null)
|
2020-08-04 11:11:23 +02:00
|
|
|
);
|
2020-07-03 18:39:41 +02:00
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
eventTarget.addEventListener(
|
|
|
|
|
EVENTS.ELEMENT_DISABLED,
|
|
|
|
|
elementDisabledHandler.bind(null)
|
2020-09-15 03:18:46 +02:00
|
|
|
);
|
2022-11-16 20:19:52 +01:00
|
|
|
|
2023-02-10 22:46:09 +01:00
|
|
|
viewportGridService.subscribe(
|
|
|
|
|
viewportGridService.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED,
|
2023-03-15 17:41:41 +01:00
|
|
|
({ viewportIndex, viewportId }) => {
|
|
|
|
|
viewportId = viewportId || `viewport-${viewportIndex}`;
|
2023-02-10 22:46:09 +01:00
|
|
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
2022-11-16 20:19:52 +01:00
|
|
|
|
2022-11-21 16:44:19 +01:00
|
|
|
if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) {
|
2022-11-16 20:19:52 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check if reference lines are active
|
|
|
|
|
const referenceLinesEnabled =
|
2022-11-21 16:44:19 +01:00
|
|
|
toolGroup._toolInstances['ReferenceLines'].mode ===
|
2022-11-16 20:19:52 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CPUModal() {
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<p>
|
|
|
|
|
Your computer does not have enough GPU power to support the default GPU
|
|
|
|
|
rendering mode. OHIF has switched to CPU rendering mode. Please note
|
|
|
|
|
that CPU rendering does not support all features such as Volume
|
|
|
|
|
Rendering, Multiplanar Reconstruction, and Segmentation Overlays.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-13 14:55:55 +01:00
|
|
|
function _showCPURenderingModal(uiModalService, hangingProtocolService) {
|
2022-11-16 20:19:52 +01:00
|
|
|
const callback = progress => {
|
|
|
|
|
if (progress === 100) {
|
2023-02-13 14:55:55 +01:00
|
|
|
uiModalService.show({
|
2022-11-16 20:19:52 +01:00
|
|
|
content: CPUModal,
|
|
|
|
|
title: 'OHIF Fell Back to CPU Rendering',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-10 22:46:09 +01:00
|
|
|
const { unsubscribe } = hangingProtocolService.subscribe(
|
2023-03-15 17:41:41 +01:00
|
|
|
hangingProtocolService.EVENTS.PROTOCOL_CHANGED,
|
|
|
|
|
() => {
|
|
|
|
|
const done = callback(100);
|
2022-11-16 20:19:52 +01:00
|
|
|
|
|
|
|
|
if (done) {
|
|
|
|
|
unsubscribe();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
2020-09-15 03:18:46 +02:00
|
|
|
}
|