Vue dcm文件预览

Vue3 + Cornerstone.js 实现 DICOM (DCM) 医学影像文件预览

📋 前言

DICOM (Digital Imaging and Communications in Medicine) 是医学影像的国际标准格式,广泛应用于医疗影像系统中。本文将介绍如何在 Vue3 项目中使用 Cornerstone.js 实现 DICOM 文件的在线预览功能。

🛠 技术栈

  • Vue 3 - 前端框架
  • TypeScript - 类型安全
  • Element Plus - UI 组件库
  • Cornerstone Core - DICOM 图像渲染引擎
  • Cornerstone WADO Image Loader - DICOM 图像加载器
  • dicom-parser - DICOM 文件解析器

📦 安装依赖

bash 复制代码
pnpm add cornerstone-core cornerstone-wado-image-loader dicom-parser

🔧 实现步骤

1. 导入必要的库

typescript 复制代码
import * as cornerstone from "cornerstone-core";
import * as cornerstoneWADOImageLoader from "cornerstone-wado-image-loader";
import * as dicomParser from "dicom-parser";

2. 初始化 Cornerstone WADO Image Loader

typescript 复制代码
const initCornerstoneWADOImageLoader = () => {
  cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
  cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
  cornerstoneWADOImageLoader.configure({
    useWebWorkers: false,
  });
};

// 组件加载时初始化
initCornerstoneWADOImageLoader();

3. 创建 DICOM 容器和状态管理

typescript 复制代码
const dicomContainer = ref<HTMLElement | null>(null);  // DICOM 容器元素
const dicomImageId = ref(""); // DICOM 图像 ID

// 判断是否为 DICOM 文件
const isDicom = computed(() => {
  return ["dcm", "dicom"].includes(fileExtension.value);
});

4. 实现 DICOM 文件加载函数

typescript 复制代码
const loadDicomFile = async (blob: Blob) => {
  try {
    console.log("开始加载 DICOM 文件,大小:", blob.size);
    
    const url = URL.createObjectURL(blob);
    const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(blob);
    
    // 先设置这些值,触发容器渲染
    dicomImageId.value = imageId;
    fileUrl.value = url;

    // 等待 DOM 更新,让容器被渲染
    await nextTick();
    
    if (!dicomContainer.value) {
      throw new Error("DICOM 容器元素不存在,请检查 DOM 渲染");
    }

    // 确保容器有尺寸
    if (dicomContainer.value.offsetWidth === 0 || dicomContainer.value.offsetHeight === 0) {
      await new Promise(resolve => setTimeout(resolve, 300));
    }
    
    // 启用 cornerstone 元素
    try {
      cornerstone.enable(dicomContainer.value);
    } catch (e) {
      console.warn("启用 cornerstone 元素警告:", e);
    }
    
    // 加载并显示图像
    console.log("开始加载图像...");
    const image = await cornerstone.loadImage(imageId);
    
    // 显示图像
    cornerstone.displayImage(dicomContainer.value, image);
    
    // 获取并设置视口(自动调整窗宽窗位)
    const viewport = cornerstone.getDefaultViewportForImage(
      dicomContainer.value,
      image
    );
    
    // 设置视口
    cornerstone.setViewport(dicomContainer.value, viewport);
    
    // 强制重绘
    cornerstone.updateImage(dicomContainer.value);
  } catch (error) {
    ElMessage.error(`DICOM 文件加载失败: ${error.message || error}`);
    throw error;
  }
};

5. 在文件加载逻辑中集成

typescript 复制代码
const loadFile = async () => {
  if (!props.fileInfo?.id) return;

  loading.value = true;
  fileUrl.value = "";
  dicomImageId.value = "";

  try {
    const res = await getFileUrl(props.fileInfo.id);

    if (res && res instanceof Blob) {
      if (isDicom.value) {
        // DICOM 文件:使用 cornerstone 渲染
        // 先关闭 loading,让容器渲染出来
        loading.value = false;
        await loadDicomFile(res);
      }
      // ... 其他文件类型处理
    }
  } finally {
    loading.value = false;
  }
};

6. 添加模板结构

vue 复制代码
<template>
  <el-dialog
    :model-value="visible"
    :title="fileInfo?.fileName || '文件预览'"
    width="65%"
    :before-close="handleClose"
    destroy-on-close
  >
    <div class="file-preview-container">
      <!-- 加载状态 -->
      <div v-if="loading" class="loading-container">
        <el-icon class="is-loading">
          <Loading />
        </el-icon>
        <span>正在加载文件...</span>
      </div>

      <!-- DICOM 预览 -->
      <div v-else-if="fileUrl && isDicom" class="preview-content">
        <div class="dicom-preview">
          <div ref="dicomContainer" class="dicom-viewer" />
        </div>
      </div>
    </div>

    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleClose">关闭</el-button>
        <el-button type="primary" @click="downloadFile">下载</el-button>
      </div>
    </template>
  </el-dialog>
</template>

7. 添加样式

scss 复制代码
.dicom-preview {
  width: 100%;
  height: 600px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;  // 医学影像标准使用黑色背景
  border-radius: 4px;
  overflow: hidden;

  .dicom-viewer {
    width: 100%;
    height: 100%;
    min-width: 512px;
    min-height: 512px;
    position: relative;
    background: #000;
  }
}

8. 资源清理

typescript 复制代码
// 关闭弹窗时清理
const handleClose = () => {
  // 清理 cornerstone 元素
  if (dicomContainer.value && isDicom.value) {
    try {
      cornerstone.disable(dicomContainer.value);
    } catch (e) {
      console.warn("清理 cornerstone 元素失败:", e);
    }
  }
  
  // 清理 blob URL
  if (fileUrl.value) {
    URL.revokeObjectURL(fileUrl.value);
    fileUrl.value = "";
  }
  
  dicomImageId.value = "";
  emit("update:visible", false);
};

// 组件卸载时清理
onUnmounted(() => {
  if (dicomContainer.value && isDicom.value) {
    try {
      cornerstone.disable(dicomContainer.value);
    } catch (e) {
      console.warn("清理 cornerstone 元素失败:", e);
    }
  }
  
  if (fileUrl.value) {
    URL.revokeObjectURL(fileUrl.value);
  }
});

✨ 功能特性

  • 自动窗宽窗位调整 - 自动优化图像对比度
  • 高性能渲染 - 基于 Canvas 的快速渲染
  • 内存管理 - 自动清理资源,避免内存泄漏
  • 错误处理 - 完善的错误提示和处理机制
  • 响应式布局 - 适应不同尺寸的容器

⚠️ 关键注意事项

1. Loading 状态控制

确保在调用 loadDicomFile 之前将 loading 设置为 false,否则容器不会渲染:

typescript 复制代码
loading.value = false;  // 先设置为 false
await loadDicomFile(res);  // 然后加载

2. 容器渲染时序

必须先设置 fileUrldicomImageId,触发 Vue 的响应式更新,容器才会渲染:

typescript 复制代码
dicomImageId.value = imageId;
fileUrl.value = url;
await nextTick();  // 等待 DOM 更新

3. 容器尺寸检查

在启用 Cornerstone 之前,确保容器有有效的尺寸:

typescript 复制代码
if (dicomContainer.value.offsetWidth === 0 || dicomContainer.value.offsetHeight === 0) {
  await new Promise(resolve => setTimeout(resolve, 300));
}

4. 资源清理

每次关闭预览或切换文件时,务必清理:

  • Cornerstone 实例 (cornerstone.disable)
  • Blob URL (URL.revokeObjectURL)
  • 图像 ID 引用

🎯 实现效果

  • 支持 .dcm.dicom 扩展名
  • 黑色背景显示,符合医学影像查看习惯
  • 自动调整窗宽窗位,获得最佳显示效果
  • 支持下载原始 DICOM 文件

🐛 常见问题

1. 预览显示黑屏

原因 :容器在 loadDicomFile 执行时还未渲染到 DOM 中

解决方案

  • 在调用 loadDicomFile 之前设置 loading.value = false
  • 确保先设置 fileUrldicomImageId
  • 使用 await nextTick() 等待 DOM 更新

2. 控制台报错:容器元素不存在

原因 :条件渲染 v-else-if 的逻辑问题

解决方案

  • 确保 fileUrl 在容器渲染前已设置
  • 检查 loading 状态是否正确控制

3. 图像显示但窗宽窗位不对

解决方案

typescript 复制代码
const viewport = cornerstone.getDefaultViewportForImage(
  dicomContainer.value,
  image
);
cornerstone.setViewport(dicomContainer.value, viewport);

📚 扩展功能

可以进一步添加以下功能:

  1. 交互工具

    • 缩放(Zoom)
    • 平移(Pan)
    • 窗宽窗位调整(Window Level)
    • 测量工具(Length、Angle)
  2. 多帧支持

    • 播放 DICOM 序列
    • 帧速率控制
  3. 图像增强

    • 伪彩色
    • 反转
    • 旋转和翻转

🔗 参考资源

📝 总结

通过 Cornerstone.js,我们可以在 Web 应用中轻松实现 DICOM 医学影像的预览功能。关键点在于:

  1. 正确的初始化顺序
  2. 容器渲染时序控制
  3. 完善的资源清理机制
  4. 错误处理和用户提示

希望这篇文章能帮助你在项目中顺利集成 DICOM 文件预览功能!


相关推荐
代码搬运媛3 小时前
Jest 测试框架详解与实现指南
前端
counterxing3 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq4 小时前
windows下nginx的安装
linux·服务器·前端
之歆4 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜4 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai108084 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
candyTong4 小时前
Claude Code 的 Edit 工具是怎么工作的
javascript·后端·架构
kyriewen6 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
humcomm6 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy7 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程