feat: 前端上传全部交后端
This commit is contained in:
parent
cc48e5742a
commit
d69de380f7
|
@ -2,6 +2,12 @@ import path from "path";
|
||||||
import * as dicomParser from "dicom-parser";
|
import * as dicomParser from "dicom-parser";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
|
export interface StructuredData {
|
||||||
|
[StudyInstanceUID: string]: {
|
||||||
|
[SeriesInstanceUID: string]: ExtractMetadata[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExtractMetadata {
|
export interface ExtractMetadata {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
StudyInstanceUID?: string;
|
StudyInstanceUID?: string;
|
||||||
|
@ -57,7 +63,7 @@ export const parseDICOMFile = async (
|
||||||
filePath,
|
filePath,
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
pixelData,
|
// pixelData,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error parsing file ${filePath}:`, error);
|
console.error(`Error parsing file ${filePath}:`, error);
|
||||||
|
@ -91,3 +97,27 @@ export const processFilesInBatches = async (
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const structureMetadata = (data: ExtractMetadata[]): StructuredData => {
|
||||||
|
const structured: StructuredData = {};
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
// 确保每个元素都有有效的 StudyInstanceUID 和 SeriesInstanceUID
|
||||||
|
if (item.StudyInstanceUID && item.SeriesInstanceUID) {
|
||||||
|
// 如果还没有为这个 StudyInstanceUID 创建记录,则初始化一个空对象
|
||||||
|
if (!structured[item.StudyInstanceUID]) {
|
||||||
|
structured[item.StudyInstanceUID] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果这个 StudyInstanceUID 下还没有这个 SeriesInstanceUID 的记录,则初始化一个空数组
|
||||||
|
if (!structured[item.StudyInstanceUID][item.SeriesInstanceUID]) {
|
||||||
|
structured[item.StudyInstanceUID][item.SeriesInstanceUID] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将当前元素添加到对应的数组中
|
||||||
|
structured[item.StudyInstanceUID][item.SeriesInstanceUID].push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return structured;
|
||||||
|
};
|
||||||
|
|
1
electron/ipcEvent/index.ts
Normal file
1
electron/ipcEvent/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const EVENT_PARSE_DICOM = "PARSE_DICOM";
|
|
@ -1,20 +1,26 @@
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { ipcMain } from "electron";
|
import { dialog, ipcMain } from "electron";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { findDcmFiles, processFilesInBatches } from "./core/dicom";
|
import { findDcmFiles, processFilesInBatches, structureMetadata } from "./core/dicom";
|
||||||
|
import { EVENT_PARSE_DICOM } from "./ipcEvent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染进程和主进程的事件调度
|
* 渲染进程和主进程的事件调度
|
||||||
*/
|
*/
|
||||||
const registerIpcMainHandlers = () => {
|
const registerIpcMainHandlers = (mainWindow) => {
|
||||||
ipcMain.on("parseDicom", async (event, file: string) => {
|
ipcMain.on(EVENT_PARSE_DICOM, async (event, file: string) => {
|
||||||
const rootFolder = path.dirname(file);
|
const dirDialog = await dialog.showOpenDialog(mainWindow, {
|
||||||
const filePaths = await findDcmFiles(rootFolder);
|
properties: ["openDirectory"],
|
||||||
|
});
|
||||||
|
if (dirDialog.filePaths.length > 0) {
|
||||||
|
const filePaths = await findDcmFiles(dirDialog.filePaths[0]);
|
||||||
const batchSize = os.cpus().length * 1 || 10;
|
const batchSize = os.cpus().length * 1 || 10;
|
||||||
console.time("分批处理");
|
console.time("分批处理");
|
||||||
const result = await processFilesInBatches(filePaths, batchSize);
|
const unraw = await processFilesInBatches(filePaths, batchSize);
|
||||||
console.log(result);
|
|
||||||
console.timeEnd("分批处理");
|
console.timeEnd("分批处理");
|
||||||
|
const result = structureMetadata(unraw);
|
||||||
|
event.reply(EVENT_PARSE_DICOM + ":RES", result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ function createWindow() {
|
||||||
titleBarStyle: "hidden", // customButtonsOnHover || hidden || hiddenInset
|
titleBarStyle: "hidden", // customButtonsOnHover || hidden || hiddenInset
|
||||||
titleBarOverlay: { height: 36, color: "#f8f8f8" }, // 渲染进程发消息动态改变这个
|
titleBarOverlay: { height: 36, color: "#f8f8f8" }, // 渲染进程发消息动态改变这个
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, "preload.mjs")
|
preload: path.join(__dirname, "preload.mjs"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ app.on("activate", () => {
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
createWindow();
|
createWindow();
|
||||||
registerIpcMainHandlers();
|
registerIpcMainHandlers(win);
|
||||||
|
|
||||||
// 设置 Dock 图标
|
// 设置 Dock 图标
|
||||||
if (process.platform === "darwin") {
|
if (process.platform === "darwin") {
|
||||||
|
|
|
@ -1,44 +1,32 @@
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useEffect, useRef } from "react";
|
import { EVENT_PARSE_DICOM } from "../../../electron/ipcEvent";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const Aorta = () => {
|
const Aorta = () => {
|
||||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
const [uploadDicomInfo, setUploadDicomInfo] = useState([]);
|
||||||
|
|
||||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
e.persist();
|
|
||||||
if (!fileInputRef.current?.files) return;
|
|
||||||
// 第一个文件给主进程
|
|
||||||
window.ipcRenderer.send("parseDicom", e.target.files?.[0].path ?? "");
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.ipcRenderer.on("parseDicomResponse", (event, data) => {
|
window.ipcRenderer.on(EVENT_PARSE_DICOM + ":RES", (event, data) => {
|
||||||
if (data.error) {
|
console.log(data);
|
||||||
console.error("Error parsing DICOM folder:", data.error);
|
if (data.error) return;
|
||||||
} else {
|
setUploadDicomInfo(data);
|
||||||
console.log("DICOM Data Sets:", data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.ipcRenderer.off("parseDicomResponse", () => {});
|
window.ipcRenderer.off(EVENT_PARSE_DICOM + ":RES", () => {});
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenDialog = () => {
|
||||||
|
window.ipcRenderer.send(EVENT_PARSE_DICOM);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||||
<Button onClick={() => fileInputRef.current?.click()}>选择dicom</Button>
|
<Button onClick={handleOpenDialog}>选择dicom</Button>
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
webkitdirectory="true"
|
|
||||||
ref={fileInputRef}
|
|
||||||
mozdirectory="true"
|
|
||||||
multiple
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={(e) => handleFileChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>{JSON.stringify(uploadDicomInfo)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
import { Outlet, Link } from "react-router-dom";
|
import { Outlet, Link } from "react-router-dom";
|
||||||
import { GoFileDirectory } from "react-icons/go";
|
import { GoFileDirectory } from "react-icons/go";
|
||||||
import { MenuBar } from './MenuBar'
|
import { MenuBar } from "./MenuBar";
|
||||||
|
|
||||||
const LayoutMain = () => {
|
const LayoutMain = () => {
|
||||||
const platform =
|
const platform =
|
||||||
document.querySelector("html")?.getAttribute("platform") ?? "macos";
|
document.querySelector("html")?.getAttribute("platform") ?? "macos";
|
||||||
|
|
||||||
|
|
||||||
const titleBarStyles =
|
const titleBarStyles =
|
||||||
platform === "macos" ? "pl-[5rem] pr-[.5rem]" : "pr-[10rem]";
|
platform === "macos" ? "pl-[5rem] pr-[.5rem]" : "pr-[10rem]";
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<div className={`title-bar drag h-[36px] flex`}>
|
<div className={`title-bar drag h-[36px] flex`}>
|
||||||
<div
|
<div
|
||||||
className={`flex-1 flex no-drag items-center justify-between ${titleBarStyles}`}
|
className={`inline-flex no-drag items-center justify-between ${titleBarStyles}`}
|
||||||
>
|
>
|
||||||
<MenuBar />
|
<MenuBar />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,11 +12,13 @@ import {
|
||||||
MenubarSubContent,
|
MenubarSubContent,
|
||||||
MenubarSubTrigger,
|
MenubarSubTrigger,
|
||||||
MenubarTrigger,
|
MenubarTrigger,
|
||||||
} from "@/components/ui/menubar"
|
} from "@/components/ui/menubar";
|
||||||
|
|
||||||
export const MenuBar = () => {
|
export const MenuBar = () => {
|
||||||
return (
|
return (
|
||||||
<Menubar>
|
<Menubar
|
||||||
|
style={{ background: "transparent", border: 0, boxShadow: "none" }}
|
||||||
|
>
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>File</MenubarTrigger>
|
<MenubarTrigger>File</MenubarTrigger>
|
||||||
<MenubarContent>
|
<MenubarContent>
|
||||||
|
@ -103,6 +105,5 @@ export const MenuBar = () => {
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</Menubar>
|
</Menubar>
|
||||||
)
|
);
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user