mne-tools / mne-python

MNE: Magnetoencephalography (MEG) and Electroencephalography (EEG) in Python
https://mne.tools
BSD 3-Clause "New" or "Revised" License
2.76k stars 1.33k forks source link

Import digitization from text file and align with individual MRI #3699

Closed noreun closed 4 years ago

noreun commented 8 years ago

A coregistration between electrodes and scalp surface is necessary in order to create a forward model.

The tutorial about Head Model and forward computation does not have much information about how it is done, except that a "-trans.fif" containing the alignment between electrodes and scalp surface is necessary:

http://martinos.org/mne/stable/auto_tutorials/plot_forward.html

And that it can be generated using mne.gui.coregistration, mne_analyse or mrilab.

If mne.gui.coregistration is used, it is necessary to have the digitization information in another ".fif" file. However it is not very clear how this information should be inserted into the mne.Raw() object, or even which information is necessary desired (fiducials, head points, etc)

I created a simple procedure that loads the digitization of electrodes positions and fiducials information from a text file using cvs.read, creates a DigMontage object using mne.channels.read_dig_montage, and update the infor['dig'] using raw.set_montage()

However I don't know if this is the correct was to do it. For example raw.set_montage do not set the electrodes positions when called with a DigMontage object, so it has to be called twice, the frist time with a normal Montage object

Here is the code, I can improve it a help to write a tutorial / update documentation if it is the case

# Creates the Digitization Montage
reader = csv.reader(open(sensor_file), delimiter=" ")
nelec = 260
positions = np.zeros((nelec, 3))
labels = [' '] * nelec
for line_idx, line in enumerate(reader):
    position = [float(zz) for zz in line[1:]]
    labels[line_idx] = line[0]
    positions[line_idx, :] = position

digitization = read_dig_montage(hsp=positions.copy(), elp=positions.copy(), point_names=labels, unit='mm')

raw.set_montage(digitization)
raw.save(result_file, overwrite=True)
agramfort commented 8 years ago

you can call:

raw.plot_sensors()

to see if it makes sense.

noreun commented 8 years ago

I just found out they do not

https://drive.google.com/open?id=0B0p7WZ2wlTFSSUh4OFQySzYwYzQ

here is the EGI electrodes for reference

https://drive.google.com/open?id=0B0p7WZ2wlTFSTDZ2bzI1NWc3VVk

Which is weird since they were not that far from the MRI...

https://drive.google.com/open?id=0B0p7WZ2wlTFSNklmcTlPakJERXc

How can I fix this? Apparently the loaded electrode positions used in raw.set_montage with the Montage object (called before the snippet posted above) where very far from the MNE reference. Is it projecting into the (X,Y) plane?

noreun commented 8 years ago

just to make it clear, they were also not that far before aligning to MRI, specially not upside-down like in the raw.plot_sensors()

larsoner commented 8 years ago

@noreun want to try plot_trans with #3707 with your data?

noreun commented 8 years ago

I did, and it looks really bad: it is like the translation is not applied to the electrodes positions. I feel like I'm missing some important step here

https://drive.google.com/open?id=0B0p7WZ2wlTFSN2JrSXh5Tkhrd1U

However, if I show the digitization points, than I can see they are in the correct position as shown during mne.gui.coregistration!

https://drive.google.com/open?id=0B0p7WZ2wlTFSN0JkVEJyOWxZOTA

Just a reminder, I first use

raw.set_montage()

with a normal Montage object, built from the electrodes positions/labels saved during the digitization process

Next, I create a DigMontage object using the same information + fiducials, call

raw.set_montage()

again passing this new object to set the digitization information. Then finally I save the Raw object and run

mne coreg

to align the fiducials using the file with the Raw object, and save the -trans.fif file.

The plot above was generated with the raw.info and the -trans.fif file resulting from this process...

noreun commented 8 years ago

btw, here is the call to the plot_trans file

    plot_trans(raw.info, trans, subject=subject_name, dig=True,
             eeg_sensors=['original', 'projected'],
             meg_sensors=[], coord_frame='head', subjects_dir=subjects_dir)
noreun commented 8 years ago

ok, I figured out the problem. The plot now looks like this:

https://drive.google.com/open?id=0B0p7WZ2wlTFSQ0VfNWwwTkJpOFE

The problem is that read_dig_montage has a default parameter Transform that project the hsp and elp points into neuromag space

On the other hand, this is not the default during read_montage

Therefore, the translation matrix created with mne.gui.coregistration do not make sense for the electrodes positions inside raw.info, creating a non-sense projection.

For me the correct solution would be to run read_dig_montage with Transform=False (and have this more explicit in the Tutorial, since I guess not everybody uses Polhemus or Neuromag).

However, it turns out that in this case, the fiducials are just ignored in montage.py!

Lines 597 to 602:

            neuromag_trans = get_ras_to_neuromag_trans(nasion, lpa, rpa)
            fids = apply_trans(neuromag_trans, [nasion, lpa, rpa])
            elp = apply_trans(neuromag_trans, elp)
            hsp = apply_trans(neuromag_trans, hsp)
        else:
            fids = [None] * 3

My current solution is to also project the eletrodes into neuromag space dugin read_montage using Transform=True.

Another weird thing is that read_montage expects the fiducials to be ('nz', 'lpa', 'rpa') while read_dig_montage expects ('nasion', 'lpa', 'rpa').

Summing-up:

agramfort commented 8 years ago

@noreun thanks a lot for looking into this.

could you send us a WIP pull request with the changes you had to do to make it work for you?

if you PR contains test it will guarantee that we won't break your workflow in the long term.

noreun commented 8 years ago

I created a PR for the change in fiducials name so that they are the same in both read_montage and read_dig_montage

However I don't know if I should I use Tranform=True during read_montage (my current solution, no changes in mne required) or Tranform=False in read_dig_montage (did not try yet, should change read_dig_montage)?

I figure that the reason the fiducials are set to [None] * 3 in read_dig_montage is because they have to be in Neuromag space, otherwise something would break. Is that correct? Otherwise, I'd be willing to fix that and update the fiducials in read_dig_montage.

Once this is decided, I'd also be willing to write a tutorial about co-registration with locations from generic input (e.g., text files), that could be referenced in the Head Model and Forward Computation Tutorial (there is not much info about this important step)

I guess I should just create a tutorial/plot_coregistration.py following these guidelines?

http://mne-tools.github.io/stable/contributing.html#checking-and-building-documentation

larsoner commented 8 years ago

@teonbrooks do you have a moment to comment on these issues? I'm not sure what the purpose of these arguments was/is.

Once this is decided, I'd also be willing to write a tutorial

Yes please reference in the head model tutorial that you need properly provided dig points. Your new tutorial could have nice sanity check plots like plot_sensors, plot_trans, etc. Whatever you used to make sure things weren't broken can help here. Even things like the histogram of distances thing we talked about might be a cool thing to add (manual matplotlib/numpy stuff like that is great if it's only a few lines to do something useful).

I guess I should just create a tutorial/plot_coregistration.py following these guidelines?

Yes. And I recommend copy-pasting an existing tutorial to get you started, and adapt from there. Things like the title need to be formatted properly or it will break, so don't start from absolute scratch. (Please also add this tidbit to the contrib instructions while you're in there if you have time.)

teonbrooks commented 8 years ago

the DigMontage wasn't intentionally meant for electrode placements but for head shape, ELP (indicator points in headshape-space) and HPI (indicator points in MEG space). Maybe the name wasn't the best choice...maybe this needs more documentation and/or a name change.

One standard way to deal with digitized electrode positions is to make a hpts file and use the read_montage. Sorry @noreun, I only had a chance to skim through the past notes so you might have mentioned this but how are you reading the digitized electrode points now to get through to properly work.

teonbrooks commented 8 years ago

I figure that the reason the fiducials are set to [None] * 3 in read_dig_montage is because they have to be in Neuromag space, otherwise something would break. Is that correct? Otherwise, I'd be willing to fix that and update the fiducials in read_dig_montage.

if you use the transform=True, it would put all the points into Neuromag space

just fyi, the keyword transform, and the Transform object serve different roles.

larsoner commented 8 years ago

@teonbrooks so Montage is for electrode points, whether from a standard (non-individualized) set or digitized set? And DigMontage is meant for all other things? I thought you had said before that Montage was really meant for standard things and DigMontage was for anything that was manually digitized... but I could be wrong.

teonbrooks commented 8 years ago

yeah, this is confusing. so DigMontage was purposed to take care of digitized head points, and digitized reference points. we should probably add an argument to handle digitized electrodes, it seems like the most fitting place.

hpts has been historically used as a placeholder for electrode/sensor points that aren't a standard format. it could be modified to handle digitized points but the standard practice of Montages is that they are preset points.

My proposal is that we could add the arguments sensor positions, which should be a numpy array and sensor_types, which should be a list of str and we could have the set_montage take care of the appropriate transformation when set to True.

@Eric89GXL, what are your thoughts on this? this would essentially make the DigMontage do what a number of people desire it to do.

larsoner commented 8 years ago

I don't use these functions so it's hard to know if this would make sense to people. @jona-sassenhagen do you use these functions? @choldgraf I saw you used them in some code?

noreun commented 8 years ago

@teonbrooks now I use read_montage with transform=True, and then read_dig_montage also with transform=True. This way the electrodes positions defined in read_montage and the fiducials positions defined in read_dig_montage are in the same space, so I can finally use mne coreg to create the Transformation matrix that put both into MRI space.

More than the fact that Montage object sets the electrodes positions and DigMontage sets only the fiducials (and elp, hpi, etc), the most confusing part to debug was the fact that transform is defaulted to False in read_montage and defaulted to True in read_dig_montage.

But it is true that if read_dig_montage is fixed to also set the electrodes positions, which will also be transformed into Neuromag space by default, it settles the problem.

choldgraf commented 8 years ago

@Eric89GXL which functions? DigMontage? I've never used them before (I'm a spoiled ecog brat so I haven't used much of the EEG/MEG API)

larsoner commented 8 years ago

I am in the process of changing DigMontage to support channel dig. It already does to some extent. @choldgraf that's what you'll want to use instead of Montage

noreun commented 8 years ago

@Eric89GXL so after the changes, there will be only one call to read_dig_montage, and the info will have the channel locations and fiducials/head points all in the same space? This will be really good.

larsoner commented 4 years ago

I think that this has been taken care of in the refactoring plus examples like:

https://mne.tools/dev/auto_tutorials/source-modeling/plot_eeg_mri_coords.html#sphx-glr-auto-tutorials-source-modeling-plot-eeg-mri-coords-py

Feel free to reopen if things are still not clear