Matrix Conversion .lta to .1D

AFNI version info (afni -ver): Version AFNI_25.1.06 'Maximinus'

Hi,

I've got a .lta file with an affine registration matrix in it from Freesurfer's mri_synthmorph. I'm trying to use that matrix to align the same volumes using 3dAllineate, but I believe there may be some coordinate space differences that I can't determine.

This is the matrix from the .lta file:

9.911992466926259e-01 7.963331062131354e-02 1.057467621861600e-01 7.393124638217159e+00
-8.446867855799430e-02 9.955381239451147e-01 4.205592368402300e-02 -2.635519463403853e-01
-1.019258913012564e-01 -5.061809843167590e-02 9.935034049977768e-01 -2.033452875078581e+00

The file also indicates "type = 1 # LINEAR_RAS_TO_RAS" and I have tried using conversions to RPI (the original AFNI file type) and RAI (AFNI standard, I believe), but neither have proved sufficient.

Any thoughts on using this matrix in 3dAllineate to translate volumes using the data computed from synthmorph (not using deformable, only affine)?

Thanks,

-C

Hi, C-

There are probably a fair number of subtle things that might be happening. (And at the moment, our HPC is down for maintenance, so I can't do a simple test here using the two softwares on the same data, unfortunately.) I don't know mri_synthmorph at all, so I can't really comment on the details of what it is outputting.

1) Directionality. For 3dAllineate with 1Dmatrix_* .., the matrix stores this information and is applied this way:

This matrix is the coordinate transformation from base
 to source DICOM coordinates. In other terms:
    Xin = Xsource = M Xout = M Xbase
             or
    Xout = Xbase = inv(M) Xin = inv(M) Xsource
 where Xin or Xsource is the 4x1 coordinates of a
 location in the input volume. Xout is the 
 coordinate of that same location in the output volume.
 Xbase is the coordinate of the corresponding location
 in the base dataset. M is ff augmented by a 4th row of
 [0 0 0 1], X. is an augmented column vector [x,y,z,1]'
To get the inverse matrix inv(M)
(source to base), use the cat_matvec program, as in
  cat_matvec fred.aff12.1D -I

That is, M is applied to a coordinate in the base (or "reference") dataset, to figure out which (x,y,z) coordinate corresponds within the source dataset. We normally talk about "sending data from the source S to the base B", but in reality the alignment here works by "pulling data to base B from source S".

2) Units. 3dAllineate matrices are working in units of mm and rotations in units of degrees. I assume that mri_synthmorph works in the same, but worth verifying.

3) Coordinate names and sign conventions. Things like RAI, LPI, etc. coordinates refer to how we know whether a given sign is left or right. Like, is an x-coordinate of value "-5" on the left or right of the brain?

  • AFNI coordinates by default follow the original DICOM notation, which is referred to originally as RAI, meaning that Right, Anterior and Inferior all have negative signs. In many AFNI programs different orientations could be specified, but again, that is the default. I believe that is the notation that matrices use universally, regardless of the orientation of how datasets are stored on disk.
  • Some other software use different default systems for their coordinates. That is, we refer to the SPM default as "LPI", because they have chosen Left, Posterior and Inferior to be on the negative side of the coordinate origin.
  • Some other software even refer to their coordinate choice in a different/opposite manner. Some software tools don't refer to their sign convention by the negative value, but instead by what gets the positive value. In that system, SPM's default coordinates would be labelled as "RAS", signifying that Right, Anterior and Superior sides of the origin have positive coordinates. Sometimes I believe people refer to this coordinate system as "RAS+", so that it is clearly that the letters are being used to define what has positive coordinates; that is, RAS+ is synonymous with LPI-.
  • So be very, very careful when thinking about what coordinate system is being described. It is all very confusing not just that different software and scanners and data formats use different systems, but that they also refer to their system differently! I believe that FreeSurfer uses the notation of "positive-side coordinates" for their labels, so their RAS would be what AFNI and DICOM (which label based on negative-coordinates) call LPI.

To summarize, I would check/verify whether the matrix of the mri_synthmorph transform is applied to the base (to pull from the source) or applied to the source (to pull from the base). Then, I would assume that you will have to change coordinates from RAS+ (= LPI-) to RAI-, as it were, to match conventions.

So, I hope you wanted some subtlety about applying coordinate transforms on your Friday afternoon!

Please let us know how it goes. I will try to see if I can apply this to a dataset on this side next week, when I have FreeSurfer available to me again.

--pt

Thank you for the tips. I'll keep working on it :)

Howdy-

I took a look at this now with a real dset. I took the MNI template, downsampled it to 2 mm iso (for speed of algorithms), and made a shifted+rotated+scaled+sheared version.

  • TEST1. I ran both mri_synthmorph and 3dAllineate on it, to compare their affine matrices.
  • TEST2. As a follow-up, I ran the same thing, when one dset had a different orientation (original MNI template orientation in in "RAI-" notation is LPI, and the updated one was ASL, to be very different/permuted).

In each case, I will just dump out the 3x4 matrix of affine fit values, ignoring the trivial "0 0 0 1" bottom row that could be added, by using AFNI's cat_matvec. I'm going to refer to the matrix and vector elements according to this structure (which is independent of coordinate system definition):

M_xx  M_xy  M_xz  V_x
M_yx  M_yy  M_yz  V_y
M_zx  M_zy  M_zz  V_z

For TEST1,

  • the output matrix by FS's mri_synthmorph was:
    $ cat_matvec mat_11_synthmorph.1D 
       0.895049   -0.00112286      0.138086      -16.5167
     -0.0704889      0.966886      0.266646       3.75521
      -0.162203     -0.267874      0.973835      -11.5305
    
  • the output matrix by AFNI's 3dAllineate was:
    $ cat_matvec mat_12_3dAllineate.1D
       0.893356   9.15132e-05     -0.138777       16.7647
     -0.0740524      0.965924       -0.2563      -3.69568
       0.161828      0.258768        0.9565      -11.1653
    

So, ignoring tiny differences in parameter fits, you can see that within the initial 3x3 matrix, for the M_xz, M_zx, M_yz and M_zy values, there is a difference of -1 (I believe the apparent difference in the M_xy values' sign is not real, just an artifact of having tiny values around 0); in the 3x1 vector at the right, there is a difference in V_x and V_y of -1.

  • The way I think about how this makes sense is that it is essentially the "x" and "y" coords that have different signs between the "LPI" and "RAI" notations, so any matrix or vector element with an odd number of x- and y- values should flip sign (and those with even counts of them shouldn't, because (-1)**2 is 1).

For TEST2,

  • the output matrix by FS's mri_synthmorph was:
    $ cat_matvec mat_21_synthmorph.1D 
       0.895387   -0.00089342      0.140195      -16.5276
     -0.0671414      0.965995      0.266748       3.72081
      -0.164349     -0.268251      0.971736       -11.616
    
  • the output matrix by AFNI's 3dAllineate was:
    $ cat_matvec mat_22_3dAllineate.1D
       0.893344   0.000101885     -0.138763        16.764
     -0.0740345      0.965924     -0.256254      -3.69615
       0.161832       0.25882      0.956493      -11.1667
    

This follows the same pattern of sign differences (if again ignoring the M_xy apparent difference, which I don't think is real, and just randomly happening when close to 0 there).

So, I think the way to do the transform adjustment from mri_synthform (which is in LPI format, if we use the notation of defining which side of coord origin are negative) -> 3dAllineate (which is in RAI format), do this. Do point-wise multiplication of the affine matrix+vector transform like:

# showing sign flips by element
 1   1  -1  -1
 1   1  -1  -1
-1  -1   1   1

... and, on the formatting note, just keep the first 12 values of the re-signed mri_synthmorph and flatten them into a single row, so 3dAllineate can apply them, i.e.:

M_xx  M_xy  M_xz  V_x M_yx  M_yy  M_yz  V_y M_zx  M_zy  M_zz  V_z

Then, if you had estimated the FS mri_synthmorph transform like this:

# source : dset F
# base   : dset E
mri_synthmorph                                                               \
    --model  affine                                                          \
    --trans  mat_21_synthmorph.1D                                            \
    --moved  MNI_21_synthmorph_F2E.nii.gz                                    \
    MNI_20_F.nii.gz                                                          \
    MNI_20_E.nii.gz

... you could apply the correct sign-flip version like this:

3dAllineate                                         \
     -1Dmatrix_apply   mat_21_synthmorph_FLIP.1D    \
     -source           MNI_20_F.nii.gz              \
    -prefix            OUT_F2E.nii.gz

For what it is worth, I will put my test do_1*.tcsh scripts at the bottom of this post; you just need to change the ${p1} path to your MNI dataset to run this.

--pt


The following are a set of 3 scripts to generate test data, and then to use FS mri_synthmorph and AFNI 3dAllineate to generate affine alignments. The should be executable in order, with adjustment to the ${p1} variable, as well as any module loading.

do_10_copy_aff.tcsh

#!/bin/tcsh

# make two copies of downsampled (template) dsets, to test alignment
# quickly-ish

set p1      = /usr/local/apps/afni/current-py3/linux_rocky_8/
set dset_in = ${p1}/MNI152_2009_template.nii.gz

# dset C (downsample in place)
3dresample                                                                   \
    -overwrite                                                               \
    -dxyz    2 2 2                                                           \
    -prefix  MNI_10_C.nii.gz                                                 \
    -input   ${dset_in}

# dset D (downsample, rotated, shift)
3dresample                                                                   \
    -overwrite                                                               \
    -dxyz    2 2 2                                                           \
    -prefix  __tmp.nii.gz                                                    \
    -input   ${dset_in}

cat <<EOF > par_10_aff_xform.1D
# x-shift   y-shift  z-shift   z-angle  x-angle   y-angle   x-scale  y-scale  z-scale  y/x-shear  z/x-shear  z/y-shear 
 -16.763908 5.966705 12.903725 0.017263 14.994987 -7.973629 1.1      0.999807 0.999654 0.03        -0.05       0.0
EOF

3dAllineate                                                                  \
    -overwrite                                                               \
    -1Dparam_apply  par_10_aff_xform.1D                                      \
    -prefix         MNI_10_D.nii.gz                                          \
    -source         __tmp.nii.gz                                             \

# clean up
\rm -f __tmp.nii.gz

echo "++ Check out new dsets:"
echo ""
echo "     afni MNI_10*.nii.gz"

exit 0

do_11_synthmorph.tcsh

#!/bin/tcsh

# Run FreeSurfer mri_synthmorph to estimate affine alignment

# ============================================================================

if ( 1 ) then
    source /etc/profile.d/modules.csh
    module load afni freesurfer/7.4.1
    source $FREESURFER_HOME/SetUpFreeSurfer.csh
endif

# ============================================================================

# source : dset D
# base   : dset C
mri_synthmorph                                                               \
    --model  affine                                                          \
    --trans  mat_11_synthmorph.1D                                            \
    --moved  MNI_11_synthmorph_D2C.nii.gz                                    \
    MNI_10_D.nii.gz                                                          \
    MNI_10_C.nii.gz


echo "++ Check out new dsets:"
echo ""
echo "     afni MNI_1*.nii.gz"

exit 0

do_12_3dAllineate.tcsh

#!/bin/tcsh

# Run AFNI's 3dAllineate to estimate affine alignment

# ============================================================================

if ( 1 ) then
    source /etc/profile.d/modules.csh
    module load afni
endif

# ============================================================================

# source : dset D
# base   : dset C
3dAllineate                                                                  \
    -overwrite                                                               \
    -lpa                                                                     \
    -autoweight                                                              \
    -source_automask                                                         \
    -twopass                                                                 \
    -1Dmatrix_save  mat_12_3dAllineate.1D                                    \
    -1Dparam_save   par_12_3dAllineate.1D                                    \
    -prefix         MNI_12_3dAllineate_D2C.nii.gz                            \
    -source         MNI_10_D.nii.gz                                          \
    -base           MNI_10_C.nii.gz


echo "++ Check out new dsets:"
echo ""
echo "     afni MNI_1*.nii.gz"

exit 0

And, in case useful, the similar do_2*.tcsh scripts, which include re-orienting one dset

do_20_copy_aff_orient.tcsh

#!/bin/tcsh

# make two copies of downsampled (template) dsets, to test alignment
# quickly-ish

set p1      = /usr/local/apps/afni/current-py3/linux_rocky_8/
set dset_in = ${p1}/MNI152_2009_template.nii.gz

# dset E (downsample in place)
3dresample                                                                   \
    -overwrite                                                               \
    -dxyz    2 2 2                                                           \
    -prefix  MNI_20_E.nii.gz                                                 \
    -input   ${dset_in}

# dset F (downsample, rotated, shift, reorient)
3dresample                                                                   \
    -overwrite                                                               \
    -dxyz    2 2 2                                                           \
    -prefix  __tmp.nii.gz                                                    \
    -input   ${dset_in}

cat <<EOF > par_20_aff_xform.1D
# x-shift   y-shift  z-shift   z-angle  x-angle   y-angle   x-scale  y-scale  z-scale  y/x-shear  z/x-shear  z/y-shear 
 -16.763908 5.966705 12.903725 0.017263 14.994987 -7.973629 1.1      0.999807 0.999654 0.03        -0.05       0.0
EOF

3dAllineate                                                                  \
    -overwrite                                                               \
    -1Dparam_apply  par_20_aff_xform.1D                                      \
    -prefix         __tmp2.nii.gz                                            \
    -source         __tmp.nii.gz                                             \

3dresample                                                                   \
    -overwrite                                                               \
    -orient         ASL                                                      \
    -prefix         MNI_20_F.nii.gz                                          \
    -input          __tmp2.nii.gz


# clean up
\rm -f __tmp.nii.gz __tmp2.nii.gz

echo "++ Check out new dsets:"
echo ""
echo "     afni MNI_10*.nii.gz"

exit 0

do_21_synthmorph.tcsh

#!/bin/tcsh

# Run FreeSurfer mri_synthmorph to estimate affine alignment

# ============================================================================

if ( 1 ) then
    source /etc/profile.d/modules.csh
    module load afni freesurfer/7.4.1
    source $FREESURFER_HOME/SetUpFreeSurfer.csh
endif

# ============================================================================

# source : dset F
# base   : dset E
mri_synthmorph                                                               \
    --model  affine                                                          \
    --trans  mat_21_synthmorph.1D                                            \
    --moved  MNI_21_synthmorph_F2E.nii.gz                                    \
    MNI_20_F.nii.gz                                                          \
    MNI_20_E.nii.gz


echo "++ Check out new dsets:"
echo ""
echo "     afni MNI_2*.nii.gz"

exit 0

do_22_3dAllineate.tcsh

#!/bin/tcsh

# Run AFNI's 3dAllineate to estimate affine alignment

# ============================================================================

if ( 1 ) then
    source /etc/profile.d/modules.csh
    module load afni
endif

# ============================================================================

# source : dset F
# base   : dset E
3dAllineate                                                                  \
    -overwrite                                                               \
    -lpa                                                                     \
    -autoweight                                                              \
    -source_automask                                                         \
    -twopass                                                                 \
    -1Dmatrix_save  mat_22_3dAllineate.1D                                    \
    -1Dparam_save   par_22_3dAllineate.1D                                    \
    -prefix         MNI_22_3dAllineate_F2E.nii.gz                            \
    -source         MNI_20_F.nii.gz                                          \
    -base           MNI_20_E.nii.gz


echo "++ Check out new dsets:"
echo ""
echo "     afni MNI_2*.nii.gz"

exit 0