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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值