feat: 上传dicom

This commit is contained in:
mozzie 2024-09-11 12:58:50 +08:00
parent 348c9df6bc
commit 34c826353b
11 changed files with 143 additions and 51 deletions

View File

@ -1,3 +0,0 @@
export const useDicomHandler = () => {
}

View File

@ -0,0 +1,12 @@
import { dialog, ipcMain } from "electron";
import { filterDicoms, uploadFilesInBatches } from "./util";
export const registerDicomHandler = () => {
ipcMain.handle("dicom:upload", async () => {
const dia = await dialog.showOpenDialog({ properties: ["openDirectory"] });
if (dia.canceled) return null;
const dcmPaths = await filterDicoms(dia.filePaths[0]);
uploadFilesInBatches(dcmPaths, 5);
// return dia.filePaths[0];
});
};

View File

@ -0,0 +1,101 @@
import path from "node:path";
import fs from "node:fs";
import axios from "axios";
import FormData from "form-data";
import log from "electron-log";
/**
* DICOM Magic Number
* @param filePath
* @returns DICOM
*/
const isDICOMFile = async (filePath: string) => {
try {
// 打开文件以进行读取
const fileHandle = await fs.promises.open(filePath, "r");
const buffer = Buffer.alloc(132); // 创建一个 132 字节的缓冲区
// 从文件中读取前 132 个字节
await fileHandle.read(buffer, 0, 132, 0);
await fileHandle.close(); // 关闭文件
// 检查 "DICM" 标识 (偏移 128-131 字节)
const magicNumber = buffer.toString("utf-8", 128, 132);
return magicNumber === "DICM";
} catch (error) {
console.error(`Error reading file ${filePath}:`, error);
return false;
}
};
/**
* .dcm文件
* @param dir
* @param fileList
* @returns
*/
export const filterDicoms = async (
dir: string,
fileList: string[] = []
): Promise<string[]> => {
const files = await fs.promises.readdir(dir, { withFileTypes: true });
await Promise.all(
files.map(async (file) => {
const filePath = path.join(dir, file.name);
if (file.isDirectory()) {
await filterDicoms(filePath, fileList); // 递归调用以遍历子目录
} else {
// if (await isDICOMFile(filePath))
fileList.push(filePath);
}
})
);
return fileList;
};
export const uploadDicomFile = async (
filePath: string,
orthancUrl: string = "http://localhost:8042"
) => {
try {
const buffer = await fs.promises.readFile(filePath);
const fd = new FormData();
fd.append("files", buffer);
const url = `${orthancUrl}/instances`;
const headers = { "Content-Type": "multipart/form-data" };
const { status } = await axios.post(url, fd, { headers });
return status === 200;
} catch (error) {
log.error("Failed to upload DICOM file:", error);
console.error("Failed to upload DICOM file:", error);
}
};
export const uploadFilesInBatches = async (
filePaths: string[],
batchSize: number,
orthancUrl: string = "http://localhost:8042"
) => {
const results = [];
const totalStartTime = Date.now(); // 记录总体开始时间
for (let i = 0; i < filePaths.length; i += batchSize) {
const batch = filePaths.slice(i, i + batchSize);
const batchResults = await Promise.allSettled(
batch.map((filePath) => uploadDicomFile(filePath, orthancUrl))
);
// 提取状态为 'fulfilled' 的结果的 value
const fulfilledResults = batchResults
.filter((result) => result.status === "fulfilled")
.map((result) => (result as PromiseFulfilledResult<boolean>).value);
results.push(...fulfilledResults);
}
const totalEndTime = Date.now(); // 记录总体结束时间
const totalSuccess = results.filter(Boolean).length;
const totalFailed = results.length - totalSuccess;
log.info(
`[上传序列] Success: ${totalSuccess}, Failed: ${totalFailed}, Total upload time: ${
totalEndTime - totalStartTime
} ms`
);
return results;
};

View File

@ -1,5 +1,5 @@
import { ipcMain } from "electron";
import { useDicomHandler } from "./dicom";
import { registerDicomHandler } from "./dicom/handler";
export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
ipcMain.removeAllListeners();
@ -9,5 +9,5 @@ export const registerIpcMainHandlers = (mainWindow: Electron.BrowserWindow) => {
*/
ipcMain.on("ipc-loaded", () => mainWindow.show());
useDicomHandler()
}
registerDicomHandler();
};

View File

@ -11,9 +11,8 @@ import { fileURLToPath } from "node:url";
import path from "node:path";
import { createDatabase } from "./core/db";
import { getMachineId } from "./core/auth";
import registerIpcMainHandlers from "./ipcMainHandlers";
import { getPacsPath, runOrthancServer } from "./core/pacs";
import { registerIpcMainHandlers } from "./ipcEvent";
// const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@ -68,7 +67,7 @@ function createWindow() {
if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL);
registerIpcMainHandlers(win);
runOrthancServer(getPacsPath(platform, true))
runOrthancServer(getPacsPath(platform, true));
// if (platform !== "macos") {
// python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
@ -78,16 +77,14 @@ function createWindow() {
// python_process = spawn(path.join(process.env.VITE_PUBLIC!, "main.exe"));
// }
win.loadFile(path.join(RENDERER_DIST, "index.html")).then(() => {
registerIpcMainHandlers(win);
runOrthancServer(getPacsPath(platform, false))
registerIpcMainHandlers(win!);
runOrthancServer(getPacsPath(platform, false));
// windows右键打开的目录路径
if (process.argv.length >= 2) {
const folderPath = process.argv[2];
win?.webContents.send("context-menu-launch", folderPath);
win!.webContents.send("context-menu-launch", folderPath);
}
});
}

View File

@ -64,7 +64,8 @@
"zod": "3.23.8",
"axios": "1.7.7",
"lodash": "4.17.21",
"electron-log": "5.2.0"
"electron-log": "5.2.0",
"form-data": "4.0.0"
},
"devDependencies": {
"@radix-ui/react-icons": "^1.3.0",

View File

@ -1,9 +0,0 @@
/**
* ipc typeinterface
*/
export namespace IPCEvents {
export enum Dicom {
Upload = 'dicom:upload',
Remove = 'dicom:remove',
}
}

View File

@ -13,7 +13,7 @@ import {
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { inferDeviceType } from "./type";
import { inferDevices } from "./constant";
@ -29,6 +29,7 @@ import {
} from "@/components/ui/dialog";
import { Progress } from "@/components/ui/progress";
import { RocketIcon } from "@radix-ui/react-icons";
import axios from "axios";
interface ScanProgress {
percentage: number;
@ -45,10 +46,10 @@ export const MenuBar = () => {
const [inferOption, setInferOption] =
useState<inferDeviceType[]>(inferDevices);
const [, setResult] = useState<[]>([]);
const [importDialogVisible, setImportDialogVisible] = useState(false);
const [, setResult] = useState<[]>([]);
useEffect(() => {
const handleScanProgress = (
_event: Electron.IpcRendererEvent,
@ -63,12 +64,6 @@ export const MenuBar = () => {
};
}, []);
useEffect(() => {
window.ipcRenderer.once("scan-start", () => {
setImportDialogVisible(true);
});
}, []);
useEffect(() => {
const handleScanFinished = (
_event: Electron.IpcRendererEvent,
@ -77,7 +72,6 @@ export const MenuBar = () => {
const { scanDuration, structDicom } = data;
console.log(data);
setResult(structDicom);
setImportDialogVisible(false);
if (data.error) {
return toast({
@ -112,15 +106,12 @@ export const MenuBar = () => {
};
}, [toast]);
useEffect(() => {
if (!importDialogVisible) {
setProgress(defaultProgress);
}
}, [importDialogVisible]);
const handleImportDicom = () => {
navigate("datasource");
window.ipcRenderer.send("import-dicom-dialog-visible");
// window.ipcRenderer.send("import-dicom-dialog-visible");
window.ipcRenderer.invoke("dicom:upload").then((value) => {
console.log(value);
});
};
/**

View File

@ -27,7 +27,7 @@
"paths": {
"@/*": [
"./src/*"
]
],
}
},
"include": [

View File

@ -3,7 +3,6 @@ import path from "node:path";
import electron from "vite-plugin-electron/simple";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [

View File

@ -112,6 +112,9 @@ importers:
flexlayout-react:
specifier: ^0.7.15
version: 0.7.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
form-data:
specifier: 4.0.0
version: 4.0.0
framer-motion:
specifier: ^11.3.24
version: 11.3.30(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -5802,7 +5805,7 @@ snapshots:
app-builder-bin@4.0.0: {}
app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/notarize': 2.2.1
@ -5816,7 +5819,7 @@ snapshots:
builder-util-runtime: 9.2.4
chromium-pickle-js: 0.2.0
debug: 4.3.6
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
ejs: 3.1.10
electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3)
electron-publish: 24.13.1
@ -6305,9 +6308,9 @@ snapshots:
dlv@1.1.3: {}
dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3):
dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
builder-util: 24.13.1
builder-util-runtime: 9.2.4
fs-extra: 10.1.0
@ -6375,7 +6378,7 @@ snapshots:
electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3):
dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
archiver: 5.3.2
builder-util: 24.13.1
fs-extra: 10.1.0
@ -6385,11 +6388,11 @@ snapshots:
electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
builder-util: 24.13.1
builder-util-runtime: 9.2.4
chalk: 4.1.2
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
fs-extra: 10.1.0
is-ci: 3.0.1
lazy-val: 1.0.5