2026-05-04-04-12-34 优化项目库导入和三维交互

This commit is contained in:
2026-05-04 04:20:30 +08:00
parent 26d3109f63
commit 4aad0f815d
8 changed files with 632 additions and 105 deletions

View File

@@ -356,6 +356,46 @@ function parseDicomPreview(filePath: string) {
};
}
function parseDicomPixels(filePath: string) {
const preview = parseDicomPreview(filePath);
return {
...preview,
pixelBuffer: Buffer.from(preview.pixels, 'base64'),
};
}
function createReformattedPreview(files: string[], plane: 'sagittal' | 'coronal', slice: number) {
const first = parseDicomPixels(path.join(dicomDir, files[0]));
const maxSlice = plane === 'sagittal' ? first.width - 1 : first.height - 1;
const clampedSlice = Math.max(0, Math.min(maxSlice, slice));
const outputWidth = files.length;
const outputHeight = plane === 'sagittal' ? first.height : first.width;
const pixels = Buffer.alloc(outputWidth * outputHeight);
files.forEach((fileName, z) => {
const frame = parseDicomPixels(path.join(dicomDir, fileName));
for (let row = 0; row < outputHeight; row += 1) {
const sourceIndex = plane === 'sagittal'
? row * frame.width + clampedSlice
: clampedSlice * frame.width + row;
const targetIndex = row * outputWidth + z;
pixels[targetIndex] = frame.pixelBuffer[sourceIndex] ?? 0;
}
});
return {
width: outputWidth,
height: outputHeight,
pixels: pixels.toString('base64'),
windowCenter: first.windowCenter,
windowWidth: first.windowWidth,
slice: clampedSlice,
total: maxSlice + 1,
fileName: `${plane}-${clampedSlice}`,
};
}
async function startServer() {
const app = express();
const host = process.argv.includes('--host') ? process.argv[process.argv.indexOf('--host') + 1] : '0.0.0.0';
@@ -445,6 +485,19 @@ async function startServer() {
res.json(project);
});
app.delete('/api/projects/:projectId', (req, res) => {
const state = readState();
const index = state.projects.findIndex((project) => project.id === req.params.projectId);
if (index < 0) {
res.status(404).json({ message: '项目不存在' });
return;
}
const [deleted] = state.projects.splice(index, 1);
writeState(state);
res.json({ ok: true, deletedId: deleted.id });
});
app.get('/api/projects/:projectId/dicom-preview', (req, res) => {
const project = findProject(readState(), req.params.projectId);
if (!project) {
@@ -458,15 +511,26 @@ async function startServer() {
return;
}
const requestedPlane = String(req.query.plane ?? 'axial');
const plane = requestedPlane === 'sagittal' || requestedPlane === 'coronal' ? requestedPlane : 'axial';
const requestedSlice = Number.parseInt(String(req.query.slice ?? '0'), 10);
const slice = Math.max(0, Math.min(files.length - 1, Number.isFinite(requestedSlice) ? requestedSlice : 0));
try {
const preview = parseDicomPreview(path.join(dicomDir, files[slice]));
if (plane === 'axial') {
const slice = Math.max(0, Math.min(files.length - 1, Number.isFinite(requestedSlice) ? requestedSlice : 0));
const preview = parseDicomPreview(path.join(dicomDir, files[slice]));
res.json({
...preview,
plane,
slice,
total: files.length,
fileName: files[slice],
});
return;
}
res.json({
...preview,
slice,
total: files.length,
fileName: files[slice],
...createReformattedPreview(files, plane, Number.isFinite(requestedSlice) ? requestedSlice : 0),
plane,
});
} catch (error) {
res.status(422).json({ message: error instanceof Error ? error.message : 'DICOM 预览失败' });