View in #cornerstone3d on Slack
@Aryan_Morady: TL/DR: I want to load scrollable multiframe XA angiograms using createImageIdsAndCacheMetaData
Hi everyone, I’m working on a project using CornerstoneJS wit an option to display coronary angiogram DICOMs stored on a MinIO server, with a FastAPI backend serving the files. I’m trying to handle multiframe DICOMs efficiently. My current approach is based on Cornerstone’s tutorials for loading files from local disk: I fetch the DICOM files from the FastAPI endpoint, convert them into blobs, and add them to Cornerstone using wadouri.fileManager.add
. For multiframe DICOMs, I use convertMultiframeImageIds
to generate image IDs for each frame. And this function only works if the file is converted into a Blob type. While this works, it feels like a workaround since createImageIdsAndCacheMetaData
doesn’t seem to handle 2D multiframe DICOMs directly, it only loads the first frame. And I noticed that createImageIdsAndCacheMetaData
is the main function is used for CT modalities in the examples when making the 3D volumes. Should I adapt createImageIdsAndCacheMetaData
to handle multiframe, or is there a better way to integrate multiframe support when fetching files from a non-DICOMweb server? Any guidance or best practices would be appreciated!
Here is my code for your referenc:
"use client";
import { useEffect, useRef, useState } from "react";
import {
RenderingEngine,
Enums,
type Types,
volumeLoader,
} from "@cornerstonejs/core";
import { init as csRenderInit } from "@cornerstonejs/core";
import { init as csToolsInit } from "@cornerstonejs/tools";
import * as cornerstoneTools from "@cornerstonejs/tools";
import cornerstoneDICOMImageLoader, {
init as dicomImageLoaderInit,
} from "@cornerstonejs/dicom-image-loader";
import createImageIdsAndCacheMetaData from "../../lib/createImageIdsAndCacheMetaData";
import {
convertMultiframeImageIds,
prefetchMetadataInformation,
} from "@/app/utils/convertMultiframeImageIds";
const { ViewportType } = Enums;
const { MouseBindings } = cornerstoneTools.Enums;
const toolGroupId = "STACK_TOOL_GROUP_ID";
const leftClickTools = [
cornerstoneTools.WindowLevelTool.toolName,
cornerstoneTools.PlanarRotateTool.toolName,
cornerstoneTools.StackScrollTool.toolName,
];
const defaultLeftClickTool = leftClickTools[0];
function App() {
const elementRef = useRef<HTMLDivElement>(null);
const running = useRef(false);
const [currentLeftClickTool, setCurrentLeftClickTool] =
useState(defaultLeftClickTool);
useEffect(() => {
const setup = async () => {
if (running.current) {
return;
}
running.current = true;
await csRenderInit();
await csToolsInit();
dicomImageLoaderInit({ maxWebWorkers: 1 });
// Add tools to Cornerstone3D
[
cornerstoneTools.PanTool,
cornerstoneTools.WindowLevelTool,
cornerstoneTools.StackScrollTool,
cornerstoneTools.ZoomTool,
cornerstoneTools.PlanarRotateTool,
].forEach((tool) => cornerstoneTools.addTool(tool));
// Define a tool group
const toolGroup =
cornerstoneTools.ToolGroupManager.createToolGroup(toolGroupId);
toolGroup.addTool(cornerstoneTools.WindowLevelTool.toolName);
toolGroup.addTool(cornerstoneTools.PanTool.toolName);
toolGroup.addTool(cornerstoneTools.ZoomTool.toolName);
toolGroup.addTool(cornerstoneTools.StackScrollTool.toolName, {
loop: false,
});
toolGroup.addTool(cornerstoneTools.PlanarRotateTool.toolName);
// Set the initial state of the tools
toolGroup.setToolActive(currentLeftClickTool, {
bindings: [{ mouseButton: MouseBindings.Primary }],
});
toolGroup.setToolActive(cornerstoneTools.PanTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Auxiliary }],
});
toolGroup.setToolActive(cornerstoneTools.ZoomTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Secondary }],
});
toolGroup.setToolActive(cornerstoneTools.StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }],
});
toolGroup.setToolActive(cornerstoneTools.PlanarRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Wheel,
modifierKey: cornerstoneTools.Enums.KeyboardBindings.Shift,
},
{ mouseButton: MouseBindings.Wheel_Primary },
],
});
//fetch the dicom file from endpoint /file/{bucket_name}/{filename:path}
//TODO
const fetchDicomFile = async (bucketName, filename) => {
try {
const response = await fetch(
`<https://ymcfz3-8152.csb.app/file/${bucketName}/${filename}>`
);
if (!response.ok) {
throw new Error(
`Failed to fetch DICOM file: ${response.statusText}`
);
}
// Convert the response to a Blob
const dicomBlob = await response.blob();
return dicomBlob;
} catch (error) {
console.error("Error fetching DICOM file:", error);
throw error;
}
};
const dicomFile = await fetchDicomFile("dcm", "0002.DCM");
console.log("dicomData", dicomFile);
const imageId =
cornerstoneDICOMImageLoader.wadouri.fileManager.add(dicomFile);
await prefetchMetadataInformation([imageId]);
const stack = convertMultiframeImageIds([imageId]);
// 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>",
// });
// console.log(imageIds[0]);
// console.log(imageIds[1]);
// Instantiate a rendering engine
const renderingEngineId = "myRenderingEngine";
const renderingEngine = new RenderingEngine(renderingEngineId);
const viewportId = "CT_STACK";
const viewportInput = {
viewportId,
type: ViewportType.STACK,
element: elementRef.current,
defaultOptions: {
background: [0.2, 0, 0.2] as Types.Point3,
},
};
renderingEngine.enableElement(viewportInput);
toolGroup.addViewport(viewportId, renderingEngineId);
const viewport = renderingEngine.getViewport(
viewportId
) as Types.IStackViewport;
viewport.setStack(stack);
viewport.render();
};
setup();
}, [currentLeftClickTool]);
return (
<div>
<div
ref={elementRef}
style={{
width: "500px",
height: "500px",
backgroundColor: "#000",
}}
></div>
<p>
Middle Click: Pan\nRight Click: Zoom\n Mouse Wheel: Stack Scroll\n Shift
or Primary + Wheel: Planar Rotate
</p>
<select
value={currentLeftClickTool}
onChange={(e) => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
toolGroup.setToolPassive(currentLeftClickTool);
toolGroup.setToolActive(e.target.value, {
bindings: [{ mouseButton: MouseBindings.Primary }],
});
setCurrentLeftClickTool(e.target.value);
}}
>
{leftClickTools.map((tool) => (
<option key={tool} value={tool}>
{tool}
</option>
))}
</select>
</div>
);
}
export default App;
@Alireza_Sedghi: There seems to be some issues with the XA, i have seen other posts too
we will look