<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VTK.js (使用ImageReslice)</title>
<script src="https://unpkg.com/vtk.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background-color: #1e1e2f;
color: #ffffff;
}
.container {
display: flex;
flex-direction: column;
max-width: 1200px;
margin: 0 auto;
background: #27293d;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
}
.header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #3a3b5a;
}
.header h1 {
margin: 0;
color: #e14eca;
font-size: 28px;
}
.header p {
margin: 5px 0 0;
color: #9a9a9a;
}
.view-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.view {
flex: 1;
min-width: 300px;
height: 300px;
border: 2px solid #3a3b5a;
border-radius: 8px;
overflow: hidden;
position: relative;
background: #1e1e2f;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.view-title {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.6);
padding: 5px 10px;
border-radius: 5px;
font-size: 14px;
z-index: 10;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}
.control-group {
flex: 1;
min-width: 300px;
background: #2d2d45;
padding: 20px;
border-radius: 8px;
}
.slider-container {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
}
.slider-label {
display: flex;
justify-content: space-between;
align-items: center;
}
.slider-value {
font-weight: bold;
color: #e14eca;
min-width: 40px;
text-align: right;
}
.slider {
width: 100%;
height: 6px;
-webkit-appearance: none;
appearance: none;
background: linear-gradient(to right, #1e1e2f, #e14eca);
outline: none;
border-radius: 3px;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #e14eca;
cursor: pointer;
box-shadow: 0 0 10px rgba(225, 78, 202, 0.7);
}
h3 {
color: #e14eca;
margin-top: 0;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #3a3b5a;
}
.info-panel {
background: #2d2d45;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}
.info-panel p {
margin: 5px 0;
font-size: 14px;
color: #9a9a9a;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>VTK.js 医学影像切片查看器</h1>
<p>使用 vtkImageReslice 实现多平面重建</p>
</div>
<div class="view-container">
<div class="view">
<div class="view-title">轴向视图 (Axial)</div>
<div id="axial-container"></div>
</div>
<div class="view">
<div class="view-title">矢状视图 (Sagittal)</div>
<div id="sagittal-container"></div>
</div>
<div class="view">
<div class="view-title">冠状视图 (Coronal)</div>
<div id="coronal-container"></div>
</div>
</div>
<div class="controls">
<div class="control-group">
<h3>轴向切片控制</h3>
<div class="slider-container">
<div class="slider-label">
<span>Z 轴位置: </span>
<span class="slider-value" id="z-value">50%</span>
</div>
<input type="range" class="slider" id="z-slider" min="0" max="100" value="50">
</div>
</div>
<div class="control-group">
<h3>矢状切片控制</h3>
<div class="slider-container">
<div class="slider-label">
<span>X 轴位置: </span>
<span class="slider-value" id="x-value">50%</span>
</div>
<input type="range" class="slider" id="x-slider" min="0" max="100" value="50">
</div>
</div>
<div class="control-group">
<h3>冠状切片控制</h3>
<div class="slider-container">
<div class="slider-label">
<span>Y 轴位置: </span>
<span class="slider-value" id="y-value">50%</span>
</div>
<input type="range" class="slider" id="y-slider" min="0" max="100" value="50">
</div>
</div>
</div>
<div class="info-panel">
<p><strong>使用说明:</strong> 使用滑块调整不同平面的切片位置。</p>
<p><strong>技术:</strong> 使用 vtkImageReslice 进行图像重切片,实现多平面重建。</p>
</div>
</div>
<script>
// 导入所需的VTK.js模块
const vtk = window.vtk;
// 获取VTK.js的子模块
const vtkGenericRenderWindow = vtk.Rendering.Misc.vtkGenericRenderWindow;
const vtkHttpDataSetReader = vtk.IO.Core.vtkHttpDataSetReader;
const vtkImageMapper = vtk.Rendering.Core.vtkImageMapper;
const vtkImageSlice = vtk.Rendering.Core.vtkImageSlice;
const vtkImageReslice = vtk.Imaging.Core.vtkImageReslice;
// 全局变量
let reader = null;
let imageData = null;
let axialReslice, sagittalReslice, coronalReslice;
let axialMapper, sagittalMapper, coronalMapper;
let axialActor, sagittalActor, coronalActor;
let axialRenderWindow, sagittalRenderWindow, coronalRenderWindow;
// 初始化函数
function init() {
// 创建三个视图的容器
const axialContainer = document.getElementById('axial-container');
const sagittalContainer = document.getElementById('sagittal-container');
const coronalContainer = document.getElementById('coronal-container');
// 创建三个渲染窗口
axialRenderWindow = vtkGenericRenderWindow.newInstance();
axialRenderWindow.setContainer(axialContainer);
sagittalRenderWindow = vtkGenericRenderWindow.newInstance();
sagittalRenderWindow.setContainer(sagittalContainer);
coronalRenderWindow = vtkGenericRenderWindow.newInstance();
coronalRenderWindow.setContainer(coronalContainer);
// 调整大小
[axialRenderWindow, sagittalRenderWindow, coronalRenderWindow].forEach(rw => {
rw.resize();
});
// 创建图像读取器
reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
// 创建三个ImageReslice实例
axialReslice = vtkImageReslice.newInstance();
axialReslice.setOutputDimensionality(2);
sagittalReslice = vtkImageReslice.newInstance();
sagittalReslice.setOutputDimensionality(2);
coronalReslice = vtkImageReslice.newInstance();
coronalReslice.setOutputDimensionality(2);
// 创建映射器
axialMapper = vtkImageMapper.newInstance();
sagittalMapper = vtkImageMapper.newInstance();
coronalMapper = vtkImageMapper.newInstance();
// 创建演员
axialActor = vtkImageSlice.newInstance();
axialActor.setMapper(axialMapper);
sagittalActor = vtkImageSlice.newInstance();
sagittalActor.setMapper(sagittalMapper);
coronalActor = vtkImageSlice.newInstance();
coronalActor.setMapper(coronalMapper);
// 设置窗口/级别
[axialActor, sagittalActor, coronalActor].forEach(actor => {
actor.getProperty().setColorWindow(400);
actor.getProperty().setColorLevel(40);
});
// 添加到渲染器
axialRenderWindow.getRenderer().addActor(axialActor);
sagittalRenderWindow.getRenderer().addActor(sagittalActor);
coronalRenderWindow.getRenderer().addActor(coronalActor);
// 加载数据
reader.setUrl('https://kitware.github.io/vtk-js/data/volume/LIDC2.vti')
.then(() => reader.loadData())
.then(() => {
imageData = reader.getOutputData();
// 获取图像范围和间距
const extent = imageData.getExtent();
const spacing = imageData.getSpacing();
const origin = imageData.getOrigin();
// 设置reslice输入
axialReslice.setInputData(imageData);
sagittalReslice.setInputData(imageData);
coronalReslice.setInputData(imageData);
// 设置初始切片位置(百分比)
updateSlicePosition(50, 50, 50);
// 连接reslice输出到mapper
axialMapper.setInputConnection(axialReslice.getOutputPort());
sagittalMapper.setInputConnection(sagittalReslice.getOutputPort());
coronalMapper.setInputConnection(coronalReslice.getOutputPort());
// 重置相机并渲染
axialRenderWindow.getRenderer().resetCamera();
sagittalRenderWindow.getRenderer().resetCamera();
coronalRenderWindow.getRenderer().resetCamera();
axialRenderWindow.getRenderWindow().render();
sagittalRenderWindow.getRenderWindow().render();
coronalRenderWindow.getRenderWindow().render();
})
.catch(error => {
console.error('Error loading data:', error);
});
// 添加滑块事件监听
document.getElementById('z-slider').addEventListener('input', function() {
const zValue = parseInt(this.value);
const xValue = parseInt(document.getElementById('x-slider').value);
const yValue = parseInt(document.getElementById('y-slider').value);
document.getElementById('z-value').textContent = zValue + '%';
updateSlicePosition(xValue, yValue, zValue);
renderAll();
});
document.getElementById('x-slider').addEventListener('input', function() {
const xValue = parseInt(this.value);
const yValue = parseInt(document.getElementById('y-slider').value);
const zValue = parseInt(document.getElementById('z-slider').value);
document.getElementById('x-value').textContent = xValue + '%';
updateSlicePosition(xValue, yValue, zValue);
renderAll();
});
document.getElementById('y-slider').addEventListener('input', function() {
const yValue = parseInt(this.value);
const xValue = parseInt(document.getElementById('x-slider').value);
const zValue = parseInt(document.getElementById('z-slider').value);
document.getElementById('y-value').textContent = yValue + '%';
updateSlicePosition(xValue, yValue, zValue);
renderAll();
});
}
// 更新切片位置
function updateSlicePosition(xPercent, yPercent, zPercent) {
if (!imageData) return;
const extent = imageData.getExtent();
const spacing = imageData.getSpacing();
const origin = imageData.getOrigin();
// 计算实际位置
const xPos = origin[0] + (extent[0] + (extent[1] - extent[0]) * xPercent / 100) * spacing[0];
const yPos = origin[1] + (extent[2] + (extent[3] - extent[2]) * yPercent / 100) * spacing[1];
const zPos = origin[2] + (extent[4] + (extent[5] - extent[4]) * zPercent / 100) * spacing[2];
// 轴向切片 (XY平面,固定Z)
axialReslice.setResliceAxes([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, zPos,
0, 0, 0, 1
]);
//axialReslice.setResliceAxesOrigin(0, 0, zPos);
// 矢状切片 (YZ平面,固定X)
sagittalReslice.setResliceAxes([
0, 0, 1, xPos,
0, 1, 0, 0,
1, 0, 0, 0,
0, 0, 0, 1
]);
//sagittalReslice.setResliceAxesOrigin(xPos, 0, 0);
// 冠状切片 (XZ平面,固定Y)
coronalReslice.setResliceAxes([
1, 0, 0, 0,
0, 0, 1, yPos,
0, 1, 0, 0,
0, 0, 0, 1
]);
//coronalReslice.setResliceAxesOrigin(0, yPos, 0);
// 更新所有reslice
axialReslice.update();
sagittalReslice.update();
coronalReslice.update();
}
// 渲染所有视图
function renderAll() {
axialRenderWindow.getRenderWindow().render();
sagittalRenderWindow.getRenderWindow().render();
coronalRenderWindow.getRenderWindow().render();
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
I have check this code in cxx ImageSlicing.cxx, I find i have set the all thing right, is any one can have a look?