EEG digitization points not visible in mne.gui.coregistration()

Hi,
we recently installed the mne 1.3.1 and now we have a problem with the coregistration. This problem only seems to occur with EEG data (fif file format), but not with MEG or MEEG. The window opens normally, but when loading the fif file which contains the digitization points, only the fiducials are displayed. The EEG sensor points are not visible. They also donā€™t appear after using fitting options or changing the scaling. Also when loading a trans file of the same recording (made with previous mne versions), only the fiducials are visible.

  • MNE version: 1.3.1
  • operating system:Windows 10

Code:

import os.path as op
import mne

subjects_dir = op.join('L:/','nttk_palva','Projects', 'VWM','derivatives','')
mne.gui.coregistration(subject="S0204", subjects_dir=subjects_dir)

Output:
mne.gui.coregistration(subject=ā€œS0204ā€, subjects_dir = subjects_dir)
Using pyvistaqt 3d backend.

Using high resolution head model in L:\nttk_palva\Projects\VWM\derivatives\S0204\bem\S0204-head-dense.fif
Triangle neighbors and vertex normalsā€¦
Using fiducials from: L:\nttk_palva\Projects\VWM\derivatives\S0204\bem\S0204-fiducials.fif.
Using high resolution head model in L:\nttk_palva\Projects\VWM\derivatives\S0006\bem\S0006-head-dense.fif
Triangle neighbors and vertex normalsā€¦
Using fiducials from: L:\nttk_palva\Projects\VWM\derivatives\S0006\bem\S0006 new-fiducials.fif.
Using fiducials from: L:\nttk_palva\Projects\VWM\derivatives\S0006\bem\S0006 new-fiducials.fif.
Placing MRI fiducials - LPA
Using high resolution head model in L:\nttk_palva\Projects\VWM\derivatives\S0204\bem\S0204-head-dense.fif
Triangle neighbors and vertex normalsā€¦
Using fiducials from: L:\nttk_palva\Projects\VWM\derivatives\S0204\bem\S0204-fiducials.fif.
Loading MRI fiducials from L:\nttk_palva\Projects\VWM\derivatives\S0204\bem\S0204-fiducials.fifā€¦ Done!
Using S0204-head-dense.fif for head surface.
1 BEM surfaces found
Reading a surfaceā€¦
[done]
1 BEM surfaces read
Loading MRI fiducials from L:\nttk_palva\Projects\VWM\derivatives\S0204\bem\S0204-fiducials.fifā€¦ Done!
Using S0204-head-dense.fif for head surface.
1 BEM surfaces found
Reading a surfaceā€¦
[done]
1 BEM surfaces read
Out[5]: <mne.gui._coreg.CoregistrationUI at 0x212a0aeec20>Using S0204-head-dense.fif for head surface.
1 BEM surfaces found
Reading a surfaceā€¦
[done]
1 BEM surfaces read
Channel types:: eeg: 257

Thanks!

@jsattelb Hi, just a quick question. Does your .fif file contains EEG montage i.e., EEG sensor locations?
Can you please post the outputs of the following: raw.info and raw.info['chs'][loc], where loc can be a index (int type) of any sensor position.

best,
Dip

Yes! Actually, we havenā€™t changed anything in the code from mne 0.23 where everything still worked (with the same files as well).

Code

raw = mne.io.read_raw_fif('L:/nttk_palva/Projects/VWM/derivatives/S0204/cleaned_ip/S0204_session1-1_041022_vwm-att_ica-py_raw.fif', preload=True)
raw.info.get_montage()
raw.info['chs']

returns:
Out[9]: <DigMontage | 0 extras (headshape), 0 HPIs, 3 fiducials, 257 channels>
ā€¦
{ā€˜scannoā€™: 239,
ā€˜lognoā€™: 239,
ā€˜kindā€™: 2 (FIFFV_EEG_CH),
ā€˜rangeā€™: 1.0,
ā€˜calā€™: 1.0044318514701445e-06,
ā€˜coil_typeā€™: 1 (FIFFV_COIL_EEG),
ā€˜locā€™: array([ 5.59019327, 5.01253891, -5.52830362, 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. ]),
ā€˜unitā€™: 107 (FIFF_UNIT_V),
ā€˜unit_mulā€™: 0 (FIFF_UNITM_NONE),
ā€˜ch_nameā€™: ā€˜EEG 239ā€™,
ā€˜coord_frameā€™: 4 (FIFFV_COORD_HEAD)},
{ā€˜scannoā€™: 240,
ā€˜lognoā€™: 240,
ā€˜kindā€™: 2 (FIFFV_EEG_CH),
ā€˜rangeā€™: 1.0,
ā€˜calā€™: 1.0052947345684515e-06,
ā€˜coil_typeā€™: 1 (FIFFV_COIL_EEG),
ā€˜locā€™: array([ 5.71067047, 4.05617571, -6.35942078, 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. ]),
ā€˜unitā€™: 107 (FIFF_UNIT_V),
ā€˜unit_mulā€™: 0 (FIFF_UNITM_NONE),
ā€˜ch_nameā€™: ā€˜EEG 240ā€™,
ā€˜coord_frameā€™: 4 (FIFFV_COORD_HEAD)},
{ā€˜scannoā€™: 241,
ā€˜lognoā€™: 241,
ā€˜kindā€™: 2 (FIFFV_EEG_CH),
ā€˜rangeā€™: 1.0,
ā€˜calā€™: 1.0054804988612887e-06,
ā€˜coil_typeā€™: 1 (FIFFV_COIL_EEG),
ā€˜locā€™: array([-4.2443285 , 5.93575907, -4.01106215, 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. ]),
ā€˜unitā€™: 107 (FIFF_UNIT_V),
ā€˜unit_mulā€™: 0 (FIFF_UNITM_NONE),
ā€˜ch_nameā€™: ā€˜EEG 241ā€™,
ā€˜coord_frameā€™: 4 (FIFFV_COORD_HEAD)}

Sorry, raw.info['chs']['loc'] does not work (TypeError: list indices must be integers or slices, not str). But anyway, the locations should be there and correct.

I think there is now a toggle (checkbox) to show or hide EEG sensors. Could you check whether itā€™s activated?

I donā€™t see one, only a checkbox for ā€œShow MEG helmetā€ but that doesnā€™t have any effect.

@jsattelb thanks for the details, I edited my previous comment. I had a small typo.

Anyway, I canā€™t replicate the issue with MNE sample data. I have created a single eeg-ave.fif from the existing data and loaded in mne coreg with fsaverge. All the eeg sensors are visible. You can see the output below:

So, I am not so sure what can cause this issue. Did you load the .fif file in path to info correctly?
best,

Sorry, I was mistaken then. My bad.

Ok thanks, I will try to replicate the problem with the sample data, to exclude any other explanations. But the experimental data should be fine, too, since Iā€™ve (just recently) used it before. Looks like there is something wrong with the mne environment/setup Iā€™m using.

I am not completely convinced with: something wrong with your mne environment/setup. Your mne coreg outputs look rather normal to me. I would advise you to load your eeg.fif with fsaverage to check if it works with a different head surface. In principle, if the ā€˜eeg.fifā€™ is correct, it should work.

best,
Dip

is it possible the channel locs are in millimeters (MNE expects meters)?

Would it be the error of the head size of your montage? When you check raw.get_montage().dig, do other pointsā€™ locations look strange (other than the fiducials since you mentioned they exist)?

Another silly problem that I encountered before was that the points were inside the head, so they appeared again as I turn up the transparency level.

I checked the my eeg.fif file with the fsaverage head, but the outcome is the same as with the actual head model:

I also checked the montage and indeed the scalings of fiducials and EEG sensors seem to be different:

raw.get_montage().dig

Output:

Out[13]: 
[<DigPoint |        LPA : (-70.2, -0.0, 0.0) mm     : head frame>,
 <DigPoint |     Nasion : (0.0, 93.7, 0.0) mm       : head frame>,
 <DigPoint |        RPA : (78.5, 0.0, 0.0) mm       : head frame>,
 <DigPoint |     EEG #1 : (7095.8, 4644.0, 1110.3) mm : head frame>,
 <DigPoint |     EEG #2 : (6695.6, 6046.0, 3479.3) mm : head frame>,
 <DigPoint |     EEG #3 : (6024.6, 7094.8, 5390.3) mm : head frame>,

So I guess this is indeed the cause of this problem!

I wonder why the outputs of raw.info['dig'] and raw.get_montage().dig are different - and which one the mne.gui.coregistration() uses.

raw.info['dig']
Out[37]: 
[<DigPoint |        LPA : (-70.2, -0.0, 0.0) mm     : head frame>,
 <DigPoint |     Nasion : (0.0, 93.7, 0.0) mm       : head frame>,
 <DigPoint |        RPA : (78.5, 0.0, 0.0) mm       : head frame>,
 <DigPoint |     EEG #1 : (71.0, 46.4, 11.1) mm     : head frame>,
 <DigPoint |     EEG #2 : (67.0, 60.5, 34.8) mm     : head frame>,
 <DigPoint |     EEG #3 : (60.2, 70.9, 53.9) mm     : head frame>,

versus

raw.get_montage().dig
Out[40]: 
[<DigPoint |        LPA : (-70.2, -0.0, 0.0) mm     : head frame>,
 <DigPoint |     Nasion : (0.0, 93.7, 0.0) mm       : head frame>,
 <DigPoint |        RPA : (78.5, 0.0, 0.0) mm       : head frame>,
 <DigPoint |     EEG #1 : (7095.8, 4644.0, 1110.3) mm : head frame>,
 <DigPoint |     EEG #2 : (6695.6, 6046.0, 3479.3) mm : head frame>,
 <DigPoint |     EEG #3 : (6024.6, 7094.8, 5390.3) mm : head frame>,

versus

mne.pick_info(raw.info, sel=range(0,257))['chs'][0]['loc']
Out[55]: 
array([7.09582329, 4.64403772, 1.11034155, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        ])```

We do rescale the digitization points after applying the montage to the raw. Is there a better way to do this?

dig = mne.channels.read_dig_egi(dig_file)
dig.ch_names[-1]= 'VREF'
raw.set_montage(dig)

# re-scaling
for i in range(len(raw.info['dig'])):
    raw.info['dig'][i]['r'] = raw.info['dig'][i]['r'] / 100

And finally, now I wonder if the source reconstruction of this file has even worked. Is there a sanity check if the sensors are 8 meters away from the scalp?

Sorry, one more thing.
I am not so sure anymore that the scaling is the problem, because if I open my eeg.fif file in the coregistration gui and fit ICP, the median distances look fine:

Channel types::	eeg: 257
Aligning using fiducials
Start median distance:   7.48 mm
End   median distance:   2.20 mm
Fitting fiducials finished in 0.22 seconds.
Channel types::	eeg: 257
Aligning using ICP
Start     median distance:   2.20 mm
  ICP  1  median distance:   2.11 mm
Fitting ICP - iteration 1
Channel types::	eeg: 257
  ICP  2  median distance:   2.12 mm
Fitting ICP - iteration 2
Channel types::	eeg: 257
  ICP  3  median distance:   2.15 mm
Fitting ICP - iteration 3
Channel types::	eeg: 257
  ICP  4  median distance:   2.08 mm
Fitting ICP - iteration 4
Channel types::	eeg: 257
  ICP  5  median distance:   2.05 mm
Fitting ICP - iteration 5
Channel types::	eeg: 257
  ICP  6  median distance:   2.02 mm
Fitting ICP - iteration 6
Channel types::	eeg: 257
  ICP  7  median distance:   1.98 mm
Fitting ICP - iteration 7
Channel types::	eeg: 257
End       median distance:   1.98 mm
Fitting ICP finished in 2.34 seconds and 7 iterations.

Could this error message after cloing the gui play a role?

2023-04-20 17:19:49.910 (1253.899s) [                ]        vtkRenderer.cxx:1172  WARN| vtkOpenGLRenderer (000002985F57A860): Resetting view-up since view plane normal is parallel

@jsattelb Hi! thanks for sharing. Indeed the values look odd to me.

raw.info['dig']
Out[37]: 
[<DigPoint |        LPA : (-70.2, -0.0, 0.0) mm     : head frame>,
 <DigPoint |     Nasion : (0.0, 93.7, 0.0) mm       : head frame>,
 <DigPoint |        RPA : (78.5, 0.0, 0.0) mm       : head frame>,
 <DigPoint |     EEG #1 : (71.0, 46.4, 11.1) mm     : head frame>,
 <DigPoint |     EEG #2 : (67.0, 60.5, 34.8) mm     : head frame>,
 <DigPoint |     EEG #3 : (60.2, 70.9, 53.9) mm     : head frame>,

comment: above values look normal to me. I would think LPA/Nasion/RPA and the EEG sensor positions are rather similar (ranges in mm) since they are not far from actual sensor position.

raw.get_montage().dig
Out[40]: 
[<DigPoint |        LPA : (-70.2, -0.0, 0.0) mm     : head frame>,
<DigPoint |     Nasion : (0.0, 93.7, 0.0) mm       : head frame>,
<DigPoint |        RPA : (78.5, 0.0, 0.0) mm       : head frame>,
<DigPoint |     EEG #1 : (7095.8, 4644.0, 1110.3) mm : head frame>,
<DigPoint |     EEG #2 : (6695.6, 6046.0, 3479.3) mm : head frame>,
<DigPoint |     EEG #3 : (6024.6, 7094.8, 5390.3) mm : head frame>,

comment: something is fishy here. I would expect raw.info['dig'] and raw.get_montage().dig values are same and in mm.

mne.pick_info(raw.info, sel=range(0,257))['chs'][0]['loc']
Out[55]: 
array([7.09582329, 4.64403772, 1.11034155, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        ])

comment: these values should be in meters as @drammock mentioned. However, these values look bigger to me. This seems like a re-scaling issue.

Can you check if the re-scaling is done across all the digitization points properly? Potentially this can be the issue. My suggestion would be you do the re-scaling before setting up the montage to make sure all the the sensor locations are in right order/range and afterwards use the mne coreg.

best,
Dip

Generally speaking it is not a good idea to modify most things inside raw.info (Iā€™m even a little surprised that it was allowed to work). Why do you need to do this? If you skip this step, does the coregistration GUI look correct?

Solved!
The issue was indeed the not rescaled positions in chs:
" When setting a montage with set_montage(), the measurement info is updated in two places (both chs and dig entries are updated) ā€“ see The Info data structure for more details. Note that dig may contain HPI, fiducial, or head shape points in addition to electrode locations."

So rescaling should be done before setting the montage. Could rescaling be implemented to the function mne.channels.read_dig_egi() ? I saw in the source code that there is already a scaling variable, but it is set to 1.