Skip to content

Conversation

@tsbinns
Copy link
Contributor

@tsbinns tsbinns commented Dec 4, 2025

Reference issue (if any)

Fixes #13483

What does this implement/fix?

Converts deprecated info["subject_info"]["birthday"] tuples to datetimes, allowing reading of spectrum/tfr objects saved before v1.8.

The issue and original forum post mentions this affecting TFRs, but spectrum objects are also affected.
Not sure if mne/utils/spectrum.py is the best place for this conversion function, but I felt it could fit since that file also contains the helper _get_instance_type_string which is being called for both spectrum & TFRs.

Have confirmed the fix works for objects created using the following code in v1.7:

import mne
import numpy as np

root = mne.datasets.sample.data_path() / "MEG" / "sample"
raw_file = root / "sample_audvis_raw.fif"
raw = mne.io.read_raw_fif(raw_file, verbose=False)

events = mne.make_fixed_length_events(raw)
epochs = mne.Epochs(raw, events=events, tmin=-0.3, tmax=0.7, preload=True)

subject_info = {"birthday" : (2025, 1, 1)}
epochs.info["subject_info"] = subject_info

freqs = np.logspace(*np.log10([6, 35]), num=8)
n_cycles = freqs / 2.0
tfr = epochs.compute_tfr(
    method="morlet",
    freqs=freqs,
    n_cycles=n_cycles,
    average=True,
    return_itc=False,
    decim=3,
)
psd = epochs.compute_psd()

mne.time_frequency.write_tfrs("tfr-1.7.0_multi-tfr.h5", tfr)
tfr.save("tfr-1.7.0-tfr.h5")
psd.save("psd-1.7.0-psd.h5")

Unsure what the recommended approach for a unit test would be that requires an object created with an older MNE version.

@larsoner
Copy link
Member

larsoner commented Dec 4, 2025

Unsure what the recommended approach for a unit test would be that requires an object created with an older MNE version.

Something like

def test_old_tfr_format(tmp_path):
    """Test reading old TFR formats."""
    ...  # get any TFR, ideally a tiny one that takes minimal time to compute
    fname = tmp_path / "test-tfr.h5"
    tfr.save(fname)
    with h5py.File(fname, "r+"):
        subject_info_root = tfr["something"]["somewhere"]  # will require some inspection to figure out
        assert subject_info_root["birthday"] == ...  # or isinstance... whatever h5io we write out nowadays via h5io 
        subject_info_root["birthday"] = ...  # you'll have to look to see how h5io writes a tuple of int
    tfr_new = read_tfrs(fname)
    ... # then assert one or two things about `tfr` and `tfr_new` being equal

and in true TDD form, I'd write this test while on main and see that it fails there on the read_tfrs line, then switch to this branch and see that it passes

@larsoner
Copy link
Member

larsoner commented Dec 4, 2025

Writing the tuple part will be a little bit annoying if you try to do it manually... you can see how h5io does it here

https://github.com/h5io/h5io/blob/80126abda226da8226d1163555b5db1c702c3b0b/h5io/_h5io.py#L244

I think the easiest way around this would be to do a h5io.write_hdf5(other_fname, date_tuple) and then load other_fname in read mode as well. Then I think you can just copy over the data from the old-style tuple one over to the fname one, overwriting the datetime representation

@tsbinns
Copy link
Contributor Author

tsbinns commented Dec 4, 2025

@larsoner I just noticed this in the TFR tests:

# test old-style meas date
sec_microsec_tuple = (1, 2)
with tfr.info._unlock():
tfr.info["meas_date"] = sec_microsec_tuple
tfr.save(fname, overwrite=True)
tfr_loaded = read_tfrs(fname)

Could modifying the unlocked info before saving also be a valid approach here without messing with h5io?

@larsoner
Copy link
Member

larsoner commented Dec 4, 2025

It might validate before saving or something which would be a problem but if that works out seems like a nice solution!

@tsbinns
Copy link
Contributor Author

tsbinns commented Dec 4, 2025

@larsoner You were right, things were still being validated in that simpler solution.
I went for something along the lines of what you suggested, but slightly modified (kept running into errors other ways).
Can confirm that in the test a tuple is being read in, and this is being corrected. Have also checked this raises an error on main.

@tsbinns
Copy link
Contributor Author

tsbinns commented Dec 5, 2025

Apparently the TFR files saved from write_tfrs() are represented differently to those from .save() on Linux, but not Windows or macOS. Have updated the tests to account for this.

from itertools import product
from pathlib import Path

import h5py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By putting this at the top, you are implicitly making h5py a required dependency for running MNE-Python tests. So instead, this should be lower with

h5py = pytest.importorskip("h5py")

probably nested in whatever test actually needs it so that non-HDF5 tests can still run

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry! Have fixed here and in test_spectrum.py.

@larsoner larsoner merged commit 718aabf into mne-tools:main Dec 12, 2025
32 checks passed
@larsoner
Copy link
Member

Thanks @tsbinns !

@tsbinns tsbinns deleted the bday_format branch December 12, 2025 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert old birthday info format on read

2 participants