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
520 stars 270 forks source link

[Feature Request] add a method to volume viewport like the method setImageIdIndex to stack viewport. #1307

Open MrWerton opened 2 months ago

MrWerton commented 2 months ago

What feature or change would you like to see made?

Hello,

I'm currently facing a challenge in my project and I would appreciate your assistance. I need to find a way to programmatically change the image in a volume viewport. So far, I've been able to do this with stackviewport by calling the setImageIdIndex method. However, I've noticed that this approach doesn't work for volume, as there is no equivalent method available.

Do you happen to know of another way to achieve this goal? Any guidance or suggestions would be greatly appreciated.

Thank you in advance for your help!

Why should we prioritize this feature?

Prioritizing the requested feature to programmatically change images in the volume viewport is crucial for enhancing user control and flexibility. This feature interacts with existing functionality by extending image manipulation capabilities to volume rendering. Its implementation ensures consistency across viewports and improves user experience by empowering efficient data management and visualization. Prioritizing this feature reflects responsiveness to user needs and contributes to long-term project success and user satisfaction.

Screenshot 2024-06-05 at 9 32 47 PM
sedghi commented 2 months ago

I think you can try the new setViewReference which accept the sliceIndex

MrWerton commented 2 months ago

@sedghi I try, but this not work. You can help-me?

My full code carbon (3)

My function to update index carbon (2)

sedghi commented 2 months ago

can you please add the code instead of screenshot

MrWerton commented 2 months ago

@sedghi this is the repo: https://github.com/MrWerton/cornerstone-dicom-viewer

and the code:

import React, { useEffect, useRef } from "react";
import "./App.css";
import { createImageIdsAndCacheMetaData, initDemo } from "./core/helpers";
import {
  Enums,
  RenderingEngine,
  StackViewport,
  Types,
  VolumeViewport,
  cache,
  getRenderingEngine,
  setVolumesForViewports,
  volumeLoader,
} from "@cornerstonejs/core";
import * as tools from "@cornerstonejs/tools";

const { ViewportType } = Enums;
const renderingEngineId = "myRenderingEngine";
const volumeId = "cornerstoneStreamingImageVolume:myVolume";
const viewportId = "CT_AXIAL";
const studyInstanceUID = "1.2.640.0.31017449.3.2.101.9.1454914.1230185";
const seriesInstanceUID =
  "1.2.840.113619.2.507.7636706.3695232.30418.1697638227.903";
const wadoRsRoot = "http://localhost:8080/orthanc/dicom-web";
const { STACK_NEW_IMAGE, VOLUME_NEW_IMAGE } = Enums.Events;

const App: React.FC = () => {
  const isInitialized = useRef(false);
  const element = useRef<HTMLDivElement>(null);
  const range = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (!isInitialized.current) {
      isInitialized.current = true;
      initializeDemo();
    }
  }, []);

  const initializeDemo = async () => {
    try {
      await initDemo();
      initializeTools();
      await renderVolumeOrStack();
    } catch (error) {
      console.error("Initialization failed:", error);
    }
  };

  const initializeTools = () => {
    tools.addTool(tools.WindowLevelTool);
    tools.addTool(tools.ZoomTool);
    tools.addTool(tools.StackScrollMouseWheelTool);
  };

  const renderVolumeOrStack = async () => {
    const renderingEngine = new RenderingEngine(renderingEngineId);
    const { imageIds, metadataTags } = await createImageIdsAndCacheMetaData({
      StudyInstanceUID: studyInstanceUID,
      SeriesInstanceUID: seriesInstanceUID,
      wadoRsRoot: wadoRsRoot,
    });
    console.log(metadataTags);

    if (range.current) {
      range.current.max = (imageIds.length - 1).toString();
    }

    if (imageIds.length < 10) {
      await renderVolume(renderingEngine, imageIds);
    } else {
      await renderStack(renderingEngine, imageIds);
    }

    console.log("Cache Size:", cache.getCacheSize());
    console.log("Bytes Available:", cache.getBytesAvailable());
  };

  const renderVolume = async (
    renderingEngine: RenderingEngine,
    imageIds: string[]
  ) => {
    const volume = await volumeLoader.createAndCacheVolume(volumeId, {
      imageIds,
    });

    element.current!.addEventListener(VOLUME_NEW_IMAGE, ((
      evt: Types.EventTypes.VolumeNewImageEvent
    ) => {
      const { imageIndex, numberOfSlices } = evt.detail;
      console.log(
        "Volume New Image",
        imageIndex,
        numberOfSlices,
        imageIds.length
      );
      range.current!.value = (imageIndex - 1).toString();
    }) as EventListener);
    const viewportInput = {
      viewportId,
      element: element.current!,
      type: ViewportType.ORTHOGRAPHIC,
      defaultOptions: {
        orientation: Enums.OrientationAxis.AXIAL,
      },
    };

    renderingEngine.enableElement(viewportInput);
    const viewport = renderingEngine.getViewport(viewportId) as VolumeViewport;

    const toolGroup = tools.ToolGroupManager.createToolGroup("3d-group")!;
    // addManipulationBindings(toolGroup, { is3DViewport: true });
    toolGroup.addTool(tools.WindowLevelTool.toolName);
    toolGroup.addTool(tools.ZoomTool.toolName);
    toolGroup.addTool(tools.StackScrollMouseWheelTool.toolName);
    toolGroup.addViewport(viewportId, renderingEngineId);
    setToolBindings(toolGroup);

    await volume.load();
    await setVolumesForViewports(
      renderingEngine,
      [{ volumeId }],
      [viewportId]
    ).then(() => {
      viewport.setProperties({
        orientation: Enums.OrientationAxis.AXIAL,
      });

      viewport.render();
    });
    renderingEngine.renderViewports([viewportId]);

    console.log("Volume set for viewport", viewport);
  };

  const renderStack = async (
    renderingEngine: RenderingEngine,
    imageIds: string[]
  ) => {
    element.current!.addEventListener(STACK_NEW_IMAGE, ((
      evt: Types.EventTypes.StackNewImageEvent
    ) => {
      const { imageIdIndex } = evt.detail;
      range.current!.value = imageIdIndex.toString();
    }) as EventListener);
    const viewportInput = {
      viewportId,
      element: element.current!,
      type: ViewportType.STACK,
    };

    renderingEngine.enableElement(viewportInput);
    const viewport = renderingEngine.getViewport(viewportId) as StackViewport;
    viewport.setStack(imageIds);

    const toolGroup = tools.ToolGroupManager.createToolGroup("2d-group")!;
    toolGroup.addTool(tools.WindowLevelTool.toolName);
    toolGroup.addTool(tools.ZoomTool.toolName);
    toolGroup.addTool(tools.StackScrollMouseWheelTool.toolName);
    toolGroup.addViewport(viewportId, renderingEngineId);

    setToolBindings(toolGroup);

    viewport.render();
  };

  const setToolBindings = (toolGroup: tools.Types.IToolGroup) => {
    toolGroup.setToolActive(tools.WindowLevelTool.toolName, {
      bindings: [{ mouseButton: tools.Enums.MouseBindings.Primary }],
    });
    toolGroup.setToolActive(tools.ZoomTool.toolName, {
      bindings: [{ mouseButton: tools.Enums.MouseBindings.Secondary }],
    });
    toolGroup.setToolActive(tools.StackScrollMouseWheelTool.toolName);
  };

  const handleRangeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = Number(event.target.value);
    updateImageIndex(value);
  };

  //here is the function to update the image index
  const updateImageIndex = (newImageIdIndex: number) => {
    const renderingEngine = getRenderingEngine(renderingEngineId);
    if (renderingEngine) {
      const viewport = renderingEngine.getViewport(viewportId)!;
      if (viewport instanceof StackViewport) {
        //here works for stack
        const numImages = viewport.getImageIds().length;
        newImageIdIndex = Math.min(newImageIdIndex, numImages - 1);
        viewport.setImageIdIndex(newImageIdIndex);

        viewport.render();
      } else if (viewport instanceof VolumeViewport) {
        viewport.setViewReference({
          FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
          sliceIndex: newImageIdIndex,
          volumeId: volumeId,

          referencedImageId: viewport.getCurrentImageId(),
        });
        viewport.render();
        console.log("VolumeViewport", viewport.getCurrentImageId());
      }
    }
  };

  return (
    <>
      <input ref={range} type="range" onChange={handleRangeChange} />
      <div style={{ width: "900px", height: "400px" }} ref={element} />
    </>
  );
};

export default App;
MrWerton commented 2 months ago

@sedghi Thank you very much, it worked. I needed to define the viewplane. If you could help me with one more thing, I would be eternally grateful. In my code, I am checking if it is a stack or volume by the number of image IDs, but I don't think this is correct. I wanted to know if there is a more correct way to verify if the image can or cannot be rendered as a volume, because some images I was testing do not render as volume but do render as stack. Could you help me with this? Thank you very much in advance!

if (imageIds.length > 10) {
      await renderVolume(renderingEngine, imageIds);
    } else {
      await renderStack(renderingEngine, imageIds);
  }
David2k13 commented 1 month ago

I think you're judging by the imagePosition