851 lines
26 KiB
Plaintext
851 lines
26 KiB
Plaintext
import {
|
|
RenderingEngine,
|
|
Types,
|
|
Enums,
|
|
setVolumesForViewports,
|
|
volumeLoader,
|
|
getRenderingEngine,
|
|
} from '@cornerstonejs/core';
|
|
import {
|
|
initDemo,
|
|
createImageIdsAndCacheMetaData,
|
|
setTitleAndDescription,
|
|
setPetColorMapTransferFunctionForVolumeActor,
|
|
setPetTransferFunctionForVolumeActor,
|
|
setCtTransferFunctionForVolumeActor,
|
|
addDropdownToToolbar,
|
|
addButtonToToolbar,
|
|
} from '../../../../utils/demo/helpers';
|
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
|
|
|
const {
|
|
ToolGroupManager,
|
|
Enums: csToolsEnums,
|
|
WindowLevelTool,
|
|
PanTool,
|
|
ZoomTool,
|
|
StackScrollMouseWheelTool,
|
|
synchronizers,
|
|
MIPJumpToClickTool,
|
|
VolumeRotateMouseWheelTool,
|
|
CrosshairsTool,
|
|
TrackballRotateTool,
|
|
} = cornerstoneTools;
|
|
|
|
const { MouseBindings } = csToolsEnums;
|
|
const { ViewportType, BlendModes } = Enums;
|
|
|
|
const { createCameraPositionSynchronizer, createVOISynchronizer } =
|
|
synchronizers;
|
|
|
|
let renderingEngine;
|
|
const wadoRsRoot = 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb';
|
|
const StudyInstanceUID =
|
|
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463';
|
|
const renderingEngineId = 'myRenderingEngine';
|
|
const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use
|
|
const ctVolumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix
|
|
const ctVolumeId = `${volumeLoaderScheme}:${ctVolumeName}`; // VolumeId with loader id + volume id
|
|
const ptVolumeName = 'PT_VOLUME_ID';
|
|
const ptVolumeId = `${volumeLoaderScheme}:${ptVolumeName}`;
|
|
const ctToolGroupId = 'CT_TOOLGROUP_ID';
|
|
const ptToolGroupId = 'PT_TOOLGROUP_ID';
|
|
const fusionToolGroupId = 'FUSION_TOOLGROUP_ID';
|
|
const mipToolGroupUID = 'MIP_TOOLGROUP_ID';
|
|
let ctImageIds;
|
|
let ptImageIds;
|
|
let ctVolume;
|
|
let ptVolume;
|
|
const axialCameraSynchronizerId = 'AXIAL_CAMERA_SYNCHRONIZER_ID';
|
|
const sagittalCameraSynchronizerId = 'SAGITTAL_CAMERA_SYNCHRONIZER_ID';
|
|
const coronalCameraSynchronizerId = 'CORONAL_CAMERA_SYNCHRONIZER_ID';
|
|
const ctVoiSynchronizerId = 'CT_VOI_SYNCHRONIZER_ID';
|
|
const ptVoiSynchronizerId = 'PT_VOI_SYNCHRONIZER_ID';
|
|
let axialCameraPositionSynchronizer;
|
|
let sagittalCameraPositionSynchronizer;
|
|
let coronalCameraPositionSynchronizer;
|
|
let ctVoiSynchronizer;
|
|
let ptVoiSynchronizer;
|
|
let mipToolGroup;
|
|
const viewportIds = {
|
|
CT: { AXIAL: 'CT_AXIAL', SAGITTAL: 'CT_SAGITTAL', CORONAL: 'CT_CORONAL' },
|
|
PT: { AXIAL: 'PT_AXIAL', SAGITTAL: 'PT_SAGITTAL', CORONAL: 'PT_CORONAL' },
|
|
FUSION: {
|
|
AXIAL: 'FUSION_AXIAL',
|
|
SAGITTAL: 'FUSION_SAGITTAL',
|
|
CORONAL: 'FUSION_CORONAL',
|
|
},
|
|
PETMIP: {
|
|
CORONAL: 'PET_MIP_CORONAL',
|
|
},
|
|
};
|
|
|
|
// ======== Set up page ======== //
|
|
setTitleAndDescription(
|
|
'PET-CT',
|
|
'PT-CT fusion layout with Crosshairs, and synchronized cameras, CT W/L and PET threshold'
|
|
);
|
|
|
|
const optionsValues = [WindowLevelTool.toolName, CrosshairsTool.toolName];
|
|
|
|
// ============================= //
|
|
addDropdownToToolbar({
|
|
options: { values: optionsValues, defaultValue: WindowLevelTool.toolName },
|
|
onSelectedValueChange: (toolNameAsStringOrNumber) => {
|
|
const toolName = String(toolNameAsStringOrNumber);
|
|
|
|
[ctToolGroupId, ptToolGroupId, fusionToolGroupId].forEach((toolGroupId) => {
|
|
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
|
|
|
|
// Set the other tools disabled so we don't get conflicts.
|
|
// Note we only strictly need to change the one which is currently active.
|
|
|
|
if (toolName === WindowLevelTool.toolName) {
|
|
// Set crosshairs passive so they are still interactable
|
|
toolGroup.setToolPassive(CrosshairsTool.toolName);
|
|
toolGroup.setToolActive(WindowLevelTool.toolName, {
|
|
bindings: [{ mouseButton: MouseBindings.Primary }],
|
|
});
|
|
} else {
|
|
toolGroup.setToolDisabled(WindowLevelTool.toolName);
|
|
toolGroup.setToolActive(CrosshairsTool.toolName, {
|
|
bindings: [{ mouseButton: MouseBindings.Primary }],
|
|
});
|
|
}
|
|
});
|
|
},
|
|
});
|
|
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
console.log('Size changed');
|
|
|
|
renderingEngine = getRenderingEngine(renderingEngineId);
|
|
|
|
if (renderingEngine) {
|
|
renderingEngine.resize(true, false);
|
|
}
|
|
});
|
|
|
|
const viewportGrid = document.createElement('div');
|
|
|
|
viewportGrid.style.display = 'grid';
|
|
viewportGrid.style.gridTemplateRows = `[row1-start] 33% [row2-start] 33% [row3-start] 33% [end]`;
|
|
viewportGrid.style.gridTemplateColumns = `[col1-start] 20% [col2-start] 20% [col3-start] 20% [col4-start] 20% [col5-start] 20%[end]`;
|
|
viewportGrid.style.width = '95vw';
|
|
viewportGrid.style.height = '80vh';
|
|
|
|
const content = document.getElementById('content');
|
|
|
|
content.appendChild(viewportGrid);
|
|
|
|
const element1_1 = document.createElement('div');
|
|
const element1_2 = document.createElement('div');
|
|
const element1_3 = document.createElement('div');
|
|
const element2_1 = document.createElement('div');
|
|
const element2_2 = document.createElement('div');
|
|
const element2_3 = document.createElement('div');
|
|
const element3_1 = document.createElement('div');
|
|
const element3_2 = document.createElement('div');
|
|
const element3_3 = document.createElement('div');
|
|
const element_mip = document.createElement('div');
|
|
|
|
// Place main 3x3 viewports
|
|
element1_1.style.gridColumnStart = '1';
|
|
element1_1.style.gridRowStart = '1';
|
|
element1_2.style.gridColumnStart = '2';
|
|
element1_2.style.gridRowStart = '1';
|
|
element1_3.style.gridColumnStart = '3';
|
|
element1_3.style.gridRowStart = '1';
|
|
element2_1.style.gridColumnStart = '1';
|
|
element2_1.style.gridRowStart = '2';
|
|
element2_2.style.gridColumnStart = '2';
|
|
element2_2.style.gridRowStart = '2';
|
|
element2_3.style.gridColumnStart = '3';
|
|
element2_3.style.gridRowStart = '2';
|
|
element3_1.style.gridColumnStart = '1';
|
|
element3_1.style.gridRowStart = '3';
|
|
element3_2.style.gridColumnStart = '2';
|
|
element3_2.style.gridRowStart = '3';
|
|
element3_3.style.gridColumnStart = '3';
|
|
element3_3.style.gridRowStart = '3';
|
|
|
|
// Place MIP viewport
|
|
element_mip.style.gridColumnStart = '4';
|
|
element_mip.style.gridRowStart = '1';
|
|
element_mip.style.gridRowEnd = 'span 3';
|
|
|
|
viewportGrid.appendChild(element1_1);
|
|
viewportGrid.appendChild(element1_2);
|
|
viewportGrid.appendChild(element1_3);
|
|
viewportGrid.appendChild(element2_1);
|
|
viewportGrid.appendChild(element2_2);
|
|
viewportGrid.appendChild(element2_3);
|
|
viewportGrid.appendChild(element3_1);
|
|
viewportGrid.appendChild(element3_2);
|
|
viewportGrid.appendChild(element3_3);
|
|
viewportGrid.appendChild(element_mip);
|
|
|
|
const elements = [
|
|
element1_1,
|
|
element1_2,
|
|
element1_3,
|
|
element2_1,
|
|
element2_2,
|
|
element2_3,
|
|
element3_1,
|
|
element3_2,
|
|
element3_3,
|
|
];
|
|
|
|
elements.forEach((element) => {
|
|
element.style.width = '100%';
|
|
element.style.height = '100%';
|
|
|
|
// Disable right click context menu so we can have right click tools
|
|
element.oncontextmenu = (e) => e.preventDefault();
|
|
|
|
resizeObserver.observe(element);
|
|
});
|
|
|
|
element_mip.style.width = '100%';
|
|
element_mip.style.height = '100%';
|
|
element_mip.oncontextmenu = (e) => e.preventDefault();
|
|
resizeObserver.observe(element_mip);
|
|
|
|
const instructions = document.createElement('p');
|
|
|
|
instructions.innerText = `
|
|
Basic Controls:
|
|
- Left click: Use selected tool
|
|
- Middle click: Pan
|
|
- Right click: Zoom
|
|
- Mouse Wheel: Stack Scroll
|
|
|
|
Window Level Tool:
|
|
- Drag to set the window level for the CT and threshold for the PET.
|
|
|
|
Crosshairs:
|
|
- When the tool is active: Click/Drag anywhere in the viewport to move the center of the crosshairs.
|
|
- Drag a reference line to move it, scrolling the other views.
|
|
- Square (closest to center): Drag these to change the thickness of the MIP slab in that plane.
|
|
- Circle (further from center): Drag these to rotate the axes.
|
|
|
|
PET MIP:
|
|
- Mouse Wheel: Rotate PET
|
|
- Left click: Jump all views to the point of highest SUV in the region clicked.
|
|
|
|
Volume_3D Controls:
|
|
- Middle click : Rotate the image
|
|
- Mouse Wheel: Rotate PET
|
|
- Right click : Pan
|
|
- Left click: Jump all views to the point of highest SUV in the region clicked.
|
|
`;
|
|
|
|
instructions.style.gridColumnStart = '5';
|
|
instructions.style.gridRowStart = '1';
|
|
instructions.style.gridRowEnd = 'span 3';
|
|
|
|
viewportGrid.append(instructions);
|
|
|
|
// ============================= //
|
|
|
|
const viewportColors = {
|
|
[viewportIds.CT.AXIAL]: 'rgb(200, 0, 0)',
|
|
[viewportIds.CT.SAGITTAL]: 'rgb(200, 200, 0)',
|
|
[viewportIds.CT.CORONAL]: 'rgb(0, 200, 0)',
|
|
[viewportIds.PT.AXIAL]: 'rgb(200, 0, 0)',
|
|
[viewportIds.PT.SAGITTAL]: 'rgb(200, 200, 0)',
|
|
[viewportIds.PT.CORONAL]: 'rgb(0, 200, 0)',
|
|
[viewportIds.FUSION.AXIAL]: 'rgb(200, 0, 0)',
|
|
[viewportIds.FUSION.SAGITTAL]: 'rgb(200, 200, 0)',
|
|
[viewportIds.FUSION.CORONAL]: 'rgb(0, 200, 0)',
|
|
};
|
|
|
|
const viewportReferenceLineControllable = [
|
|
viewportIds.CT.AXIAL,
|
|
viewportIds.CT.SAGITTAL,
|
|
viewportIds.CT.CORONAL,
|
|
viewportIds.PT.AXIAL,
|
|
viewportIds.PT.SAGITTAL,
|
|
viewportIds.PT.CORONAL,
|
|
viewportIds.FUSION.AXIAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
];
|
|
|
|
const viewportReferenceLineDraggableRotatable = [
|
|
viewportIds.CT.AXIAL,
|
|
viewportIds.CT.SAGITTAL,
|
|
viewportIds.CT.CORONAL,
|
|
viewportIds.PT.AXIAL,
|
|
viewportIds.PT.SAGITTAL,
|
|
viewportIds.PT.CORONAL,
|
|
viewportIds.FUSION.AXIAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
];
|
|
|
|
const viewportReferenceLineSlabThicknessControlsOn = [
|
|
viewportIds.CT.AXIAL,
|
|
viewportIds.CT.SAGITTAL,
|
|
viewportIds.CT.CORONAL,
|
|
viewportIds.PT.AXIAL,
|
|
viewportIds.PT.SAGITTAL,
|
|
viewportIds.PT.CORONAL,
|
|
viewportIds.FUSION.AXIAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
];
|
|
|
|
function getReferenceLineColor(viewportId) {
|
|
return viewportColors[viewportId];
|
|
}
|
|
|
|
function getReferenceLineControllable(viewportId) {
|
|
const index = viewportReferenceLineControllable.indexOf(viewportId);
|
|
return index !== -1;
|
|
}
|
|
|
|
function getReferenceLineDraggableRotatable(viewportId) {
|
|
const index = viewportReferenceLineDraggableRotatable.indexOf(viewportId);
|
|
return index !== -1;
|
|
}
|
|
|
|
function getReferenceLineSlabThicknessControlsOn(viewportId) {
|
|
const index =
|
|
viewportReferenceLineSlabThicknessControlsOn.indexOf(viewportId);
|
|
return index !== -1;
|
|
}
|
|
|
|
function setUpToolGroups() {
|
|
// Add tools to Cornerstone3D
|
|
cornerstoneTools.addTool(WindowLevelTool);
|
|
cornerstoneTools.addTool(PanTool);
|
|
cornerstoneTools.addTool(ZoomTool);
|
|
cornerstoneTools.addTool(StackScrollMouseWheelTool);
|
|
cornerstoneTools.addTool(MIPJumpToClickTool);
|
|
cornerstoneTools.addTool(VolumeRotateMouseWheelTool);
|
|
cornerstoneTools.addTool(CrosshairsTool);
|
|
cornerstoneTools.addTool(TrackballRotateTool);
|
|
|
|
// Define tool groups for the main 9 viewports.
|
|
// Crosshairs currently only supports 3 viewports for a toolgroup due to the
|
|
// way it is constructed, but its configuration input allows us to synchronize
|
|
// multiple sets of 3 viewports.
|
|
const ctToolGroup = ToolGroupManager.createToolGroup(ctToolGroupId);
|
|
const ptToolGroup = ToolGroupManager.createToolGroup(ptToolGroupId);
|
|
const fusionToolGroup = ToolGroupManager.createToolGroup(fusionToolGroupId);
|
|
|
|
ctToolGroup.addViewport(viewportIds.CT.AXIAL, renderingEngineId);
|
|
ctToolGroup.addViewport(viewportIds.CT.SAGITTAL, renderingEngineId);
|
|
ctToolGroup.addViewport(viewportIds.CT.CORONAL, renderingEngineId);
|
|
ptToolGroup.addViewport(viewportIds.PT.AXIAL, renderingEngineId);
|
|
ptToolGroup.addViewport(viewportIds.PT.SAGITTAL, renderingEngineId);
|
|
ptToolGroup.addViewport(viewportIds.PT.CORONAL, renderingEngineId);
|
|
fusionToolGroup.addViewport(viewportIds.FUSION.AXIAL, renderingEngineId);
|
|
fusionToolGroup.addViewport(viewportIds.FUSION.SAGITTAL, renderingEngineId);
|
|
fusionToolGroup.addViewport(viewportIds.FUSION.CORONAL, renderingEngineId);
|
|
|
|
// Manipulation Tools
|
|
[ctToolGroup, ptToolGroup].forEach((toolGroup) => {
|
|
toolGroup.addTool(PanTool.toolName);
|
|
toolGroup.addTool(ZoomTool.toolName);
|
|
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
|
|
toolGroup.addTool(CrosshairsTool.toolName, {
|
|
getReferenceLineColor,
|
|
getReferenceLineControllable,
|
|
getReferenceLineDraggableRotatable,
|
|
getReferenceLineSlabThicknessControlsOn,
|
|
});
|
|
});
|
|
|
|
fusionToolGroup.addTool(PanTool.toolName);
|
|
fusionToolGroup.addTool(ZoomTool.toolName);
|
|
fusionToolGroup.addTool(StackScrollMouseWheelTool.toolName);
|
|
fusionToolGroup.addTool(CrosshairsTool.toolName, {
|
|
getReferenceLineColor,
|
|
getReferenceLineControllable,
|
|
getReferenceLineDraggableRotatable,
|
|
getReferenceLineSlabThicknessControlsOn,
|
|
// Only set CT volume to MIP in the fusion viewport
|
|
filterActorUIDsToSetSlabThickness: [ctVolumeId],
|
|
});
|
|
|
|
// Here is the difference in the toolGroups used, that we need to specify the
|
|
// volume to use for the WindowLevelTool for the fusion viewports
|
|
ctToolGroup.addTool(WindowLevelTool.toolName);
|
|
ptToolGroup.addTool(WindowLevelTool.toolName);
|
|
fusionToolGroup.addTool(WindowLevelTool.toolName, {
|
|
volumeId: ptVolumeId,
|
|
});
|
|
|
|
[ctToolGroup, ptToolGroup, fusionToolGroup].forEach((toolGroup) => {
|
|
toolGroup.setToolActive(WindowLevelTool.toolName, {
|
|
bindings: [
|
|
{
|
|
mouseButton: MouseBindings.Primary, // Left Click
|
|
},
|
|
],
|
|
});
|
|
toolGroup.setToolActive(PanTool.toolName, {
|
|
bindings: [
|
|
{
|
|
mouseButton: MouseBindings.Auxiliary, // Middle Click
|
|
},
|
|
],
|
|
});
|
|
toolGroup.setToolActive(ZoomTool.toolName, {
|
|
bindings: [
|
|
{
|
|
mouseButton: MouseBindings.Secondary, // Right Click
|
|
},
|
|
],
|
|
});
|
|
|
|
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
|
|
toolGroup.setToolPassive(CrosshairsTool.toolName);
|
|
});
|
|
|
|
// MIP Tool Groups
|
|
mipToolGroup = ToolGroupManager.createToolGroup(mipToolGroupUID);
|
|
mipToolGroup.addTool('VolumeRotateMouseWheel');
|
|
mipToolGroup.addTool('MIPJumpToClickTool', {
|
|
toolGroupId: ptToolGroupId,
|
|
});
|
|
|
|
// Set the initial state of the tools, here we set one tool active on left click.
|
|
// This means left click will draw that tool.
|
|
mipToolGroup.setToolActive('MIPJumpToClickTool', {
|
|
bindings: [
|
|
{
|
|
mouseButton: MouseBindings.Primary, // Left Click
|
|
},
|
|
],
|
|
});
|
|
// As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
|
|
// hook instead of mouse buttons, it does not need to assign any mouse button.
|
|
mipToolGroup.setToolActive('VolumeRotateMouseWheel');
|
|
|
|
mipToolGroup.addViewport(viewportIds.PETMIP.CORONAL, renderingEngineId);
|
|
console.debug(mipToolGroup);
|
|
}
|
|
|
|
function setUpSynchronizers() {
|
|
axialCameraPositionSynchronizer = createCameraPositionSynchronizer(
|
|
axialCameraSynchronizerId
|
|
);
|
|
sagittalCameraPositionSynchronizer = createCameraPositionSynchronizer(
|
|
sagittalCameraSynchronizerId
|
|
);
|
|
coronalCameraPositionSynchronizer = createCameraPositionSynchronizer(
|
|
coronalCameraSynchronizerId
|
|
);
|
|
ctVoiSynchronizer = createVOISynchronizer(ctVoiSynchronizerId);
|
|
ptVoiSynchronizer = createVOISynchronizer(ptVoiSynchronizerId);
|
|
// Add viewports to camera synchronizers
|
|
[
|
|
viewportIds.CT.AXIAL,
|
|
viewportIds.PT.AXIAL,
|
|
viewportIds.FUSION.AXIAL,
|
|
].forEach((viewportId) => {
|
|
axialCameraPositionSynchronizer.add({
|
|
renderingEngineId,
|
|
viewportId,
|
|
});
|
|
});
|
|
[
|
|
viewportIds.CT.SAGITTAL,
|
|
viewportIds.PT.SAGITTAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
].forEach((viewportId) => {
|
|
sagittalCameraPositionSynchronizer.add({
|
|
renderingEngineId,
|
|
viewportId,
|
|
});
|
|
});
|
|
[
|
|
viewportIds.CT.CORONAL,
|
|
viewportIds.PT.CORONAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
].forEach((viewportId) => {
|
|
coronalCameraPositionSynchronizer.add({
|
|
renderingEngineId,
|
|
viewportId,
|
|
});
|
|
});
|
|
|
|
// Add viewports to VOI synchronizers
|
|
[
|
|
viewportIds.CT.AXIAL,
|
|
viewportIds.CT.SAGITTAL,
|
|
viewportIds.CT.CORONAL,
|
|
].forEach((viewportId) => {
|
|
ctVoiSynchronizer.add({
|
|
renderingEngineId,
|
|
viewportId,
|
|
});
|
|
});
|
|
[
|
|
viewportIds.FUSION.AXIAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
].forEach((viewportId) => {
|
|
// In this example, the fusion viewports are only targets for CT VOI
|
|
// synchronization, not sources
|
|
ctVoiSynchronizer.addTarget({
|
|
renderingEngineId,
|
|
viewportId,
|
|
});
|
|
});
|
|
[
|
|
viewportIds.PT.AXIAL,
|
|
viewportIds.PT.SAGITTAL,
|
|
viewportIds.PT.CORONAL,
|
|
viewportIds.FUSION.AXIAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
viewportIds.PETMIP.CORONAL,
|
|
].forEach((viewportId) => {
|
|
ptVoiSynchronizer.add({
|
|
renderingEngineId,
|
|
viewportId,
|
|
});
|
|
});
|
|
}
|
|
async function getPtImageIds() {
|
|
return await createImageIdsAndCacheMetaData({
|
|
StudyInstanceUID,
|
|
SeriesInstanceUID:
|
|
'1.3.6.1.4.1.14519.5.2.1.7009.2403.879445243400782656317561081015',
|
|
wadoRsRoot,
|
|
});
|
|
}
|
|
async function getCtImageIds() {
|
|
return await createImageIdsAndCacheMetaData({
|
|
StudyInstanceUID,
|
|
SeriesInstanceUID:
|
|
'1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561',
|
|
wadoRsRoot,
|
|
});
|
|
}
|
|
|
|
async function setUpDisplay() {
|
|
// Create the viewports
|
|
|
|
const viewportInputArray = [
|
|
{
|
|
viewportId: viewportIds.CT.AXIAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element1_1,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.AXIAL,
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.CT.SAGITTAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element1_2,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.SAGITTAL,
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.CT.CORONAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element1_3,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.CORONAL,
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.PT.AXIAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element2_1,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.AXIAL,
|
|
background: <Types.Point3>[1, 1, 1],
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.PT.SAGITTAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element2_2,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.SAGITTAL,
|
|
background: <Types.Point3>[1, 1, 1],
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.PT.CORONAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element2_3,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.CORONAL,
|
|
background: <Types.Point3>[1, 1, 1],
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.FUSION.AXIAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element3_1,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.AXIAL,
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.FUSION.SAGITTAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element3_2,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.SAGITTAL,
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.FUSION.CORONAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element3_3,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.CORONAL,
|
|
},
|
|
},
|
|
{
|
|
viewportId: viewportIds.PETMIP.CORONAL,
|
|
type: ViewportType.ORTHOGRAPHIC,
|
|
element: element_mip,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.CORONAL,
|
|
background: <Types.Point3>[1, 1, 1],
|
|
},
|
|
},
|
|
];
|
|
|
|
renderingEngine.setViewports(viewportInputArray);
|
|
|
|
// Set the volumes to load
|
|
ptVolume.load();
|
|
ctVolume.load();
|
|
|
|
// Set volumes on the viewports
|
|
await setVolumesForViewports(
|
|
renderingEngine,
|
|
[
|
|
{
|
|
volumeId: ctVolumeId,
|
|
callback: setCtTransferFunctionForVolumeActor,
|
|
},
|
|
],
|
|
[viewportIds.CT.AXIAL, viewportIds.CT.SAGITTAL, viewportIds.CT.CORONAL]
|
|
);
|
|
|
|
await setVolumesForViewports(
|
|
renderingEngine,
|
|
[
|
|
{
|
|
volumeId: ptVolumeId,
|
|
callback: setPetTransferFunctionForVolumeActor,
|
|
},
|
|
],
|
|
[viewportIds.PT.AXIAL, viewportIds.PT.SAGITTAL, viewportIds.PT.CORONAL]
|
|
);
|
|
|
|
await setVolumesForViewports(
|
|
renderingEngine,
|
|
[
|
|
{
|
|
volumeId: ctVolumeId,
|
|
callback: setCtTransferFunctionForVolumeActor,
|
|
},
|
|
{
|
|
volumeId: ptVolumeId,
|
|
callback: setPetColorMapTransferFunctionForVolumeActor,
|
|
},
|
|
],
|
|
[
|
|
viewportIds.FUSION.AXIAL,
|
|
viewportIds.FUSION.SAGITTAL,
|
|
viewportIds.FUSION.CORONAL,
|
|
]
|
|
);
|
|
|
|
// Calculate size of fullBody pet mip
|
|
const ptVolumeDimensions = ptVolume.dimensions;
|
|
|
|
// Only make the MIP as large as it needs to be.
|
|
const slabThickness = Math.sqrt(
|
|
ptVolumeDimensions[0] * ptVolumeDimensions[0] +
|
|
ptVolumeDimensions[1] * ptVolumeDimensions[1] +
|
|
ptVolumeDimensions[2] * ptVolumeDimensions[2]
|
|
);
|
|
|
|
setVolumesForViewports(
|
|
renderingEngine,
|
|
[
|
|
{
|
|
volumeId: ptVolumeId,
|
|
callback: setPetTransferFunctionForVolumeActor,
|
|
blendMode: BlendModes.MAXIMUM_INTENSITY_BLEND,
|
|
slabThickness,
|
|
},
|
|
],
|
|
[viewportIds.PETMIP.CORONAL]
|
|
);
|
|
|
|
initializeCameraSync(renderingEngine);
|
|
|
|
// Render the viewports
|
|
renderingEngine.render();
|
|
}
|
|
|
|
addButtonToToolbar({
|
|
title: 'toggle volume3d',
|
|
onClick: async () => {
|
|
const viewportInput = {
|
|
viewportId: viewportIds.PETMIP.CORONAL,
|
|
type: ViewportType.VOLUME_3D,
|
|
element: element_mip,
|
|
defaultOptions: {
|
|
orientation: Enums.OrientationAxis.CORONAL,
|
|
background: <Types.Point3>[1, 1, 1],
|
|
},
|
|
};
|
|
const volume3dToolGroup = mipToolGroup;
|
|
toggleVolume(viewportInput);
|
|
setupVolume3dSynchronizer(viewportInput);
|
|
setUpVolume3dToolGroup(volume3dToolGroup);
|
|
|
|
renderingEngine.render();
|
|
},
|
|
});
|
|
|
|
function toggleVolume(viewportInput) {
|
|
renderingEngine.enableElement(viewportInput);
|
|
|
|
const viewport = <Types.IVolumeViewport>(
|
|
renderingEngine.getViewport(viewportIds.PETMIP.CORONAL)
|
|
);
|
|
|
|
const ptVolumeDimensions = ptVolume.dimensions;
|
|
|
|
// Only make the MIP as large as it needs to be.
|
|
const slabThickness = Math.sqrt(
|
|
ptVolumeDimensions[0] * ptVolumeDimensions[0] +
|
|
ptVolumeDimensions[1] * ptVolumeDimensions[1] +
|
|
ptVolumeDimensions[2] * ptVolumeDimensions[2]
|
|
);
|
|
|
|
viewport.setVolumes([
|
|
{
|
|
volumeId: ptVolumeId,
|
|
callback: setPetTransferFunctionForVolumeActor,
|
|
slabThickness: slabThickness,
|
|
blendMode: BlendModes.MAXIMUM_INTENSITY_BLEND,
|
|
},
|
|
]);
|
|
}
|
|
function setupVolume3dSynchronizer(viewportInput) {
|
|
ptVoiSynchronizer.addTarget({
|
|
renderingEngineId,
|
|
viewportId: viewportInput.viewportId,
|
|
});
|
|
}
|
|
function setUpVolume3dToolGroup(toolGroup) {
|
|
toolGroup.addTool(PanTool.toolName);
|
|
toolGroup.addTool(TrackballRotateTool.toolName);
|
|
|
|
toolGroup.setToolActive(PanTool.toolName, {
|
|
bindings: [
|
|
{
|
|
mouseButton: MouseBindings.Secondary,
|
|
},
|
|
],
|
|
});
|
|
toolGroup.setToolActive(TrackballRotateTool.toolName, {
|
|
bindings: [
|
|
{
|
|
mouseButton: MouseBindings.Auxiliary,
|
|
},
|
|
],
|
|
});
|
|
toolGroup.addViewport(viewportIds.PETMIP.CORONAL, renderingEngineId);
|
|
}
|
|
function initializeCameraSync(renderingEngine) {
|
|
// The fusion scene is the target as it is scaled to both volumes.
|
|
// TODO -> We should have a more generic way to do this,
|
|
// So that when all data is added we can synchronize zoom/position before interaction.
|
|
|
|
const axialCtViewport = renderingEngine.getViewport(viewportIds.CT.AXIAL);
|
|
const sagittalCtViewport = renderingEngine.getViewport(
|
|
viewportIds.CT.SAGITTAL
|
|
);
|
|
const coronalCtViewport = renderingEngine.getViewport(viewportIds.CT.CORONAL);
|
|
|
|
const axialPtViewport = renderingEngine.getViewport(viewportIds.PT.AXIAL);
|
|
const sagittalPtViewport = renderingEngine.getViewport(
|
|
viewportIds.PT.SAGITTAL
|
|
);
|
|
const coronalPtViewport = renderingEngine.getViewport(viewportIds.PT.CORONAL);
|
|
|
|
const axialFusionViewport = renderingEngine.getViewport(
|
|
viewportIds.FUSION.AXIAL
|
|
);
|
|
const sagittalFusionViewport = renderingEngine.getViewport(
|
|
viewportIds.FUSION.SAGITTAL
|
|
);
|
|
const coronalFusionViewport = renderingEngine.getViewport(
|
|
viewportIds.FUSION.CORONAL
|
|
);
|
|
|
|
initCameraSynchronization(axialFusionViewport, axialCtViewport);
|
|
initCameraSynchronization(axialFusionViewport, axialPtViewport);
|
|
|
|
initCameraSynchronization(sagittalFusionViewport, sagittalCtViewport);
|
|
initCameraSynchronization(sagittalFusionViewport, sagittalPtViewport);
|
|
|
|
initCameraSynchronization(coronalFusionViewport, coronalCtViewport);
|
|
initCameraSynchronization(coronalFusionViewport, coronalPtViewport);
|
|
|
|
renderingEngine.render();
|
|
}
|
|
|
|
function initCameraSynchronization(sViewport, tViewport) {
|
|
// Initialise the sync as they viewports will have
|
|
// Different initial zoom levels for viewports of different sizes.
|
|
|
|
const camera = sViewport.getCamera();
|
|
|
|
tViewport.setCamera(camera);
|
|
}
|
|
|
|
/**
|
|
* Runs the demo
|
|
*/
|
|
async function run() {
|
|
// Init Cornerstone and related libraries
|
|
await initDemo();
|
|
|
|
// Instantiate a rendering engine
|
|
renderingEngine = new RenderingEngine(renderingEngineId);
|
|
// Get Cornerstone imageIds and fetch metadata into RAM
|
|
ctImageIds = await getCtImageIds();
|
|
|
|
ptImageIds = await getPtImageIds();
|
|
|
|
// Define a volume in memory
|
|
ctVolume = await volumeLoader.createAndCacheVolume(ctVolumeId, {
|
|
imageIds: ctImageIds,
|
|
});
|
|
// Define a volume in memory
|
|
ptVolume = await volumeLoader.createAndCacheVolume(ptVolumeId, {
|
|
imageIds: ptImageIds,
|
|
});
|
|
|
|
// Display needs to be set up first so that we have viewport to reference for tools and synchronizers.
|
|
await setUpDisplay();
|
|
|
|
// Tools and synchronizers can be set up in any order.
|
|
setUpToolGroups();
|
|
setUpSynchronizers();
|
|
}
|
|
|
|
run();
|