Repository: mne-tools/mne-python

Issue:
old but new - info["meas_date"] must be a datetime object in UTC...

The following command returns an old error (likely system incompatibility?); mne-python is up to date. The plotting of the AverageTFR named "blah" otherwise works on a linux system. 

===========
blah is an  'AverageTFR'
===========

#### Describe the bug
blah.plot() returns

Traceback (most recent call last):

  File "<ipython-input-7-0b72d8cff926>", line 1, in <module>
    blah.plot()

  File "<decorator-gen-54>", line 21, in plot

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\time_frequency\tfr.py", line 1205, in plot
    exclude=exclude, verbose=verbose)

  File "<decorator-gen-55>", line 21, in _plot

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\time_frequency\tfr.py", line 1227, in _plot
    baseline, exclude, copy)

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\time_frequency\tfr.py", line 2372, in _preproc_tfr_instance
    picks = _picks_to_idx(tfr.info, picks, exclude='bads')

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\io\pick.py", line 986, in _picks_to_idx
    orig_picks)

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\io\pick.py", line 1035, in _picks_str_to_idx
    with_ref_meg=with_ref_meg)

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\io\pick.py", line 922, in _pick_data_channels
    **_PICK_TYPES_DATA_DICT)

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\io\pick.py", line 383, in pick_types
    exclude = _check_info_exclude(info, exclude)

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\io\pick.py", line 295, in _check_info_exclude
    info._check_consistency()

  File "C:\Users\vv221713\AppData\Local\Continuum\anaconda3\lib\site-packages\mne\io\meas_info.py", line 693, in _check_consistency
    % (prepend_error, repr(self['meas_date']),))

RuntimeError: info["meas_date"] must be a datetime object in UTC or None, got "'(1559814887, 215756)'"


#### System info 
I am using spyder 3.7

mne sys_info

Platform:      Windows-10-10.0.17763-SP0
Python:        3.7.4 (default, Aug  9 2019, 18:34:13) [MSC v.1915 64 bit (AMD64)]
Executable:    c:\users\vv221713\appdata\local\continuum\anaconda3\python.exe
CPU:           Intel64 Family 6 Model 142 Stepping 10, GenuineIntel: 8 cores
Memory:        15.9 GB

mne:           0.20.5
numpy:         1.16.5 {blas=mkl_rt, lapack=mkl_rt}
scipy:         1.3.1
matplotlib:    3.1.1 {backend=Qt5Agg}

sklearn:       0.21.3
numba:         0.45.1
nibabel:       Not found
cupy:          Not found
pandas:        0.25.1
dipy:          Not found
mayavi:        Not found
pyvista:       Not found
vtk:           Not found






#### Steps to reproduce
〈Replace this text with a code snippet or minimal working example [MWE] to
replicate your problem, using one of the [built-in datasets], preferably the
one called [sample]. If you can't replicate on a built-in dataset, provide also
a link to a small, anonymized portion of your data that does yield the error.〉

[MWE]: https://en.wikipedia.org/wiki/Minimal_Working_Example
[built-in datasets]: https://mne.tools/dev/overview/datasets_index.html
[sample]: https://mne.tools/dev/overview/datasets_index.html#sample


#### Expected results
〈Replace this text with a clear and concise description of what you expected to
happen.〉


#### Actual results
〈Replace this text with the actual output, traceback, screenshot, or other
description of the results.〉


#### Additional information
〈Replace this text with the output of `mne.sys_info()`.〉



Discussion:
if you read the tfr from disk? can you replicate using sample data?

>

When we read from h5 files we probably need to be careful about converting `meas_date` tuple to `datetime` objects. It makes sense that this could happen, I can try to fix it soon
yes, same error when reading from local disk

I suspect it's IO with .h5 files. @larsoner ?

>

> larsoner commented 6 minutes ago
> When we read from h5 files we probably need to be careful about converting meas_date tuple to datetime objects.

Read your emails more carefully @agramfort :)

Resolving patch:
diff --git a/mne/io/meas_info.py b/mne/io/meas_info.py
--- a/mne/io/meas_info.py
+++ b/mne/io/meas_info.py
@@ -522,9 +522,20 @@ class Info(dict, MontageMixin):
 
     def __init__(self, *args, **kwargs):
         super(Info, self).__init__(*args, **kwargs)
-        t = self.get('dev_head_t', None)
-        if t is not None and not isinstance(t, Transform):
+        # Deal with h5io writing things as dict
+        try:
+            t = self['dev_head_t']
+        except KeyError:
+            pass
+        else:
             self['dev_head_t'] = Transform(t['from'], t['to'], t['trans'])
+        # Old files could have meas_date as tuple instead of datetime
+        try:
+            meas_date = self['meas_date']
+        except KeyError:
+            pass
+        else:
+            self['meas_date'] = _ensure_meas_date_none_or_dt(meas_date)
 
     def copy(self):
         """Copy the instance.
@@ -1356,11 +1367,7 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
     info['proj_name'] = proj_name
     if meas_date is None:
         meas_date = (info['meas_id']['secs'], info['meas_id']['usecs'])
-    if np.array_equal(meas_date, DATE_NONE):
-        meas_date = None
-    else:
-        meas_date = _stamp_to_dt(meas_date)
-    info['meas_date'] = meas_date
+    info['meas_date'] = _ensure_meas_date_none_or_dt(meas_date)
     info['utc_offset'] = utc_offset
 
     info['sfreq'] = sfreq
@@ -1402,6 +1409,14 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
     return info, meas
 
 
+def _ensure_meas_date_none_or_dt(meas_date):
+    if meas_date is None or np.array_equal(meas_date, DATE_NONE):
+        meas_date = None
+    elif not isinstance(meas_date, datetime.datetime):
+        meas_date = _stamp_to_dt(meas_date)
+    return meas_date
+
+
 def _check_dates(info, prepend_error=''):
     """Check dates before writing as fif files.
 
diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py
--- a/mne/time_frequency/tfr.py
+++ b/mne/time_frequency/tfr.py
@@ -2286,6 +2286,7 @@ def read_tfrs(fname, condition=None):
     tfr_data = read_hdf5(fname, title='mnepython', slash='replace')
     for k, tfr in tfr_data:
         tfr['info'] = Info(tfr['info'])
+        tfr['info']._check_consistency()
         if 'metadata' in tfr:
             tfr['metadata'] = _prepare_read_metadata(tfr['metadata'])
     is_average = 'nave' in tfr


Test patch:
diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py
--- a/mne/time_frequency/tests/test_tfr.py
+++ b/mne/time_frequency/tests/test_tfr.py
@@ -409,7 +409,9 @@ def test_io():
 
     info = mne.create_info(['MEG 001', 'MEG 002', 'MEG 003'], 1000.,
                            ['mag', 'mag', 'mag'])
-    info['meas_date'] = datetime.datetime(year=2020, month=2, day=5)
+    info['meas_date'] = datetime.datetime(year=2020, month=2, day=5,
+                                          tzinfo=datetime.timezone.utc)
+    info._check_consistency()
     tfr = AverageTFR(info, data=data, times=times, freqs=freqs,
                      nave=20, comment='test', method='crazy-tfr')
     tfr.save(fname)
@@ -426,6 +428,8 @@ def test_io():
     pytest.raises(IOError, tfr.save, fname)
 
     tfr.comment = None
+    # test old meas_date
+    info['meas_date'] = (1, 2)
     tfr.save(fname, overwrite=True)
     assert_equal(read_tfrs(fname, condition=0).comment, tfr.comment)
     tfr.comment = 'test-A'