最近在做一个医学影像处理的项目,要求是使用pyqt5实现T1.nii文件和靶区文件的叠加显示。之前有web前端开发和一些python基础,pyqt5和医学影像文件(nii格式文件)处理都是第一次接触。趁着十一假期比较清闲,记录一下该功能实现的过程(pyqt5相关基础就不说了,B站很多新手教程)。
一、认识nii文件(包括如何使用python处理nii文件)
参考了如下文章
python 笔记:打开nii格式(nibabel 库)-CSDN博客
一文看懂如何用 Python 查看三维数据 (nii.gz格式) 的各种图像参数_.nii.gz-CSDN博客
【医学影像数据处理】nii 数据格式文件操作汇总_nii文件-CSDN博客
BraTs数据集处理及python读取.nii文件-CSDN博客
二、python实现T1.nii文件和靶区文件的叠加显示
在这部分,我直接面向人工智能编程,它提供了如下代码,尝试了可以运行,效果不错。
import nibabel as nib
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
# 读取NIfTI文件
ti_img = nib.load('TI.nii')
target_img = nib.load('target.nii')
# 获取图像数据
ti_data = ti_img.get_fdata()
target_data = target_img.get_fdata()
# 确保两者数据形状相同
if ti_data.shape != target_data.shape:
raise ValueError("两个NIfTI图像的形状不一致,请检查.")
# 图像维度
x_dim, y_dim, z_dim = ti_data.shape
# 创建图形和子图
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# 设置滑块位置
ax_slider_x = plt.axes([0.2, 0.01, 0.6, 0.03]) # X轴滑块
ax_slider_y = plt.axes([0.2, 0.05, 0.6, 0.03]) # Y轴滑块
ax_slider_z = plt.axes([0.2, 0.09, 0.6, 0.03]) # Z轴滑块
# 创建滑块
slider_x = Slider(ax_slider_x, 'X', 0, x_dim - 1, valinit=x_dim // 2, valstep=1)
slider_y = Slider(ax_slider_y, 'Y', 0, y_dim - 1, valinit=y_dim // 2, valstep=1)
slider_z = Slider(ax_slider_z, 'Z', 0, z_dim - 1, valinit=z_dim // 2, valstep=1)
# 用于更新显示函数
def update_images(val):
x_idx = int(slider_x.val)
y_idx = int(slider_y.val)
z_idx = int(slider_z.val)
# 清空子图,然后重绘
for ax in axs:
ax.clear()
# 提取三个切片
ti_slice_x = ti_data[x_idx, :, :]
target_slice_x = target_data[x_idx, :, :]
ti_slice_y = ti_data[:, y_idx, :]
target_slice_y = target_data[:, y_idx, :]
ti_slice_z = ti_data[:, :, z_idx]
target_slice_z = target_data[:, :, z_idx]
# 绘制剖面
axs[0].imshow(ti_slice_x, cmap='gray', alpha=0.5)
axs[0].imshow(target_slice_x, cmap='jet', alpha=0.5)
axs[0].set_title(f'X Slice: {x_idx}')
axs[1].imshow(ti_slice_y.T, cmap='gray', alpha=0.5)
axs[1].imshow(target_slice_y.T, cmap='jet', alpha=0.5)
axs[1].set_title(f'Y Slice: {y_idx}')
axs[2].imshow(ti_slice_z.T, cmap='gray', alpha=0.5)
axs[2].imshow(target_slice_z.T, cmap='jet', alpha=0.5)
axs[2].set_title(f'Z Slice: {z_idx}')
# 更新图像
for ax in axs:
ax.axis('off')
plt.draw()
# 连接滑块与更新函数
slider_x.on_changed(update_images)
slider_y.on_changed(update_images)
slider_z.on_changed(update_images)
# 初次绘制
update_images(None)
plt.tight_layout()
plt.show()
运行结果如下:
到现在为止,已经通过python实现了两个相同大小的nii文件的叠加显示。剩下的工作就是将上述功能集成到pyqt页面中显示。
三、在pyqt5页面中实现nii叠加功能(不另外弹出弹窗)
整体思路就是将上面的代码封装成几个函数,里面的数据替换成PYQT组件能读取和设置的值,然后绑定好组件和槽函数之间的调用关系即可。
1.默认加载T1图象
import nibabel as nib
import matplotlib.pyplot as plt
import pyqtgraph as pg
#默认加载T1图象
def init_optional(self):
global headModule_path
ti_img = nib.load(headModule_path+'/T1.nii.gz')
ti_data1 = ti_img.get_fdata()
ti_data = np.flip(ti_data1, axis=1) # 例如,翻转 X 轴
self.ui.horizontalSlider.setMaximum(ti_data.shape[0])
self.ui.horizontalSlider_2.setMaximum(ti_data.shape[1])
self.ui.horizontalSlider_3.setMaximum(ti_data.shape[2])
self.ui.horizontalSlider.setValue(round(ti_data.shape[0] / 2))
self.ui.horizontalSlider_2.setValue(round(ti_data.shape[1] / 2))
self.ui.horizontalSlider_3.setValue(round(ti_data.shape[2] / 2))
# Set up matplotlib figure and canvas
self.fig, self.axs = self.setup_matplotlib()
self.canvas = FigureCanvas(self.fig)
# Create horizontal layout in the central widget for the canvas
layout = self.ui.verticalLayout_13
layout.addWidget(self.canvas) # Add the canvas to the layout
self.update_images1(ti_data)
self.ui.horizontalSlider.valueChanged.connect(lambda:self.update_images1(ti_data))
self.ui.horizontalSlider_2.valueChanged.connect(lambda:self.update_images1(ti_data))
self.ui.horizontalSlider_3.valueChanged.connect(lambda:self.update_images1(ti_data))
plt.show()
2.setup_matplotlib函数
def setup_matplotlib(self):
# Create matplotlib figure and axes
fig = Figure(figsize=(50,5))
axs = [fig.add_subplot(2, 2, i + 1) for i in range(3)]
fig.tight_layout()
return fig, axs
3.update_images1函数
def update_images1(self, ti_data):
x_idx = self.ui.horizontalSlider.value()
y_idx = self.ui.horizontalSlider_2.value()
z_idx = self.ui.horizontalSlider_3.value()
# Clear previous images
for ax in self.axs:
ax.clear()
# Display slices
self.axs[0].imshow(ti_data[x_idx, :, :], cmap='gray', alpha=0.5)
self.axs[0].set_title(f'X Slice: {x_idx}')
self.axs[1].imshow(ti_data[:, y_idx, :].T, cmap='gray', alpha=0.5)
self.axs[1].set_title(f'Y Slice: {y_idx}')
self.axs[2].imshow(ti_data[:, :, z_idx].T, cmap='gray', alpha=0.5)
self.axs[2].set_title(f'Z Slice: {z_idx}')
for ax in self.axs:
ax.axis('off')
self.canvas.draw() # Refresh the canvas
4.选择需要叠加的nii文件(subject空间),进行叠加(update_images函数类似,不再重复了)
def showpicture_subject(self):
global headModule_path
ti_img = nib.load(headModule_path + '/T1.nii.gz')
target_img = nib.load(self.ui.label_46.text())
ti_data1 = ti_img.get_fdata()
target_data1 = target_img.get_fdata()
ti_data = np.flip(ti_data1, axis=1) # 例如,翻转 X 轴
target_data = np.flip(target_data1, axis=1) # 例如,翻转 X 轴
if ti_data.shape != target_data.shape:
raise ValueError("两个NIfTI图像的形状不一致,请检查.")
print(ti_data.shape)
print(target_data.shape)
self.ui.horizontalSlider.setMaximum(ti_data.shape[0])
self.ui.horizontalSlider_2.setMaximum(ti_data.shape[1])
self.ui.horizontalSlider_3.setMaximum(ti_data.shape[2])
self.ui.horizontalSlider.setValue(round(ti_data.shape[0]/2))
self.ui.horizontalSlider_2.setValue(round(ti_data.shape[1]/2))
self.ui.horizontalSlider_3.setValue(round(ti_data.shape[2]/2))
self.update_images(ti_data,target_data)
self.ui.horizontalSlider.valueChanged.connect(lambda:self.update_images(ti_data,target_data))
self.ui.horizontalSlider_2.valueChanged.connect(lambda:self.update_images(ti_data,target_data))
self.ui.horizontalSlider_3.valueChanged.connect(lambda:self.update_images(ti_data,target_data))
#plt.tight_layout()
plt.show()
5. 选择需要叠加的nii文件(MNI空间),进行叠加(update_images函数类似,不再重复了)
import SimpleITK as sitk
#叠加MNI空间靶区/靶网络nii文件时,调用此函数
def showpicture_MNI(self):
global headModule_path
ti_img = sitk.ReadImage(headModule_path + '/MNI152_T1_1mm.nii.gz')
target_img = sitk.ReadImage(self.ui.label_46.text())
t1_spacing = ti_img.GetSpacing()
# 重采样分割图像,使其和 T1 图像大小一致
resampler = sitk.ResampleImageFilter()
resampler.SetSize(ti_img.GetSize())
resampler.SetOutputSpacing(t1_spacing)
resampler.SetOutputOrigin(ti_img.GetOrigin())
resampler.SetOutputDirection(ti_img.GetDirection())
resampler.SetDefaultPixelValue(0) # 如果在重采样时出现空值,使用 0 填充
# 使用线性插值进行重采样
resampler.SetInterpolator(sitk.sitkNearestNeighbor) # 分割图通常用邻近插值
resampled_seg_image = resampler.Execute(target_img)
# 将图像转换为 numpy 数组
ti_data1 = sitk.GetArrayFromImage(ti_img)
target_data1 = sitk.GetArrayFromImage(resampled_seg_image)
ti_data = np.flip(ti_data1, axis=0) # 例如,翻转 X 轴
target_data = np.flip(target_data1, axis=0) # 例如,翻转 X 轴
self.ui.horizontalSlider.setMaximum(ti_data.shape[0])
self.ui.horizontalSlider_2.setMaximum(ti_data.shape[1])
self.ui.horizontalSlider_3.setMaximum(ti_data.shape[2])
self.ui.horizontalSlider.setValue(round(ti_data.shape[0]/2))
self.ui.horizontalSlider_2.setValue(round(ti_data.shape[1]/2))
self.ui.horizontalSlider_3.setValue(round(ti_data.shape[2]/2))
self.update_images(ti_data,target_data)
self.ui.horizontalSlider.valueChanged.connect(lambda:self.update_images(ti_data,target_data))
self.ui.horizontalSlider_2.valueChanged.connect(lambda:self.update_images(ti_data,target_data))
self.ui.horizontalSlider_3.valueChanged.connect(lambda:self.update_images(ti_data,target_data))
#plt.tight_layout()
plt.show()
四、总结
在叠加两个nii图像时,发现python的nibabel库更简单易用,适合处理同在subject空间大小相同的nii文件(即两个nii文件的fdata.shape相同),但如果需要对大小不同的两个nii文件(同属MNI空间)图像的拉伸、重采样和插值等操作时,最好使用SimpleITK库,功能更完善。
标签:叠加,nii,target,self,horizontalSlider,pyqt5,ui,ti,data From: https://blog.csdn.net/m0_50746196/article/details/142682646