diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index d034c33..166ca56 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -71,6 +71,7 @@
"@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"
},
"devDependencies": {
diff --git a/apps/desktop/src/components/base/SideBarLeft/constant.tsx b/apps/desktop/src/components/base/SideBarLeft/constant.tsx
index 3f334be..abaebfc 100644
--- a/apps/desktop/src/components/base/SideBarLeft/constant.tsx
+++ b/apps/desktop/src/components/base/SideBarLeft/constant.tsx
@@ -1,11 +1,17 @@
import { MenuItem } from "./type";
-import { BrainCircuit, Package, HardDrive, Wrench } from "lucide-react";
+import {
+ BrainCircuit,
+ Package,
+ HardDrive,
+ Wrench,
+ Rotate3DIcon,
+} from "lucide-react";
export const menuItems: MenuItem[] = [
{ to: "/", name: "自动分析", icon: },
{ to: "/datasource", name: "数据列表", icon: },
+ { to: "/viewer", name: "MPR阅片", icon: },
{ to: "/models", name: "模型管理", icon: },
{ to: "/tools", name: "小工具", icon: },
- // { to: "/help", name: "帮助", icon: },
// { to: "/setting", name: "设置", icon: },
];
diff --git a/apps/desktop/src/pages/Datasource/CarouselSeries.tsx b/apps/desktop/src/pages/Datasource/CarouselSeries.tsx
deleted file mode 100644
index 2bfba56..0000000
--- a/apps/desktop/src/pages/Datasource/CarouselSeries.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { Card } from "@/components/ui/card";
-import {
- Carousel,
- CarouselContent,
- CarouselItem,
-} from "@/components/ui/carousel";
-import {
- EllipsisIcon,
- FileDown,
- Rotate3DIcon,
- Sparkles,
- Torus,
-} from "lucide-react";
-import { SeriesInfo } from "./type";
-import { Badge } from "@/components/ui/badge";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuShortcut,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-
-export type CarouselAction =
- | "viewMpr"
- | "view3D"
- | "exportMeaurement"
- | "viewReport";
-
-interface CarouselSeriesProps {
- seriesList: SeriesInfo[];
- onClickItem?: (series: SeriesInfo, action: CarouselAction) => void;
-}
-
-export function CarouselSeries(props: CarouselSeriesProps) {
- return (
-
-
-
- {props.seriesList.map((series) => (
-
-
-
-
-
-
- 序号 {series.MainDicomTags.SeriesNumber}
-
-
-
-
-
-
-
-
-
-
- props.onClickItem?.(series, "viewMpr")
- }
- >
- MPR 阅片
-
-
-
-
-
- props.onClickItem?.(series, "view3D")
- }
- >
- 3D 重建
-
-
-
-
-
- props.onClickItem?.(series, "exportMeaurement")
- }
- >
- 导出测量
-
-
-
-
-
- props.onClickItem?.(series, "viewReport")
- }
- >
- 查看报告
-
-
-
-
-
-
-
-
-
-
- {series.MainDicomTags.SeriesDescription}
-
-
-
-
- {series.Instances.length}
- {series.MainDicomTags.BodyPartExamined && (
-
- {series.MainDicomTags.BodyPartExamined}
-
- )}
-
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/Crosshair.tsx b/apps/desktop/src/pages/Datasource/MprViewer/Crosshair.tsx
deleted file mode 100644
index 7771914..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/Crosshair.tsx
+++ /dev/null
@@ -1,246 +0,0 @@
-import { useEffect, useRef } from "react";
-import initDemo from "./util/initDemo.js";
-import {
- RenderingEngine,
- Types,
- Enums,
- setVolumesForViewports,
- volumeLoader,
- getRenderingEngine,
-} from "@cornerstonejs/core";
-import * as cornerstoneTools from "@cornerstonejs/tools";
-import createImageIdsAndCacheMetaData from "./util/createImageIdsAndCacheMetaData";
-import { PublicViewportInput } from "@cornerstonejs/core/dist/types/types/IViewport.js";
-import setCtTransferFunctionForVolumeActor from "./util/setCtTransferFunctionForVolumeActor";
-import * as cornerstone from "@cornerstonejs/core";
-
-const {
- ToolGroupManager,
- Enums: csToolsEnums,
- CrosshairsTool,
- StackScrollMouseWheelTool,
-} = cornerstoneTools;
-
-const { ViewportType } = Enums;
-
-interface CrosshairMprProps {
- children?: JSX.Element;
-}
-
-const viewportId1 = "CT_AXIAL";
-const viewportId2 = "CT_SAGITTAL";
-const viewportId3 = "CT_CORONAL";
-const toolGroupId = "group";
-
-const viewportColors = {
- [viewportId1]: "rgb(200, 0, 0)",
- [viewportId2]: "rgb(200, 200, 0)",
- [viewportId3]: "rgb(0, 200, 0)",
-};
-
-const viewportReferenceLineControllable = [
- viewportId1,
- viewportId2,
- viewportId3,
-];
-
-const viewportReferenceLineDraggableRotatable = [
- viewportId1,
- viewportId2,
- viewportId3,
-];
-
-const viewportReferenceLineSlabThicknessControlsOn = [
- viewportId1,
- viewportId2,
- viewportId3,
-];
-
-// Define a unique id for the volume
-const volumeName = "CT_VOLUME_ID"; // Id of the volume less loader prefix
-const volumeLoaderScheme = "cornerstoneStreamingImageVolume"; // Loader id which defines which volume loader to use
-const volumeId = `${volumeLoaderScheme}:${volumeName}`; // VolumeId with loader id + volume id
-
-function getReferenceLineColor(viewportId) {
- return viewportColors[viewportId];
-}
-
-function getReferenceLineControllable(viewportId) {
- const index = viewportReferenceLineControllable.indexOf(viewportId);
- return index !== -1;
-}
-
-function getReferenceLineDraggableRotatable(viewportId) {
- const index = viewportReferenceLineDraggableRotatable.indexOf(viewportId);
- return index !== -1;
-}
-
-function getReferenceLineSlabThicknessControlsOn(viewportId) {
- const index =
- viewportReferenceLineSlabThicknessControlsOn.indexOf(viewportId);
- return index !== -1;
-}
-
-const renderingEngineId = "myRenderingEngine";
-
-interface CrosshairMprProps {
- // StudyInstanceUID: string;
- // SeriesInstanceUID: string;
-}
-
-export const CrosshairMpr = (props: CrosshairMprProps) => {
- const viewportId1Ref = useRef(null);
- const viewportId2Ref = useRef(null);
- const viewportId3Ref = useRef(null);
- const renderingEngine = useRef();
- const imageIds = useRef();
-
- const run = async () => {
- initDemo();
-
- // Get Cornerstone imageIds for the source data and fetch metadata into RAM
- imageIds.current = await createImageIdsAndCacheMetaData({
- StudyInstanceUID:
- "1.2.840.113564.118796721496052.50228.637325565454648183.8",
- SeriesInstanceUID:
- "1.3.12.2.1107.5.1.4.73399.30000020080900171669200001479",
- wadoRsRoot: "http://localhost:8042/dicom-web",
- });
-
- // Define a volume in memory
- const volume = await volumeLoader.createAndCacheVolume(volumeId, {
- imageIds: imageIds.current ?? [],
- });
-
- // Instantiate a rendering engine
- renderingEngine.current = new RenderingEngine(renderingEngineId);
-
- // Create the viewports
- const viewportInputArray: PublicViewportInput[] = [
- {
- viewportId: viewportId1,
- type: ViewportType.ORTHOGRAPHIC,
- element: viewportId1Ref.current!,
- defaultOptions: {
- orientation: Enums.OrientationAxis.AXIAL,
- background: [0, 0, 0],
- },
- },
- {
- viewportId: viewportId2,
- type: ViewportType.ORTHOGRAPHIC,
- element: viewportId2Ref.current!,
- defaultOptions: {
- orientation: Enums.OrientationAxis.SAGITTAL,
- background: [0, 0, 0],
- },
- },
- {
- viewportId: viewportId3,
- type: ViewportType.ORTHOGRAPHIC,
- element: viewportId3Ref.current!,
- defaultOptions: {
- orientation: Enums.OrientationAxis.CORONAL,
- background: [0, 0, 0],
- },
- },
- ];
-
- renderingEngine.current.setViewports(viewportInputArray);
-
- console.log(volume);
-
- // Set the volume to load
- volume.load();
-
- // Set volumes on the viewports
- await setVolumesForViewports(
- renderingEngine.current,
- [
- {
- volumeId,
- callback: setCtTransferFunctionForVolumeActor,
- },
- ],
- [viewportId1, viewportId2, viewportId3]
- );
-
- // Define tool groups to add the segmentation display tool to
- const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
-
- if (toolGroup) {
- // 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, renderingEngineId);
- toolGroup.addViewport(viewportId2, renderingEngineId);
- toolGroup.addViewport(viewportId3, renderingEngineId);
-
- // Manipulation Tools
- toolGroup.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, {
- getReferenceLineColor,
- getReferenceLineControllable,
- getReferenceLineDraggableRotatable,
- getReferenceLineSlabThicknessControlsOn,
- });
-
- toolGroup.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);
- }
-
- renderingEngine.current.renderViewports([
- viewportId1,
- viewportId2,
- viewportId3,
- ]);
- };
-
- useEffect(() => {
- run();
- cornerstoneTools.addTool(StackScrollMouseWheelTool);
- cornerstoneTools.addTool(CrosshairsTool);
-
- return () => {
- // TODO: 是否需要写在组件,根据页面需要
- console.log("卸载tool2");
- cornerstoneTools.removeTool(StackScrollMouseWheelTool);
- cornerstoneTools.removeTool(CrosshairsTool);
- };
- }, []);
-
- useEffect(() => {
- window.addEventListener("resize", () => {
- renderingEngine.current?.resize();
- });
- }, []);
-
- const test = () => {
- // console.log(volumeLoader)
- console.log(cornerstone.cache.getVolume(volumeId).getScalarData());
- };
-
- useEffect(() => {
- document.addEventListener(Enums.Events.VOLUME_CACHE_VOLUME_ADDED, () => {
- console.log(111);
- });
- }, []);
-
- return (
-
- );
-};
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/index.tsx b/apps/desktop/src/pages/Datasource/MprViewer/index.tsx
deleted file mode 100644
index b23ee89..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useEffect } from "react";
-import { SeriesInfo } from "../type";
-import { CrosshairMpr } from "./Crosshair";
-
-interface MprViewerProps {
- series?: SeriesInfo;
-}
-
-function MprViewer(props: MprViewerProps) {
- useEffect(() => {
- if (props.series) console.log(props.series);
- }, [props.series]);
- return (
-
-
-
- );
-}
-
-export default MprViewer;
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/convertMultiframeImageIds.js b/apps/desktop/src/pages/Datasource/MprViewer/util/convertMultiframeImageIds.js
deleted file mode 100644
index 8f2c333..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/convertMultiframeImageIds.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { metaData } from '@cornerstonejs/core';
-import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
-/**
- * preloads imageIds metadata in memory
- **/
-async function prefetchMetadataInformation(imageIdsToPrefetch) {
- for (let i = 0; i < imageIdsToPrefetch.length; i++) {
- await cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i])
- .promise;
- }
-}
-
-function getFrameInformation(imageId) {
- if (imageId.includes('wadors:')) {
- const frameIndex = imageId.indexOf('/frames/');
- const imageIdFrameless =
- frameIndex > 0 ? imageId.slice(0, frameIndex + 8) : imageId;
- return {
- frameIndex,
- imageIdFrameless,
- };
- } else {
- const frameIndex = imageId.indexOf('&frame=');
- let imageIdFrameless =
- frameIndex > 0 ? imageId.slice(0, frameIndex + 7) : imageId;
- if (!imageIdFrameless.includes('&frame=')) {
- imageIdFrameless = imageIdFrameless + '&frame=';
- }
- return {
- frameIndex,
- imageIdFrameless,
- };
- }
-}
-/**
- * Receives a list of imageids possibly referring to multiframe dicom images
- * and returns a list of imageid where each imageid referes to one frame.
- * For each imageId representing a multiframe image with n frames,
- * it will create n new imageids, one for each frame, and returns the new list of imageids
- * If a particular imageid no refer to a mutiframe image data, it will be just copied into the new list
- * @returns new list of imageids where each imageid represents a frame
- */
-function convertMultiframeImageIds(imageIds) {
- const newImageIds = [];
- imageIds.forEach((imageId) => {
- const { imageIdFrameless } = getFrameInformation(imageId);
- const instanceMetaData = metaData.get('multiframeModule', imageId);
- if (
- instanceMetaData &&
- instanceMetaData.NumberOfFrames &&
- instanceMetaData.NumberOfFrames > 1
- ) {
- const NumberOfFrames = instanceMetaData.NumberOfFrames;
- for (let i = 0; i < NumberOfFrames; i++) {
- const newImageId = imageIdFrameless + (i + 1);
- newImageIds.push(newImageId);
- }
- } else {
- newImageIds.push(imageId);
- }
- });
- return newImageIds;
-}
-
-export { convertMultiframeImageIds, prefetchMetadataInformation };
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/createImageIdsAndCacheMetaData.js b/apps/desktop/src/pages/Datasource/MprViewer/util/createImageIdsAndCacheMetaData.js
deleted file mode 100644
index 4c7852c..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/createImageIdsAndCacheMetaData.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { api } from 'dicomweb-client';
-import dcmjs from 'dcmjs';
-import { utilities } from '@cornerstonejs/core';
-import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
-import getPixelSpacingInformation from './getPixelSpacingInformation';
-import { convertMultiframeImageIds } from './convertMultiframeImageIds';
-import removeInvalidTags from './removeInvalidTags';
-
-const { DicomMetaDictionary } = dcmjs.data;
-const { calibratedPixelSpacingMetadataProvider } = utilities;
-
-/**
-/**
- * Uses dicomweb-client to fetch metadata of a study, cache it in cornerstone,
- * and return a list of imageIds for the frames.
- *
- * Uses the app config to choose which study to fetch, and which
- * dicom-web server to fetch it from.
- *
- * @returns {string[]} An array of imageIds for instances in the study.
- */
-
-export default async function createImageIdsAndCacheMetaData({
- StudyInstanceUID,
- SeriesInstanceUID,
- SOPInstanceUID = null,
- wadoRsRoot,
- client = null,
-}) {
- const SOP_INSTANCE_UID = '00080018';
- const SERIES_INSTANCE_UID = '0020000E';
- const MODALITY = '00080060';
-
- const studySearchOptions = {
- studyInstanceUID: StudyInstanceUID,
- seriesInstanceUID: SeriesInstanceUID,
- };
-
- client = client || new api.DICOMwebClient({ url: wadoRsRoot });
- let instances = await client.retrieveSeriesMetadata(studySearchOptions);
-
- // if sop instance is provided we should filter the instances to only include the one we want
- if (SOPInstanceUID) {
- instances = instances.filter((instance) => {
- return instance[SOP_INSTANCE_UID].Value[0] === SOPInstanceUID;
- });
- }
-
- const modality = instances[0][MODALITY].Value[0];
- let imageIds = instances.map((instanceMetaData) => {
- const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0];
- const SOPInstanceUIDToUse =
- SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0];
-
- const prefix = 'wadors:';
-
- const imageId =
- prefix +
- wadoRsRoot +
- '/studies/' +
- StudyInstanceUID +
- '/series/' +
- SeriesInstanceUID +
- '/instances/' +
- SOPInstanceUIDToUse +
- '/frames/1';
-
- cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
- imageId,
- instanceMetaData
- );
- return imageId;
- });
-
- // if the image ids represent multiframe information, creates a new list with one image id per frame
- // if not multiframe data available, just returns the same list given
- imageIds = convertMultiframeImageIds(imageIds);
-
- imageIds.forEach((imageId) => {
- let instanceMetaData =
- cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId);
-
- // It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower
- instanceMetaData = removeInvalidTags(instanceMetaData);
-
- if (instanceMetaData) {
- // Add calibrated pixel spacing
- const metadata = DicomMetaDictionary.naturalizeDataset(instanceMetaData);
- const pixelSpacing = getPixelSpacingInformation(metadata);
-
- if (pixelSpacing) {
- calibratedPixelSpacingMetadataProvider.add(imageId, {
- rowPixelSpacing: parseFloat(pixelSpacing[0]),
- columnPixelSpacing: parseFloat(pixelSpacing[1]),
- });
- }
- }
- });
-
- return imageIds;
-}
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/getPixelSpacingInformation.js b/apps/desktop/src/pages/Datasource/MprViewer/util/getPixelSpacingInformation.js
deleted file mode 100644
index 5ddcde5..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/getPixelSpacingInformation.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// See https://github.com/OHIF/Viewers/blob/94a9067fe3d291d30e25a1bda5913511388edea2/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js
-
-export default function getPixelSpacingInformation(instance) {
- // See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing
-
- // TODO: Add Ultrasound region spacing
- // TODO: Add manual calibration
-
- // TODO: Use ENUMS from dcmjs
- const projectionRadiographSOPClassUIDs = [
- '1.2.840.10008.5.1.4.1.1.1', // CR Image Storage
- '1.2.840.10008.5.1.4.1.1.1.1', // Digital X-Ray Image Storage – for Presentation
- '1.2.840.10008.5.1.4.1.1.1.1.1', // Digital X-Ray Image Storage – for Processing
- '1.2.840.10008.5.1.4.1.1.1.2', // Digital Mammography X-Ray Image Storage – for Presentation
- '1.2.840.10008.5.1.4.1.1.1.2.1', // Digital Mammography X-Ray Image Storage – for Processing
- '1.2.840.10008.5.1.4.1.1.1.3', // Digital Intra – oral X-Ray Image Storage – for Presentation
- '1.2.840.10008.5.1.4.1.1.1.3.1', // Digital Intra – oral X-Ray Image Storage – for Processing
- '1.2.840.10008.5.1.4.1.1.12.1', // X-Ray Angiographic Image Storage
- '1.2.840.10008.5.1.4.1.1.12.1.1', // Enhanced XA Image Storage
- '1.2.840.10008.5.1.4.1.1.12.2', // X-Ray Radiofluoroscopic Image Storage
- '1.2.840.10008.5.1.4.1.1.12.2.1', // Enhanced XRF Image Storage
- '1.2.840.10008.5.1.4.1.1.12.3', // X-Ray Angiographic Bi-plane Image Storage Retired
- ];
-
- const {
- PixelSpacing,
- ImagerPixelSpacing,
- SOPClassUID,
- PixelSpacingCalibrationType,
- PixelSpacingCalibrationDescription,
- EstimatedRadiographicMagnificationFactor,
- SequenceOfUltrasoundRegions,
- } = instance;
-
- const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);
-
- const TYPES = {
- NOT_APPLICABLE: 'NOT_APPLICABLE',
- UNKNOWN: 'UNKNOWN',
- CALIBRATED: 'CALIBRATED',
- DETECTOR: 'DETECTOR',
- };
-
- if (!isProjection) {
- return PixelSpacing;
- }
-
- if (isProjection && !ImagerPixelSpacing) {
- // If only Pixel Spacing is present, and this is a projection radiograph,
- // PixelSpacing should be used, but the user should be informed that
- // what it means is unknown
- return {
- PixelSpacing,
- type: TYPES.UNKNOWN,
- isProjection,
- };
- } else if (
- PixelSpacing &&
- ImagerPixelSpacing &&
- PixelSpacing === ImagerPixelSpacing
- ) {
- // If Imager Pixel Spacing and Pixel Spacing are present and they have the same values,
- // then the user should be informed that the measurements are at the detector plane
- return {
- PixelSpacing,
- type: TYPES.DETECTOR,
- isProjection,
- };
- } else if (
- PixelSpacing &&
- ImagerPixelSpacing &&
- PixelSpacing !== ImagerPixelSpacing
- ) {
- // If Imager Pixel Spacing and Pixel Spacing are present and they have different values,
- // then the user should be informed that these are "calibrated"
- // (in some unknown manner if Pixel Spacing Calibration Type and/or
- // Pixel Spacing Calibration Description are absent)
- return {
- PixelSpacing,
- type: TYPES.CALIBRATED,
- isProjection,
- PixelSpacingCalibrationType,
- PixelSpacingCalibrationDescription,
- };
- } else if (!PixelSpacing && ImagerPixelSpacing) {
- let CorrectedImagerPixelSpacing = ImagerPixelSpacing;
- if (EstimatedRadiographicMagnificationFactor) {
- // Note that in IHE Mammo profile compliant displays, the value of Imager Pixel Spacing is required to be corrected by
- // Estimated Radiographic Magnification Factor and the user informed of that.
- // TODO: should this correction be done before all of this logic?
- CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(
- (pixelSpacing) =>
- pixelSpacing / EstimatedRadiographicMagnificationFactor
- );
- } else {
- console.warn(
- 'EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.'
- );
- }
-
- return {
- PixelSpacing: CorrectedImagerPixelSpacing,
- isProjection,
- };
- } else if (
- SequenceOfUltrasoundRegions &&
- typeof SequenceOfUltrasoundRegions === 'object'
- ) {
- const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions;
- const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];
-
- return {
- PixelSpacing: USPixelSpacing,
- };
- } else if (
- SequenceOfUltrasoundRegions &&
- Array.isArray(SequenceOfUltrasoundRegions) &&
- SequenceOfUltrasoundRegions.length > 1
- ) {
- console.warn(
- 'Sequence of Ultrasound Regions > one entry. This is not yet implemented, all measurements will be shown in pixels.'
- );
- } else if (isProjection === false && !ImagerPixelSpacing) {
- // If only Pixel Spacing is present, and this is not a projection radiograph,
- // we can stop here
- return {
- PixelSpacing,
- type: TYPES.NOT_APPLICABLE,
- isProjection,
- };
- }
-
- console.warn(
- 'Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.'
- );
-}
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/initCornerstoneDICOMImageLoader.js b/apps/desktop/src/pages/Datasource/MprViewer/util/initCornerstoneDICOMImageLoader.js
deleted file mode 100644
index d6ef6b8..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/initCornerstoneDICOMImageLoader.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import dicomParser from 'dicom-parser';
-import * as cornerstone from '@cornerstonejs/core';
-import * as cornerstoneTools from '@cornerstonejs/tools';
-import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
-
-window.cornerstone = cornerstone;
-window.cornerstoneTools = cornerstoneTools;
-const { preferSizeOverAccuracy, useNorm16Texture } =
- cornerstone.getConfiguration().rendering;
-
-export default function initCornerstoneDICOMImageLoader() {
- // TODO: 此处非常的蛇皮
- cornerstone.setUseSharedArrayBuffer(false)
- cornerstone.setConfiguration({
- detectGPUConfig: {
- // benchmarksURL: "http://localhost:9000"
- }
- })
- cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
- cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
- cornerstoneDICOMImageLoader.configure({
- useWebWorkers: true,
- decodeConfig: {
- convertFloatPixelDataToInt: false,
- use16BitDataType: preferSizeOverAccuracy || useNorm16Texture,
- },
- });
-
- const config = {
- maxWebWorkers: navigator.hardwareConcurrency ? Math.min(navigator.hardwareConcurrency, 7) : 1,
- startWebWorkersOnDemand: false,
- taskConfiguration: {
- decodeTask: {
- initializeCodecsOnStartup: false,
- strict: false,
- },
- },
- };
-
- cornerstoneDICOMImageLoader.webWorkerManager.initialize(config);
-}
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/initDemo.js b/apps/desktop/src/pages/Datasource/MprViewer/util/initDemo.js
deleted file mode 100644
index ec5eb4e..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/initDemo.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import initProviders from './initProviders.js';
-import initCornerstoneDICOMImageLoader from './initCornerstoneDICOMImageLoader';
-import initVolumeLoader from './initVolumeLoader';
-import { init as csRenderInit } from '@cornerstonejs/core';
-import { init as csToolsInit } from '@cornerstonejs/tools';
-
-export default async function initDemo() {
- initProviders();
- initCornerstoneDICOMImageLoader();
- initVolumeLoader();
- await csRenderInit();
- await csToolsInit();
-}
\ No newline at end of file
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/initProviders.js b/apps/desktop/src/pages/Datasource/MprViewer/util/initProviders.js
deleted file mode 100644
index 3d12f3f..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/initProviders.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as cornerstone from '@cornerstonejs/core';
-import ptScalingMetaDataProvider from './ptScalingMetaDataProvider';
-
-const { calibratedPixelSpacingMetadataProvider } = cornerstone.utilities;
-
-export default function initProviders() {
- cornerstone.metaData.addProvider(
- ptScalingMetaDataProvider.get.bind(ptScalingMetaDataProvider),
- 10000
- );
- cornerstone.metaData.addProvider(
- calibratedPixelSpacingMetadataProvider.get.bind(
- calibratedPixelSpacingMetadataProvider
- ),
- 11000
- );
-}
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/initVolumeLoader.js b/apps/desktop/src/pages/Datasource/MprViewer/util/initVolumeLoader.js
deleted file mode 100644
index 22a81d6..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/initVolumeLoader.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { volumeLoader } from '@cornerstonejs/core';
-import {
- cornerstoneStreamingImageVolumeLoader,
- cornerstoneStreamingDynamicImageVolumeLoader,
-} from '@cornerstonejs/streaming-image-volume-loader';
-
-export default function initVolumeLoader() {
- volumeLoader.registerUnknownVolumeLoader(
- cornerstoneStreamingImageVolumeLoader
- );
- volumeLoader.registerVolumeLoader(
- 'cornerstoneStreamingImageVolume',
- cornerstoneStreamingImageVolumeLoader
- );
- volumeLoader.registerVolumeLoader(
- 'cornerstoneStreamingDynamicImageVolume',
- cornerstoneStreamingDynamicImageVolumeLoader
- );
-}
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/ptScalingMetaDataProvider.js b/apps/desktop/src/pages/Datasource/MprViewer/util/ptScalingMetaDataProvider.js
deleted file mode 100644
index 52fa9c8..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/ptScalingMetaDataProvider.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { utilities as csUtils } from '@cornerstonejs/core';
-
-const scalingPerImageId = {};
-
-function addInstance(imageId, scalingMetaData) {
- const imageURI = csUtils.imageIdToURI(imageId);
- scalingPerImageId[imageURI] = scalingMetaData;
-}
-
-function get(type, imageId) {
- if (type === 'scalingModule') {
- const imageURI = csUtils.imageIdToURI(imageId);
- return scalingPerImageId[imageURI];
- }
-}
-
-export default { addInstance, get };
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/removeInvalidTags.js b/apps/desktop/src/pages/Datasource/MprViewer/util/removeInvalidTags.js
deleted file mode 100644
index abf7652..0000000
--- a/apps/desktop/src/pages/Datasource/MprViewer/util/removeInvalidTags.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Remove invalid tags from a metadata and return a new object.
- *
- * At this time it is only removing tags that has `null` or `undefined` values
- * which is our main goal because that breaks when `naturalizeDataset(...)` is
- * called.
- *
- * Validating the tag id using regex like /^[a-fA-F0-9]{8}$/ make it run
- * +50% slower and looping through all characteres (split+every+Set or simple
- * FOR+Set) double the time it takes to run. It is currently taking +12ms/1k
- * images on average which can change depending on the machine.
- *
- * @param srcMetadata - source metadata
- * @returns new metadata object without invalid tags
- */
-function removeInvalidTags(srcMetadata) {
- // Object.create(null) make it ~9% faster
- const dstMetadata = Object.create(null);
- const tagIds = Object.keys(srcMetadata);
- let tagValue;
-
- tagIds.forEach((tagId) => {
- tagValue = srcMetadata[tagId];
-
- if (tagValue !== undefined && tagValue !== null) {
- dstMetadata[tagId] = tagValue;
- }
- });
-
- return dstMetadata;
-}
-
-export { removeInvalidTags as default, removeInvalidTags };
diff --git a/apps/desktop/src/pages/Datasource/index.tsx b/apps/desktop/src/pages/Datasource/index.tsx
index 8b562bc..e9303ca 100644
--- a/apps/desktop/src/pages/Datasource/index.tsx
+++ b/apps/desktop/src/pages/Datasource/index.tsx
@@ -7,16 +7,36 @@ import {
} from "@/components/ui/resizable";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Card } from "@/components/ui/card";
-import { PatientInfo, SeriesInfo } from "./type";
-import { CarouselAction, CarouselSeries } from "./CarouselSeries";
+import { PatientInfo, SeriesInfo, StudyInfo } from "./type";
import { Input } from "@/components/ui/input";
import { Search } from "lucide-react";
-import MprViewer from "./MprViewer";
+import {
+ EllipsisIcon,
+ FileDown,
+ Rotate3DIcon,
+ Sparkles,
+ Torus,
+} from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { useNavigate } from "react-router-dom";
+
+interface SeriesClickItem {
+ action: "viewMpr" | "view3D" | "exportMeaurement" | "viewReport";
+ series: SeriesInfo;
+}
export const Datasource = () => {
const rawPatientsRef = useRef([]);
const [patients, setPatients] = useState([]);
- const [activeSeries, setActiveSeries] = useState();
+ const navigate = useNavigate();
useEffect(() => {
window.ipcRenderer
@@ -34,15 +54,22 @@ export const Datasource = () => {
});
}, []);
- const onClickSeriesItem = (series: SeriesInfo, action: CarouselAction) => {
- console.log(action, series);
- switch (action) {
+ const onClickItem = (p: SeriesClickItem) => {
+ const activeStudy = patients
+ .find((p) => p.active)
+ ?.children.find((s) => s.active);
+ const StudyInstanceUID = activeStudy?.MainDicomTags.StudyInstanceUID;
+ const SeriesInstanceUID = p.series.MainDicomTags.SeriesInstanceUID;
+ const query = `StudyInstanceUID=${StudyInstanceUID}&SeriesInstanceUID=${SeriesInstanceUID}`;
+ switch (p.action) {
case "viewMpr":
- setActiveSeries(series);
+ navigate(`/viewer?${query}`, { replace: true });
break;
+
default:
break;
}
+ console.log(StudyInstanceUID, p.action, p.series);
};
const handlePatientSearch = (filterValue: string) => {
@@ -158,12 +185,6 @@ export const Datasource = () => {
{study.MainDicomTags.InstitutionName}
-
-
-
))}
@@ -172,7 +193,91 @@ export const Datasource = () => {
-
+
+
+
+ {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}
+
+ )}
+
+
+
+ ))}
+
+
+
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/convertMultiframeImageIds.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/convertMultiframeImageIds.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/convertMultiframeImageIds.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/convertMultiframeImageIds.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/getPTImageIdInstanceMetadata.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/getPTImageIdInstanceMetadata.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/getPTImageIdInstanceMetadata.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/getPTImageIdInstanceMetadata.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/getPixelSpacingInformation.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/getPixelSpacingInformation.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/getPixelSpacingInformation.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/getPixelSpacingInformation.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/init.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/init.ts
similarity index 69%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/init.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/init.ts
index ac5fbcd..8e1d19e 100644
--- a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/init.ts
+++ b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/init.ts
@@ -1,10 +1,14 @@
+import initProviders from "./initProviders.js";
import { initCornerstoneDICOMImageLoader } from "./initCornerstoneDicomImageLoader";
import initVolumeLoader from "./initVolumeLoader";
import { init as csRenderInit } from "@cornerstonejs/core";
+import { init as csToolsInit } from "@cornerstonejs/tools";
// 入口
export const initCornerstone = async () => {
+ initProviders();
initCornerstoneDICOMImageLoader();
initVolumeLoader();
await csRenderInit();
+ await csToolsInit();
};
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initCornerstoneDicomImageLoader.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initCornerstoneDicomImageLoader.ts
similarity index 88%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initCornerstoneDicomImageLoader.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initCornerstoneDicomImageLoader.ts
index da17d5d..ae1e79f 100644
--- a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initCornerstoneDicomImageLoader.ts
+++ b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initCornerstoneDicomImageLoader.ts
@@ -2,17 +2,18 @@ import dicomParser from "dicom-parser";
import * as cornerstone from "@cornerstonejs/core";
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
-window.cornerstone = cornerstone;
-
export const initCornerstoneDICOMImageLoader = () => {
const { preferSizeOverAccuracy, useNorm16Texture } =
cornerstone.getConfiguration().rendering;
+ cornerstone.setUseSharedArrayBuffer(false);
cornerstone.setConfiguration({
detectGPUConfig: {
- benchmarksURL: "http://localhost:9000",
+ // benchmarksURL: "http://localhost:9000",
},
rendering: cornerstone.getConfiguration().rendering,
+ isMobile: false,
+ enableCacheOptimization: false,
});
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initProviders.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initProviders.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initProviders.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initProviders.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initVolumeLoader.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initVolumeLoader.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/initVolumeLoader.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/initVolumeLoader.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/ptScalingMetaDataProvider.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/ptScalingMetaDataProvider.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/ptScalingMetaDataProvider.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/ptScalingMetaDataProvider.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/removeInvalidTags.ts b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/removeInvalidTags.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/CornerstoneDicomLoader/removeInvalidTags.ts
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/removeInvalidTags.ts
diff --git a/apps/desktop/src/pages/Datasource/MprViewer/util/setCtTransferFunctionForVolumeActor.js b/apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/setCtTransferFunctionForVolumeActor.ts
similarity index 100%
rename from apps/desktop/src/pages/Datasource/MprViewer/util/setCtTransferFunctionForVolumeActor.js
rename to apps/desktop/src/pages/Viewer/MprViewer/CornerstoneDicomLoader/setCtTransferFunctionForVolumeActor.ts
diff --git a/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.config.tsx b/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.config.tsx
new file mode 100644
index 0000000..d109039
--- /dev/null
+++ b/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.config.tsx
@@ -0,0 +1,35 @@
+export const renderingEngineId = "mprRenderingEngine";
+
+export const viewportId1 = "CT_AXIAL";
+export const viewportId2 = "CT_SAGITTAL";
+export const viewportId3 = "CT_CORONAL";
+export const toolGroupId = "group";
+
+export const viewportColors = {
+ [viewportId1]: "rgb(200, 0, 0)",
+ [viewportId2]: "rgb(200, 200, 0)",
+ [viewportId3]: "rgb(0, 200, 0)",
+};
+
+export const viewportReferenceLineControllable = [
+ viewportId1,
+ viewportId2,
+ viewportId3,
+];
+
+export const viewportReferenceLineDraggableRotatable = [
+ viewportId1,
+ viewportId2,
+ viewportId3,
+];
+
+export const viewportReferenceLineSlabThicknessControlsOn = [
+ viewportId1,
+ viewportId2,
+ viewportId3,
+];
+
+// Define a unique id for the volume
+export const volumeName = "CT_VOLUME_ID"; // Id of the volume less loader prefix
+export const volumeLoaderScheme = "cornerstoneStreamingImageVolume"; // Loader id which defines which volume loader to use
+export const volumeId = `${volumeLoaderScheme}:${volumeName}`; // VolumeId with loader id + volume id
diff --git a/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx b/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx
new file mode 100644
index 0000000..32dc909
--- /dev/null
+++ b/apps/desktop/src/pages/Viewer/MprViewer/Crosshair.tsx
@@ -0,0 +1,201 @@
+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 {
+ viewportColors,
+ viewportReferenceLineControllable,
+ viewportReferenceLineDraggableRotatable,
+ viewportReferenceLineSlabThicknessControlsOn,
+ viewportId1,
+ viewportId2,
+ viewportId3,
+ toolGroupId,
+ volumeId,
+ renderingEngineId,
+} from "./Crosshair.config";
+import { initCornerstone } from "./CornerstoneDicomLoader/init";
+
+const { ToolGroupManager, CrosshairsTool, StackScrollMouseWheelTool } =
+ cornerstoneTools;
+
+const { ViewportType } = CoreEnums;
+
+interface CrosshairMprProps {
+ children?: JSX.Element;
+}
+
+function getReferenceLineColor(viewportId: string | number) {
+ return viewportColors[viewportId];
+}
+
+function getReferenceLineControllable(viewportId: string) {
+ const index = viewportReferenceLineControllable.indexOf(viewportId);
+ return index !== -1;
+}
+
+function getReferenceLineDraggableRotatable(viewportId: string) {
+ const index = viewportReferenceLineDraggableRotatable.indexOf(viewportId);
+ return index !== -1;
+}
+
+function getReferenceLineSlabThicknessControlsOn(viewportId: string) {
+ const index =
+ viewportReferenceLineSlabThicknessControlsOn.indexOf(viewportId);
+ return index !== -1;
+}
+
+interface CrosshairMprProps {
+ StudyInstanceUID: string;
+ SeriesInstanceUID: string;
+}
+
+export const CrosshairMpr = (props: CrosshairMprProps) => {
+ const viewportId1Ref = useRef(null);
+ const viewportId2Ref = useRef(null);
+ const viewportId3Ref = useRef(null);
+ const renderingEngine = useRef();
+ const imageIds = useRef();
+
+ useEffect(() => {
+ const run = async () => {
+ imageIds.current = await createImageIdsAndCacheMetaData({
+ StudyInstanceUID: props.StudyInstanceUID,
+ SeriesInstanceUID: props.SeriesInstanceUID,
+ wadoRsRoot: "http://localhost:8042/dicom-web",
+ });
+
+ renderingEngine.current = new RenderingEngine(props.SeriesInstanceUID);
+
+ // Define a volume in memory
+ const volume = await volumeLoader.createAndCacheVolume(
+ props.SeriesInstanceUID,
+ {
+ imageIds: imageIds.current ?? [],
+ }
+ );
+
+ // Create the viewports
+ const viewportInputArray: PublicViewportInput[] = [
+ {
+ viewportId: viewportId1,
+ type: ViewportType.ORTHOGRAPHIC,
+ element: viewportId1Ref.current!,
+ defaultOptions: {
+ orientation: CoreEnums.OrientationAxis.AXIAL,
+ background: [0, 0, 0],
+ },
+ },
+ {
+ viewportId: viewportId2,
+ type: ViewportType.ORTHOGRAPHIC,
+ element: viewportId2Ref.current!,
+ defaultOptions: {
+ orientation: CoreEnums.OrientationAxis.SAGITTAL,
+ background: [0, 0, 0],
+ },
+ },
+ {
+ viewportId: viewportId3,
+ type: ViewportType.ORTHOGRAPHIC,
+ element: viewportId3Ref.current!,
+ defaultOptions: {
+ orientation: CoreEnums.OrientationAxis.CORONAL,
+ background: [0, 0, 0],
+ },
+ },
+ ];
+
+ renderingEngine.current.setViewports(viewportInputArray);
+
+ // Set the volume to load
+ volume.load();
+
+ // Set volumes on the viewports
+ await setVolumesForViewports(
+ renderingEngine.current,
+ [
+ {
+ volumeId: props.SeriesInstanceUID,
+ callback: setCtTransferFunctionForVolumeActor,
+ },
+ ],
+ [viewportId1, viewportId2, viewportId3]
+ );
+
+ const toolGroup = ToolGroupManager.createToolGroup(
+ props.SeriesInstanceUID
+ );
+
+ if (toolGroup) {
+ // 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);
+ toolGroup.addViewport(viewportId2, props.SeriesInstanceUID);
+ toolGroup.addViewport(viewportId3, props.SeriesInstanceUID);
+
+ // Manipulation Tools
+ toolGroup.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, {
+ getReferenceLineColor,
+ getReferenceLineControllable,
+ getReferenceLineDraggableRotatable,
+ getReferenceLineSlabThicknessControlsOn,
+ });
+
+ toolGroup.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);
+ }
+
+ renderingEngine.current.renderViewports([
+ viewportId1,
+ viewportId2,
+ viewportId3,
+ ]);
+ };
+
+ if (!props.SeriesInstanceUID && !props.StudyInstanceUID) return;
+ run();
+ cornerstoneTools.addTool(StackScrollMouseWheelTool);
+ cornerstoneTools.addTool(CrosshairsTool);
+
+ return () => {
+ cornerstoneTools.removeTool(StackScrollMouseWheelTool);
+ cornerstoneTools.removeTool(CrosshairsTool);
+ ToolGroupManager.destroyToolGroup(props.SeriesInstanceUID);
+ };
+ }, [props.StudyInstanceUID, props.SeriesInstanceUID]);
+
+ useEffect(() => {
+ const resize = () => renderingEngine.current?.resize();
+ window.addEventListener("resize", resize);
+ return () => {
+ window.removeEventListener("resize", resize);
+ };
+ }, []);
+
+ return (
+
+ );
+};
diff --git a/apps/desktop/src/pages/Viewer/index.tsx b/apps/desktop/src/pages/Viewer/index.tsx
index 02778df..4a39471 100644
--- a/apps/desktop/src/pages/Viewer/index.tsx
+++ b/apps/desktop/src/pages/Viewer/index.tsx
@@ -1,15 +1,27 @@
import { useLocation } from "react-router-dom";
+import { initCornerstone } from "./MprViewer/CornerstoneDicomLoader/init";
+import { CrosshairMpr } from "./MprViewer/Crosshair";
+import { useEffect, useState } from "react";
export const Viewer = () => {
+ const [cornerstoneLoaded, setCornerstoneLoaded] = useState(false);
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const SeriesInstanceUID = queryParams.get("SeriesInstanceUID"); // 获取URL参数
+ const StudyInstanceUID = queryParams.get("StudyInstanceUID"); // 获取URL参数
+
+ useEffect(() => {
+ initCornerstone().then(() => setCornerstoneLoaded(true));
+ }, []);
return (
- {SeriesInstanceUID}
-
找到一个本地dicom的mpr方案
- {/*
*/}
+ {cornerstoneLoaded && StudyInstanceUID && SeriesInstanceUID && (
+
+ )}
);
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 710000c..1123cd5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,6 +13,9 @@ importers:
'@ant-design/icons':
specifier: ^5.4.0
version: 5.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@cornerstonejs/calculate-suv':
+ specifier: 1.1.0
+ version: 1.1.0
'@cornerstonejs/core':
specifier: 1.84.4
version: 1.84.4(@babel/preset-env@7.25.4(@babel/core@7.25.2))(autoprefixer@10.4.20(postcss@8.4.41))(webpack@5.94.0)(wslink@2.1.3)
@@ -897,6 +900,10 @@ packages:
resolution: {integrity: sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==}
engines: {node: '>=6.9.0'}
+ '@cornerstonejs/calculate-suv@1.1.0':
+ resolution: {integrity: sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w==}
+ engines: {node: '>=10'}
+
'@cornerstonejs/codec-charls@1.2.3':
resolution: {integrity: sha512-qKUe6DN0dnGzhhfZLYhH9UZacMcudjxcaLXCrpxJImT/M/PQvZCT2rllu6VGJbWKJWG+dMVV2zmmleZcdJ7/cA==}
engines: {node: '>=0.14'}
@@ -6298,6 +6305,8 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
+ '@cornerstonejs/calculate-suv@1.1.0': {}
+
'@cornerstonejs/codec-charls@1.2.3': {}
'@cornerstonejs/codec-libjpeg-turbo-8bit@1.2.2': {}