想实现类似的功能查阅资料的时候查阅到这篇文章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>