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 文件预览功能!


相关推荐
C_心欲无痕2 小时前
Docker 本地部署 SSR 前端项目实战指南
前端·docker·容器
梵得儿SHI2 小时前
Vue 高级特性:组件高级用法(动态组件、异步组件、组件缓存 keep-alive)
前端·javascript·vue.js·keep-alive·异步组件·动态组件·vue组件高级特性
EndingCoder2 小时前
泛型类和高级用法
linux·运维·前端·ubuntu·typescript
lili-felicity2 小时前
React Native for Harmony 数字验证码输入功能
javascript·react native·react.js
ℋᙚᵐⁱᒻᵉ鲸落2 小时前
【Vue3】Element Plus 表单显示自定义校验错误
前端·javascript·vue.js
程序员小寒2 小时前
聊一聊 CommonJS 和 ES6 Module
前端·ecmascript·es6
Java后端的Ai之路2 小时前
【AI应用开发工程师】-Gemini写前端的一个坑
前端·人工智能·gemini·ai应用开发工程师
亿元程序员2 小时前
最近很火的一个拼图游戏,老板让我用Cocos3.8做一个...
前端
m0_748250032 小时前
C++ Web 编程
开发语言·前端·c++