feat: stack viewport
This commit is contained in:
parent
6c6d2178a0
commit
3226b47790
|
@ -12,6 +12,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.4.0",
|
||||
"@cornerstonejs/calculate-suv": "1.1.0",
|
||||
"@cornerstonejs/core": "1.84.4",
|
||||
"@cornerstonejs/dicom-image-loader": "1.84.4",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "1.84.4",
|
||||
"@cornerstonejs/tools": "1.84.4",
|
||||
"@google-cloud/spanner": "^7.12.0",
|
||||
"@hookform/resolvers": "3.9.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
|
@ -23,6 +28,7 @@
|
|||
"@radix-ui/react-radio-group": "^1.2.0",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
|
@ -31,19 +37,25 @@
|
|||
"@types/react-icons": "^3.0.0",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"antd": "^5.20.0",
|
||||
"axios": "1.7.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"custom-electron-titlebar": "^4.2.8",
|
||||
"date-fns": "^3.6.0",
|
||||
"dayjs": "1.11.13",
|
||||
"dcmjs": "0.34.1",
|
||||
"dexie": "^4.0.8",
|
||||
"dicom-parser": "1.8.21",
|
||||
"dicomweb-client": "0.10.4",
|
||||
"dockview": "^1.15.2",
|
||||
"electron-log": "5.2.0",
|
||||
"electron-store": "^10.0.0",
|
||||
"embla-carousel-react": "^8.2.0",
|
||||
"flexlayout-react": "^0.7.15",
|
||||
"form-data": "4.0.0",
|
||||
"framer-motion": "^11.3.24",
|
||||
"lodash": "4.17.21",
|
||||
"lowdb": "^7.0.1",
|
||||
"lucide-react": "^0.408.0",
|
||||
"node-machine-id": "1.1.12",
|
||||
|
@ -55,29 +67,19 @@
|
|||
"react-desktop": "^0.3.9",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "14.2.3",
|
||||
"react-grid-layout": "1.4.4",
|
||||
"react-hook-form": "7.53.0",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-resizable": "3.0.5",
|
||||
"react-resizable-panels": "^2.1.1",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "3.23.8",
|
||||
"axios": "1.7.7",
|
||||
"lodash": "4.17.21",
|
||||
"electron-log": "5.2.0",
|
||||
"form-data": "4.0.0",
|
||||
"@cornerstonejs/core": "1.84.4",
|
||||
"@cornerstonejs/tools": "1.84.4",
|
||||
"@cornerstonejs/dicom-image-loader": "1.84.4",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "1.84.4",
|
||||
"dicomweb-client": "0.10.4",
|
||||
"@cornerstonejs/calculate-suv": "1.1.0",
|
||||
"dcmjs": "0.34.1",
|
||||
"react-grid-layout": "1.4.4",
|
||||
"react-resizable": "3.0.5"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@types/lodash": "4.17.7",
|
||||
"@types/node": "22.5.2",
|
||||
"@types/react": "^18.2.64",
|
||||
"@types/react-dom": "^18.2.21",
|
||||
|
@ -96,7 +98,6 @@
|
|||
"vite": "^5.1.6",
|
||||
"vite-plugin-electron": "^0.28.6",
|
||||
"vite-plugin-electron-renderer": "^0.14.5",
|
||||
"@types/lodash": "4.17.7",
|
||||
"vite-plugin-node-polyfills": "0.22.0"
|
||||
}
|
||||
}
|
28
apps/desktop/src/components/ui/slider.tsx
Normal file
28
apps/desktop/src/components/ui/slider.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Slider = React.forwardRef<
|
||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none select-none items-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
))
|
||||
Slider.displayName = SliderPrimitive.Root.displayName
|
||||
|
||||
export { Slider }
|
|
@ -22,6 +22,7 @@ interface CreateImageIdsAndCacheMetaDataOptions {
|
|||
SOPInstanceUID?: string | null;
|
||||
wadoRsRoot: string;
|
||||
client?: api.DICOMwebClient | null;
|
||||
convertMultiframe?: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +71,8 @@ export const createImageIdsAndCacheMetaData = async (
|
|||
return imageId;
|
||||
});
|
||||
|
||||
imageIds = convertMultiframeImageIds(imageIds);
|
||||
if (convertMultiframeImageIds)
|
||||
imageIds = convertMultiframeImageIds(imageIds);
|
||||
|
||||
imageIds.forEach((imageId) => {
|
||||
let instanceMetaData =
|
||||
|
@ -84,11 +86,8 @@ export const createImageIdsAndCacheMetaData = async (
|
|||
const pixelSpacing = getPixelSpacingInformation(metadata) as Number[];
|
||||
|
||||
if (pixelSpacing) {
|
||||
// FIXME: cornerstone类型定义有问题,这里.add方法缺少type属性
|
||||
calibratedPixelSpacingMetadataProvider.add(imageId, {
|
||||
// @ts-ignore
|
||||
rowPixelSpacing: pixelSpacing[0],
|
||||
// @ts-ignore
|
||||
columnPixelSpacing: pixelSpacing[1],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import * as cornerstoneTools from "@cornerstonejs/tools";
|
||||
import { PublicViewportInput } from "@cornerstonejs/core/dist/types/types/IViewport.js";
|
||||
import { createImageIdsAndCacheMetaData } from "./CornerstoneDicomLoader/createImageIdsAndCacheMetaData";
|
||||
import setCtTransferFunctionForVolumeActor from "./CornerstoneDicomLoader/setCtTransferFunctionForVolumeActor";
|
||||
import {
|
||||
RenderingEngine,
|
||||
setVolumesForViewports,
|
||||
volumeLoader,
|
||||
Enums as CoreEnums,
|
||||
} from "@cornerstonejs/core";
|
||||
import {
|
||||
|
@ -19,7 +17,6 @@ import {
|
|||
viewportId3,
|
||||
ViewportId,
|
||||
} from "./Crosshair.config";
|
||||
import { IStackViewport } from "@cornerstonejs/core/dist/types/types";
|
||||
|
||||
const {
|
||||
ToolGroupManager,
|
||||
|
@ -34,10 +31,6 @@ const { MouseBindings } = csToolsEnums;
|
|||
|
||||
const { ViewportType } = CoreEnums;
|
||||
|
||||
interface CrosshairMprProps {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
function getReferenceLineColor(vpId: ViewportId) {
|
||||
return viewportColors[vpId];
|
||||
}
|
||||
|
@ -58,23 +51,25 @@ function getReferenceLineSlabThicknessControlsOn(vpId: ViewportId) {
|
|||
}
|
||||
|
||||
interface CrosshairMprProps {
|
||||
StudyInstanceUID: string;
|
||||
SeriesInstanceUID: string;
|
||||
wwwl: {
|
||||
windowCenter: number;
|
||||
windowWidth: number;
|
||||
};
|
||||
volumeId: string;
|
||||
}
|
||||
|
||||
export const CrosshairMpr = (props: CrosshairMprProps) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const viewportStack = useRef<HTMLDivElement | null>(null);
|
||||
const viewportRef_AXIAL = useRef<HTMLDivElement | null>(null);
|
||||
const viewportRef_SAGITTAL = useRef<HTMLDivElement | null>(null);
|
||||
const viewportRef_CORONAL = useRef<HTMLDivElement | null>(null);
|
||||
const renderingEngine = useRef<RenderingEngine>();
|
||||
const toolGroupIdRef = useRef<string>("");
|
||||
const toolGroupRef = useRef<cornerstoneTools.Types.IToolGroup | undefined>(
|
||||
undefined
|
||||
);
|
||||
const imageIds = useRef<string[]>();
|
||||
const ts = "-" + Date.now();
|
||||
const toolGroupId = "mprToolGroup" + ts;
|
||||
const renderingEngineId = "mprRenderingEngine" + ts;
|
||||
|
||||
useEffect(() => {
|
||||
cornerstoneTools.addTool(StackScrollMouseWheelTool);
|
||||
|
@ -82,8 +77,6 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
cornerstoneTools.addTool(WindowLevelTool);
|
||||
cornerstoneTools.addTool(ZoomTool);
|
||||
|
||||
const toolGroupId = props.SeriesInstanceUID + ts;
|
||||
toolGroupIdRef.current = toolGroupId;
|
||||
toolGroupRef.current = ToolGroupManager.createToolGroup(toolGroupId);
|
||||
|
||||
const run = async () => {
|
||||
|
@ -94,40 +87,7 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
)
|
||||
return;
|
||||
|
||||
renderingEngine.current = new RenderingEngine(
|
||||
props.SeriesInstanceUID + ts
|
||||
);
|
||||
|
||||
imageIds.current = await createImageIdsAndCacheMetaData({
|
||||
StudyInstanceUID: props.StudyInstanceUID,
|
||||
SeriesInstanceUID: props.SeriesInstanceUID,
|
||||
wadoRsRoot: "http://localhost:8042/dicom-web",
|
||||
});
|
||||
|
||||
// Define a volume in memory
|
||||
const volume = await volumeLoader.createAndCacheVolume(
|
||||
props.SeriesInstanceUID + ts,
|
||||
{
|
||||
imageIds: imageIds.current ?? [],
|
||||
}
|
||||
);
|
||||
|
||||
// 默认windowWidtth
|
||||
const { windowCenter, windowWidth } = volume.cornerstoneImageMetaData;
|
||||
const defaultWindowCenter = 50;
|
||||
const defaultWindowWidth = 850;
|
||||
console.log("采用默认窗宽/位: ", windowCenter, windowWidth);
|
||||
|
||||
// 定义 Stack 视口
|
||||
const stackViewport = "stack" + props.SeriesInstanceUID + ts;
|
||||
const viewportInput: PublicViewportInput = {
|
||||
viewportId: stackViewport,
|
||||
type: CoreEnums.ViewportType.STACK, // 用于 Stack 视图
|
||||
element: viewportStack.current!,
|
||||
defaultOptions: {
|
||||
background: [0, 0, 0],
|
||||
},
|
||||
};
|
||||
renderingEngine.current = new RenderingEngine(renderingEngineId);
|
||||
|
||||
// Create the viewports
|
||||
const viewportInputArray: PublicViewportInput[] = [
|
||||
|
@ -160,31 +120,19 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
},
|
||||
];
|
||||
|
||||
renderingEngine.current.setViewports([
|
||||
...viewportInputArray,
|
||||
viewportInput,
|
||||
]);
|
||||
|
||||
const viewport = renderingEngine.current.getViewport(
|
||||
stackViewport
|
||||
) as IStackViewport;
|
||||
|
||||
viewport.setStack(imageIds.current, 80);
|
||||
|
||||
// Set the volume to load
|
||||
volume.load();
|
||||
renderingEngine.current.setViewports(viewportInputArray);
|
||||
|
||||
// Set volumes on the viewports
|
||||
await setVolumesForViewports(
|
||||
renderingEngine.current,
|
||||
[
|
||||
{
|
||||
volumeId: props.SeriesInstanceUID + ts,
|
||||
volumeId: props.volumeId,
|
||||
callback: ({ volumeActor }) =>
|
||||
setCtTransferFunctionForVolumeActor({
|
||||
volumeActor,
|
||||
defaultWindowCenter,
|
||||
defaultWindowWidth,
|
||||
defaultWindowCenter: props.wwwl.windowCenter,
|
||||
defaultWindowWidth: props.wwwl.windowWidth,
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
@ -194,18 +142,9 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
if (toolGroupRef.current) {
|
||||
// For the crosshairs to operate, the viewports must currently be
|
||||
// added ahead of setting the tool active. This will be improved in the future.
|
||||
toolGroupRef.current.addViewport(
|
||||
viewportId1,
|
||||
props.SeriesInstanceUID + ts
|
||||
);
|
||||
toolGroupRef.current.addViewport(
|
||||
viewportId2,
|
||||
props.SeriesInstanceUID + ts
|
||||
);
|
||||
toolGroupRef.current.addViewport(
|
||||
viewportId3,
|
||||
props.SeriesInstanceUID + ts
|
||||
);
|
||||
toolGroupRef.current.addViewport(viewportId1, renderingEngineId);
|
||||
toolGroupRef.current.addViewport(viewportId2, renderingEngineId);
|
||||
toolGroupRef.current.addViewport(viewportId3, renderingEngineId);
|
||||
|
||||
/**
|
||||
* zoom影像
|
||||
|
@ -256,9 +195,7 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
]);
|
||||
};
|
||||
|
||||
if (props.SeriesInstanceUID && props.StudyInstanceUID) {
|
||||
run();
|
||||
}
|
||||
run();
|
||||
|
||||
return () => {
|
||||
// 禁用视口
|
||||
|
@ -270,7 +207,7 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
renderingEngine.current?.destroy();
|
||||
|
||||
// 从 ToolGroupManager 中移除工具组
|
||||
ToolGroupManager.destroyToolGroup(toolGroupIdRef.current);
|
||||
ToolGroupManager.destroyToolGroup(toolGroupId);
|
||||
|
||||
// 移出工具注册
|
||||
cornerstoneTools.removeTool(StackScrollMouseWheelTool);
|
||||
|
@ -278,7 +215,7 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
cornerstoneTools.removeTool(WindowLevelTool);
|
||||
cornerstoneTools.removeTool(ZoomTool);
|
||||
};
|
||||
}, [props, ts]);
|
||||
}, [props, renderingEngineId, toolGroupId]);
|
||||
|
||||
/**
|
||||
* mpr resize
|
||||
|
@ -288,7 +225,6 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
if (!container) return;
|
||||
let resizeTimeout: NodeJS.Timeout | null = null;
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
console.log("mpr resize");
|
||||
if (resizeTimeout) clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(() => renderingEngine.current?.resize(), 100);
|
||||
});
|
||||
|
@ -302,10 +238,9 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
|||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full h-full flex flex-col">
|
||||
<div className="w-full h-1/4" ref={viewportRef_AXIAL} />
|
||||
<div className="w-full h-1/4" ref={viewportRef_SAGITTAL} />
|
||||
<div className="w-full h-1/4" ref={viewportRef_CORONAL} />
|
||||
<div className="w-full h-1/4" ref={viewportStack} />
|
||||
<div className="w-full h-1/3" ref={viewportRef_AXIAL} />
|
||||
<div className="w-full h-1/3" ref={viewportRef_SAGITTAL} />
|
||||
<div className="w-full h-1/3" ref={viewportRef_CORONAL} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
79
apps/desktop/src/pages/Viewer/StackViewer/index.tsx
Normal file
79
apps/desktop/src/pages/Viewer/StackViewer/index.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { PublicViewportInput } from "@cornerstonejs/core/dist/types/types/IViewport.js";
|
||||
import { Enums as CoreEnums, RenderingEngine } from "@cornerstonejs/core";
|
||||
import { IStackViewport } from "@cornerstonejs/core/dist/types/types";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
|
||||
export interface StackViewerProps {
|
||||
imageIds: string[];
|
||||
}
|
||||
|
||||
export const StackViewer = (props: StackViewerProps) => {
|
||||
const viewportStackRef = useRef<HTMLDivElement | null>(null);
|
||||
const renderingEngineRef = useRef<RenderingEngine>();
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const viewportId = "stackViewport";
|
||||
const renderingEngineId = "stackRenderingEngine";
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewportStackRef.current) return;
|
||||
renderingEngineRef.current = new RenderingEngine(renderingEngineId);
|
||||
const viewportInput: PublicViewportInput = {
|
||||
viewportId,
|
||||
type: CoreEnums.ViewportType.STACK, // 用于 Stack 视图
|
||||
element: viewportStackRef.current,
|
||||
defaultOptions: {
|
||||
background: [0, 0, 0],
|
||||
},
|
||||
};
|
||||
renderingEngineRef.current.enableElement(viewportInput);
|
||||
const viewport = renderingEngineRef.current.getViewport(
|
||||
viewportId
|
||||
) as IStackViewport;
|
||||
|
||||
viewport.setStack(props.imageIds);
|
||||
|
||||
return () => {
|
||||
renderingEngineRef.current?.disableElement(viewportId);
|
||||
renderingEngineRef.current?.destroy();
|
||||
};
|
||||
}, [props.imageIds, renderingEngineId, viewportId]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
let resizeTimeout: NodeJS.Timeout | null = null;
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (resizeTimeout) clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(
|
||||
() => renderingEngineRef.current?.resize(),
|
||||
100
|
||||
);
|
||||
});
|
||||
resizeObserver.observe(container);
|
||||
return () => {
|
||||
if (resizeTimeout) clearTimeout(resizeTimeout);
|
||||
resizeObserver.unobserve(container);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onChangeIndex = (value: number[]) => {
|
||||
if (renderingEngineRef.current) {
|
||||
const viewport = renderingEngineRef.current.getViewport(viewportId);
|
||||
(viewport as IStackViewport).setImageIdIndex(value[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full h-full flex flex-col pb-4">
|
||||
<div className="w-full h-full" ref={viewportStackRef} />
|
||||
<Slider
|
||||
defaultValue={[0]}
|
||||
max={props.imageIds.length - 1}
|
||||
step={1}
|
||||
onValueChange={onChangeIndex}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -7,47 +7,56 @@ import {
|
|||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import { ToolBarMenu } from "./MprViewer/ToolBarMenu";
|
||||
import { RenderingEngine } from "@cornerstonejs/core";
|
||||
import { StackViewer } from "./StackViewer";
|
||||
import { createImageIdsAndCacheMetaData } from "./MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData";
|
||||
import { volumeLoader } from "@cornerstonejs/core";
|
||||
|
||||
export interface CurrentDicom {
|
||||
SeriesInstanceUID: string | null;
|
||||
StudyInstanceUID: string | null;
|
||||
}
|
||||
const wadoRsRoot = "http://localhost:8042/dicom-web";
|
||||
|
||||
export const Viewer = () => {
|
||||
const [cornerstoneLoaded, setCornerstoneLoaded] = useState(false);
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const [currentDicom, setCurrentDicom] = useState<CurrentDicom>({
|
||||
SeriesInstanceUID: queryParams.get("SeriesInstanceUID"),
|
||||
StudyInstanceUID: queryParams.get("StudyInstanceUID"),
|
||||
});
|
||||
const SeriesInstanceUID = queryParams.get("SeriesInstanceUID");
|
||||
const StudyInstanceUID = queryParams.get("StudyInstanceUID");
|
||||
const imageIdsRef = useRef<string[]>();
|
||||
const volumeId = "volume";
|
||||
const wwwl = { windowCenter: 50, windowWidth: 850 };
|
||||
|
||||
useEffect(() => {
|
||||
const { SeriesInstanceUID, StudyInstanceUID } = currentDicom;
|
||||
if (StudyInstanceUID && SeriesInstanceUID) {
|
||||
const metadata = { SeriesInstanceUID, StudyInstanceUID };
|
||||
localStorage.setItem("viewCache", JSON.stringify(metadata));
|
||||
}
|
||||
}, [currentDicom]);
|
||||
const setImageOrderCache = async () => {
|
||||
if (!StudyInstanceUID || !SeriesInstanceUID) return;
|
||||
// imageIds此时由于流式加载for mpr,是错乱的图片顺序
|
||||
const imageIds = await createImageIdsAndCacheMetaData({
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
wadoRsRoot,
|
||||
});
|
||||
// 这一步会对imageIds进行排序,如果不排序imageIds会错误乱,stackViewport顺序会错误
|
||||
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
|
||||
imageIds,
|
||||
});
|
||||
volume.load();
|
||||
imageIdsRef.current = volume.imageIds;
|
||||
|
||||
// 默认windowWidtth
|
||||
const { windowCenter, windowWidth } = volume.cornerstoneImageMetaData;
|
||||
console.log("默认窗宽/位: ", windowCenter, windowWidth);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(window.location.href);
|
||||
initCornerstone(() => {
|
||||
setCornerstoneLoaded(true);
|
||||
});
|
||||
}, [queryParams]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(cornerstoneLoaded);
|
||||
}, [cornerstoneLoaded]);
|
||||
initCornerstone(() => {
|
||||
setImageOrderCache();
|
||||
});
|
||||
|
||||
}, [SeriesInstanceUID, StudyInstanceUID]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<div className="flex-shrink-0 border-b border-secondary">
|
||||
{/* <div className="flex-shrink-0 border-b border-secondary">
|
||||
<ToolBarMenu />
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="flex-grow">
|
||||
<ResizablePanelGroup direction="horizontal" className="w-full h-full">
|
||||
<ResizablePanel defaultSize={50}>
|
||||
|
@ -57,7 +66,11 @@ export const Viewer = () => {
|
|||
className="w-full h-full"
|
||||
>
|
||||
<ResizablePanel defaultSize={50}>
|
||||
<div id="stackElement" className="w-full h-full"></div>
|
||||
<div className="w-full h-full">
|
||||
{cornerstoneLoaded && imageIdsRef.current && (
|
||||
<StackViewer imageIds={imageIdsRef.current} />
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={50}>
|
||||
|
@ -68,14 +81,9 @@ export const Viewer = () => {
|
|||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={50}>
|
||||
{cornerstoneLoaded &&
|
||||
currentDicom.StudyInstanceUID &&
|
||||
currentDicom.SeriesInstanceUID && (
|
||||
<CrosshairMpr
|
||||
StudyInstanceUID={currentDicom.StudyInstanceUID}
|
||||
SeriesInstanceUID={currentDicom.SeriesInstanceUID}
|
||||
/>
|
||||
)}
|
||||
{cornerstoneLoaded && imageIdsRef.current && (
|
||||
<CrosshairMpr wwwl={wwwl} volumeId={volumeId} />
|
||||
)}
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
|
|
|
@ -61,6 +61,9 @@ importers:
|
|||
'@radix-ui/react-select':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-slider':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
|
@ -1742,6 +1745,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slider@1.2.0':
|
||||
resolution: {integrity: sha512-dAHCDA4/ySXROEPaRtaMV5WHL8+JB/DbtyTbJjYkY0RXmKMO2Ln8DFZhywG5/mVQ4WqHDBc8smc14yPXPqZHYA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.0.2':
|
||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||
peerDependencies:
|
||||
|
@ -7233,6 +7249,25 @@ snapshots:
|
|||
'@types/react': 18.3.4
|
||||
'@types/react-dom': 18.3.0
|
||||
|
||||
'@radix-ui/react-slider@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/number': 1.1.0
|
||||
'@radix-ui/primitive': 1.1.0
|
||||
'@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
'@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
'@radix-ui/react-use-size': 1.1.0(@types/react@18.3.4)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.4
|
||||
'@types/react-dom': 18.3.0
|
||||
|
||||
'@radix-ui/react-slot@1.0.2(@types/react@18.3.4)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.25.4
|
||||
|
|
Loading…
Reference in New Issue
Block a user