ReferenceLine and Update Volume

How to support ReferenceLine to two different volumes on one page, and make updated volume’s image visible?

  • I want to have reference lines on two volumes on one page. I have followed the samples here cornerstone3D/packages/tools/examples/volumeSlabScroll/index.ts, I can easily have one volume showing reference lines without any problem. But when I add another volume on the same page and use the same way to show the reference line, it does not show the lines. Tons of time have been spent, but still no luck.

  • I am NOT using OHIF yet. Just using Cornerstone 3D, latest version.

What steps can we follow to reproduce the bug?

  1. I am using one function called prepareToolGroupAndRenderingEngine() to prepare each volume’s ToolGroup and RenderingEngine. This function is basically copying the content from the volumeSlabScroll example, as shown below:
function prepareToolGroupAndRenderingEngine(toolGroupId:string, volumeId:string, viewportIds:string[], targetViewportId:string, renderingEngineId:string, contentElement:string){
    const size = '300px';
    const content = document.getElementById(contentElement);
    const viewportGrid = document.createElement('div');
    
    viewportGrid.style.display = 'flex';
    viewportGrid.style.flexDirection = 'row';
    
    const element1 = document.createElement('div');
    const element2 = document.createElement('div');
    const element3 = document.createElement('div');
    element1.oncontextmenu = () => false;
    element2.oncontextmenu = () => false;
    element3.oncontextmenu = () => false;
    
    element1.style.width = size;
    element1.style.height = size;
    element2.style.width = size;
    element2.style.height = size;
    element3.style.width = size;
    element3.style.height = size;
    
    viewportGrid.appendChild(element1);
    viewportGrid.appendChild(element2);
    viewportGrid.appendChild(element3);
    
    content.appendChild(viewportGrid);
    
    const instructions = document.createElement('p');
    instructions.innerText =
      'Choose the level of thickness you want to view the volume in 3D ' + contentElement;
    
    content.append(instructions);
    
     // Define a tool group, which defines how mouse events map to tool commands for
    // Any viewport using the group
    let toolGroup;
    toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
  
    // Add the tools to the tool group and specify which volume they are pointing at
    toolGroup.addTool(WindowLevelTool.toolName, { volumeId });
    toolGroup.addTool(ReferenceLines.toolName, { volumeId });
    
    toolGroup.addTool(PanTool.toolName, { volumeId });
    toolGroup.addTool(ZoomTool.toolName, { volumeId });
    toolGroup.addTool(StackScrollMouseWheelTool.toolName);

    // Set the initial state of the tools, here we set one tool active on left click.
    // This means left click will draw that tool.
    toolGroup.setToolActive(WindowLevelTool.toolName, {
      bindings: [
        {
          mouseButton: MouseBindings.Primary, // Left Click
        },
      ],
    });
    toolGroup.setToolActive(PanTool.toolName, {
      bindings: [
        {
          mouseButton: MouseBindings.Auxiliary, // Left Click
        },
      ],
    });
  
    toolGroup.setToolActive(ZoomTool.toolName, {
      bindings: [
        {
          mouseButton: MouseBindings.Secondary, // Right Click
        },
      ],
    });

    toolGroup.setToolEnabled(ReferenceLines.toolName);
    toolGroup.setToolConfiguration(ReferenceLines.toolName, {
      sourceViewportId: targetViewportId,
    });

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

    // Instantiate a rendering engine
    let renderingEngine = new RenderingEngine(renderingEngineId);
  
    // Create the viewports
    const viewportInputArray = [
      {
        viewportId: viewportIds[0],
        type: ViewportType.ORTHOGRAPHIC,
        element: element1,
        defaultOptions: {
          orientation: Enums.OrientationAxis.AXIAL,
          background: <Types.Point3>[0.2, 0, 0.2],
        },
      },
      {
        viewportId: viewportIds[1],
        type: ViewportType.ORTHOGRAPHIC,
        element: element2,
        defaultOptions: {
          orientation: Enums.OrientationAxis.SAGITTAL,
          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)
    );

    return [toolGroup, renderingEngine];
  }
  1. In the run function, I call the above function two times, and define two volumes:
async function run() {
    // Init Cornerstone and related libraries
    await initDemo();

    // Viewports for T1MAP volume:
    const toolGroupId_T1MAP = 'TOOL_GROUP_ID_T1MAP';  
    const renderingEngineId_T1MAP = 'myRenderingEngine_T1MAP';
    let res = prepareToolGroup(toolGroupId_T1MAP, volumeId_T1MAP, viewportIds_T1MAP, activeViewportId_T1MAP, renderingEngineId_T1MAP, "content_T1MAP");
    toolGroup_T1MAP = res[0]
    renderingEngine_T1MAP = res[1];

    // Viewports for IR volume:
    const toolGroupId_IR = 'TOOL_GROUP_ID_IR';  
    const renderingEngineId_IR = 'myRenderingEngine_IR';
    res = prepareToolGroup(toolGroupId_IR, volumeId_IR, viewportIds_IR, activeViewportId_IR, renderingEngineId_IR, "content_IR");
    toolGroup_IR = res[0]
    renderingEngine_IR = res[1];

   ...

    // Define two volumes in memory
    const volume_T1MAP = await volumeLoader.createAndCacheVolume(volumeId_T1MAP, {imageIds,});
    const volume_IR = await volumeLoader.createAndCacheDerivedVolume(volumeId_T1MAP, { volumeId: volumeId_IR });

}

I also fill-in both volumes’ scalarData buffer with random data:

    const fillInVolume = (volume:Record<string, any>)=>{
      for(let i=480*512*2; i < 480*512*176; i++){
        volume.scalarData[i] = Math.floor(Math.random() * 1500) + 1
      }
    } 

and set both volumes’ viewport as below:

   // Set T1Map volume's viewports
    setVolumesForViewports(renderingEngine_T1MAP, [{ volumeId: volumeId_T1MAP }], viewportIds_T1MAP);
    renderingEngine_T1MAP.renderViewports(viewportIds_T1MAP);
    setActiveViewPort_T1MAP("SAGITTAL_T1MAP");
    toolGroup_T1MAP.setToolConfiguration(StackScrollMouseWheelTool.toolName, {scrollSlabs: true});
    
    // Set IR volume's viewports
    setVolumesForViewports(renderingEngine_IR, [{ volumeId: volumeId_IR }], viewportIds_IR);
    renderingEngine_IR.renderViewports(viewportIds_IR);
    setActiveViewPort_IR("SAGITTAL_IR");
    toolGroup_IR.setToolConfiguration(StackScrollMouseWheelTool.toolName, {scrollSlabs: true});
  1. However I change the code, the reference lines ONLY appear on the first volume’s viewport. Here, by “first” I mean the first time calling the function prepareToolGroupAndRenderingEngine().

Please advise to solve my two questions:

  1. How can I have reference lines on two volumes, each with its own viewport, toolGroup and rendering Engine?

  2. I want to load the volume images for the first volume, and after the load finishes, I fill-in the second volume’s image data (scalarData?). I have written a callback function for the first volume, which will be called once the volume images are all loaded. That works.

Inside the callback function, I have tried to fill-in the 2nd volume’s scalar data. But even though the 2nd’ volume’s scalar data has been filled, the displayed image does not change at all. I don’t know how to update the viewport to make the volume images visible.

Thank you very much!