进行 3D MRI 扫描A
, B
, and C
我想执行仿射(联合)配准B
onto A
,取配准的变换仿射矩阵并将其应用于C
.
我的问题是配准变换的仿射矩阵的符号错误。也许是因为方向错误?
The TransformParameters
包含 12 个值,其中前 9 个是行优先顺序的旋转矩阵,后 3 个是平移值。
TransformParameters = [R1, R2, R3, R4, R5, R6, R7, R8, R9, Tx, Ty, Tz]
registration_affine = [[R1, R2, R3, Tx],
[R4, R5, R6, Ty],
[R7, R8, R9, Tz],
[0, 0, 0, 1 ]]
我知道 ITK 将图像保存在LPS
方向和尼巴贝尔RAS
。
所以我尝试对方向差异进行更改transform_affine
但这并没有成功。
我无法获得与 ITK 相同的注册输出,下面我将展示一些数字示例和我的最小代码示例。
为了测试这一点,我对现有图像应用了仿射变换。该变换矩阵的逆矩阵是配准可以找到的真实仿射。
array([[ 1.02800583, 0.11462834, -0.11426342, -0.43383606],
[ 0.11462834, 1.02800583, -0.11426342, 0.47954143],
[-0.11426342, -0.11426342, 1.02285268, -0.20457054],
[ 0. , 0. , 0. , 1. ]])
但是按照上面解释的方式构建的仿射会产生:
array([[ 1.02757335, 0.11459412, 0.11448339, 0.23000557],
[ 0.11410441, 1.02746452, 0.11413955, -0.20848751],
[ 0.11398788, 0.11411115, 1.02255042, -0.04884404],
[ 0. , 0. , 0. , 1. ]])
您可以看到这些值非常接近,但只有符号错误。
事实上,如果我手动设置与“真实”矩阵中相同的符号,则变换矩阵是好的。
在 MONAI 的 ITK 加载器中,我发现建议执行以下操作以将 ITK 仿射转换为 nibabel 仿射的代码:
np.diag([-1, -1, 1, 1]) @ registration_affine
如果我使用 nibabelsornt_transform
从中获取 ornt 变换的方法LPS
to RAS
,这返回[-1, -1, 1]
并与 MONAI 的 ITK 加载器中所做的匹配。
但是将其应用于上面的仿射实际上并不会产生正确的符号(仅在平移位中):
array([[-1.02757335, -0.11459412, -0.11448339, -0.23000557],
[-0.11410441, -1.02746452, -0.11413955, 0.20848751],
[ 0.11398788, 0.11411115, 1.02255042, -0.04884404],
[ 0. , 0. , 0. , 1. ]])
所以我有点卡在这里。
这是一个完整的最小代码示例来运行我正在做/尝试做的事情。另请参阅下面的示例数据和包版本。
import nibabel
import numpy as np
from monai.transforms import Affine
from nibabel import Nifti1Image
import itk
# Import Images
moving_image = itk.imread('moving_2mm.nii.gz', itk.F)
fixed_image = itk.imread('fixed_2mm.nii.gz', itk.F)
# Import Default Parameter Map
parameter_object = itk.ParameterObject.New()
affine_parameter_map = parameter_object.GetDefaultParameterMap('affine', 4)
affine_parameter_map['FinalBSplineInterpolationOrder'] = ['1']
parameter_object.AddParameterMap(affine_parameter_map)
# Call registration function
result_image, result_transform_parameters = itk.elastix_registration_method(
fixed_image, moving_image, parameter_object=parameter_object)
parameter_map = result_transform_parameters.GetParameterMap(0)
transform_parameters = np.array(parameter_map['TransformParameters'], dtype=float)
itk.imwrite(result_image, 'reg_itk.nii.gz', compression=True)
# Convert ITK params to affine matrix
rotation = transform_parameters[:9].reshape(3, 3)
translation = transform_parameters[-3:][..., np.newaxis]
reg_affine: np.ndarray = np.append(rotation, translation, axis=1) # type: ignore
reg_affine = np.append(reg_affine, [[0, 0, 0, 1]], axis=0) # type: ignore
# Apply affine transform matrix via MONAI
moving_image_ni: Nifti1Image = nibabel.load('moving_2mm.nii.gz')
fixed_image_ni: Nifti1Image = nibabel.load('fixed_2mm.nii.gz')
moving_image_np: np.ndarray = moving_image_ni.get_fdata() # type: ignore
LPS = nibabel.orientations.axcodes2ornt(('L', 'P', 'S'))
RAS = nibabel.orientations.axcodes2ornt(('R', 'A', 'S'))
ornt_transform = nibabel.orientations.ornt_transform(LPS, RAS)[:, -1] # type: ignore
affine_transform = Affine(affine=np.diag([*ornt_transform, 1]) @ reg_affine, image_only=False)
out_img, out_affine = affine_transform(moving_image_np[np.newaxis, ...])
reg_monai = np.squeeze(out_img)
out = Nifti1Image(reg_monai, fixed_image_ni.affine, header=fixed_image_ni.header)
nibabel.save(out, 'reg_monai.nii.gz')
输入数据:
- 固定_2mm.nii.gz https://github.com/InsightSoftwareConsortium/ITKElastix/files/8226904/fixed_2mm.nii.gz
- moving_2mm.nii.gz https://github.com/InsightSoftwareConsortium/ITKElastix/files/8226905/moving_2mm.nii.gz
输出数据:
- reg_itk.nii.gz https://github.com/InsightSoftwareConsortium/ITKElastix/files/8226906/reg_itk.nii.gz
- reg_monai.nii.gz https://github.com/InsightSoftwareConsortium/ITKElastix/files/8226907/reg_monai.nii.gz
封装版本:
itk-elastix==0.12.0
monai==0.8.0
nibabel==3.1.1
numpy==1.19.2
我之前确实在 GitHub 上的 ITKElastix 项目上问过这个问题#145 https://github.com/InsightSoftwareConsortium/ITKElastix/issues/145但无法解决我的问题。感谢 dzenanz 和 mstaring 试图在那里提供帮助。