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 fs from "fs";
|
||||
|
||||
export interface StructuredData {
|
||||
[StudyInstanceUID: string]: {
|
||||
[SeriesInstanceUID: string]: ExtractMetadata[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExtractMetadata {
|
||||
filePath: string;
|
||||
StudyInstanceUID?: string;
|
||||
|
@ -57,7 +63,7 @@ export const parseDICOMFile = async (
|
|||
filePath,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
pixelData,
|
||||
// pixelData,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error parsing file ${filePath}:`, error);
|
||||
|
@ -91,3 +97,27 @@ export const processFilesInBatches = async (
|
|||
}
|
||||
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 { ipcMain } from "electron";
|
||||
import { dialog, ipcMain } from "electron";
|
||||
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 = () => {
|
||||
ipcMain.on("parseDicom", async (event, file: string) => {
|
||||
const rootFolder = path.dirname(file);
|
||||
const filePaths = await findDcmFiles(rootFolder);
|
||||
const batchSize = os.cpus().length * 1 || 10;
|
||||
console.time("分批处理");
|
||||
const result = await processFilesInBatches(filePaths, batchSize);
|
||||
console.log(result);
|
||||
console.timeEnd("分批处理");
|
||||
const registerIpcMainHandlers = (mainWindow) => {
|
||||
ipcMain.on(EVENT_PARSE_DICOM, async (event, file: string) => {
|
||||
const dirDialog = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (dirDialog.filePaths.length > 0) {
|
||||
const filePaths = await findDcmFiles(dirDialog.filePaths[0]);
|
||||
const batchSize = os.cpus().length * 1 || 10;
|
||||
console.time("分批处理");
|
||||
const unraw = await processFilesInBatches(filePaths, batchSize);
|
||||
console.timeEnd("分批处理");
|
||||
const result = structureMetadata(unraw);
|
||||
event.reply(EVENT_PARSE_DICOM + ":RES", result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ function createWindow() {
|
|||
titleBarStyle: "hidden", // customButtonsOnHover || hidden || hiddenInset
|
||||
titleBarOverlay: { height: 36, color: "#f8f8f8" }, // 渲染进程发消息动态改变这个
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.mjs")
|
||||
preload: path.join(__dirname, "preload.mjs"),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -117,7 +117,7 @@ app.on("activate", () => {
|
|||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
registerIpcMainHandlers();
|
||||
registerIpcMainHandlers(win);
|
||||
|
||||
// 设置 Dock 图标
|
||||
if (process.platform === "darwin") {
|
||||
|
@ -130,4 +130,4 @@ app.whenReady().then(() => {
|
|||
// 注销全局快捷键,当应用退出时
|
||||
app.on("will-quit", () => {
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
});
|
|
@ -1,44 +1,32 @@
|
|||
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 fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
e.persist();
|
||||
if (!fileInputRef.current?.files) return;
|
||||
// 第一个文件给主进程
|
||||
window.ipcRenderer.send("parseDicom", e.target.files?.[0].path ?? "");
|
||||
};
|
||||
const [uploadDicomInfo, setUploadDicomInfo] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
window.ipcRenderer.on("parseDicomResponse", (event, data) => {
|
||||
if (data.error) {
|
||||
console.error("Error parsing DICOM folder:", data.error);
|
||||
} else {
|
||||
console.log("DICOM Data Sets:", data);
|
||||
}
|
||||
window.ipcRenderer.on(EVENT_PARSE_DICOM + ":RES", (event, data) => {
|
||||
console.log(data);
|
||||
if (data.error) return;
|
||||
setUploadDicomInfo(data);
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.ipcRenderer.off("parseDicomResponse", () => {});
|
||||
window.ipcRenderer.off(EVENT_PARSE_DICOM + ":RES", () => {});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleOpenDialog = () => {
|
||||
window.ipcRenderer.send(EVENT_PARSE_DICOM);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Button onClick={() => fileInputRef.current?.click()}>选择dicom</Button>
|
||||
<input
|
||||
type="file"
|
||||
webkitdirectory="true"
|
||||
ref={fileInputRef}
|
||||
mozdirectory="true"
|
||||
multiple
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => handleFileChange(e)}
|
||||
/>
|
||||
<Button onClick={handleOpenDialog}>选择dicom</Button>
|
||||
</div>
|
||||
<div>{JSON.stringify(uploadDicomInfo)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import { Outlet, Link } from "react-router-dom";
|
||||
import { GoFileDirectory } from "react-icons/go";
|
||||
import { MenuBar } from './MenuBar'
|
||||
import { MenuBar } from "./MenuBar";
|
||||
|
||||
const LayoutMain = () => {
|
||||
const platform =
|
||||
document.querySelector("html")?.getAttribute("platform") ?? "macos";
|
||||
|
||||
|
||||
const titleBarStyles =
|
||||
platform === "macos" ? "pl-[5rem] pr-[.5rem]" : "pr-[10rem]";
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className={`title-bar drag h-[36px] flex`}>
|
||||
<div
|
||||
className={`flex-1 flex no-drag items-center justify-between ${titleBarStyles}`}
|
||||
className={`inline-flex no-drag items-center justify-between ${titleBarStyles}`}
|
||||
>
|
||||
<MenuBar />
|
||||
</div>
|
||||
|
|
|
@ -1,108 +1,109 @@
|
|||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from "@/components/ui/menubar"
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from "@/components/ui/menubar";
|
||||
|
||||
export const MenuBar = () => {
|
||||
return (
|
||||
<Menubar>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
New Window <MenubarShortcut>⌘N</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled>New Incognito Window</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Email link</MenubarItem>
|
||||
<MenubarItem>Messages</MenubarItem>
|
||||
<MenubarItem>Notes</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>
|
||||
Print... <MenubarShortcut>⌘P</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Edit</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Find</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Search the web</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Find...</MenubarItem>
|
||||
<MenubarItem>Find Next</MenubarItem>
|
||||
<MenubarItem>Find Previous</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Cut</MenubarItem>
|
||||
<MenubarItem>Copy</MenubarItem>
|
||||
<MenubarItem>Paste</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>
|
||||
Always Show Full URLs
|
||||
</MenubarCheckboxItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Reload <MenubarShortcut>⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled inset>
|
||||
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Hide Sidebar</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Profiles</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarRadioGroup value="benoit">
|
||||
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
|
||||
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
|
||||
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
|
||||
</MenubarRadioGroup>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Edit...</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Add Profile...</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
)
|
||||
|
||||
}
|
||||
return (
|
||||
<Menubar
|
||||
style={{ background: "transparent", border: 0, boxShadow: "none" }}
|
||||
>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
New Window <MenubarShortcut>⌘N</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled>New Incognito Window</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Email link</MenubarItem>
|
||||
<MenubarItem>Messages</MenubarItem>
|
||||
<MenubarItem>Notes</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>
|
||||
Print... <MenubarShortcut>⌘P</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Edit</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Find</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Search the web</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Find...</MenubarItem>
|
||||
<MenubarItem>Find Next</MenubarItem>
|
||||
<MenubarItem>Find Previous</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Cut</MenubarItem>
|
||||
<MenubarItem>Copy</MenubarItem>
|
||||
<MenubarItem>Paste</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>
|
||||
Always Show Full URLs
|
||||
</MenubarCheckboxItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Reload <MenubarShortcut>⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled inset>
|
||||
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Hide Sidebar</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Profiles</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarRadioGroup value="benoit">
|
||||
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
|
||||
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
|
||||
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
|
||||
</MenubarRadioGroup>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Edit...</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Add Profile...</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user