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",
"classnames": "^2.3.2",
"@cornerstonejs/adapters": "^0.4.1",
"@cornerstonejs/core": "^0.33.2",
"@cornerstonejs/core": "^0.36.0",
"@cornerstonejs/tools": "^0.50.2"
}
}

View File

@ -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",

View File

@ -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) {

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

View File

@ -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 };

View File

@ -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<VolumeViewportData> {
// 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,
};
}

View File

@ -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,

View File

@ -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,
});
});

View File

@ -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`
);

View File

@ -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",

View File

@ -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;

View File

@ -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"