EEG dipole fitting using fsaverage: Are "projected" sensors used?

Hi all, I am trying to fit dipoles on my data using fit_dipole.

I am using “fsaverage”, and currently I am even using template EEG sensor positions (rather than digitized positions), for testing.

To make the situation worse, my template EEG sensor positions were (probably) calculated on a sphere. So, the coregistration of my EEG sensors and “fsaverage” is very bad, see:



:backhand_index_pointing_up:

The left image shows the situation with trans="fsaverage", here NAS, LPA, and RPA are well aligned, but the sensors are partially quite far away from the scalp surface.

The right image shows the situation with a trans that I computed automatically following Using an automated approach to coregistration — MNE 1.9.0 documentation, so trying to minimize the distance of the sensors from the scalp surface. The result is slightly better (however, NAS, LPA, and RPA are no longer aligned) … but several sensors are still off, and partially too deep inside of the skull.

My question pertains to the sensor positions that are actually used in fit_dipole. Are the “scalp-projected” sensor positions used (the dark red discs in the images), or are the actual sensor positions used (the light red balls in the images)?

The mne.viz.plot_alignment docstring hints at the “scalp-projected” sensors, see:



And this would also make sense to me, because otherwise, if a sensor is outside of the head, floating in the air, then what “conductivity” could possibly be used.

Does somebody know the answer?

If the “scalp-projected” sensor positions would be used, this would actually allow me to proceed testing with this. I would then likely proceed with the trans that minimizes the distance of sensors to the scalp (the right side image), even if NAS, LPA, RPA are misaligned.

Eventually, I will have digitized sensor positions, rather than the template EEG sensor positions that I am conducting tests with here.

Hello @sappelhoff, which montage did you use, specifically?

:waving_hand:

It is a custom .bvef file by EasyCap that looks a bit like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Generated by EasyCap Configurator 10.01.2020 -->
<Electrodes defaults="false">
  <Electrode>
    <Name>GND</Name>
    <Theta>70</Theta>
    <Phi>80</Phi>
    <Radius>1</Radius>
  </Electrode>
  <Electrode>
    <Name>1</Name>
    <Theta>0</Theta>
    <Phi>0</Phi>
    <Radius>1</Radius>
    <Number>1</Number>
  </Electrode>
  <Electrode>
    <Name>2</Name>
    <Theta>20</Theta>
    <Phi>90</Phi>
    <Radius>1</Radius>
    <Number>2</Number>
  </Electrode>
...
</Electrodes>

Note how the Radius is always 1, implying that the sensors were computed on a sphere.

It’s a bit like the MNE-Python-shipped Easycap-M1, … note how in that file, there is only Theta and Phi, implying that the Radius is equal for all sensors, that is, the sensors were computed on a sphere there, too.

:backhand_index_pointing_up: NOTE: Neither of these two montage files contain landmarks (NAS, LPA, RPA), so mne is “inferring” them / “guessing” them in some way. I don’t know how this is done specifically, but the visualization of the sensors seem meaningful (especially when plotted on a sphere).

You need to use one of the built-in montages whose name starts with the standard_ prefix so that the trans we ship for fsaverage works nicely.

Thanks and yes, I know that that’s the way for 10-20 style sensor positions. Unfortunately my positions are of an “equidistant scheme”, and do not correspond to that.

Hence my original question of which locations actually get used in the dipole fitting: the “scalp-projected” ones, or the real ones. :slightly_smiling_face:

If it’s the “scalp-projected” ones, then I do not see a big problem, as long as the projected sensors look okay-ish (I am just in a testing phase, after all).

If it’s the “real ones”, then I’d really like to somehow “extract” the “scalp-projected” positions and make a montage out of that myself. It is probably how the mne standard_* montages were created, too, as you yourself have also hypothesized back then in the good old Built-in, standard, and template montages, and layouts 🤯 · Issue #10741 · mne-tools/mne-python · GitHub

I have to say I still don’t understand why the montage plot doesn’t show T7<->Cz<->T8 in a straight line, but that’s probably because I don’t fully understand how that fit was achieved. Did one start out with a sphere with optimally placed electrodes according to 10-05 / 10-20, and then moved the electrodes radially until they were all placed on the scalp level of fsaverage ? Could anybody please explain?

:backhand_index_pointing_up: quoted from Built-in, standard, and template montages, and layouts 🤯 · Issue #10741 · mne-tools/mne-python · GitHub … which I don’t think anyone could answer :thinking:

1 Like

@larsoner with your extensive knowledge of the codebase and the community, would you perhaps know some answers? … or people that I could ask about this?

Quick summary:

  • in dipole fitting, are the “scalp-projected” sensor positions used? Or the “true” sensor positions?
  • is there a way to “extract” the “scalp-projected” sensor positions from the fsaverage plot after plot_alignment?

I basically need to go from my spherical template EEG positions to some “realistic biological” template positions in order to continue testing.

(eventually, I will have digitized, realistic sensor positions)


Did one start out with a sphere with optimally placed electrodes according to 10-05 / 10-20, and then moved the electrodes radially until they were all placed on the scalp level of fsaverage ? Could anybody please explain?

@richard I read through all our montage related issues again, and dug up that the mne realistic standard_* montages are actually from here: High-density EEG electrode placement | Robert Oostenveld’s blog

1 Like

Sensors are projected to the head surface (conceptual reasoning is because voltages are measured on the scalp in reality; practical reasoning is that the voltages are computed on the outermost BEM or sphere shell in the forward code).

No way to extract AFAIK. We should maybe add a montage.project_to_scalp(...) or similar, would be a neat add. But the heavy lifting is done by a private function, so assuming you get the scalp surface (should be a smoothed / decimated one!) transformed to head coords you can use something like:

                eegp_loc, eegp_nn = _project_onto_surface(
                    sens_loc, head_surf, project_rrs=True, return_nn=True
                )[2:4]

it’s what we do in mne/viz/_3d.py. But of course this is private code so to future proof it, the public method proposed above would be much better to implement!

2 Likes

Thanks a lot! I will look into it, I also think it would be a neat feature.

EDIT: opened Extract scalp-projected sensor locations as DigMontage · Issue #13188 · mne-tools/mne-python · GitHub

EDIT2: for posterity, here are the functions that “show” that projected sensor positions are used in dipole fitting:


_prep_field_computation in fit_dipole: mne-python/mne/dipole.py at 618932d7fbb6ef7e4d9f110c90a1cc77f51ab082 · mne-tools/mne-python · GitHub

bem_specify_els in _prep_field_computation mne-python/mne/forward/_compute_forward.py at 618932d7fbb6ef7e4d9f110c90a1cc77f51ab082 · mne-tools/mne-python · GitHub

project_onto_surface in bem_specify_els mne-python/mne/forward/_compute_forward.py at 618932d7fbb6ef7e4d9f110c90a1cc77f51ab082 · mne-tools/mne-python · GitHub

(and project_onto_surface is doing the scalp-projection)

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.