feat: 前端上传全部交后端

This commit is contained in:
mozzie 2024-08-07 10:28:05 +08:00
parent cc48e5742a
commit d69de380f7
7 changed files with 174 additions and 149 deletions

View File

@ -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;
};

View File

@ -0,0 +1 @@
export const EVENT_PARSE_DICOM = "PARSE_DICOM";

View File

@ -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);
}
});
};

View File

@ -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();
});
});

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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>
);
};