feat(VolumeViewport3D): add volumeViewport3Dd sample hp (#3241)

* feat(VolumeViewport3D)
- importing and using Cornerstone 3D's VolumeViewport3D viewport
- added mprAnd3DVolumeViewport hanging protocol
- allowed for W/L preset to be applied to a viewport

* Updated cornerstone core dependency

* Removed pan and zoom from the volume3d tool group so that tackballrotate is never dropped for that viewport.
Removed overlays from 3D volume viewport.
Throw an error and display a message whenever an unapplicable tool is selected.

* The default/initial orientation for a 3D volume viewport can now be set.
Using the 'interleaveTopToBottom' image load strategy for the 'mprAnd3DVolumeViewport' hanging protocol.

* Do not set orientation for stack viewport.
This commit is contained in:
Joe Boccanfuso 2023-03-21 16:09:50 -04:00 committed by GitHub
parent 5ad5bd232d
commit 550918dd31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 230 additions and 17 deletions

View File

@ -46,7 +46,7 @@
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"@cornerstonejs/adapters": "^0.4.1", "@cornerstonejs/adapters": "^0.4.1",
"@cornerstonejs/core": "^0.33.2", "@cornerstonejs/core": "^0.36.0",
"@cornerstonejs/tools": "^0.50.2" "@cornerstonejs/tools": "^0.50.2"
} }
} }

View File

@ -44,7 +44,7 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^0.4.1", "@cornerstonejs/adapters": "^0.4.1",
"@cornerstonejs/core": "^0.33.2", "@cornerstonejs/core": "^0.36.0",
"@cornerstonejs/streaming-image-volume-loader": "^0.14.1", "@cornerstonejs/streaming-image-volume-loader": "^0.14.1",
"@cornerstonejs/tools": "^0.50.2", "@cornerstonejs/tools": "^0.50.2",
"@kitware/vtk.js": "26.5.6", "@kitware/vtk.js": "26.5.6",

View File

@ -181,6 +181,17 @@ const commandsModule = ({
return; 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(); const activeToolName = toolGroup.getActivePrimaryMouseButtonTool();
if (activeToolName) { if (activeToolName) {

View File

@ -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() { function getHangingProtocolModule() {
return [ return [
{ {
id: 'mpr', id: 'mpr',
protocol: mpr, protocol: mpr,
}, },
{
id: mprAnd3DVolumeViewport.id,
protocol: mprAnd3DVolumeViewport,
},
]; ];
} }

View File

@ -23,6 +23,7 @@ import {
addTool, addTool,
annotation, annotation,
ReferenceLinesTool, ReferenceLinesTool,
TrackballRotateTool,
} from '@cornerstonejs/tools'; } from '@cornerstonejs/tools';
import CalibrationLineTool from './tools/CalibrationLineTool'; import CalibrationLineTool from './tools/CalibrationLineTool';
@ -51,6 +52,7 @@ export default function initCornerstoneTools(configuration = {}) {
addTool(SegmentationDisplayTool); addTool(SegmentationDisplayTool);
addTool(ReferenceLinesTool); addTool(ReferenceLinesTool);
addTool(CalibrationLineTool); addTool(CalibrationLineTool);
addTool(TrackballRotateTool);
// Modify annotation tools to use dashed lines on SR // Modify annotation tools to use dashed lines on SR
const annotationStyle = { const annotationStyle = {
@ -90,6 +92,7 @@ const toolNames = {
SegmentationDisplay: SegmentationDisplayTool.toolName, SegmentationDisplay: SegmentationDisplayTool.toolName,
ReferenceLines: ReferenceLinesTool.toolName, ReferenceLines: ReferenceLinesTool.toolName,
CalibrationLine: CalibrationLineTool.toolName, CalibrationLine: CalibrationLineTool.toolName,
TrackballRotateTool: TrackballRotateTool.toolName,
}; };
export { toolNames }; export { toolNames };

View File

@ -62,12 +62,20 @@ class CornerstoneCacheService {
viewportData = await this._getStackViewportData( viewportData = await this._getStackViewportData(
dataSource, dataSource,
displaySets, displaySets,
initialImageIndex initialImageIndex,
cs3DViewportType
); );
} }
if (cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC) { if (
viewportData = await this._getVolumeViewportData(dataSource, displaySets); cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC ||
cs3DViewportType === Enums.ViewportType.VOLUME_3D
) {
viewportData = await this._getVolumeViewportData(
dataSource,
displaySets,
cs3DViewportType
);
} }
viewportData.viewportType = cs3DViewportType; viewportData.viewportType = cs3DViewportType;
@ -100,7 +108,8 @@ class CornerstoneCacheService {
const newViewportData = await this._getVolumeViewportData( const newViewportData = await this._getVolumeViewportData(
dataSource, dataSource,
displaySets displaySets,
viewportData.viewportType
); );
return newViewportData; return newViewportData;
@ -109,7 +118,8 @@ class CornerstoneCacheService {
private _getStackViewportData( private _getStackViewportData(
dataSource, dataSource,
displaySets, displaySets,
initialImageIndex initialImageIndex,
viewportType: Enums.ViewportType
): StackViewportData { ): StackViewportData {
// For Stack Viewport we don't have fusion currently // For Stack Viewport we don't have fusion currently
const displaySet = displaySets[0]; const displaySet = displaySets[0];
@ -126,7 +136,7 @@ class CornerstoneCacheService {
const { displaySetInstanceUID, StudyInstanceUID } = displaySet; const { displaySetInstanceUID, StudyInstanceUID } = displaySet;
const StackViewportData: StackViewportData = { const StackViewportData: StackViewportData = {
viewportType: Enums.ViewportType.STACK, viewportType,
data: { data: {
StudyInstanceUID, StudyInstanceUID,
displaySetInstanceUID, displaySetInstanceUID,
@ -143,7 +153,8 @@ class CornerstoneCacheService {
private async _getVolumeViewportData( private async _getVolumeViewportData(
dataSource, dataSource,
displaySets displaySets,
viewportType: Enums.ViewportType
): Promise<VolumeViewportData> { ): Promise<VolumeViewportData> {
// Todo: Check the cache for multiple scenarios to see if we need to // Todo: Check the cache for multiple scenarios to see if we need to
// decache the volume data from other viewports or not // decache the volume data from other viewports or not
@ -207,7 +218,7 @@ class CornerstoneCacheService {
} }
return { return {
viewportType: Enums.ViewportType.ORTHOGRAPHIC, viewportType,
data: volumeData, data: volumeData,
}; };
} }

View File

@ -6,7 +6,10 @@ import {
getRenderingEngine, getRenderingEngine,
utilities as csUtils, utilities as csUtils,
VolumeViewport, VolumeViewport,
VolumeViewport3D,
cache, cache,
utilities,
CONSTANTS,
} from '@cornerstonejs/core'; } from '@cornerstonejs/core';
import { utilities as csToolsUtils } from '@cornerstonejs/tools'; import { utilities as csToolsUtils } from '@cornerstonejs/tools';
@ -682,7 +685,12 @@ class CornerstoneViewportService extends PubSubService
} }
_getVOICallbacks(volumeId, displaySetOptions) { _getVOICallbacks(volumeId, displaySetOptions) {
const { voi, voiInverted: inverted, colormap } = displaySetOptions; const {
voi,
voiInverted: inverted,
colormap,
presetName,
} = displaySetOptions;
const voiCallbackArray = []; 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; return voiCallbackArray;
} }
@ -723,7 +741,10 @@ class CornerstoneViewportService extends PubSubService
viewportInfo, viewportInfo,
presentations presentations
); );
} else if (viewport instanceof VolumeViewport) { } else if (
viewport instanceof VolumeViewport ||
viewport instanceof VolumeViewport3D
) {
this._setVolumeViewport( this._setVolumeViewport(
viewport, viewport,
viewportData as VolumeViewportData, viewportData as VolumeViewportData,

View File

@ -63,6 +63,7 @@ export type PublicDisplaySetOptions = {
blendMode?: string; blendMode?: string;
slabThickness?: number; slabThickness?: number;
colormap?: string; colormap?: string;
presetName?: string;
}; };
export type DisplaySetOptions = { export type DisplaySetOptions = {
@ -72,6 +73,7 @@ export type DisplaySetOptions = {
blendMode?: Enums.BlendModes; blendMode?: Enums.BlendModes;
slabThickness?: number; slabThickness?: number;
colormap?: string; colormap?: string;
presetName?: string;
}; };
type VOI = { type VOI = {
@ -84,7 +86,6 @@ export type DisplaySet = {
}; };
const STACK = 'stack'; const STACK = 'stack';
const VOLUME = 'volume';
const DEFAULT_TOOLGROUP_ID = 'default'; const DEFAULT_TOOLGROUP_ID = 'default';
class ViewportInfo { class ViewportInfo {
@ -198,10 +199,8 @@ class ViewportInfo {
} }
// map SAGITTAL, AXIAL, CORONAL orientation to be used by cornerstone // map SAGITTAL, AXIAL, CORONAL orientation to be used by cornerstone
if (viewportOptionsEntry.viewportType?.toLowerCase() === VOLUME) { if (viewportOptionsEntry.viewportType?.toLowerCase() !== STACK) {
orientation = getCornerstoneOrientation(viewportOptionsEntry.orientation); orientation = getCornerstoneOrientation(viewportOptionsEntry.orientation);
} else {
orientation = Enums.OrientationAxis.AXIAL;
} }
this.setViewportOptions({ this.setViewportOptions({
@ -282,6 +281,7 @@ class ViewportInfo {
colormap: option.colormap, colormap: option.colormap,
slabThickness: option.slabThickness, slabThickness: option.slabThickness,
blendMode, blendMode,
presetName: option.presetName,
}); });
}); });

View File

@ -3,6 +3,7 @@ import { Enums } from '@cornerstonejs/core';
const STACK = 'stack'; const STACK = 'stack';
const VOLUME = 'volume'; const VOLUME = 'volume';
const ORTHOGRAPHIC = 'orthographic'; const ORTHOGRAPHIC = 'orthographic';
const VOLUME_3D = 'volume3d';
export default function getCornerstoneViewportType( export default function getCornerstoneViewportType(
viewportType: string viewportType: string
@ -16,6 +17,10 @@ export default function getCornerstoneViewportType(
return Enums.ViewportType.ORTHOGRAPHIC; return Enums.ViewportType.ORTHOGRAPHIC;
} }
if (lowerViewportType === VOLUME_3D) {
return Enums.ViewportType.VOLUME_3D;
}
throw new Error( throw new Error(
`Invalid viewport type: ${viewportType}. Valid types are: stack, volume` `Invalid viewport type: ${viewportType}. Valid types are: stack, volume`
); );

View File

@ -32,7 +32,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"@cornerstonejs/core": "^0.33.2", "@cornerstonejs/core": "^0.36.0",
"@cornerstonejs/tools": "^0.50.2", "@cornerstonejs/tools": "^0.50.2",
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
"dcmjs": "^0.29.4", "dcmjs": "^0.29.4",

View File

@ -215,6 +215,24 @@ function initMPRToolGroup(extensionManager, toolGroupService, commandsManager) {
toolGroupService.createToolGroupAndAddTools('mpr', tools, toolsConfig); 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) { function initToolGroups(extensionManager, toolGroupService, commandsManager) {
initDefaultToolGroup( initDefaultToolGroup(
@ -225,6 +243,7 @@ function initToolGroups(extensionManager, toolGroupService, commandsManager) {
); );
initSRToolGroup(extensionManager, toolGroupService, commandsManager); initSRToolGroup(extensionManager, toolGroupService, commandsManager);
initMPRToolGroup(extensionManager, toolGroupService, commandsManager); initMPRToolGroup(extensionManager, toolGroupService, commandsManager);
initVolume3DToolGroup(extensionManager, toolGroupService);
} }
export default initToolGroups; export default initToolGroups;

View File

@ -1459,6 +1459,14 @@
detect-gpu "^4.0.45" detect-gpu "^4.0.45"
lodash.clonedeep "4.5.0" 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": "@cornerstonejs/streaming-image-volume-loader@^0.14.1":
version "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" resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.14.1.tgz#9830e1cbb65aa0e050336b7f5cb9c81d23e09fc5"