cornerstonejs / cornerstone3D

Cornerstone is a set of JavaScript libraries that can be used to build web-based medical imaging applications. It provides a framework to build radiology applications such as the OHIF Viewer.
https://cornerstonejs.org
MIT License
494 stars 256 forks source link

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

Open krishnauppili opened 2 weeks ago

krishnauppili commented 2 weeks ago

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

}); });

/**

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!

sedghi commented 2 weeks ago

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

krishnauppili commented 1 week ago

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?

sedghi commented 1 week ago

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?