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"],
const batchSize = os.cpus().length * 1 || 10; });
console.time("分批处理"); if (dirDialog.filePaths.length > 0) {
const result = await processFilesInBatches(filePaths, batchSize); const filePaths = await findDcmFiles(dirDialog.filePaths[0]);
console.log(result); const batchSize = os.cpus().length * 1 || 10;
console.timeEnd("分批处理"); 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 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

@ -1,108 +1,109 @@
import { import {
Menubar, Menubar,
MenubarCheckboxItem, MenubarCheckboxItem,
MenubarContent, MenubarContent,
MenubarItem, MenubarItem,
MenubarMenu, MenubarMenu,
MenubarRadioGroup, MenubarRadioGroup,
MenubarRadioItem, MenubarRadioItem,
MenubarSeparator, MenubarSeparator,
MenubarShortcut, MenubarShortcut,
MenubarSub, MenubarSub,
MenubarSubContent, MenubarSubContent,
MenubarSubTrigger, MenubarSubTrigger,
MenubarTrigger, MenubarTrigger,
} from "@/components/ui/menubar" } from "@/components/ui/menubar";
export const MenuBar = () => { export const MenuBar = () => {
return ( return (
<Menubar> <Menubar
<MenubarMenu> style={{ background: "transparent", border: 0, boxShadow: "none" }}
<MenubarTrigger>File</MenubarTrigger> >
<MenubarContent> <MenubarMenu>
<MenubarItem> <MenubarTrigger>File</MenubarTrigger>
New Tab <MenubarShortcut>T</MenubarShortcut> <MenubarContent>
</MenubarItem> <MenubarItem>
<MenubarItem> New Tab <MenubarShortcut>T</MenubarShortcut>
New Window <MenubarShortcut>N</MenubarShortcut> </MenubarItem>
</MenubarItem> <MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem> New Window <MenubarShortcut>N</MenubarShortcut>
<MenubarSeparator /> </MenubarItem>
<MenubarSub> <MenubarItem disabled>New Incognito Window</MenubarItem>
<MenubarSubTrigger>Share</MenubarSubTrigger> <MenubarSeparator />
<MenubarSubContent> <MenubarSub>
<MenubarItem>Email link</MenubarItem> <MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarItem>Messages</MenubarItem> <MenubarSubContent>
<MenubarItem>Notes</MenubarItem> <MenubarItem>Email link</MenubarItem>
</MenubarSubContent> <MenubarItem>Messages</MenubarItem>
</MenubarSub> <MenubarItem>Notes</MenubarItem>
<MenubarSeparator /> </MenubarSubContent>
<MenubarItem> </MenubarSub>
Print... <MenubarShortcut>P</MenubarShortcut> <MenubarSeparator />
</MenubarItem> <MenubarItem>
</MenubarContent> Print... <MenubarShortcut>P</MenubarShortcut>
</MenubarMenu> </MenubarItem>
<MenubarMenu> </MenubarContent>
<MenubarTrigger>Edit</MenubarTrigger> </MenubarMenu>
<MenubarContent> <MenubarMenu>
<MenubarItem> <MenubarTrigger>Edit</MenubarTrigger>
Undo <MenubarShortcut>Z</MenubarShortcut> <MenubarContent>
</MenubarItem> <MenubarItem>
<MenubarItem> Undo <MenubarShortcut>Z</MenubarShortcut>
Redo <MenubarShortcut>Z</MenubarShortcut> </MenubarItem>
</MenubarItem> <MenubarItem>
<MenubarSeparator /> Redo <MenubarShortcut>Z</MenubarShortcut>
<MenubarSub> </MenubarItem>
<MenubarSubTrigger>Find</MenubarSubTrigger> <MenubarSeparator />
<MenubarSubContent> <MenubarSub>
<MenubarItem>Search the web</MenubarItem> <MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSeparator /> <MenubarSubContent>
<MenubarItem>Find...</MenubarItem> <MenubarItem>Search the web</MenubarItem>
<MenubarItem>Find Next</MenubarItem> <MenubarSeparator />
<MenubarItem>Find Previous</MenubarItem> <MenubarItem>Find...</MenubarItem>
</MenubarSubContent> <MenubarItem>Find Next</MenubarItem>
</MenubarSub> <MenubarItem>Find Previous</MenubarItem>
<MenubarSeparator /> </MenubarSubContent>
<MenubarItem>Cut</MenubarItem> </MenubarSub>
<MenubarItem>Copy</MenubarItem> <MenubarSeparator />
<MenubarItem>Paste</MenubarItem> <MenubarItem>Cut</MenubarItem>
</MenubarContent> <MenubarItem>Copy</MenubarItem>
</MenubarMenu> <MenubarItem>Paste</MenubarItem>
<MenubarMenu> </MenubarContent>
<MenubarTrigger>View</MenubarTrigger> </MenubarMenu>
<MenubarContent> <MenubarMenu>
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem> <MenubarTrigger>View</MenubarTrigger>
<MenubarCheckboxItem checked> <MenubarContent>
Always Show Full URLs <MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
</MenubarCheckboxItem> <MenubarCheckboxItem checked>
<MenubarSeparator /> Always Show Full URLs
<MenubarItem inset> </MenubarCheckboxItem>
Reload <MenubarShortcut>R</MenubarShortcut> <MenubarSeparator />
</MenubarItem> <MenubarItem inset>
<MenubarItem disabled inset> Reload <MenubarShortcut>R</MenubarShortcut>
Force Reload <MenubarShortcut>R</MenubarShortcut> </MenubarItem>
</MenubarItem> <MenubarItem disabled inset>
<MenubarSeparator /> Force Reload <MenubarShortcut>R</MenubarShortcut>
<MenubarItem inset>Toggle Fullscreen</MenubarItem> </MenubarItem>
<MenubarSeparator /> <MenubarSeparator />
<MenubarItem inset>Hide Sidebar</MenubarItem> <MenubarItem inset>Toggle Fullscreen</MenubarItem>
</MenubarContent> <MenubarSeparator />
</MenubarMenu> <MenubarItem inset>Hide Sidebar</MenubarItem>
<MenubarMenu> </MenubarContent>
<MenubarTrigger>Profiles</MenubarTrigger> </MenubarMenu>
<MenubarContent> <MenubarMenu>
<MenubarRadioGroup value="benoit"> <MenubarTrigger>Profiles</MenubarTrigger>
<MenubarRadioItem value="andy">Andy</MenubarRadioItem> <MenubarContent>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem> <MenubarRadioGroup value="benoit">
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem> <MenubarRadioItem value="andy">Andy</MenubarRadioItem>
</MenubarRadioGroup> <MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarSeparator /> <MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
<MenubarItem inset>Edit...</MenubarItem> </MenubarRadioGroup>
<MenubarSeparator /> <MenubarSeparator />
<MenubarItem inset>Add Profile...</MenubarItem> <MenubarItem inset>Edit...</MenubarItem>
</MenubarContent> <MenubarSeparator />
</MenubarMenu> <MenubarItem inset>Add Profile...</MenubarItem>
</Menubar> </MenubarContent>
) </MenubarMenu>
</Menubar>
} );
};