ANTsX / ANTsPy

A fast medical imaging analysis library in Python with algorithms for registration, segmentation, and more.
https://antspyx.readthedocs.io
Apache License 2.0
633 stars 161 forks source link

Motion correction: improve output #71

Closed dangom closed 7 months ago

dangom commented 5 years ago

Is your feature request related to a problem? Please describe. Currently, ants.motion_correction outputs motion_parameters as a list of MAT files that are saved to a temporary folder. This makes it so that if the user wants to save these transformations (to my understanding), he has to move these outputs somewhere else, manually, and write a mapping.txt file that specifies which file belongs to each volume, given that names are random. It's also cumbersome to extract common motion parameter regressors from these MAT files from within python. My knowledge is that this requires calling in scipy or hdf5 to load the MAT files, extracting the affine from the dict, parsing it and storing the translation and rotation parameters manually. This also means that the end user has to know what the ANTs internal representation of an affine transformation looks like, which is well documented, but outside of ANTsPy.

Describe the solution you'd like

  1. have the user be able to specify where to save the transformation matrices, and have their filenames sorted by index. This would ease storing files and allow easy identification of which affine belongs to which volume. If the folder already contains files with the same name, either throw an error before starting the motion correction, have a parameter to toggle overwrite (could default to True I guess), or add a suffix (the FSL solution).

  2. output an extra motion_parameter_estimates np.array or dictionary containing translation and rotation estimates for better interop with other software packages.

Describe alternatives you've considered Open to suggestions.

dangom commented 5 years ago

I don't know if this is a correct start. It's a partial translation of this matlab code, which is discussed at the ANTs main repo, into python. It's missing a final RAS to LPS conversion, so there may be some sign flips. It also adds in a dependency on transforms3d, which may potentially be undesirable.

def ants_to_human(aff, fixed):
    """Convert an ATNs affine_3x3 and a fixed center of rotation into a 4x4
    affine matrix.
    """
    mat = np.hstack((np.reshape(aff[:9], (3, 3)).T, aff[9:]))
    m_translation = mat[:, 3]
    mat = np.vstack((mat, [0, 0, 0, 1]))

    m_offset = np.zeros((3,))

    for i in range(3):
        m_offset[i] = m_translation[i] + fixed[i]
        for j in range(3):
            m_offset[i] -= mat[i, j] * fixed[j]

    mat[:3, 3] = m_offset
    mat = np.linalg.inv(mat)
    return mat

def human_to_more_human(mat):
    """Return translation and rotation parameter estimates from 4x4 affine matrix.
    """
    tra, mrot, zoom, shear = transforms3d.affines.decompose44(mat)
    rx = np.arctan2(mrot[1, 2], mrot[2, 2])
    ry = np.arctan2(-mrot[2, 0], np.sqrt(mrot[2, 1] ** 2 + mrot[2, 2] ** 2))
    rz = np.arctan2(mrot[1, 0], mrot[0, 0])
    rot = np.array((rx, ry, rz))
    return np.hstack((tra, rot))

But a throw-away example shows that the estimated FD does not match what is currently implemented (but magnitudes are in the same ballpark).

from scipy.io import loadmat

affines = [loadmat(x[0])['AffineTransform_float_3_3'] for x in data_mcf['motion_parameters']]
fix = [loadmat(x[0])['fixed'].squeeze() for x in data_mcf['motion_parameters']]

mpars = np.vstack([human_to_more_human(ants_to_human(a, f)) for (a, f) in zip(affines, fix)])

fd_est = np.sum(np.abs(np.diff(mpars, axis=0)), axis=1)
plt.scatter(data_mcf['FD'], np.insert(fd_est, 0, 0, axis=0))

scatterfd

stnava commented 5 years ago

We have both a transform to point Operation and transform classes to do all of this in a (c)lean way and consistently with other ants tools ... under some of this: https://github.com/stnava/structuralFunctionalJointRegistration/blob/master/src/Default%20Mode%20Connectivity%20in%20ANTsPy.ipynb

dangom commented 5 years ago

That's awesome. Thanks for pointing out. It still only uses the FD though, not the X, Y, Z and rot_X, rot_Y, rot_Z, which I think are the most commonly used regressors in the fMRI literature (not judging whether that's a good thing or not).

stnava commented 5 years ago

this is how we report those in ANTsR:

https://github.com/ANTsX/ANTsR/blob/69d65b697b14af6f2edf9fbd096a84c5fbc4d944/R/antsrMotionCalculation.R#L171-L189

obviously, fairly easy to port to python w/o dependencies

dangom commented 5 years ago

Thanks.

def _ants_affine_to_distance(affine):

    dx, dy, dz = affine[9:]

    rot_x = np.arcsin(affine[6])
    cos_rot_x = np.cos(rot_x)
    rot_y = np.arctan2(affine[7] / cos_rot_x, affine[8] / cos_rot_x)
    rot_z = np.arctan2(affine[3] / cos_rot_x, affine[0] / cos_rot_x)

    deg = np.degrees

    return dx, dy, dz, deg(rot_x), deg(rot_y), deg(rot_z)

I think the 6 parameter estimates would be a great addition to the dictionary returned by ants.motion_correction. I can open a PR if you think this would make sense. And just for clarification, FD is computed based on rotations in radians, correct?

stnava commented 5 years ago

one note: this is only valid for a rigid map. the generic FD just uses point transformations. the

fdOffset: offset value to use in framewise displacement calculation

gives the amount one offsets from the center of mass of the image in order to compute the FD.

https://github.com/ANTsX/ANTsPy/blob/c3986e269f5c6430922665a9c37930d454b44beb/ants/registration/interface.py#L846-L852

stnava commented 5 years ago

note edit of previous comment.

stnava commented 5 years ago

also here:

https://github.com/ANTsX/ANTsPy/blob/c3986e269f5c6430922665a9c37930d454b44beb/ants/registration/interface.py#L827-L831

dangom commented 5 years ago

Thanks. As you mentioned, computing the FD directly from the motion estimates (from the function above _ants_affine_to_distance) is in good agreement with the FD as currently implemented in ANTs for rigid transforms. If you think adding these estimates to the output of motion_correction would make sense (for Rigid and Affine), I could try a PR. The only issue here for ANTsPy is that reading the mat files from registration requires pulling in scipy as an extra dependency (at least for my knowledge - I haven't considered other ways of reading mat files from python yet without scipy.io). Otherwise, feel free to close this issue

stnava commented 5 years ago

OK, I would definitely appreciate a pull request ... antsrtransform class And its associated functions will get you all the things you need without scipy ... We certainly appreciate the feedback and help, thank you.

On Tue, Jul 16, 2019 at 4:14 PM Daniel Gomez notifications@github.com wrote:

Thanks. As you mentioned, computing the FD directly from the motion estimates (from the function above _ants_affine_to_distance) is in good agreement with the FD as currently implemented in ANTs for rigid transforms. If you think adding these estimates to the output of motion_correction would make sense (for Rigid and Affine), I could try a PR. The only issue here for ANTsPy is that reading the mat files from registration requires pulling in scipy as an extra dependency (at least for my knowledge - I haven't considered other ways of reading mat files from python yet without scipy.io).

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/ANTsX/ANTsPy/issues/71?email_source=notifications&email_token=AACPE7W5NLUJOKZENH3ERBTP7YTZZA5CNFSM4H7HZ2W2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2CAGQI#issuecomment-511968065, or mute the thread https://github.com/notifications/unsubscribe-auth/AACPE7VXPPGZEMR7QEGWCNLP7YTZZANCNFSM4H7HZ2WQ .

--

brian

stnava commented 5 years ago

ants.read_transform and ants.write_transform ,,, they also have get parameters and related ops.

wangyibin0011 commented 4 years ago

谢谢。如您所提到的,直接从运动估计(根据上述函数_ants_affine_to_distance)计算出FD 与目前在ANT中为刚性变换实现的FD完全一致。 如果您认为将这些估算值添加到的输出中motion_correction是有意义的(对于Rigid和Affine),我可以尝试PR。对于ANTsPy来说,唯一的问题是要从中读取Mat文件registration需要scipy额外的依赖(至少据我所知-我还没有考虑过从python读取scipy.io的其他方法)。 否则,请随时关闭此问题

谢谢。如您所提到的,直接从运动估计(根据上述函数_ants_affine_to_distance)计算出FD 与目前在ANT中为刚性变换实现的FD完全一致。 如果您认为将这些估算值添加到的输出中motion_correction是有意义的(对于Rigid和Affine),我可以尝试PR。ANTsPy唯一的问题是从中读取Mat文件registration需要scipy额外的依赖(至少据我所知-我还没有考虑过从python读取scipy.io的其他方法)。 否则,请随时关闭此问题

Hello, I got this temporary mat file after using ants' affine registration. I opened it with scipy.io, as follows. Then I got a matrix according to your ants_to_human, but it seems that I cannot convert the move image to fixed according to this matrix. I want to know: how do I get the rotation matrix of 4x4 according to this mat file? Can the two nii.gz before and after registration be converted by a 4x4 matrix in affine mode? I am a novice in this field and I look forward to your reply 13

stnava commented 4 years ago

@wangyibin0011 please open a different issue for this question