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