cvpilot-tool/apps/desktop/electron/core/pacs.ts

259 lines
7.6 KiB
TypeScript
Raw Normal View History

2024-09-10 22:03:12 +08:00
import { spawn } from "node:child_process";
2024-09-20 15:55:17 +08:00
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import log from "electron-log";
2024-09-10 22:03:12 +08:00
import path from "node:path";
import FormData from "form-data";
import { readFile } from "fs/promises";
import axios from "axios";
2024-09-20 15:55:17 +08:00
import { InstanceInfo, PatientInfo, SeriesInfo, StudyInfo } from "./pacs.type";
import { app } from "electron";
import pLimit from "p-limit";
2024-10-13 10:10:05 +08:00
import { pluginPath } from "./initial";
2024-09-10 22:03:12 +08:00
export const OrthancServerRoot = "http://localhost:8042";
2024-10-13 10:10:05 +08:00
export const getPacsPath = (platform: "macos" | "windows"): string => {
2024-09-10 22:03:12 +08:00
const orthancExecFile = {
macos: "orthanc-mac-24.8.1/Orthanc",
2024-10-13 10:10:05 +08:00
windows: "Orthanc.exe",
};
2024-10-13 10:10:05 +08:00
const execFilePath = path.join(
pluginPath,
"orthanc",
orthancExecFile[platform]
);
return execFilePath;
};
2024-09-10 22:03:12 +08:00
export const runOrthancServer = (pacsPath: string) => {
if (existsSync(pacsPath)) {
2024-09-12 12:55:37 +08:00
const configPath = path.join(path.dirname(pacsPath), "config.json");
const child_process = spawn(pacsPath, [configPath]);
2024-09-13 15:55:29 +08:00
child_process.stdout.on("data", (data) => log.info(data.toString()));
child_process.stderr.on("data", (data) => log.error(data.toString()));
2024-09-10 22:03:12 +08:00
} else {
console.error("pacsPath is a not exist");
log.error("pacsPath is a not exist");
}
};
/**
* pacs
* @param {string} filePath
* @param {string} orthancUrl orthanc的服务地址
* @returns
*/
export const uploadDicomFile = async (
filePath: string,
orthancUrl: string = OrthancServerRoot
): Promise<boolean> => {
try {
const buffer = await 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);
return false;
}
};
export const selectStructuredDicom = async (
orthancUrl: string = OrthancServerRoot
) => {
try {
const response = await axios.get(`${orthancUrl}/patients`);
const patientIds: string[] = response.data;
const patients: PatientInfo[] = [];
for (const patientId of patientIds) {
const patientDetailsResponse = await axios.get<PatientInfo>(
`${orthancUrl}/patients/${patientId}`
);
const patientDetails = patientDetailsResponse.data;
const studyIds = patientDetails.Studies;
const studies: StudyInfo[] = [];
for (const studyId of studyIds) {
const studyDetailsResponse = await axios.get<StudyInfo>(
`${orthancUrl}/studies/${studyId}`
);
const studyDetails = studyDetailsResponse.data;
const seriesIds = studyDetails.Series;
const series: SeriesInfo[] = [];
for (const seriesId of seriesIds) {
const seriesDetailsResponse = await axios.get<SeriesInfo>(
`${orthancUrl}/series/${seriesId}`
);
const seriesDetails = seriesDetailsResponse.data;
series.push({ ...seriesDetails });
}
studies.push({ ...studyDetails, children: series });
}
patients.push({ ...patientDetails, children: studies });
}
return patients;
} catch (error) {
console.error("Error fetching detailed patient information:", error);
throw error; // or handle it accordingly
2024-09-10 22:03:12 +08:00
}
};
2024-09-20 15:55:17 +08:00
/**
* SeriesInstanceUID DICOM
* @param seriesInstanceUID SeriesInstanceUID
* @returns
*/
export const downloadSeriesDicomFiles = async (
seriesInstanceUID: string
): Promise<string> => {
try {
// 使用 Orthanc 的查询接口查找匹配的序列 ID
const findResponse = await axios.post<string[]>(
`${OrthancServerRoot}/tools/find`,
{
Level: "Series",
Query: {
SeriesInstanceUID: seriesInstanceUID,
},
}
);
const seriesIds = findResponse.data;
if (seriesIds.length === 0) {
throw new Error("在 Orthanc 中未找到指定的 SeriesInstanceUID。");
}
const seriesId = seriesIds[0]; // 假设只有一个匹配的序列
// 获取该序列中的所有实例DICOM 文件)
const instancesResponse = await axios.get<string[]>(
`${OrthancServerRoot}/series/${seriesId}/instances`
);
const instances = instancesResponse.data as unknown as InstanceInfo[];
// 创建保存 DICOM 文件的本地目录
const saveDir = path.join(
app.getPath("userData"),
"dicom",
seriesInstanceUID
);
if (!existsSync(saveDir)) {
mkdirSync(saveDir, { recursive: true });
}
// 并发限制
const limit = pLimit(5); // 限制同时进行的请求数为 5
// 并行下载 DICOM 文件
await Promise.all(
instances.map((instance) => {
const instanceId = instance.ID;
return limit(async () => {
const dicomResponse = await axios.get<ArrayBuffer>(
`${OrthancServerRoot}/instances/${instanceId}/file`,
{
responseType: "arraybuffer",
}
);
const filePath = path.join(saveDir, `${instanceId}.dcm`);
writeFileSync(filePath, Buffer.from(dicomResponse.data), "binary");
});
})
);
console.log(`所有 DICOM 文件已保存至:${saveDir}`);
return saveDir;
} catch (error) {
console.error("下载 DICOM 文件时出错:", error);
throw error;
}
};
2024-09-23 17:14:07 +08:00
/**
* SeriesInstanceUID获取总的扫描长度
* @param seriesInstanceUID UID
* @returns
*/
2024-09-23 17:14:07 +08:00
export const getTotalScanLength = async (
seriesInstanceUID: string
): Promise<number | null> => {
try {
// 1. 查找序列ID
const findResponse = await axios.post(`${OrthancServerRoot}/tools/find`, {
Level: "Series",
Query: {
SeriesInstanceUID: seriesInstanceUID,
},
});
const seriesIds = findResponse.data;
if (seriesIds.length === 0) {
console.error("Series not found");
return null;
}
const seriesId = seriesIds[0];
// 2. 获取共享标签提取SliceThickness
const tagsResponse = await axios.get(
`${OrthancServerRoot}/series/${seriesId}/shared-tags`
);
const tags = tagsResponse.data;
const sliceThickness = parseFloat(tags["0018,0050"]["Value"]); // (0018,0050) SliceThickness
if (isNaN(sliceThickness)) {
console.error("SliceThickness not found");
return null;
}
// 3. 获取实例列表,计算层数
const instancesResponse = await axios.get(
`${OrthancServerRoot}/series/${seriesId}/instances`
);
const instances = instancesResponse.data;
const numberOfSlices = instances.length;
// 4. 计算总的扫描长度
const totalLength = Number(sliceThickness) * numberOfSlices;
return totalLength;
} catch (error) {
console.error("Error:", error);
return null;
}
};
2024-10-11 16:26:38 +08:00
/**
* metadata
* @param seriesInstanceUID
* @returns
*/
export const getMetadata = async (seriesInstanceUID: string) => {
const findResponse = await axios.post(`${OrthancServerRoot}/tools/find`, {
Level: "Series",
Query: {
SeriesInstanceUID: seriesInstanceUID,
},
});
const seriesIds = findResponse.data;
if (seriesIds.length === 0) {
console.error("Series not found");
return null;
}
const seriesId = seriesIds[0];
const tagsResponse = await axios.get(
`${OrthancServerRoot}/series/${seriesId}/shared-tags`
);
const tags = tagsResponse.data;
return tags;
};