mne-tools / mne-python

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

After loading from file, CrossSpectralDensity.projs are a list of dicts rather than a list of Projections #10757

Closed jhouck closed 2 years ago

jhouck commented 2 years ago

In mne 1.1.dev0 it looks like there's an issue with saving/reloading a CrossSpectralDensity object that has SSP vectors. After reloading from a hdf5 file, csd.projs is a list of dicts rather than a list of Projectors. Using that with e.g. mne.beamformer.make_dics would return the TypeError: All entries in projs list must be Projection instances, but projs[0] is type <class 'dict'>

For example using the sample data:

import mne
print(mne.__version__)
from mne.datasets import sample
from mne.time_frequency import csd_fourier
from mne.preprocessing import compute_proj_ecg
data_path = sample.data_path()
meg_path = data_path / 'MEG' / 'sample'
fname_raw = meg_path / 'sample_audvis_raw.fif'
fname_event = meg_path / 'sample_audvis_raw-eve.fif'
fname_csd = '/tmp/csd_test'
n_jobs = 16

raw = mne.io.read_raw_fif(fname_raw)
events = mne.find_events(raw)
picks = mne.pick_types(raw.info, meg='grad')
projs, ecg_events = compute_proj_ecg(raw, n_grad=1, n_mag=1, n_eeg=1, reject=None)
raw.add_proj(projs)
epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=1,
                    picks=picks, baseline=(None, 0),
                    reject=dict(grad=4000e-13), preload=True)
csd = csd_fourier(epochs, fmin=15, fmax=20, n_jobs=n_jobs)
csd.save(fname_csd, overwrite=True)
csd_test = mne.time_frequency.read_csd(fname_csd)
print(type(csd.projs[0]))
print(type(csd_test.projs[0]))

As you can see, the original csd.projs is an mne.io.proj.Projection, while after reloading it is a dict. It's easy enough to convert this dict back to an Projection via

testproj = mne.io.proj.Projection(data=csd_test.projs[0]['data'], desc=csd_test.projs[0]['desc'],
                                  kind=csd_test.projs[0]['kind'], active=csd_test.projs[0]['active'],
                                  explained_var=csd_test.projs[0]['explained_var'])
csd_test.projs=[testproj]
print(type(csd_test.projs[0]))

but this introduces more opportunities for things to go wrong.

And maybe an issue for a separate... issue, there is also a problem saving/loading hdf5 files when the filename is a pathlib.Path object. For example:

import pathlib
fname_csd = pathlib.Path('/tmp/csd_test')
csd_test = mne.time_frequency.read_csd(fname_csd)

Will get you the message 'PosixPath' object has no attribute 'endswith', which is coming from this bit in _import_h5io_funcs:

if not fname.endswith('.h5'):
    fname += '.h5'

The easy workaround is to load the file using csd_test = mne.time_frequency.read_csd(str(fname_csd)) but this probably defeats the purpose of Path.

larsoner commented 2 years ago

Indeed there are two issues here:

  1. The first should be fixed by fixing the private function that Info-izes when loading from HDF5
  2. The second should be fixed by using fname = _check_fname(fname, must_exist=True, overwrite='read') in the right place
drammock commented 2 years ago

@cgohil8 is working on this

cgohil8 commented 2 years ago

There are two issues here:

  1. The read h5 function should return a CrossSpectralDensity object with the correct .projs attribute. This is fixed in https://github.com/mne-tools/mne-python/pull/11072.
  2. The function should work with Path objects being passed instead of str. This issue is covered here: https://github.com/mne-tools/mne-python/issues/10496.