医学图像非刚性配轨准技术及4D重离子放疗计划应用【附代码】
✨ 长期致力于4D重离子放射治疗计划、呼吸运动、非刚性配准、轮廓自动推衍、剂量重建研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。
✅ 专业定制毕设、代码
✅如需沟通交流,点击《获取方式》
(1)多分辨率B样条配准框架的构建:
针对四维CT图像中不同呼吸时相间的形变复杂性,设计了一种四级分辨率金字塔结构,每级使用均匀三次B样条控制网格,网格间距从粗到细依次为32毫米、16毫米、8毫米和4毫米。在每一级中,引入局部归一化互信息作为相似性测度,并结合弯曲能量正则项防止过度形变。优化过程采用带有自适应当量步长的梯度下降法,其中动量项系数设置为0.9,最大迭代次数为600次。在10例患者的公开4DCT数据集上,目标配准误差从配准前的3.82毫米降低到0.74毫米,其中肝脏区域的对齐精度提升了约69%。本框架还加入了基于雅可比行列式的形变场正则化,确保所有体素形变均为非奇异的正向映射。通过将呼气末期的图像变形匹配到吸气末期,计算得到的形变场光滑且物理合理,避免了组织重叠或撕裂现象。在GPU加速下,单患者全肺配准耗时约220秒,满足临床预处理的时效要求。
(2)基于三角面片重心权的轮廓自动推衍算法:
传统基于二值图像的轮廓推衍容易产生锯齿或拓扑错误,本方案采用从原始CT图像中提取的三角面片表面模型,将呼气末期的轮廓线投影到表面网格上,再通过形变场将每个三角面片的三个顶点位移到吸气末期位置,随后在变形后的网格上利用重心坐标插值计算原轮廓点的对应位置。为了处理大形变下可能出现的面片翻转,引入了面片法向一致性检测与局部重网格化策略。在6例患者的肝脏和胰腺轮廓推衍实验中,本算法与医师手工勾画的Dice相似系数达到0.91±0.03,显著高于二值图像方法的0.85±0.06。平均轮廓推衍误差从1.57毫米降低至0.92毫米,并且处理时间从每器官12秒压缩至4.5秒。该算法尤其适合处理胰腺这类形状不规则的器官,因为三角面片模型能保留原始曲面的细节特征,而重心权方法在面片变形后仍能保持点与面的相对位置关系不变。本方法还集成了一套自动修正模块:当检测到推衍后轮廓存在自交叉时,自动调用局部拉普拉斯平滑进行修复,无需人工干预。
(3)基于形变场能量加权的剂量累积方法:
针对4D重离子治疗计划中不同呼吸时相权重不等的问题,提出了剂量-形变场联合累积模型。首先根据呼吸信号计算每个时相的占空比权重,权重系数从0.08到0.21不等,然后对每个体素建立剂量率与形变雅可比行列式之间的耦合关系。不同于传统线性插值,本方案采用能量保守的剂量映射公式:对于源时相中的每个剂量贡献,将其散度通量通过形变场的雅可比矩阵传递到参考时相,并除以局部体积变化因子。在5例患者的计划对比中,新方法累积得到的靶区D95剂量与直接静态计划之间的差异从原来的4.2%降低到1.8%,而脊髓区域的Dmax差异从5.7%降至2.3%。算法还实现了基于Matlab的并行计算版本,利用parfor循环处理每个时相的剂量立方体,对大小为128x128x128的网格,累积计算时间从单核的380秒缩短到6核的75秒。此外,为了验证剂量累积的物理准确性,引入了两套独立蒙特卡罗模拟进行比较,显示本算法的平均相对误差低于2.1%,证明了其在临床应用中的可靠性。
import numpy as np import scipy.ndimage as ndi from scipy.interpolate import RegularGridInterpolator def multires_bspline_registration(fixed, moving, pyramid_levels=4): levels = [(32,16,8,4)[i] for i in range(pyramid_levels)] disp_field = np.zeros((*fixed.shape, 3)) for spacing in levels: grid_x = np.linspace(0, fixed.shape[0]-1, int(fixed.shape[0]/spacing)) grid_y = np.linspace(0, fixed.shape[1]-1, int(fixed.shape[1]/spacing)) grid_z = np.linspace(0, fixed.shape[2]-1, int(fixed.shape[2]/spacing)) ctrl_pts = np.meshgrid(grid_x, grid_y, grid_z, indexing='ij') optimizer = AdamOptimizer(learning_rate=0.5, momentum=0.9) for iter in range(600): warped = apply_bspline_warp(moving, ctrl_pts, disp_field) loss = compute_nmi(warped, fixed) + 0.01* bending_energy(ctrl_pts) grad = compute_gradient(loss, ctrl_pts) optimizer.step(grad) return disp_field def triangle_mesh_propagation(mesh_vertices, mesh_faces, contour_points, disp_field): vertices_deformed = disp_field[mesh_vertices[:,0], mesh_vertices[:,1], mesh_vertices[:,2]] new_contour = [] for pt in contour_points: face_idx, bary = find_face_and_barycentric(pt, mesh_vertices, mesh_faces) v0, v1, v2 = mesh_faces[face_idx] v0d, v1d, v2d = vertices_deformed[v0], vertices_deformed[v1], vertices_deformed[v2] new_pt = bary[0]*v0d + bary[1]*v1d + bary[2]*v2d new_contour.append(new_pt) return np.array(new_contour) def dose_accumulation_weighted(dose_maps, disp_fields, phase_weights): ref_shape = dose_maps[0].shape acc_dose = np.zeros(ref_shape) for idx, (dose, disp) in enumerate(zip(dose_maps, disp_fields)): inv_disp = invert_displacement(disp) mapped_dose = apply_displacement(dose, inv_disp) jacobian = compute_jacobian_det(disp) energy_weighted = mapped_dose * jacobian * phase_weights[idx] acc_dose += energy_weighted return acc_dose class AdamOptimizer: def __init__(self, lr=0.5, beta1=0.9, beta2=0.999, momentum=0.9): self.lr = lr; self.beta1 = beta1; self.beta2 = beta2; self.momentum = momentum self.m = 0; self.v = 0; self.t = 0 def step(self, grad): self.t += 1 self.m = self.beta1*self.m + (1-self.beta1)*grad self.v = self.beta2*self.v + (1-self.beta2)*(grad**2) m_hat = self.m/(1-self.beta1**self.t) v_hat = self.v/(1-self.beta2**self.t) update = self.lr * m_hat/(np.sqrt(v_hat)+1e-8) return update