Order of operation when catenate transforms with cat_matvec

Hi everyone,

I’m sorry but I’m getting a little confused about cat_matvec.
I defined two 3x3 matrices A and B, and did
cat_matvec A B > C
and fould C=B*A, which suggests transform A is applied first, and then B is applied.
This is exactly what the doc describes.

So if I’d like to volreg a dset, then align it to anat, I should do
cat_matvec volreg align > combined
3dAllineate -1Dmatrix_apply combined
Is it correct?

But I found the script generated by afni_proc.py seemed to do the opposite:
cat_matvec align volreg > combined

How to understand the order of operation during xform concatenation?

Thanks very much!

Howdy-

Yeah, it’s confusing, because there are (at least) two levels of backwards involved.

NB: my own learning on the topic came from and has been memorialized in this pretty long thread discussing this kind of topic:
https://afni.nimh.nih.gov/afni/community/board/read.php?1,147114,147119

From the cat_matvec helpfile:


=== COMPUTATIONS ===
If [ U] [v] are the matrix/vector for the first mfile, and
   [A] [b] are the matrix/vector for the second mfile, then
the catenated transformation is
  matrix = [A][ U]   vector = [A][v] + [b]
That is, the second mfile transformation follows the first.
** Thus, the order of matrix multiplication is exactly the  **
** opposite of the order of the inputs on the command line! **

So, firstly, algebra notation with left-multiplication means that in “[A][ U] dset”, first the matrix [ U] is applied to the dset, and then matrix [A].

The second level of backwards (I think) is what it means to apply a matrix: normally we thing of “sending” data from one space to another, so the order would be like jumping from source to base: space A → space B and space B → to space C. So, then, reading like the above, we’d want:


cat_matvec  map_AtoB mapBtoC > map_AtoC

which should theoretically do: [mapBtoC][map_AtoB] dset.
However, the way the mapping works is to start with the grid of the base/master dset and pull data. Therefore, conceptually we’d want to start in space C, and first pull from space B, and then pull from space A.

It might appear backwards from how we think about it, but it’s because instead of starting from source and moving to base, mapping starts in the base space and pulling data from the source (that way, the final grid is made, and one just decides where to pull data from to populate it). So, start in C and the first operation is to pull from B, then be in B and use the second operation to pull from A. So, the order from the AFNI Bootcamp demo looks like:


cat_matvec -ONELINE                                         \
               FT_anat_ns+tlrc::WARP_DATA -I                    \      # 1) into TLRC from anat
               FT_anat_al_junk_mat.aff12.1D -I                  \        # 2) into anat from one EPI
               mat.r$run.vr.aff12.1D > mat.r$run.warp.aff12.1D    # 3) within EPI 

So, hopefully from a “base originating” rather than a “source originating” point of view (-> insert reference to the “all your base are belong to us” here, if that helps), then it makes more sense how to mesh the helpfile with the implementation.

–pt

Many thanks ptaylor~ Nicely explained.

I’ve read this mechanism in the help for 3dNwarpApply, but I didn’t realize that it also applies to cat_matvec.

An important question arises though: when to use -I to take the inversion?

If I align_epi_anat.py dset1 to dset2, which gives me a dset1_mat.aff12.1D,
and align_epi_anat.py dset2 to dset3, which gives me a dset2_mat.aff12.1D.
Now I want dset1 to align with dset3, shall I do
cat_matvec -ONELINE
dset2_mat.aff12.1D
dset1_mat.aff12.1D > combined.1D
Or
cat_matvec -ONELINE
dset2_mat.aff12.1D -I
dset1_mat.aff12.1D -I > combined.1D
And then
3dAllineate -input dset1+orig -1Dmatrix_apply combined.1D

In the example, why
FT_anat_ns+tlrc::WARP_DATA -I \ # 1) into TLRC from anat
FT_anat_al_junk_mat.aff12.1D -I \ # 2) into anat from one EPI
are followed with -I, but
mat.r$run.vr.aff12.1D
is not?

Thanks!

Hi-

For when to use “-I” to invert a transform or not depends solely on what the “base” and “source” dsets are when you make your transform, relative to how you want your data to actually move when you apply it.

Below, I’m looking in the “proc.FT” file made from the AFNI_data6/FT_analysis directory of the AFNI Bootcamp data.

In these cases, the volreg steps were all made by aligning each volume to a reference one: that is the correct way we want things to move, so we don’t invert.

In aligning the EPI and anatomical, the command in afni_proc.py was run as:


align_epi_anat.py -anat2epi -anat FT_anat+orig \
       -save_skullstrip -suffix _al_junk       \
       -epi vr_base+orig -epi_base 0           \
       -epi_strip 3dAutomask                   \
       -volreg off -tshift off

… which calculates the transform from anat (source) → epi (base), even though eventually we want the epi → anat space, so therefore that gets inverted in the cat_matvec to be the “forward” transform we want.

To align anat and template spaces, here the @auto_tlrc was used as follows:


# ================================== tlrc ==================================
# warp anatomy to standard space
@auto_tlrc -base TT_N27+tlrc -input FT_anat_ns+orig -no_ss

# store forward transformation matrix in a text file
cat_matvec FT_anat_ns+tlrc::WARP_DATA -I > warp.anat.Xat.1D

… and the cat_matvec command is provided with the transform stored in the file’s header, “FT_anat_ns+tlrc::WARP_DATA”, which as described here is made to be the “forward” transform from anat → tlrc space

So, for thinking about the need to invert a transform or not:
if “source” dset heading to “base” space is what we want, then apply that “forward” transform as is; if it the opposite, then apply the inverse.

When thinking about the mathematics/mechanics of what the transform is, think about being in grid B pulling from grid A.

(And Rick can probably comment more on afni_proc specificness.)

–pt

Thank you very much! All the pieces now fit to each other:D

Just as a quiz to myself,

FT_anat_ns+tlrc::WARP_DATA must be a matrix that maps points in standard space back to native space. Right?:slight_smile: