diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index ab7ddb077..3bae4e3c1 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -46,7 +46,7 @@ "@babel/runtime": "^7.20.13", "classnames": "^2.3.2", "@cornerstonejs/adapters": "^0.4.1", - "@cornerstonejs/core": "^0.33.2", + "@cornerstonejs/core": "^0.36.0", "@cornerstonejs/tools": "^0.50.2" } } diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index b981ec98c..cf4b42d94 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -44,7 +44,7 @@ "dependencies": { "@babel/runtime": "^7.20.13", "@cornerstonejs/adapters": "^0.4.1", - "@cornerstonejs/core": "^0.33.2", + "@cornerstonejs/core": "^0.36.0", "@cornerstonejs/streaming-image-volume-loader": "^0.14.1", "@cornerstonejs/tools": "^0.50.2", "@kitware/vtk.js": "26.5.6", diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts index 878c174d7..6298792c9 100644 --- a/extensions/cornerstone/src/commandsModule.ts +++ b/extensions/cornerstone/src/commandsModule.ts @@ -181,6 +181,17 @@ const commandsModule = ({ return; } + if (!toolGroup.getToolInstance(toolName)) { + uiNotificationService.show({ + title: `${toolName} tool`, + message: `The ${toolName} tool is not available in this viewport.`, + type: 'info', + duration: 3000, + }); + + throw new Error(`ToolGroup ${toolGroup.id} does not have this tool.`); + } + const activeToolName = toolGroup.getActivePrimaryMouseButtonTool(); if (activeToolName) { diff --git a/extensions/cornerstone/src/getHangingProtocolModule.ts b/extensions/cornerstone/src/getHangingProtocolModule.ts index 20a962b44..755bf030e 100644 --- a/extensions/cornerstone/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone/src/getHangingProtocolModule.ts @@ -160,12 +160,147 @@ const mpr: Types.HangingProtocol.Protocol = { ], }; +const mprAnd3DVolumeViewport = { + id: 'mprAnd3DVolumeViewport', + locked: true, + hasUpdatedPriorsInformation: false, + name: 'mpr', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveTopToBottom', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'mpr3Stage', + name: 'mpr', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + presetName: 'CT-Bone', + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; + function getHangingProtocolModule() { return [ { id: 'mpr', protocol: mpr, }, + { + id: mprAnd3DVolumeViewport.id, + protocol: mprAnd3DVolumeViewport, + }, ]; } diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js index 291e02924..2d305af8a 100644 --- a/extensions/cornerstone/src/initCornerstoneTools.js +++ b/extensions/cornerstone/src/initCornerstoneTools.js @@ -23,6 +23,7 @@ import { addTool, annotation, ReferenceLinesTool, + TrackballRotateTool, } from '@cornerstonejs/tools'; import CalibrationLineTool from './tools/CalibrationLineTool'; @@ -51,6 +52,7 @@ export default function initCornerstoneTools(configuration = {}) { addTool(SegmentationDisplayTool); addTool(ReferenceLinesTool); addTool(CalibrationLineTool); + addTool(TrackballRotateTool); // Modify annotation tools to use dashed lines on SR const annotationStyle = { @@ -90,6 +92,7 @@ const toolNames = { SegmentationDisplay: SegmentationDisplayTool.toolName, ReferenceLines: ReferenceLinesTool.toolName, CalibrationLine: CalibrationLineTool.toolName, + TrackballRotateTool: TrackballRotateTool.toolName, }; export { toolNames }; diff --git a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts index 01a359368..8a29bcab4 100644 --- a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts +++ b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts @@ -62,12 +62,20 @@ class CornerstoneCacheService { viewportData = await this._getStackViewportData( dataSource, displaySets, - initialImageIndex + initialImageIndex, + cs3DViewportType ); } - if (cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC) { - viewportData = await this._getVolumeViewportData(dataSource, displaySets); + if ( + cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC || + cs3DViewportType === Enums.ViewportType.VOLUME_3D + ) { + viewportData = await this._getVolumeViewportData( + dataSource, + displaySets, + cs3DViewportType + ); } viewportData.viewportType = cs3DViewportType; @@ -100,7 +108,8 @@ class CornerstoneCacheService { const newViewportData = await this._getVolumeViewportData( dataSource, - displaySets + displaySets, + viewportData.viewportType ); return newViewportData; @@ -109,7 +118,8 @@ class CornerstoneCacheService { private _getStackViewportData( dataSource, displaySets, - initialImageIndex + initialImageIndex, + viewportType: Enums.ViewportType ): StackViewportData { // For Stack Viewport we don't have fusion currently const displaySet = displaySets[0]; @@ -126,7 +136,7 @@ class CornerstoneCacheService { const { displaySetInstanceUID, StudyInstanceUID } = displaySet; const StackViewportData: StackViewportData = { - viewportType: Enums.ViewportType.STACK, + viewportType, data: { StudyInstanceUID, displaySetInstanceUID, @@ -143,7 +153,8 @@ class CornerstoneCacheService { private async _getVolumeViewportData( dataSource, - displaySets + displaySets, + viewportType: Enums.ViewportType ): Promise { // Todo: Check the cache for multiple scenarios to see if we need to // decache the volume data from other viewports or not @@ -207,7 +218,7 @@ class CornerstoneCacheService { } return { - viewportType: Enums.ViewportType.ORTHOGRAPHIC, + viewportType, data: volumeData, }; } diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index 81623a738..0729c783d 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -6,7 +6,10 @@ import { getRenderingEngine, utilities as csUtils, VolumeViewport, + VolumeViewport3D, cache, + utilities, + CONSTANTS, } from '@cornerstonejs/core'; import { utilities as csToolsUtils } from '@cornerstonejs/tools'; @@ -682,7 +685,12 @@ class CornerstoneViewportService extends PubSubService } _getVOICallbacks(volumeId, displaySetOptions) { - const { voi, voiInverted: inverted, colormap } = displaySetOptions; + const { + voi, + voiInverted: inverted, + colormap, + presetName, + } = displaySetOptions; const voiCallbackArray = []; @@ -707,6 +715,16 @@ class CornerstoneViewportService extends PubSubService ); } + if (presetName) { + voiCallbackArray.push(volumeActor => { + utilities.applyPreset( + volumeActor, + CONSTANTS.VIEWPORT_PRESETS.find(preset => { + return preset.name === presetName; + }) + ); + }); + } return voiCallbackArray; } @@ -723,7 +741,10 @@ class CornerstoneViewportService extends PubSubService viewportInfo, presentations ); - } else if (viewport instanceof VolumeViewport) { + } else if ( + viewport instanceof VolumeViewport || + viewport instanceof VolumeViewport3D + ) { this._setVolumeViewport( viewport, viewportData as VolumeViewportData, diff --git a/extensions/cornerstone/src/services/ViewportService/Viewport.ts b/extensions/cornerstone/src/services/ViewportService/Viewport.ts index aefbc07ce..5221827ce 100644 --- a/extensions/cornerstone/src/services/ViewportService/Viewport.ts +++ b/extensions/cornerstone/src/services/ViewportService/Viewport.ts @@ -63,6 +63,7 @@ export type PublicDisplaySetOptions = { blendMode?: string; slabThickness?: number; colormap?: string; + presetName?: string; }; export type DisplaySetOptions = { @@ -72,6 +73,7 @@ export type DisplaySetOptions = { blendMode?: Enums.BlendModes; slabThickness?: number; colormap?: string; + presetName?: string; }; type VOI = { @@ -84,7 +86,6 @@ export type DisplaySet = { }; const STACK = 'stack'; -const VOLUME = 'volume'; const DEFAULT_TOOLGROUP_ID = 'default'; class ViewportInfo { @@ -198,10 +199,8 @@ class ViewportInfo { } // map SAGITTAL, AXIAL, CORONAL orientation to be used by cornerstone - if (viewportOptionsEntry.viewportType?.toLowerCase() === VOLUME) { + if (viewportOptionsEntry.viewportType?.toLowerCase() !== STACK) { orientation = getCornerstoneOrientation(viewportOptionsEntry.orientation); - } else { - orientation = Enums.OrientationAxis.AXIAL; } this.setViewportOptions({ @@ -282,6 +281,7 @@ class ViewportInfo { colormap: option.colormap, slabThickness: option.slabThickness, blendMode, + presetName: option.presetName, }); }); diff --git a/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts b/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts index 88f386eeb..47a5ede85 100644 --- a/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts +++ b/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts @@ -3,6 +3,7 @@ import { Enums } from '@cornerstonejs/core'; const STACK = 'stack'; const VOLUME = 'volume'; const ORTHOGRAPHIC = 'orthographic'; +const VOLUME_3D = 'volume3d'; export default function getCornerstoneViewportType( viewportType: string @@ -16,6 +17,10 @@ export default function getCornerstoneViewportType( return Enums.ViewportType.ORTHOGRAPHIC; } + if (lowerViewportType === VOLUME_3D) { + return Enums.ViewportType.VOLUME_3D; + } + throw new Error( `Invalid viewport type: ${viewportType}. Valid types are: stack, volume` ); diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index 01c45ccdc..0facf190a 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -32,7 +32,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "classnames": "^2.3.2", - "@cornerstonejs/core": "^0.33.2", + "@cornerstonejs/core": "^0.36.0", "@cornerstonejs/tools": "^0.50.2", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "dcmjs": "^0.29.4", diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js index fe77aa52f..261b37090 100644 --- a/modes/longitudinal/src/initToolGroups.js +++ b/modes/longitudinal/src/initToolGroups.js @@ -215,6 +215,24 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) { toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); } +function initVolume3DToolGroup(extensionManager, toolGroupService) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames, Enums } = utilityModule.exports; + + const tools = { + active: [ + { + toolName: toolNames.TrackballRotateTool, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + ], + }; + + toolGroupService.createToolGroupAndAddTools('volume3d', tools); +} function initToolGroups(extensionManager, toolGroupService, commandsManager) { initDefaultToolGroup( @@ -225,6 +243,7 @@ function initToolGroups(extensionManager, toolGroupService, commandsManager) { ); initSRToolGroup(extensionManager, toolGroupService, commandsManager); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); + initVolume3DToolGroup(extensionManager, toolGroupService); } export default initToolGroups; diff --git a/yarn.lock b/yarn.lock index dbc9e8400..5f948c92d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1459,6 +1459,14 @@ detect-gpu "^4.0.45" lodash.clonedeep "4.5.0" +"@cornerstonejs/core@^0.36.0": + version "0.36.0" + resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.36.0.tgz#b8e798e04cfceb6106ce4700b2aef14532d814f0" + integrity sha512-vU1wYhezq4x99MT3nuuNS2YVTUn1UkMJ5B9PPSyayQeWxuC0pV0KWBllUAjJA3VR3YDmDUKAfLh9K10u6javYg== + dependencies: + detect-gpu "^4.0.45" + lodash.clonedeep "4.5.0" + "@cornerstonejs/streaming-image-volume-loader@^0.14.1": version "0.14.1" resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.14.1.tgz#9830e1cbb65aa0e050336b7f5cb9c81d23e09fc5"