Quick clarification on MNE Coordinate Systems - mni_tal vs fs_tal

  • MNE-Python version: 0.22

I am wondering in the context of iEEG data, what do the different coordinate systems mean and how are they used in mne-python?

For example, mni_tal is MNI space I presume, so this corresponds to the fsaverage subject in FreeSurfer. However, then what is fs_tal in MNE? Is this also… FreeSurfer, and so fs_tal == mni_tal?

Current documentation on coordinate systems: Source alignment and coordinate frames — MNE 0.23.dev0 documentation

Reference discussion

For reference, we are trying to map coordinate frames in MNE-Python to BIDS-accepted keywords. Right now, the only one I can think of that works is mni_tal -> fsaverage.

1 Like

cc @larsoner, who might be able to help here

I actually don’t know for sure what FS_TAL is, in the constants it’s called “FreeSurfer Talairach” but it’s not used anywhere in MNE-Python. So I would stick with mni_tal, knowing that fsaverage is in that space, and that it’s an MNI305 space. I’m thinking that FS_TAL corresponds to FreeSurfer’s estimate of plain Talairach coordinates, but not in a very convincing way, see the “Note on Talairach” here.

If you take some points known to be somewhere in MNI305 coordinates and plot them on fsaverage do they show up in the right place?

Would it be safe to say fs_tal was intended for FreeSurfer tkras coordinates? That is, the surface coordinates? It’s basically RAS, but on the FreeSurfer surface grid.

Re: MNI305, I think so… but to my knowledge, I think if I specify a coord_frame in DigMontage like mni_tal, there is no internal transformation occurring, so it’s just giving it an explicit name?

Would it be safe to say fs_tal was intended for FreeSurfer tkras coordinates? That is, the surface coordinates? It’s basically RAS, but on the FreeSurfer surface grid.

I wouldn’t use fs_tal, I don’t think you need it.

mni_tal, which corresponds to MNI305, should work both for tkras and standard RAS as they are the same for fsaverage (everything is easier there).

to my knowledge, I think if I specify a coord_frame in DigMontage like mni_tal, there is no internal transformation occurring, so it’s just giving it an explicit name?

If you make a digmontage this way with nasion, LPA, and RPA, when you apply it to your raw instance it will transform the coordinates to Neuromag head coordinate frame. This transformation is given by montage.compute_head_t. This is meant to be covered here, can you look and see if it helps?

https://mne.tools/dev/auto_tutorials/misc/plot_seeg.html

I know we might’ve discussed this on github in the past, but just to clarify the following statement:

" Now we get the trans that transforms from our MRI coordinate system to the head coordinate frame. This transform will be applied to the data when applying the montage so that standard plotting functions like mne.viz.plot_evoked_topomap() will be aligned properly." in the tutorial

Does this mean that any frame besides coord_frame == 'head' will attempt to transform to “head” space (if the landmarks are present). So that’s what the trans = compute_native_head_t does? and if you would like to “revert” that back to coord_frame == 'mni_tal' space, then you need to invert trans and apply to the raw object’s montage?

Is there any reason to want sEEG data in “head” coordinate frame?

Sorry for the long questions. I’m just a bit confused sometimes when it comes to the coord_frame stuff in mne. I can make a short PR to update the tutorial based on our discussion here to perhaps alleviate any future questions.

Does this mean… you need to invert trans and apply to the raw object’s montage?

Yes. But when do you need to do this? (See below…)

Is there any reason to want sEEG data in “head” coordinate frame?

Yes, among other reasons this is one:

so that standard plotting functions like mne.viz.plot_evoked_topomap() will be aligned properly

Basically there are a number of places in MNE that we assume things are in the head coordinate frame. For example when plotting a topomap, because we draw a head circle and we want this to mean the same thing in all datasets, data should be in the head coordinate frame. Another is when writing FIF, EEG-like channels are assumed to be in the head coordinate frame historically (and to be safe / for maximum compatibility it’s best to store them there even today). Another is when doing forward modeling. Another is when doing plot_alignment. So to support EEG coordinates in the instance in something other than the head coordinate frame requires changing a lot of functions.

… coming back to the above, I would flip the question around and say, when do you want positions in something other than the head coordinate frame? We can add trans arguments to those for example where if you passed the mni_trans you get from head_mni_trans = montage.compute_native_head_t().inverse() to whatever plotting functions you need. (We’d also need to add a new Transform.inverse() method for this line to work, but that’s a detail.)

1 Like

Ah okay that makes sense!

… coming back to the above, I would flip the question around and say, when do you want positions in something other than the head coordinate frame? We can add trans arguments to those for example where if you passed the mni_trans you get from head_mni_trans = montage.compute_native_head_t().inverse() to whatever plotting functions you need. (We’d also need to add a new Transform.inverse() method for this line to work, but that’s a detail.)
[/quote]

I haven’t validated that this is an issue yet, but sometimes my coordinates look funky, so not sure if it’s cuz my localization was messed up, or if the plotting in mne is doing some magic behind the scenes related to transformations.

If one is using things like stc_near_sensors, and using a brain (say… fsaverage) and then coordinates are in that space too. If then you do raw.set_montage and it unknowingly transforms to head, wouldn’t the resulting plot get messed up? If so, then this would be desirable to move the raw.montage back to its native coordinate frame (e.g. mni_tal in this case).

At least in the case of stc_near_sensors, trans is an obligatory argument:

https://mne.tools/dev/generated/mne.stc_near_sensors.html

So in theory you should be prevented at least somewhat protected from making a mistake here. But you are right that if people don’t pay attention and just do trans=None for example, there will be a problem.