Skip to content

Commit

Permalink
Cast paths to string before saving to h5 (mne-tools#12803)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathieu Scheltienne authored Aug 21, 2024
1 parent ab69d3e commit 223d4aa
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 21 deletions.
1 change: 1 addition & 0 deletions doc/changes/devel/12803.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix handling of MRI file-path in :class:`mne.SourceSpaces` and safeguard saving of :class:`pathlib.Path` with ``h5io`` by casting to :class:`str`, by `Mathieu Scheltienne`_.
35 changes: 19 additions & 16 deletions mne/_freesurfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,27 +599,30 @@ def read_talxfm(subject, subjects_dir=None, verbose=None):
return mri_mni_t


def _check_mri(mri, subject, subjects_dir):
def _check_mri(mri, subject, subjects_dir) -> str:
"""Check whether an mri exists in the Freesurfer subject directory."""
_validate_type(mri, "path-like", "mri")
if op.isfile(mri) and op.basename(mri) != mri:
return mri
if not op.isfile(mri):
_validate_type(mri, "path-like", mri)
mri = Path(mri)
if mri.is_file() and mri.name != mri:
return str(mri)
elif not mri.is_file():
if subject is None:
raise FileNotFoundError(
f"MRI file {mri!r} not found and no subject provided"
f"MRI file {mri!r} not found and no subject provided."
)
subjects_dir = str(get_subjects_dir(subjects_dir, raise_error=True))
mri = op.join(subjects_dir, subject, "mri", mri)
if not op.isfile(mri):
raise FileNotFoundError(f"MRI file {mri!r} not found")
if op.basename(mri) == mri:
err = (
f"Ambiguous filename - found {mri!r} in current folder.\n"
"If this is correct prefix name with relative or absolute path"
subjects_dir = get_subjects_dir(subjects_dir, raise_error=True)
mri = subjects_dir / subject / "mri" / mri
if not mri.is_file():
raise FileNotFoundError(
f"MRI file {mri!r} not found in the subjects directory "
f"{subjects_dir!r} for subject {subject}."
)
if mri.name == mri:
raise OSError(
f"Ambiguous filename - found {mri!r} in current folder. "
"If this is correct prefix name with relative or absolute path."
)
raise OSError(err)
return mri
return str(mri)


def _read_mri_info(path, units="m", return_img=False, use_nibabel=False):
Expand Down
7 changes: 3 additions & 4 deletions mne/source_space/_source_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -1665,7 +1665,7 @@ def setup_volume_source_space(
.. note:: For a discrete source space (``pos`` is a dict),
``mri`` must be None.
mri : str | None
mri : path-like | None
The filename of an MRI volume (mgh or mgz) to create the
interpolation matrix over. Source estimates obtained in the
volume source space can then be morphed onto the MRI volume
Expand Down Expand Up @@ -1791,9 +1791,8 @@ def setup_volume_source_space(
mri = _check_mri(mri, subject, subjects_dir)
if isinstance(pos, dict):
raise ValueError(
"Cannot create interpolation matrix for "
"discrete source space, mri must be None if "
"pos is a dict"
"Cannot create interpolation matrix for discrete source space, mri "
"must be None if pos is a dict"
)

if volume_label is not None:
Expand Down
30 changes: 29 additions & 1 deletion mne/utils/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,35 @@ def _import_h5py():

def _import_h5io_funcs():
h5io = _soft_import("h5io", "HDF5-based I/O")
return h5io.read_hdf5, h5io.write_hdf5

# Saving to HDF5 does not support pathlib.Path objects, which are more and more used
# in MNE-Python.
# Related issue in h5io: https://github.com/h5io/h5io/issues/113
def cast_path_to_str(data: dict) -> dict:
"""Cast all paths value to string in data."""
keys2cast = []
for key, value in data.items():
if isinstance(value, dict):
cast_path_to_str(value)
if isinstance(value, Path):
data[key] = value.as_posix()
if isinstance(key, Path):
keys2cast.append(key)
for key in keys2cast:
data[key.as_posix()] = data.pop(key)
return data

def write_hdf5(fname, data, *args, **kwargs):
"""Write h5 and cast all paths to string in data."""
if isinstance(data, dict):
data = cast_path_to_str(data)
elif isinstance(data, list):
for k, elt in enumerate(data):
if isinstance(elt, dict):
data[k] = cast_path_to_str(elt)
h5io.write_hdf5(fname, data, *args, **kwargs)

return h5io.read_hdf5, write_hdf5


def _import_pymatreader_funcs(purpose):
Expand Down

0 comments on commit 223d4aa

Please sign in to comment.