vite+vue3+ITK-wasm+VTK.js 编写Dicom阅片器

在这里插入图片描述

想实现类似的功能查阅资料的时候查阅到这篇文章vue + vtk.js读取CT序列,显示3d影像(三个面显示)
但是里面的ITK版本和组件已经不适用了当前版本
结合这篇文章写了一个适用新版本的
首先安装vtk.js

npm install @kitware/vtk.js

安装itk-wasm及相关组件,其他相关组件可以在这个网站中查找ITK-WASM Packages列表

npm install itk-wasm @itk-wasm/dicom

我使用的是vite,需要在vite.config中加入

  optimizeDeps: {
    exclude: ['@itk-wasm/dicom', 'itk-wasm'] // 添加这一行
  },
  server: {
    headers: {
      'Cross-Origin-Embedder-Policy': 'require-corp',
      'Cross-Origin-Opener-Policy': 'same-origin'
    }
  }

实现代码
具体详情看原帖

<template>
  <div :style="{ background: '#000', width: '100%', height: '100%' }">
    <div class="posabs r-0 p-20" :style="{ background: '#fff', width: '300px', 'z-index': 2 }">
      <input ref="itkFile" type="file" multiple name="" id="" :style="{ 'z-index': 2 }" @change="handleFile">
      <div>
        <span :style="{ width: '110px' }">Slice I</span>
        <el-slider v-model="sliceI.val" :min="sliceI.min" :max="sliceI.max"
          @input="updateSliceI"></el-slider>
      </div>
      <div>
        <span :style="{ width: '110px' }">Slice J</span>
        <el-slider v-model="sliceJ.val" :min="sliceJ.min" :max="sliceJ.max"
          @input="updateSliceJ"></el-slider>
      </div>
      <div>
        <span :style="{ width: '110px' }">Slice K</span>
        <el-slider v-model="sliceK.val" :min="sliceK.min" :max="sliceK.max"
          @input="updateSliceK"></el-slider>
      </div>
      <div>
        <span :style="{ width: '110px' }">Color level</span>
        <el-slider v-model="colorLevel.val" :min="colorLevel.min" :max="colorLevel.max"
          @input="updateColorLevel"></el-slider>
      </div>
      <div>
        <span :style="{ width: '110px' }">ColorWindow</span>
        <el-slider v-model="colorWindow.val" :min="colorWindow.min" :max="colorWindow.max"
          @input="updateColorWindow"></el-slider>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, getCurrentInstance } from "vue";
import { ElSlider } from 'element-plus'
import '@kitware/vtk.js/Rendering/Profiles/Volume'
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'
import vtkITKHelper from '@kitware/vtk.js/Common/DataModel/ITKHelper'
import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper'
import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice'
import * as ITKDicom from '@itk-wasm/dicom';

const { proxy } = getCurrentInstance();
const imageActorI = vtkImageSlice.newInstance()
const imageActorJ = vtkImageSlice.newInstance()
const imageActorK = vtkImageSlice.newInstance()


const sliceI = ref({
  val: 0,
  min: 0,
  max: 1
})
const sliceJ = ref({
  val: 0,
  min: 0,
  max: 1
})
const sliceK = ref({
  val: 0,
  min: 0,
  max: 1
})
const colorLevel = ref({
  val: 0,
  min: 0,
  max: 1
})
const colorWindow = ref({
  val: 0,
  min: 0,
  max: 1
})

const renderWindow = ref()


function handleFile(evt) {
  const files = Array.from(proxy.$refs.itkFile.files);


  ITKDicom.readImageDicomFileSeries({ inputImages: files })
    .then(({ outputImage, sortedFilenames, webWorkerPool }) => {
      console.log(outputImage, sortedFilenames, webWorkerPool)
      // webWorkerPool.terminate()
      const imageOrMesh = outputImage || sortedFilenames
      const imageData = vtkITKHelper.convertItkToVtkImage(imageOrMesh)
      addImageSliceMesh(imageData)
    }).catch((err) => {

      console.log(err)
      // $message.error('影像读取失败')
    })
}

// 添加影像到网格中
function addImageSliceMesh(source) {

  const renderMainBox = proxy.$refs.renderMain
  const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({

    container: renderMainBox,
    background: [1, 1, 1]
  })
  const renderer = fullScreenRenderer.getRenderer()
  renderWindow.value = fullScreenRenderer.getRenderWindow()
  const dataRange = source.getPointData().getScalars().getRange()
  const extent = source.getExtent()
  const imageMapperK = vtkImageMapper.newInstance()
  imageMapperK.setInputData(source)
  imageMapperK.setKSlice(30)
  imageActorK.setMapper(imageMapperK)

  const imageMapperJ = vtkImageMapper.newInstance()
  imageMapperJ.setInputData(source)
  imageMapperJ.setJSlice(30)
  imageActorJ.setMapper(imageMapperJ)

  const imageMapperI = vtkImageMapper.newInstance()
  imageMapperI.setInputData(source)
  imageMapperI.setISlice(30)
  imageActorI.setMapper(imageMapperI)

  renderer.addActor(imageActorI)
  renderer.addActor(imageActorJ)
  renderer.addActor(imageActorK)
  renderer.resetCamera()
  renderer.resetCameraClippingRange()
  renderWindow.value.render()

  sliceI.value = {

    val: 30,
    min: extent[0 * 2 + 0],
    max: extent[0 * 2 + 1]
  }
  sliceJ.value = {

    val: 30,
    min: extent[1 * 2 + 0],
    max: extent[1 * 2 + 1]
  }
  sliceK.value = {

    val: 30,
    min: extent[2 * 2 + 0],
    max: extent[2 * 2 + 1]
  }
  colorWindow.value.val = dataRange[1]
  colorLevel.value.val = (dataRange[0] + dataRange[1]) / 4
  colorWindow.value.max = dataRange[1]
  colorLevel.value.max = dataRange[1]
  updateColorLevel()
  updateColorWindow()
}
// 改变SliceI
function updateSliceI() {

  if (renderWindow.value) {

    imageActorI.getMapper().setISlice(sliceI.value.val)
    renderWindow.value.render()
  }
}
// 改变SliceK
function updateSliceJ() {

  if (renderWindow.value) {

    imageActorJ.getMapper().setJSlice(sliceJ.value.val)
    renderWindow.value.render()
  }
}
// 改变SliceK
function updateSliceK() {

  if (renderWindow.value) {

    imageActorK.getMapper().setKSlice(sliceK.value.val)
    renderWindow.value.render()
  }
}
// 改变窗位
function updateColorLevel(e) {

  if (renderWindow.value) {

    imageActorI.getProperty().setColorLevel(colorLevel.value.val)
    imageActorJ.getProperty().setColorLevel(colorLevel.value.val)
    imageActorK.getProperty().setColorLevel(colorLevel.value.val)
    renderWindow.value.render()
  }
}
// 改变窗宽
function updateColorWindow(e) {
  if (renderWindow.value) {

    imageActorI.getProperty().setColorWindow(colorWindow.value.val)
    imageActorJ.getProperty().setColorWindow(colorWindow.value.val)
    imageActorK.getProperty().setColorWindow(colorWindow.value.val)
    renderWindow.value.render()
  }
}

</script>

<style>
.posabs {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 15px;
}
</style>
在当今的医疗影像领域,数字医学影像(DICOM)文件的处理和浏览是至关重要的。cornerstone3D是一个开源的JavaScript库,它能够在Web应用程序中渲染医学影像。本文档详细介绍了一个基于cornerstone3D开发的DICOM影像浏览的源码,旨在向开发者展示如何使用vue3框架,结合cornerstone3D库,创建出功能完善的医疗影像浏览工具。 从源码中的.gitignore文件可以看出,这个项目可能会忽略掉node_modules文件夹和其他一些常见的临时文件,这表明开发者使用了npm或yarn这类包管理工具来管理项目依赖,并且使用git作为版本控制工具。 接下来,index.html文件通常是整个Web应用的入口文件,它可能包含基础的HTML结构,并通过引入其他JavaScript和CSS文件来构建应用的用户界面。由于涉及到vue3,我们可以推断这个文件可能使用了Vue3框架来创建单页应用程序(SPA)。 httpdir.js文件可能是一个用于处理HTTP请求的JavaScript文件,它可能包含了一些用于从服务获取DICOM文件或者其他资源的函数和逻辑。由于涉及到网络请求,这可能利用了fetch API或者axios这类HTTP客户端库。 vite.config.js文件表明这个项目使用了Vite作为构建工具,Vite是一个现代的Web开发构建工具,它能够提供快速的开发服务启动和热更新功能。该配置文件可能包含了项目的配置信息,如入口文件、构建输出目录、开发服务设置等。 package-lock.json和package.json文件的存在表明项目使用了npm作为包管理工具。package.json文件中记录了项目的名称、版本、依赖信息和脚本命令,而package-lock.json文件用于确保依赖的版本一致性,避免因版本变动带来的潜在问题。 README.md文件通常是项目的文档,它会包含如何安装项目、如何运行项目以及项目的基本功能介绍。开发者可以通过该文件快速了解项目,并开始自己的工作。 版权申明.md文件则包含了项目的版权信息和使用协议,这通常涉及到软件许可、作者权利声明以及相关的法律条文。 .prettierrc文件是Prettier的配置文件,Prettier是一个流行的代码格式化工具,它可以自动格式化代码以确保代码风格的一致性。通过这个配置文件,开发者可以定义自己的代码风格规则。 这个源码项目是一个基于vue3和cornerstone3D开发的DICOM影像浏览,它集成了现代Web开发的常用技术和库,包括Vite构建工具、npm包管理、HTTP客户端库和代码格式化工具Prettier。项目提供了完整的配置和文档,能够帮助开发者快速搭建起一个专业的医疗影像浏览环境。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值