-
+
@@ -105,178 +94,37 @@ export const Datasource = () => {
/>
-
-
- {patients.map((patient) => (
-
- setPatients((p) =>
- p.map((i) => ({ ...i, active: i.ID === patient.ID }))
- )
- }
- className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${
- patient.active ? "bg-accent" : ""
- }`}
- >
-
-
-
-
- {patient.MainDicomTags.PatientName}
-
-
-
-
- {patient.MainDicomTags.PatientSex}
-
-
-
- {patient.MainDicomTags.PatientBirthDate}
-
-
-
- 上次更新: {patient.LastUpdate}
-
-
- ))}
-
-
+
{
+ setSelectedPatientId(id);
+ setSelectedStudyId(null);
+ }}
+ />
-
+
-
-
- {patients.find((p) => p.active) &&
- patients
- .find((p) => p.active)
- ?.children.map((study) => (
-
- setPatients((p) =>
- p.map((i) => ({
- ...i,
- children: i.children.map((s) => ({
- ...s,
- active: s.ID === study.ID,
- })),
- }))
- )
- }
- className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${
- study.active ? "bg-accent" : ""
- }`}
- >
-
-
-
-
- {study.MainDicomTags.StudyID ?? "无"}
-
-
-
- {study.MainDicomTags.StudyDate}
-
-
-
-
- {study.MainDicomTags.InstitutionName}
-
-
- ))}
-
-
+ {selectedPatient && (
+
setSelectedStudyId(id)}
+ />
+ )}
-
+
-
-
- {patients.find((p) => p.active) &&
- patients
- .find((p) => p.active)
- ?.children.find((study) => study.active)
- ?.children.map((series) => (
-
-
-
-
-
- 序号 {series.MainDicomTags.SeriesNumber}
-
-
-
-
-
-
-
-
-
-
- onClickItem({
- series,
- action: "viewMpr",
- })
- }
- >
- MPR 阅片
-
-
-
-
-
- 3D 重建
-
-
-
-
-
- 导出测量
-
-
-
-
-
- 查看报告
-
-
-
-
-
-
-
-
-
-
-
- {series.MainDicomTags.SeriesDescription}
-
-
-
-
-
-
- {series.Instances.length}
-
- {series.MainDicomTags.BodyPartExamined && (
-
- {series.MainDicomTags.BodyPartExamined}
-
- )}
-
-
-
- ))}
-
-
+ {selectedStudy && (
+
+ )}
diff --git a/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx b/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx
index ae73eef..3a5f603 100644
--- a/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx
+++ b/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from "react";
+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";
@@ -20,17 +20,12 @@ import {
ViewportId,
} from "./Crosshair.config";
-import GridLayout from 'react-grid-layout';
-import 'react-grid-layout/css/styles.css';
-import 'react-resizable/css/styles.css';
-import { DraftingCompassIcon, MoveIcon } from "lucide-react";
-import { Card } from "@/components/ui/card";
-
const {
ToolGroupManager,
CrosshairsTool,
StackScrollMouseWheelTool,
WindowLevelTool,
+ ZoomTool,
Enums: csToolsEnums,
} = cornerstoneTools;
@@ -72,14 +67,22 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
const viewportRef_SAGITTAL = useRef
(null);
const viewportRef_CORONAL = useRef(null);
const renderingEngine = useRef();
+ const toolGroupIdRef = useRef("");
+ const toolGroupRef = useRef(
+ undefined
+ );
const imageIds = useRef();
const ts = "-" + Date.now();
-
useEffect(() => {
cornerstoneTools.addTool(StackScrollMouseWheelTool);
cornerstoneTools.addTool(CrosshairsTool);
cornerstoneTools.addTool(WindowLevelTool);
+ cornerstoneTools.addTool(ZoomTool);
+
+ const toolGroupId = props.SeriesInstanceUID + ts;
+ toolGroupIdRef.current = toolGroupId;
+ toolGroupRef.current = ToolGroupManager.createToolGroup(toolGroupId);
const run = async () => {
if (
@@ -92,9 +95,6 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
renderingEngine.current = new RenderingEngine(
props.SeriesInstanceUID + ts
);
- const toolGroup = ToolGroupManager.createToolGroup(
- props.SeriesInstanceUID + ts
- );
imageIds.current = await createImageIdsAndCacheMetaData({
StudyInstanceUID: props.StudyInstanceUID,
@@ -124,7 +124,7 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
element: viewportRef_AXIAL.current,
defaultOptions: {
orientation: CoreEnums.OrientationAxis.AXIAL,
- background: [0, 0, 0],
+ background: [0, 0, 0],
},
},
{
@@ -169,38 +169,59 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
[viewportId1, viewportId2, viewportId3]
);
- if (toolGroup) {
+ 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.
- toolGroup.addViewport(viewportId1, props.SeriesInstanceUID + ts);
- toolGroup.addViewport(viewportId2, props.SeriesInstanceUID + ts);
- toolGroup.addViewport(viewportId3, props.SeriesInstanceUID + ts);
+ toolGroupRef.current.addViewport(
+ viewportId1,
+ props.SeriesInstanceUID + ts
+ );
+ toolGroupRef.current.addViewport(
+ viewportId2,
+ props.SeriesInstanceUID + ts
+ );
+ toolGroupRef.current.addViewport(
+ viewportId3,
+ props.SeriesInstanceUID + ts
+ );
+
+ /**
+ * zoom影像
+ */
+ toolGroupRef.current.addTool(ZoomTool.toolName);
+ toolGroupRef.current.setToolActive(ZoomTool.toolName, {
+ bindings: [
+ {
+ mouseButton: MouseBindings.Secondary, // 鼠标中键
+ },
+ ],
+ });
// Manipulation Tools
- toolGroup.addTool(StackScrollMouseWheelTool.toolName);
+ toolGroupRef.current.addTool(StackScrollMouseWheelTool.toolName);
// Add Crosshairs tool and configure it to link the three viewports
// These viewports could use different tool groups. See the PET-CT example
// for a more complicated used case.
- toolGroup.addTool(CrosshairsTool.toolName, {
+ toolGroupRef.current.addTool(CrosshairsTool.toolName, {
getReferenceLineColor,
getReferenceLineControllable,
getReferenceLineDraggableRotatable,
getReferenceLineSlabThicknessControlsOn,
});
- toolGroup.setToolActive(CrosshairsTool.toolName, {
+ toolGroupRef.current.setToolActive(CrosshairsTool.toolName, {
bindings: [{ mouseButton: 1 }],
});
// 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);
+ toolGroupRef.current.setToolActive(StackScrollMouseWheelTool.toolName);
- toolGroup.addTool(WindowLevelTool.toolName);
- toolGroup.setToolActive(WindowLevelTool.toolName, {
+ toolGroupRef.current.addTool(WindowLevelTool.toolName);
+ toolGroupRef.current.setToolActive(WindowLevelTool.toolName, {
bindings: [
{
- mouseButton: MouseBindings.Secondary,
+ mouseButton: MouseBindings.Auxiliary,
},
],
});
@@ -218,37 +239,50 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
}
return () => {
+ // 禁用视口
+ renderingEngine.current?.disableElement(viewportId1);
+ renderingEngine.current?.disableElement(viewportId2);
+ renderingEngine.current?.disableElement(viewportId3);
+
+ // 销毁渲染引擎
+ renderingEngine.current?.destroy();
+
+ // 从 ToolGroupManager 中移除工具组
+ ToolGroupManager.destroyToolGroup(toolGroupIdRef.current);
+
+ // 移出工具注册
cornerstoneTools.removeTool(StackScrollMouseWheelTool);
cornerstoneTools.removeTool(CrosshairsTool);
cornerstoneTools.removeTool(WindowLevelTool);
- renderingEngine.current?.destroy();
- ToolGroupManager.destroyToolGroup(props.SeriesInstanceUID + ts);
+ cornerstoneTools.removeTool(ZoomTool);
};
}, [props, ts]);
+ /**
+ * mpr resize
+ */
useEffect(() => {
- const resize = () => renderingEngine.current?.resize()
- window.addEventListener("resize", resize);
+ const container = containerRef.current;
+ 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);
+ });
+ resizeObserver.observe(container);
return () => {
- window.removeEventListener("resize", resize);
+ if (resizeTimeout) clearTimeout(resizeTimeout);
+ resizeObserver.unobserve(container);
+ resizeObserver.disconnect();
};
}, []);
- const onDBClickViewport = (viewportRef) => {
- console.log("dblcik", viewportRef);
- };
-
return (
-
- onDBClickViewport(viewportRef_AXIAL)} ref={viewportRef_AXIAL}>
-
-
-
-
-
-
-
+
+
+
);
};
diff --git a/apps/desktop/src/pages/Viewer/MprViewer/ToolBarMenu/index.tsx b/apps/desktop/src/pages/Viewer/MprViewer/ToolBarMenu/index.tsx
new file mode 100644
index 0000000..a9faf9f
--- /dev/null
+++ b/apps/desktop/src/pages/Viewer/MprViewer/ToolBarMenu/index.tsx
@@ -0,0 +1,39 @@
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { MoonIcon, PersonStanding } from "lucide-react";
+
+export const ToolBarMenu = () => {
+ return (
+
+
+
+
+
+
+
+ 窗宽/窗位
+
+
+
+
+
+
+
+
+
+ MIP
+
+
+
+
+ );
+};
diff --git a/apps/desktop/src/pages/Viewer/index.tsx b/apps/desktop/src/pages/Viewer/index.tsx
index d7f5407..f8d7c04 100644
--- a/apps/desktop/src/pages/Viewer/index.tsx
+++ b/apps/desktop/src/pages/Viewer/index.tsx
@@ -2,7 +2,12 @@ import { useLocation } from "react-router-dom";
import { initCornerstone } from "./MprViewer/CornerstoneDicomLoader/init";
import { CrosshairMpr } from "./MprViewer/Crosshair";
import { useEffect, useState } from "react";
-
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/components/ui/resizable";
+import { ToolBarMenu } from "./MprViewer/ToolBarMenu";
export interface CurrentDicom {
SeriesInstanceUID: string | null;
@@ -27,7 +32,7 @@ export const Viewer = () => {
}, [currentDicom]);
useEffect(() => {
- console.log(window.location.href)
+ console.log(window.location.href);
initCornerstone(() => {
setCornerstoneLoaded(true);
});
@@ -37,19 +42,42 @@ export const Viewer = () => {
console.log(cornerstoneLoaded);
}, [cornerstoneLoaded]);
-
return (
-
-
vr
-
- {cornerstoneLoaded &&
- currentDicom.StudyInstanceUID &&
- currentDicom.SeriesInstanceUID && (
-
- )}
+
+
+
+
+
+
+
+
+
+
+ top
+
+
+
+ bototm
+
+
+
+
+
+
+ {cornerstoneLoaded &&
+ currentDicom.StudyInstanceUID &&
+ currentDicom.SeriesInstanceUID && (
+
+ )}
+
+
+
);
};