cornerstone3D icon indicating copy to clipboard operation
cornerstone3D copied to clipboard

[Bug] Volume viewport synchronization - Couldn't handle volume viewport synchronization for different viewports (Sagittal, axial and coronal))

Open krishnauppili opened this issue 1 year ago • 1 comments

Describe the Bug

Need to synchronize different volume viewports having different orientation (Sagittal/Axial/Coronal). But now when I click Camera A and B and scroll any one, the other view is also getting changed to the first viewport present.

Steps to Reproduce

import { RenderingEngine, Types, Enums, volumeLoader, setVolumesForViewports, } from '@cornerstonejs/core'; import { initDemo, createImageIdsAndCacheMetaData, setTitleAndDescription, addToggleButtonToToolbar, } from '../../../../utils/demo/helpers'; import * as cornerstoneTools from '@cornerstonejs/tools';

// This is for debugging purposes console.warn( 'Click on index.ts to open source code for this example --------->' );

const { PanTool, WindowLevelTool, ZoomTool, ToolGroupManager, StackScrollMouseWheelTool, Enums: csToolsEnums, synchronizers, SynchronizerManager, } = cornerstoneTools;

const { ViewportType } = Enums; const { MouseBindings } = csToolsEnums;

const { createCameraPositionSynchronizer, createVOISynchronizer } = synchronizers;

// Define a unique id for the volume const volumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefix const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use const volumeId = ${volumeLoaderScheme}:${volumeName}; // VolumeId with loader id + volume id

const cameraSynchronizerId = 'CAMERA_SYNCHRONIZER_ID'; const voiSynchronizerId = 'VOI_SYNCHRONIZER_ID';

const renderingEngineId = 'myRenderingEngine'; const viewportIds = [ 'CT_SAGITTAL_STACK_1', 'CT_AXIAL_STACK_2', 'CT_CORONAL_STACK_3', ];

// ======== Set up page ======== // setTitleAndDescription( 'Synchronization of Volume Viewport Properties', 'Here we demonstrate camera and window/level synchronization across viewports.' );

const size = '500px'; const content = document.getElementById('content'); const viewportGrid = document.createElement('div');

viewportGrid.style.display = 'flex'; viewportGrid.style.display = 'flex'; viewportGrid.style.flexDirection = 'row';

const element1 = document.createElement('div'); const element2 = document.createElement('div'); const element3 = document.createElement('div'); element1.style.width = size; element1.style.height = size; element2.style.width = size; element2.style.height = size; element3.style.width = size; element3.style.height = size;

// Disable right click context menu so we can have right click tools element1.oncontextmenu = (e) => e.preventDefault(); // Disable right click context menu so we can have right click tools element2.oncontextmenu = (e) => e.preventDefault(); // Disable right click context menu so we can have right click tools element3.oncontextmenu = (e) => e.preventDefault();

viewportGrid.appendChild(element1); viewportGrid.appendChild(element2); viewportGrid.appendChild(element3);

content.appendChild(viewportGrid);

const instructions = document.createElement('p'); instructions.innerText = ` Left Click to change window/level Use the mouse wheel to scroll through the stack.

Toggle the controls to add viewports to the synchronization groups. `;

content.append(instructions); // ============================= //

const SynchronizerButtonInfo = [ { viewportLabel: 'A', viewportId: viewportIds[0] }, { viewportLabel: 'B', viewportId: viewportIds[1] }, { viewportLabel: 'C', viewportId: viewportIds[2] }, ];

SynchronizerButtonInfo.forEach(({ viewportLabel, viewportId }) => { addToggleButtonToToolbar({ title: Camera ${viewportLabel}, onClick: (toggle) => { const synchronizer = SynchronizerManager.getSynchronizer(cameraSynchronizerId);

  if (!synchronizer) {
    return;
  }

  if (toggle) {
    synchronizer.add({ renderingEngineId, viewportId });
  } else {
    synchronizer.remove({ renderingEngineId, viewportId });
  }
},

}); });

SynchronizerButtonInfo.forEach(({ viewportLabel, viewportId }) => { addToggleButtonToToolbar({ title: VOI ${viewportLabel}, onClick: (toggle) => { const synchronizer = SynchronizerManager.getSynchronizer(voiSynchronizerId);

  if (!synchronizer) {
    return;
  }

  if (toggle) {
    synchronizer.add({ renderingEngineId, viewportId });
  } else {
    synchronizer.remove({ renderingEngineId, viewportId });
  }
},

}); });

/**

  • Runs the demo */ async function run() { // Init Cornerstone and related libraries await initDemo();

const toolGroupId = 'TOOL_GROUP_ID';

// Add tools to Cornerstone3D cornerstoneTools.addTool(PanTool); cornerstoneTools.addTool(WindowLevelTool); cornerstoneTools.addTool(StackScrollMouseWheelTool); cornerstoneTools.addTool(ZoomTool);

// Define a tool group, which defines how mouse events map to tool commands for // Any viewport using the group const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);

// Add tools to the tool group toolGroup.addTool(WindowLevelTool.toolName, { volumeId }); toolGroup.addTool(PanTool.toolName); toolGroup.addTool(ZoomTool.toolName); toolGroup.addTool(StackScrollMouseWheelTool.toolName);

// Set the initial state of the tools, here all tools are active and bound to // Different mouse inputs 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 }, ], }); // 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. toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);

// Create synchronizers createCameraPositionSynchronizer(cameraSynchronizerId); createVOISynchronizer(voiSynchronizerId);

// Get Cornerstone imageIds and fetch metadata into RAM const imageIds = await createImageIdsAndCacheMetaData({ StudyInstanceUID: '1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463', SeriesInstanceUID: '1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561', wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb', });

// Instantiate a rendering engine const renderingEngine = new RenderingEngine(renderingEngineId);

// Create the viewports const viewportInputArray = [ { viewportId: viewportIds[0], type: ViewportType.ORTHOGRAPHIC, element: element1, defaultOptions: { orientation: Enums.OrientationAxis.SAGITTAL, background: <Types.Point3>[0.2, 0, 0.2], }, }, { viewportId: viewportIds[1], type: ViewportType.ORTHOGRAPHIC, element: element2, defaultOptions: { orientation: Enums.OrientationAxis.AXIAL, background: <Types.Point3>[0.2, 0, 0.2], }, }, { viewportId: viewportIds[2], type: ViewportType.ORTHOGRAPHIC, element: element3, defaultOptions: { orientation: Enums.OrientationAxis.CORONAL, background: <Types.Point3>[0.2, 0, 0.2], }, }, ];

renderingEngine.setViewports(viewportInputArray);

// Set the tool group on the viewports viewportIds.forEach((viewportId) => toolGroup.addViewport(viewportId, renderingEngineId) );

// Define a volume in memory const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds, });

// Set the volume to load volume.load();

setVolumesForViewports(renderingEngine, [{ volumeId }], viewportIds);

// Render the image renderingEngine.renderViewports(viewportIds); }

run();

The current behavior

When Camera A and Camera B are clicked and Viewport 1 is scrolled, Viewport 2 is also changing into Sagittal view.

The expected behavior

Need to retain the orientation such that when camera A and B are clicked and Viewport 1 is scrolled, Viewport 2 should be retained in Axial view but it should be synchronous with Viewport 1.

OS

MacOS

Node version

20

Browser

Chrome

Initial Render Screenshot 2024-07-03 at 10 30 03 AM

After selecting Camera A and B and scrolling Viewport A Screenshot 2024-07-03 at 10 30 51 AM

Please assist!

krishnauppili avatar Jul 02 '24 18:07 krishnauppili

If the orientations are different you need to use the view synchronization see this example https://www.cornerstonejs.org/live-examples/viewreferencepresentation

sedghi avatar Jul 05 '24 17:07 sedghi

Hey @sedghi, thanks for your response! In this example also, when I scroll any view in the top line the bottom line views also gets converted to the same orientation. What I need is when I scroll any view in Sagittal orientation, the other views should be in Axial and Coronal view but must be able to scroll automatically in sync with Sagittal view images I'm scrolling! Is there any default option in cornerstone to do it or do we have to write custom logic for the synchronizer?

krishnauppili avatar Jul 08 '24 06:07 krishnauppili

Unfortunately, I'm not able to understand what you are saying. Would it be possible for you to draw something or record a video and narrate it?

sedghi avatar Jul 12 '24 18:07 sedghi

Dear @sedghi , I ran into the same issue when using camerapoisiton type to try to synchronize the MPR Viewport which ohif deals with as a volume, is this scenario available to implement using current cornerstone tools and ohif services.

Scenario: user opens a ct series and switches to MPR viewport user activates ImageSliceSync user scrolls in any MPR view, lets say Axial other views coronal and sagittal gets updated to the same slice the user scrolled to in the axial viewport, while keeping their orientation (the behaviour here: https://www.cornerstonejs.org/live-examples/viewreferencepresentation is that all viewports orientation change to match the orientation of the active viewport)

omarshata0 avatar Aug 12 '25 13:08 omarshata0