Files
his/MD/specs/RECONSTRUCTION_3D_DEEP_DESIGN.md
华佗 f87b9215c1 feat(V31): CSSD消毒供应追溯+影像3D重建(选配模块深度实现)
- V31 Flyway: 8张新表(CSSD器械包/追溯记录/灭菌批次/灭菌明细/过期预警+3D重建任务/结果/报告)
- CSSD: 全流程追溯(回收→清洗→消毒→包装→灭菌→储存→发放)+扫码+灭菌三要素+过期预警
- 3D重建: Cornerstone.js+VTK.js架构设计+VR/MPR/MIP渲染+传递函数预设+测量工具
- 608行深度技术设计文档(MD/specs/RECONSTRUCTION_3D_DEEP_DESIGN.md)
- 2个前端页面(CSSD追溯/3D重建查看器)
- 后端编译通过,前端构建通过
2026-06-07 09:30:00 +08:00

24 KiB
Raw Blame History

影像3D重建 — 深度技术设计文档

文档类型: 深度技术设计 版本: v1.0 编制日期: 2026-06-07 技术栈: Cornerstone.js(DICOM解析) + VTK.js(3D渲染) + Spring Boot(后端处理)


一、技术选型分析

1.1 前端3D渲染方案对比

方案 优点 缺点 推荐度
Cornerstone.js + VTK.js 专为医学影像设计DICOM原生支持WebGL GPU加速 学习曲线较陡
Three.js 通用3D引擎社区大 无DICOM支持需自行解析
OHIF Viewer 完整PACS查看器 太重,集成复杂
MITK 功能全面的医学影像工具包 C++为主Web支持弱

推荐方案: Cornerstone.js + VTK.js

  • Cornerstone.js: DICOM图像解析、2D查看、MPR重建
  • VTK.js: 容积渲染(VR)、等值面提取、3D测量

1.2 技术架构

┌─────────────────────────────────────────────────────────┐
│                    前端 (Vue 3 + Vite)                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────┐ │
│  │Cornerstone│  │  VTK.js  │  │测量工具栏│  │报告编辑器│ │
│  │ DICOM解析 │  │ 3D渲染   │  │距离/角度 │  │所见/印象 │ │
│  │ 2D/MPR   │  │VR/MIP    │  │体积/面积 │  │结论     │ │
│  └─────┬────┘  └─────┬────┘  └────┬─────┘  └────┬────┘ │
│        └─────────────┴────────────┴──────────────┘      │
│                          ↓ HTTP/REST API                  │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│                 后端 (Spring Boot 4.0.6)                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────┐ │
│  │DICOM解析  │  │任务调度   │  │结果存储   │  │PACS对接  │ │
│  │dcm4che   │  │异步处理   │  │MinIO/NFS │  │WADO-RS  │ │
│  └──────────┘  └──────────┘  └──────────┘  └─────────┘ │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│                   存储层                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │PostgreSQL │  │文件存储   │  │PACS系统   │              │
│  │ 元数据    │  │MinIO/NFS │  │DICOM节点  │              │
│  └──────────┘  └──────────┘  └──────────┘              │
└─────────────────────────────────────────────────────────┘

二、后端深度设计

2.1 DICOM解析服务

2.1.1 dcm4che集成

// DICOM文件解析流程
public class DicomParserService {
    
    // 解析DICOM文件元数据
    public DicomMetadata parseDicomFile(InputStream dicomStream) {
        // 1. 使用dcm4che读取DICOM文件
        Dataset ds = DicomInputStream.read(dicomStream);
        
        // 2. 提取关键元数据
        DicomMetadata metadata = new DicomMetadata();
        metadata.setPatientName(ds.getString(Tag.PatientName));        // 患者姓名
        metadata.setPatientId(ds.getString(Tag.PatientID));            // 患者ID
        metadata.setStudyInstanceUID(ds.getString(Tag.StudyInstanceUID)); // 检查UID
        metadata.setSeriesInstanceUID(ds.getString(Tag.SeriesInstanceUID)); // 序列UID
        metadata.setSopInstanceUID(ds.getString(Tag.SOPInstanceUID));  // 实例UID
        metadata.setModality(ds.getString(Tag.Modality));              // CT/MRI/US
        metadata.setStudyDate(ds.getString(Tag.StudyDate));            // 检查日期
        metadata.setBodyPartExamined(ds.getString(Tag.BodyPartExamined)); // 检查部位
        metadata.setImageOrientationPatient(ds.getStrings(Tag.ImageOrientationPatient)); // 图像方向
        metadata.setImagePositionPatient(ds.getStrings(Tag.ImagePositionPatient)); // 图像位置
        metadata.setPixelSpacing(ds.getStrings(Tag.PixelSpacing));     // 像素间距
        metadata.setSliceThickness(ds.getString(Tag.SliceThickness));  // 层厚
        metadata.setRows(ds.getInt(Tag.Rows));                         // 行数
        metadata.setColumns(ds.getInt(Tag.Columns));                   // 列数
        metadata.setBitsAllocated(ds.getInt(Tag.BitsAllocated));       // 位深
        metadata.setWindowCenter(ds.getString(Tag.WindowCenter));      // 窗位
        metadata.setWindowWidth(ds.getString(Tag.WindowWidth));        // 窗宽
        
        // 3. 提取像素数据
        byte[] pixelData = ds.getBytes(Tag.PixelData);
        metadata.setPixelData(pixelData);
        
        return metadata;
    }
    
    // 批量解析同一Series的所有DICOM文件
    public List<DicomMetadata> parseSeries(List<InputStream> dicomFiles) {
        List<DicomMetadata> series = new ArrayList<>();
        for (InputStream file : dicomFiles) {
            series.add(parseDicomFile(file));
        }
        // 按ImagePositionPatient排序(确保层序正确)
        series.sort(Comparator.comparing(m -> 
            Double.parseDouble(m.getImagePositionPatient()[2])));
        return series;
    }
}

2.1.2 3D重建处理服务

// 3D重建处理服务
@Service
public class ReconstructionProcessingService {
    
    @Async("reconstructionExecutor")
    public void processReconstruction(Long taskId) {
        // 1. 获取任务信息
        ReconstructionTask task = taskMapper.selectById(taskId);
        task.setTaskStatus("PROCESSING");
        taskMapper.updateById(task);
        
        try {
            // 2. 加载DICOM序列数据
            List<DicomMetadata> series = loadDicomSeries(task.getApplyId());
            
            // 3. 预处理: 去噪 + 窗宽窗位调整
            float[][][] volumeData = preprocessVolume(series);
            
            // 4. 根据重建类型执行
            switch (task.getReconstructionType()) {
                case "VR":  // 容积渲染
                    processVolumeRendering(task, volumeData);
                    break;
                case "MPR": // 多平面重建
                    processMPR(task, volumeData);
                    break;
                case "MIP": // 最大密度投影
                    processMIP(task, volumeData);
                    break;
                case "VR+MPR": // 混合重建
                    processVolumeRendering(task, volumeData);
                    processMPR(task, volumeData);
                    break;
            }
            
            // 5. 生成结果截图
            saveResultImages(task);
            
            // 6. 更新任务状态
            task.setTaskStatus("COMPLETED");
            task.setCompleteTime(new Date());
            task.setResultPath("/reconstruction/" + taskId + "/");
            taskMapper.updateById(task);
            
        } catch (Exception e) {
            task.setTaskStatus("FAILED");
            taskMapper.updateById(task);
            log.error("3D重建任务失败: {}", taskId, e);
        }
    }
    
    // 容积渲染(Volume Rendering)
    private void processVolumeRendering(ReconstructionTask task, float[][][] volume) {
        // 1. 建立体数据(Volume Data)
        int dimX = volume.length;
        int dimY = volume[0].length;
        int dimZ = volume[0][0].length;
        
        // 2. 传递函数(Transfer Function)设置
        // CT值 → 颜色+透明度
        // 骨骼: 高CT值(>300), 不透明, 白色
        // 软组织: 中CT值(30-300), 半透明, 粉色
        // 空气: 低CT值(<-500), 全透明
        TransferFunction tf = new TransferFunction();
        tf.addMapping(-1000, 0.0f, 0.0f, 0.0f, 0.0f);   // 空气: 全透明
        tf.addMapping(-500,  0.0f, 0.0f, 0.0f, 0.0f);    // 肺: 全透明
        tf.addMapping(30,    0.8f, 0.2f, 0.2f, 0.4f);    // 软组织: 半透明粉红
        tf.addMapping(300,   0.9f, 0.9f, 0.8f, 0.9f);    // 骨骼: 不透明白
        tf.addMapping(3000,  1.0f, 1.0f, 1.0f, 1.0f);    // 金属: 全不透明
        
        // 3. 光线投射(Ray Casting)算法
        // 从每个像素发射光线,沿光线采样,累积颜色和透明度
        // C(积累) = Σ(Ci * αi * Π(1-αj))
        
        // 4. 保存渲染结果为PNG
        saveVolumeRenderingResult(task, tf);
    }
    
    // 多平面重建(Multi-Planar Reconstruction)
    private void processMPR(ReconstructionTask task, float[][][] volume) {
        // 1. 矢状面(Sagittal)重建: 沿X轴切割
        float[][] sagittalPlane = extractSagittalPlane(volume, volume.length / 2);
        
        // 2. 冠状面(Coronal)重建: 沿Y轴切割
        float[][] coronalPlane = extractCoronalPlane(volume, volume[0].length / 2);
        
        // 3. 轴位(Axial)重建: 沿Z轴切割(原始方向)
        float[][] axialPlane = extractAxialPlane(volume, volume[0][0].length / 2);
        
        // 4. 交互式切割: 支持任意角度平面
        // 通过旋转矩阵变换采样坐标
        
        saveMPRResult(task, sagittalPlane, coronalPlane, axialPlane);
    }
    
    // 最大密度投影(Maximum Intensity Projection)
    private void processMIP(ReconstructionTask task, float[][][] volume) {
        int dimX = volume.length;
        int dimY = volume[0].length;
        int dimZ = volume[0][0].length;
        
        float[][] mipImage = new float[dimX][dimY];
        for (int x = 0; x < dimX; x++) {
            for (int y = 0; y < dimY; y++) {
                float maxVal = Float.MIN_VALUE;
                for (int z = 0; z < dimZ; z++) {
                    maxVal = Math.max(maxVal, volume[x][y][z]);
                }
                mipImage[x][y] = maxVal;
            }
        }
        saveMIPResult(task, mipImage);
    }
}

2.2 DICOM存储方案

存储架构:
├── PostgreSQL     → 元数据(患者/检查/序列/任务/报告)
├── MinIO/NFS      → DICOM原始文件 + 重建结果(截图/体数据)
└── PACS系统       → 通过WADO-RS/DICOMweb获取DICOM图像

获取DICOM数据流程:
1. 任务创建时 → 从PACS获取StudyUID对应的DICOM文件
2. 使用WADO-RS协议: GET /dicomweb/studies/{studyUid}/series/{seriesUid}
3. 下载到本地临时目录 → 解析 → 处理 → 清理临时文件
4. 重建结果保存到MinIO → 元数据保存到PostgreSQL

2.3 接口设计(完整版)

API 方法 说明 参数
/reconstruction/task/page GET 任务列表(分页+筛选) patientName,modality,status,pageNo,pageSize
/reconstruction/task/add POST 新建任务(从PACS拉取) patientId,studyUid,modality,bodyPart,reconstructionType
/reconstruction/task/{id} GET 任务详情 -
/reconstruction/task/cancel/{id} PUT 取消任务 -
/reconstruction/result/list/{taskId} GET 重建结果列表 -
/reconstruction/result/{id}/image GET 获取结果截图 width,height,window
/reconstruction/result/{id}/volume GET 获取体数据(JSON格式) resolution
/reconstruction/report/add POST 新建报告 taskId,findings,impression,conclusion
/reconstruction/report/{id} GET 报告详情 -
/reconstruction/report/verify/{id} PUT 审核报告 verifyDoctor
/reconstruction/stats GET 统计概览 startDate,endDate

三、前端深度设计

3.1 技术栈

{
  "cornerstone-core": "^2.6.1",        // DICOM图像解析与2D显示
  "cornerstone-wado-image-loader": "^4.13.2",  // WADO加载器
  "cornerstone-tools": "^7.1.0",       // 交互工具(测量/标注)
  "vtk.js": "^29.0.0",                // 3D渲染引擎(WebGL)
  "dicom-parser": "^1.8.21"           // DICOM文件解析
}

3.2 组件架构

src/views/reconstruction/
├── index.vue                    # 主页面(任务列表+工作台)
├── api.js                       # API接口
├── components/
│   ├── DicomViewer.vue          # 2D DICOM查看器(Cornerstone)
│   ├── MprViewer.vue            # MPR多平面重建查看器
│   ├── VrViewer.vue             # VR容积渲染查看器(VTK.js)
│   ├── MipViewer.vue            # MIP最大密度投影查看器
│   ├── MeasurementToolbar.vue   # 测量工具栏
│   ├── ReconstructionTaskList.vue # 任务列表
│   ├── ReconstructionReport.vue # 报告编辑器
│   └── ReconstructionStats.vue  # 统计面板

3.3 核心组件实现

DicomViewer.vue (2D DICOM查看器)

<template>
  <div class="dicom-viewer">
    <div ref="viewerContainer" class="viewer-container" />
    <div class="toolbar">
      <el-button-group>
        <el-button @click="setTool('windowing')">窗宽窗位</el-button>
        <el-button @click="setTool('pan')">平移</el-button>
        <el-button @click="setTool('zoom')">缩放</el-button>
        <el-button @click="setTool('length')">距离测量</el-button>
        <el-button @click="setTool('angle')">角度测量</el-button>
        <el-button @click="setTool('area')">面积测量</el-button>
        <el-button @click="setTool('ellipse')">椭圆面积</el-button>
        <el-button @click="setTool('probe')">CT值探针</el-button>
      </el-button-group>
    </div>
    <div class="info-overlay">
      <div>Patient: {{ patientInfo.name }}</div>
      <div>Window: {{ windowCenter }}/{{ windowWidth }}</div>
      <div>Zoom: {{ zoomLevel }}</div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneTools from 'cornerstone-tools'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
import dicomParser from 'dicom-parser'

// 初始化Cornerstone
cornerstoneWADOImageLoader.external.dicomParser = dicomParser
cornerstoneWADOImageLoader.external.cornerstone = cornerstone

const viewerContainer = ref(null)
let enabledElement = null

onMounted(() => {
  // 启用Cornerstone
  enabledElement = cornerstone.getEnabledElement(viewerContainer.value)
  cornerstone.enable(viewerContainer.value)
  
  // 加载DICOM图像
  loadDicomImage()
})

const loadDicomImage = async () => {
  const imageId = `wado:${wadoRoot}?requestType=WADO&studyUID=${studyUid}&seriesUID=${seriesUid}&objectUID=${sopUid}`
  const image = await cornerstone.loadAndCacheImage(imageId)
  cornerstone.displayImage(enabledElement, image)
  
  // 启用工具
  cornerstoneTools.mouseInput.enable(enabledElement)
  cornerstoneTools.mouseWheelInput.enable(enabledElement)
  cornerstoneTools.wwwc.activate(enabledElement, 1) // 左键: 窗宽窗位
  cornerstoneTools.pan.activate(enabledElement, 2)  // 右键: 平移
  cornerstoneTools.zoom.activate(enabledElement, 4)  // 中键: 缩放
  cornerstoneTools.length.enable(enabledElement)      // 距离测量
  cornerstoneTools.angle.enable(enabledElement)       // 角度测量
  cornerstoneTools.ellipseRoi.enable(enabledElement)  // 椭圆面积
  cornerstoneTools.probe.enable(enabledElement)       // CT值探针
}

const setTool = (toolName) => {
  // 切换工具并高亮按钮
}
</script>

VrViewer.vue (VR容积渲染)

<template>
  <div class="vr-viewer">
    <div ref="vtkContainer" class="vtk-container" />
    <div class="transfer-function-panel">
      <div class="preset-buttons">
        <el-button @click="applyPreset('bone')">骨骼</el-button>
        <el-button @click="applyPreset('softTissue')">软组织</el-button>
        <el-button @click="applyPreset('lung')">肺部</el-button>
        <el-button @click="applyPreset('angio')">血管</el-button>
        <el-button @click="applyPreset('skin')">皮肤</el-button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue'
import vtkProxyManager from 'vtk.js/Sources/Proxy/ProxyManager'

let proxyManager = null

onMounted(() => {
  initVtk()
})

const initVtk = () => {
  // 创建VTK ProxyManager
  proxyManager = vtkProxyManager.newInstance({ container: vtkContainer.value })
  
  // 加载体数据
  const source = proxyManager.createProxy('Sources', 'TrivialProducer')
  source.setInputData(volumeData)
  
  // 创建VR表示
  const representation = proxyManager.createProxy('Representations', 'Volume')
  representation.setInput(source)
  
  // 设置传递函数
  const actor = representation.getActor()
  const property = actor.getProperty()
  
  // 设置预设
  applyPreset('bone')
}

const applyPreset = (preset) => {
  const presets = {
    bone: [
      { value: -1000, opacity: 0, color: [0, 0, 0] },
      { value: -300,  opacity: 0, color: [0, 0, 0] },
      { value: 200,   opacity: 0.2, color: [0.8, 0.4, 0.3] },
      { value: 500,   opacity: 0.8, color: [1, 1, 1] },
      { value: 3000,  opacity: 1, color: [1, 1, 1] }
    ],
    softTissue: [
      { value: -1000, opacity: 0, color: [0, 0, 0] },
      { value: -500,  opacity: 0, color: [0, 0, 0] },
      { value: 30,    opacity: 0.3, color: [0.8, 0.3, 0.3] },
      { value: 100,   opacity: 0.5, color: [0.9, 0.5, 0.5] },
      { value: 300,   opacity: 0.8, color: [1, 0.8, 0.7] },
      { value: 3000,  opacity: 1, color: [1, 1, 1] }
    ],
    lung: [
      { value: -1000, opacity: 0.3, color: [0.2, 0.2, 0.3] },
      { value: -700,  opacity: 0.1, color: [0.4, 0.4, 0.5] },
      { value: -300,  opacity: 0.3, color: [0.6, 0.6, 0.7] },
      { value: 0,     opacity: 0.8, color: [0.8, 0.8, 0.8] },
      { value: 3000,  opacity: 1, color: [1, 1, 1] }
    ],
    angio: [
      { value: -1000, opacity: 0, color: [0, 0, 0] },
      { value: 100,   opacity: 0, color: [0, 0, 0] },
      { value: 200,   opacity: 0.5, color: [1, 0, 0] },
      { value: 500,   opacity: 0.9, color: [1, 0.2, 0.2] },
      { value: 3000,  opacity: 1, color: [1, 0.5, 0.5] }
    ],
    skin: [
      { value: -1000, opacity: 0, color: [0, 0, 0] },
      { value: -300,  opacity: 0, color: [0, 0, 0] },
      { value: -100,  opacity: 0.2, color: [0.9, 0.7, 0.6] },
      { value: 50,    opacity: 0.5, color: [0.95, 0.8, 0.7] },
      { value: 200,   opacity: 0.8, color: [1, 0.9, 0.85] },
      { value: 3000,  opacity: 1, color: [1, 1, 1] }
    ]
  }
  
  // 应用传递函数到VTK actor
  const preset = presets[preset] || presets.bone
  applyTransferFunction(preset)
}
</script>

MeasurementToolbar.vue (测量工具)

<template>
  <div class="measurement-toolbar">
    <el-button-group>
      <el-tooltip content="距离测量(Ctrl+D)">
        <el-button :type="activeTool==='length'?'primary':''" @click="activateTool('length')">
          <el-icon><Ruler /></el-icon> 距离
        </el-button>
      </el-tooltip>
      <el-tooltip content="角度测量(Ctrl+A)">
        <el-button :type="activeTool==='angle'?'primary':''" @click="activateTool('angle')">
          <el-icon><ScaleToOriginal /></el-icon> 角度
        </el-button>
      </el-tooltip>
      <el-tooltip content="面积测量(Ctrl+M)">
        <el-button :type="activeTool==='area'?'primary':''" @click="activateTool('area')">
          <el-icon><Grid /></el-icon> 面积
        </el-button>
      </el-tooltip>
      <el-tooltip content="CT值探针(Ctrl+P)">
        <el-button :type="activeTool==='probe'?'primary':''" @click="activateTool('probe')">
          <el-icon><Position /></el-icon> CT值
        </el-button>
      </el-tooltip>
    </el-button-group>
    
    <!-- 测量结果列表 -->
    <div class="measurement-results" v-if="measurements.length">
      <el-table :data="measurements" size="small" border>
        <el-table-column prop="type" label="类型" width="80"/>
        <el-table-column prop="value" label="测量值" width="120"/>
        <el-table-column prop="unit" label="单位" width="60"/>
        <el-table-column label="操作" width="60">
          <template #default="{row}">
            <el-button type="danger" link size="small" @click="removeMeasurement(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import * as cornerstoneTools from 'cornerstone-tools'

const activeTool = ref('')
const measurements = ref([])

const activateTool = (toolName) => {
  activeTool.value = toolName
  
  // 禁用所有工具
  cornerstoneTools.length.deactivate(enabledElement, 1)
  cornerstoneTools.angle.deactivate(enabledElement, 1)
  cornerstoneTools.ellipseRoi.deactivate(enabledElement, 1)
  cornerstoneTools.probe.deactivate(enabledElement, 1)
  
  // 激活选中工具
  switch(toolName) {
    case 'length':
      cornerstoneTools.length.activate(enabledElement, 1)
      break
    case 'angle':
      cornerstoneTools.angle.activate(enabledElement, 1)
      break
    case 'area':
      cornerstoneTools.ellipseRoi.activate(enabledElement, 1)
      break
    case 'probe':
      cornerstoneTools.probe.activate(enabledElement, 1)
      break
  }
}
</script>

四、数据库设计(补充)

4.1 补充字段

-- 在reconstruction_task表补充字段
ALTER TABLE reconstruction_task ADD COLUMN slice_count INT;          -- 层数
ALTER TABLE reconstruction_task ADD COLUMN pixel_spacing_x DECIMAL(6,3); -- X像素间距
ALTER TABLE reconstruction_task ADD COLUMN pixel_spacing_y DECIMAL(6,3); -- Y像素间距
ALTER TABLE reconstruction_task ADD COLUMN table_position VARCHAR(50);   -- 床位位置
ALTER TABLE reconstruction_task ADD COLUMN kvp INT;                      -- 管电压
ALTER TABLE reconstruction_task ADD COLUMN mas DECIMAL(8,2);             -- 管电流时间积

-- 在reconstruction_result表补充字段
ALTER TABLE reconstruction_result ADD COLUMN rendering_time_ms INT;     -- 渲染耗时
ALTER TABLE reconstruction_result ADD COLUMN file_size_bytes BIGINT;    -- 文件大小
ALTER TABLE reconstruction_result ADD COLUMN thumbnail_path VARCHAR(500); -- 缩略图

五、部署架构

前端构建: npm run build → dist/ → Nginx
后端部署: Spring Boot JAR → Docker / 直接运行
存储: MinIO(对象存储) / NFS(文件系统)
PACS对接: WADO-RS / DICOM C-STORE
GPU加速: 前端WebGL(VTK.js自带) / 后端可选CUDA加速(处理大型数据集)

六、性能优化策略

6.1 前端优化

  • LOD(Level of Detail): 根据缩放级别加载不同分辨率
  • 瓦片加载: 大图像分块加载,减少内存占用
  • Web Worker: DICOM解析和预处理在Worker线程执行
  • 缓存策略: Cornerstone缓存最近查看的图像

6.2 后端优化

  • 异步处理: 3D重建任务异步执行不阻塞请求
  • 批量解析: 一次IO读取整个Series的DICOM文件
  • 结果缓存: 重建结果缓存到Redis/文件系统
  • 并行处理: 多个重建任务并行执行

6.3 存储优化

  • 压缩存储: 体数据使用LZ4压缩
  • 增量保存: 只保存变化部分
  • 分层存储: 热数据SSD冷数据HDD

七、安全设计

  1. 访问控制: 只有影像科医生可以发起重建任务
  2. 数据脱敏: 患者敏感信息在非工作场景脱敏显示
  3. 操作审计: 所有重建操作记录审计日志
  4. 数据加密: DICOM文件传输使用HTTPS/TLS
  5. 权限分级: 普通医生查看,主治以上审核报告