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();
 |