mne-tools / mne-python

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

mne.io.read_info() of CTF .ds returns IsADirectoryError #12031

Closed mjcourte closed 11 months ago

mjcourte commented 11 months ago

Description of the problem

Hi all,

I have CTF 275 channel .ds, which reads normally with mne.io.read_raw_ctf().

When imported, the raw.info attribute 'appears' to be a complete mne.Info object, with about 19 keys.

However, mne.io.read_info() returns error: IsADirectoryError: [Errno 21] Is a directory 'path/to/my/data.ds',

When, later in script, I run mne.make_forward_solution(), which requires an mne.Info arg, I get the same IsADirectoryError while checking the inputs, if I pass raw.info (as opposed to trying a read_info() earlier). (Edit: this was a typo on my behalf)

While this is technically correct that the CTF .ds spec is a directory, is this expected behaviour? I don't see any specific docs about doing format conversion for CTF (eg .ds to .fif) to produce an info object.

Thanks, Matt

Steps to reproduce

import mne
from mne.datasets.brainstorm import bst_auditory

data_path = bst_auditory.data_path()
subject = 'bst_auditory'
subjects_dir = data_path / 'subjects'

raw_fname = data_path / "MEG" / subject / "S01_AEF_20131218_01.ds"

raw = mne.io.read_raw_ctf(raw_fname)
print("Info from read_raw_ctf raw.info:")
print(raw.info)

info = mne.io.read_info(raw_fname)

Link to data

No response

Expected results

mne.io.read_info of CTF data returns an mne.Info object, or, raw.info from mne.io.read_raw_ctf is interpreted as an mne.Info object

Actual results

In [8]: raw = mne.io.read_raw_ctf(raw_fname)
ds directory : /home/matt/mne_data/MNE-brainstorm-data/bst_auditory/MEG/bst_auditory/S01_AEF_20131218_01.ds
    res4 data read.
    hc data read.
    Separate EEG position data file read.
    Quaternion matching (desired vs. transformed):
       2.51   74.26    0.00 mm <->    2.51   74.26    0.00 mm (orig :  -56.69   50.20 -264.38 mm) diff =    0.000 mm
      -2.51  -74.26    0.00 mm <->   -2.51  -74.26    0.00 mm (orig :   50.89  -52.31 -265.88 mm) diff =    0.000 mm
     108.63    0.00    0.00 mm <->  108.63    0.00    0.00 mm (orig :   67.41   77.68 -239.53 mm) diff =    0.000 mm
    Coordinate transformations established.
    Reading digitizer points from ['/home/matt/mne_data/MNE-brainstorm-data/bst_auditory/MEG/bst_auditory/S01_AEF_20131218_01.ds/S01_20131218_01.pos']...
    Polhemus data for 3 HPI coils added
    Device coordinate locations for 3 HPI coils added
    5 extra points added to Polhemus data.
    Measurement info composed.
Finding samples for /home/matt/mne_data/MNE-brainstorm-data/bst_auditory/MEG/bst_auditory/S01_AEF_20131218_01.ds/S01_AEF_20131218_01.meg4: 
    System clock channel is available, checking which samples are valid.
    360 x 2400 = 864000 samples from 340 chs
Current compensation grade : 3
``
In [9]: print(raw.info)
<Info | 16 non-empty values
 bads: []
 ch_names: UDIO001, UPPT001, UTRG001, SCLK01-177, BG1-4408, BG2-4408, ...
 chs: 3 Stimulus, 32 misc, 26 Reference Magnetometers, 274 Magnetometers, 5 EEG
 comps: 5 items (list)
 ctf_head_t: CTF/4D/KIT head -> head transform
 custom_ref_applied: False
 dev_ctf_t: MEG device -> CTF/4D/KIT head transform
 dev_head_t: MEG device -> head transform
 dig: 263 items (3 Cardinal, 260 Extra)
 experimenter: EAB
 highpass: 0.0 Hz
 hpi_results: 1 item (list)
 lowpass: 1200.0 Hz
 meas_date: 2013-12-18 09:43:00 UTC
 meas_id: 4 items (dict)
 nchan: 340
 projs: []
 sfreq: 2400.0 Hz
 subject_info: 1 item (dict)
>
In [10]: info = mne.io.read_info(raw_fname)
---------------------------------------------------------------------------
IsADirectoryError                         Traceback (most recent call last)
Cell In[10], line 1
----> 1 info = mne.io.read_info(raw_fname)

File <decorator-gen-42>:12, in read_info(fname, verbose)

File ~/envs/mne/lib/python3.10/site-packages/mne/io/meas_info.py:1992, in read_info(fname, verbose)
   1978 @verbose
   1979 def read_info(fname, verbose=None):
   1980     """Read measurement info from a file.
   1981 
   1982     Parameters
   (...)
   1990     %(info_not_none)s
   1991     """
-> 1992     f, tree, _ = fiff_open(fname)
   1993     with f as fid:
   1994         info = read_meas_info(fid, tree)[0]

File <decorator-gen-10>:12, in fiff_open(fname, preload, verbose)

File ~/envs/mne/lib/python3.10/site-packages/mne/io/open.py:124, in fiff_open(fname, preload, verbose)
    100 @verbose
    101 def fiff_open(fname, preload=False, verbose=None):
    102     """Open a FIF file.
    103 
    104     Parameters
   (...)
    122         A list of tags.
    123     """
--> 124     fid = _fiff_get_fid(fname)
    125     try:
    126         return _fiff_open(fname, fid, preload)

File ~/envs/mne/lib/python3.10/site-packages/mne/io/open.py:52, in _fiff_get_fid(fname)
     50     else:
     51         logger.debug("Using normal I/O")
---> 52         fid = open(fname, "rb")  # Open in binary mode
     53 return fid

IsADirectoryError: [Errno 21] Is a directory: '/home/matt/mne_data/MNE-brainstorm-data/bst_auditory/MEG/bst_auditory/S01_AEF_20131218_01.ds'

Additional information

Windows 11/WSL2/Ubuntu 22.04 (such that FreeSurfer is accessible also within WSL)

Platform             Linux-5.15.90.1-microsoft-standard-WSL2-x86_64-with-glibc2.35
Python               3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]
Executable           /home/matt/envs/mne/bin/python
CPU                  x86_64 (16 cores)
Memory               31.3 GB

Core
├☑ mne               1.5.1
├☑ numpy             1.26.0 (OpenBLAS 0.3.23.dev with 16 threads)
├☑ scipy             1.11.3
├☑ matplotlib        3.8.0 (backend=module://matplotlib_inline.backend_inline)
├☑ pooch             1.7.0
└☑ jinja2            3.1.2

Numerical (optional)
├☑ sklearn           1.3.1
├☑ nibabel           5.1.0
├☑ pandas            2.1.1
└☐ unavailable       numba, nilearn, dipy, openmeeg, cupy

Visualization (optional)
├☑ pyvistaqt         0.11.0
├☑ vtk               9.2.6
├☑ ipympl            0.9.3
├☑ mne-qt-browser    0.5.2
├☑ ipywidgets        8.1.1
├☑ trame_client      2.12.0
├☑ trame_server      2.12.0
├☑ trame_vtk         2.5.8
├☑ trame_vuetify     2.3.1
└☐ unavailable       pyvista, qtpy, pyqtgraph

Ecosystem (optional)
├☑ mne-connectivity  0.5.0
├☑ mne-icalabel      0.4
└☐ unavailable       mne-bids, mne-nirs, mne-features, mne-bids-pipeline
larsoner commented 11 months ago

read_info is really only meant to read FIF info files. You should be able to use

info = mne.io.read_raw_ctf(...).info

instead. If this makes sense would you be up for making a PR to improve the documentation of read_info to mention that it's designed for FIF info only but you can use code like the above to get info for other file formats?

mjcourte commented 11 months ago

I confirm that read_info works with sample_audvis_raw.fif.

I must have had a typo, as after going back to my script, the ctf raw.info appears to do the job.

I'll get to a PR for the read_info docs this afternoon.

hoechenberger commented 11 months ago

@larsoner Why don't we fail early if the filename extension is not .fif?

larsoner commented 11 months ago

Checking for .fif / .fif.gz is a good idea. I think check_fname could do that and emit a warning

hoechenberger commented 11 months ago

Why just a warning and not fail hard?

larsoner commented 11 months ago

Technically you can read and write FIF (and many other formats) just fine with no file extension. IIRC for many other functions like read raw fif etc we just emit a warning when you use non standard naming so we should continue that practice