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

View File

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

View File

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

View File

@ -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") {

View File

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

View File

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

View File

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