feat: dicom文件hash去重 & 渲染进度
This commit is contained in:
parent
abf1cc06b5
commit
3ada54cf0f
|
@ -1,6 +1,7 @@
|
|||
import path from "path";
|
||||
import * as dicomParser from "dicom-parser";
|
||||
import fs from "fs";
|
||||
import crypto from 'crypto';
|
||||
|
||||
export interface StructuredData {
|
||||
[SeriesInstanceUID: string]: ExtractMetadata[];
|
||||
|
@ -128,35 +129,58 @@ export interface StructuredMetadata {
|
|||
StudyInstanceUID?: string;
|
||||
SeriesInstanceUID?: string;
|
||||
PatientName?: string;
|
||||
fileHash: string[]
|
||||
}
|
||||
|
||||
export const structureMetadata = (
|
||||
data: ExtractMetadata[]
|
||||
): StructuredMetadata[] => {
|
||||
export interface ScanProgress {
|
||||
percentage: number
|
||||
}
|
||||
|
||||
// 计算文件的哈希值
|
||||
async function calculateFileHash(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
const stream = fs.createReadStream(filePath);
|
||||
|
||||
stream.on('error', reject);
|
||||
stream.pipe(hash).on('finish', () => resolve(hash.digest('hex')));
|
||||
});
|
||||
}
|
||||
|
||||
export const structureMetadata = async (
|
||||
data: ExtractMetadata[],
|
||||
progressCallback?: (progress: ScanProgress) => void
|
||||
): Promise<StructuredMetadata[]> => {
|
||||
const result: StructuredMetadata[] = [];
|
||||
// 序列级别
|
||||
const keyProp = 'SeriesInstanceUID'
|
||||
|
||||
data.forEach((item) => {
|
||||
// 查找是否已经有相同 UID 和 PatientName 的条目
|
||||
const existingEntry = result.find(
|
||||
(entry) =>
|
||||
entry.StudyInstanceUID === item.StudyInstanceUID &&
|
||||
entry.SeriesInstanceUID === item.SeriesInstanceUID &&
|
||||
entry.PatientName === item.PatientName
|
||||
);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i]
|
||||
const existItem = result.find((i) => i[keyProp] === item[keyProp]);
|
||||
const hash = await calculateFileHash(item.filePath);
|
||||
|
||||
if (existingEntry) {
|
||||
if (existItem) {
|
||||
// 如果找到了相同的条目,合并 filePath
|
||||
existingEntry.filePaths.push(item.filePath);
|
||||
if (!existItem.fileHash.includes(hash)) {
|
||||
existItem.filePaths.push(item.filePath);
|
||||
existItem.fileHash.push(hash)
|
||||
}
|
||||
} else {
|
||||
// 如果没有找到,创建一个新的条目
|
||||
result.push({
|
||||
filePaths: [item.filePath],
|
||||
fileHash: [hash],
|
||||
StudyInstanceUID: item.StudyInstanceUID,
|
||||
SeriesInstanceUID: item.SeriesInstanceUID,
|
||||
PatientName: item.PatientName,
|
||||
});
|
||||
}
|
||||
});
|
||||
const progress: ScanProgress = {
|
||||
percentage: ((i + 1) / data.length) * 100
|
||||
};
|
||||
progressCallback?.(progress);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -47,17 +47,19 @@ const registerIpcMainHandlers = (
|
|||
running ? pythonManager.startFlask() : pythonManager.stopFlask();
|
||||
});
|
||||
|
||||
ipcMain.on("scan-dicom", async () => {
|
||||
ipcMain.on("scan-dicom", async (event) => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (result.canceled) return null;
|
||||
const filePaths = result.filePaths[0];
|
||||
const dcmPaths = await findDcmFiles(filePaths);
|
||||
const items = await processFilesInBatches(dcmPaths, 4);
|
||||
const structDicom = await structureMetadata(items);
|
||||
Database.getDb().data.posts.push("hello world");
|
||||
return structDicom;
|
||||
const dcmPaths = await findDcmFiles(filePaths)
|
||||
const batchSize = os.cpus().length * 1 || 10;
|
||||
const items = await processFilesInBatches(dcmPaths, batchSize);
|
||||
const structDicom = await structureMetadata(items, (progress) => {
|
||||
event.reply('scan-progress', progress)
|
||||
});
|
||||
event.reply('scan-progress-done', structDicom)
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>Cvpilot AI</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
@ -1,12 +1,46 @@
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface ScanProgress {
|
||||
percentage: number
|
||||
}
|
||||
|
||||
export const Datasource = () => {
|
||||
const [progress, setProgress] = useState<ScanProgress>()
|
||||
const [result, setResult] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
window.ipcRenderer.on('scan-progress', (event, data) => {
|
||||
setProgress(data)
|
||||
if (data.error) return;
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.ipcRenderer.off('scan-progress', () => { });
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.ipcRenderer.on('scan-progress-done', (event, data) => {
|
||||
console.log('%capps\desktop\src\pages\Datasource\index.tsx:25 data', 'color: #007acc;', data);
|
||||
setResult(data)
|
||||
if (data.error) return;
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.ipcRenderer.off('scan-progress-done', () => { });
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div>
|
||||
<Button onClick={() => window.ipcRenderer.send("scan-dicom")}>
|
||||
上传
|
||||
</Button>
|
||||
<div>{progress?.percentage}</div>
|
||||
<div>{JSON.stringify(result)}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user