Skip to content

Commit

Permalink
[MRG+2] Unify meas_date (to use a tuple) (mne-tools#5500)
Browse files Browse the repository at this point in the history
  • Loading branch information
larsoner authored and massich committed Sep 10, 2018
1 parent 613df87 commit d1e2cc8
Show file tree
Hide file tree
Showing 17 changed files with 47 additions and 38 deletions.
2 changes: 1 addition & 1 deletion mne/forward/forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ def _fill_measurement_info(info, fwd, sfreq):
sec = np.floor(now)
usec = 1e6 * (now - sec)

info['meas_date'] = np.array([sec, usec], dtype=np.int32)
info['meas_date'] = (int(sec), int(usec))
info['highpass'] = 0.0
info['lowpass'] = sfreq / 2.0
info['sfreq'] = sfreq
Expand Down
2 changes: 1 addition & 1 deletion mne/io/artemis123/artemis123.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def _get_artemis123_info(fname, pos_fname=None):
try:
date = datetime.datetime.strptime(
op.basename(fname).split('_')[2], '%Y-%m-%d-%Hh-%Mm')
meas_date = calendar.timegm(date.utctimetuple())
meas_date = (calendar.timegm(date.utctimetuple()), 0)
except Exception:
meas_date = None

Expand Down
5 changes: 2 additions & 3 deletions mne/io/brainvision/brainvision.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import numpy as np

from ..write import DATE_NONE
from ...utils import verbose, logger, warn
from ..constants import FIFF
from ..meas_info import _empty_info
Expand Down Expand Up @@ -542,11 +541,11 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
unix_time = (meas_date - epoch).total_seconds()
unix_secs = int(modf(unix_time)[1])
microsecs = int(modf(unix_time)[0] * 1e6)
info['meas_date'] = [unix_secs, microsecs]
info['meas_date'] = (unix_secs, microsecs)
break

else:
info['meas_date'] = DATE_NONE
info['meas_date'] = None

# load channel labels
nchan = cfg.getint(cinfostr, 'NumberOfChannels') + 1
Expand Down
5 changes: 2 additions & 3 deletions mne/io/brainvision/tests/test_brainvision.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from mne.io.constants import FIFF
from mne.io import read_raw_fif, read_raw_brainvision
from mne.io.tests.test_raw import _test_raw_reader
from mne.io.write import DATE_NONE
from mne.datasets import testing

FILE = inspect.getfile(inspect.currentframe())
Expand Down Expand Up @@ -82,7 +81,7 @@ def test_vmrk_meas_date():
# Test files with no date, we should get DATE_NONE from mne.io.write
with pytest.warns(RuntimeWarning, match='coordinate information'):
raw = read_raw_brainvision(vhdr_v2_path)
assert_allclose(raw.info['meas_date'], DATE_NONE)
assert raw.info['meas_date'] is None
assert 'unspecified' in repr(raw.info)


Expand Down Expand Up @@ -607,7 +606,7 @@ def test_brainvision_with_montage():
def test_brainvision_neuroone_export():
"""Test Brainvision file exported with neuroone system."""
raw = read_raw_brainvision(neuroone_vhdr, verbose='error')
assert raw.info['meas_date'] == DATE_NONE
assert raw.info['meas_date'] is None
assert len(raw.info['chs']) == 66
assert raw.info['sfreq'] == 5000.

Expand Down
2 changes: 1 addition & 1 deletion mne/io/bti/bti.py
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ def _get_bti_info(pdf_fname, config_fname, head_shape_fname, rotation_x,
if pdf_fname is not None:
info = _empty_info(sfreq)
date = bti_info['processes'][0]['timestamp']
info['meas_date'] = [date, 0]
info['meas_date'] = (date, 0)
else: # these cannot be guessed from config, see docstring
info = _empty_info(1.0)
info['sfreq'] = None
Expand Down
2 changes: 1 addition & 1 deletion mne/io/cnt/cnt.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format):
date = datetime.datetime(int(date[2]), int(date[0]),
int(date[1]), int(time[0]),
int(time[1]), int(time[2]))
meas_date = np.array([calendar.timegm(date.utctimetuple()), 0])
meas_date = (calendar.timegm(date.utctimetuple()), 0)
else:
warn(' Could not parse meas date from the header. '
'Setting to None.')
Expand Down
6 changes: 3 additions & 3 deletions mne/io/edf/edf.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def _read_edf_header(fname, annot, annotmap, exclude):
ch_names=ch_names, data_offset=header_nbytes,
digital_max=digital_max, digital_min=digital_min,
highpass=highpass, sel=sel, lowpass=lowpass,
meas_date=calendar.timegm(date.utctimetuple()),
meas_date=(calendar.timegm(date.utctimetuple()), 0),
n_records=n_records, n_samps=n_samps, nchan=nchan,
subject_info=patient, physical_max=physical_max,
physical_min=physical_min, record_length=record_length,
Expand Down Expand Up @@ -800,7 +800,7 @@ def _read_gdf_header(fname, stim_channel, exclude):
dtype_byte=[gdftype_byte[t] for t in dtype],
dtype_np=[gdftype_np[t] for t in dtype], exclude=exclude,
highpass=highpass, sel=sel, lowpass=lowpass,
meas_date=calendar.timegm(date.utctimetuple()),
meas_date=(calendar.timegm(date.utctimetuple()), 0),
meas_id=meas_id, n_records=n_records, n_samps=n_samps,
nchan=nchan, subject_info=patient, physical_max=physical_max,
physical_min=physical_min, record_length=record_length,
Expand Down Expand Up @@ -1023,7 +1023,7 @@ def _read_gdf_header(fname, stim_channel, exclude):
digital_min=digital_min, digital_max=digital_max,
exclude=exclude, gnd=gnd, highpass=highpass, sel=sel,
impedance=impedance, lowpass=lowpass,
meas_date=calendar.timegm(date.utctimetuple()),
meas_date=(calendar.timegm(date.utctimetuple()), 0),
meas_id=meas_id, n_records=n_records, n_samps=n_samps,
nchan=nchan, notch=notch, subject_info=patient,
physical_max=physical_max, physical_min=physical_min,
Expand Down
2 changes: 1 addition & 1 deletion mne/io/egi/egi.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def __init__(self, input_fname, montage=None, eog=None, misc=None,
egi_info['year'], egi_info['month'], egi_info['day'],
egi_info['hour'], egi_info['minute'], egi_info['second'])
my_timestamp = time.mktime(my_time.timetuple())
info['meas_date'] = np.array([my_timestamp, 0], dtype=np.float32)
info['meas_date'] = (my_timestamp, 0)
ch_names = [channel_naming % (i + 1) for i in
range(egi_info['n_channels'])]
ch_names.extend(list(egi_info['event_codes']))
Expand Down
2 changes: 1 addition & 1 deletion mne/io/egi/egimff.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ def __init__(self, input_fname, montage=None, eog=None, misc=None,
egi_info['year'], egi_info['month'], egi_info['day'],
egi_info['hour'], egi_info['minute'], egi_info['second'])
my_timestamp = time.mktime(my_time.timetuple())
info['meas_date'] = np.array([my_timestamp, 0], dtype=np.float32)
info['meas_date'] = (my_timestamp, 0)
ch_names = [channel_naming % (i + 1) for i in
range(egi_info['n_channels'])]
ch_names.extend(list(egi_info['event_codes']))
Expand Down
2 changes: 1 addition & 1 deletion mne/io/fiff/tests/test_raw_fiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -1219,7 +1219,7 @@ def test_save():

# test abspath support and annotations
annot = Annotations([10], [5], ['test'],
raw.info['meas_date'] +
raw.info['meas_date'][0] +
raw.first_samp / raw.info['sfreq'])
raw.set_annotations(annot)
new_fname = op.join(op.abspath(op.curdir), 'break_raw.fif')
Expand Down
2 changes: 1 addition & 1 deletion mne/io/kit/kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ def get_kit_info(rawfile, allow_unknown_format):

# Create raw.info dict for raw fif object with SQD data
info = _empty_info(float(sqd['sfreq']))
info.update(meas_date=create_time, lowpass=sqd['lowpass'],
info.update(meas_date=(create_time, 0), lowpass=sqd['lowpass'],
highpass=sqd['highpass'], kit_system_id=sysid)

# Creates a list of dicts of meg channels for raw.info
Expand Down
39 changes: 25 additions & 14 deletions mne/io/meas_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class Info(dict):
Tilt angle of the gantry in degrees.
lowpass : float | None
Lowpass corner frequency in Hertz.
meas_date : list of int
meas_date : tuple of int
The first element of this list is a UNIX timestamp (seconds since
1970-01-01 00:00:00) denoting the date and time at which the
measurement was taken. The second element is the additional number of
Expand Down Expand Up @@ -409,8 +409,8 @@ def __repr__(self):
{0: 'ff', 1: 'n'}[p['active']] for p in v)
if len(entr) >= 56:
entr = _summarize_str(entr)
elif k == 'meas_date' and np.iterable(v):
if np.array_equal(v, DATE_NONE):
elif k == 'meas_date':
if v is None:
entr = 'unspecified'
else:
# first entry in meas_date is meaningful
Expand Down Expand Up @@ -449,6 +449,13 @@ def _check_consistency(self):
if len(missing) > 0:
raise RuntimeError('bad channel(s) %s marked do not exist in info'
% (missing,))
meas_date = self.get('meas_date')
if meas_date is not None and (
not isinstance(self['meas_date'], tuple) or
len(self['meas_date']) != 2):
raise RuntimeError('info["meas_date"] must be a tuple of length '
'2 or None, got "%r"'
% (repr(self['meas_date']),))

chs = [ch['ch_name'] for ch in self['chs']]
if len(self['ch_names']) != len(chs) or any(
Expand Down Expand Up @@ -943,7 +950,9 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
highpass = float(tag.data)
elif kind == FIFF.FIFF_MEAS_DATE:
tag = read_tag(fid, pos)
meas_date = tag.data
meas_date = tuple(tag.data)
if len(meas_date) == 1: # can happen from old C conversions
meas_date = (meas_date[0], 0)
elif kind == FIFF.FIFF_COORD_TRANS:
tag = read_tag(fid, pos)
cand = tag.data
Expand Down Expand Up @@ -1236,7 +1245,7 @@ def read_meas_info(fid, tree, clean_bads=False, verbose=None):
info['proj_id'] = proj_id
info['proj_name'] = proj_name
if meas_date is None:
meas_date = [info['meas_id']['secs'], info['meas_id']['usecs']]
meas_date = (info['meas_id']['secs'], info['meas_id']['usecs'])
if np.array_equal(meas_date, DATE_NONE):
meas_date = None
info['meas_date'] = meas_date
Expand Down Expand Up @@ -1528,17 +1537,18 @@ def write_info(fname, info, data_type=None, reset_range=True):


@verbose
def _merge_dict_values(dicts, key, verbose=None):
def _merge_info_values(infos, key, verbose=None):
"""Merge things together.
Fork for {'dict', 'list', 'array', 'other'}
and consider cases where one or all are of the same type.
Does special things for "projs", "bads", and "meas_date".
"""
values = [d[key] for d in dicts]
values = [d[key] for d in infos]
msg = ("Don't know how to merge '%s'. Make sure values are "
"compatible." % key)
"compatible, got types:\n %s"
% (key, [type(v) for v in values]))

def _flatten(lists):
return [item for sublist in lists for item in sublist]
Expand All @@ -1552,7 +1562,7 @@ def _where_isinstance(values, kind):

# list
if _check_isinstance(values, list, all):
lists = (d[key] for d in dicts)
lists = (d[key] for d in infos)
if key == 'projs':
return _uniquify_projs(_flatten(lists))
elif key == 'bads':
Expand All @@ -1564,7 +1574,7 @@ def _where_isinstance(values, kind):
if len(idx) == 1:
return values[int(idx)]
elif len(idx) > 1:
lists = (d[key] for d in dicts if isinstance(d[key], list))
lists = (d[key] for d in infos if isinstance(d[key], list))
return _flatten(lists)
# dict
elif _check_isinstance(values, dict, all):
Expand All @@ -1580,8 +1590,9 @@ def _where_isinstance(values, kind):
elif len(idx) > 1:
raise RuntimeError(msg)
# ndarray
elif _check_isinstance(values, np.ndarray, all):
is_qual = all(np.all(values[0] == x) for x in values[1:])
elif _check_isinstance(values, np.ndarray, all) or \
_check_isinstance(values, tuple, all):
is_qual = all(np.array_equal(values[0], x) for x in values[1:])
if is_qual:
return values[0]
elif key == 'meas_date':
Expand All @@ -1590,7 +1601,7 @@ def _where_isinstance(values, kind):
return None
else:
raise RuntimeError(msg)
elif _check_isinstance(values, np.ndarray, any):
elif _check_isinstance(values, (np.ndarray, tuple), any):
idx = _where_isinstance(values, np.ndarray)
if len(idx) == 1:
return values[int(idx)]
Expand Down Expand Up @@ -1709,7 +1720,7 @@ def _merge_info(infos, force_update_to_first=False, verbose=None):
'proj_id', 'proj_name', 'projs', 'sfreq', 'gantry_angle',
'subject_info', 'sfreq', 'xplotter_layout', 'proc_history']
for k in other_fields:
info[k] = _merge_dict_values(infos, k)
info[k] = _merge_info_values(infos, k)

info._check_consistency()
return info
Expand Down
2 changes: 1 addition & 1 deletion mne/io/nicolet/nicolet.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def _get_nicolet_info(fname, ch_type, eog, ecg, emg, misc):
date = datetime.datetime(int(date[0]), int(date[1]), int(date[2]),
int(time[0]), int(time[1]), int(sec), int(msec))
info = _empty_info(header_info['sample_freq'])
info['meas_date'] = calendar.timegm(date.utctimetuple())
info['meas_date'] = (calendar.timegm(date.utctimetuple()), 0)

if ch_type == 'eeg':
ch_coil = FIFF.FIFFV_COIL_EEG
Expand Down
3 changes: 2 additions & 1 deletion mne/io/tests/test_meas_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,8 @@ def test_anonymize():
for key in ('file_id', 'meas_id'):
assert inst.info[key]['secs'] != DATE_NONE[0]
assert inst.info[key]['usecs'] != DATE_NONE[1]
assert tuple(inst.info['meas_date']) != DATE_NONE
assert isinstance(inst.info['meas_date'], tuple)
assert inst.info['meas_date'] != DATE_NONE
inst.anonymize()
assert 'subject_info' not in inst.info.keys()
for key in ('file_id', 'meas_id'):
Expand Down
2 changes: 1 addition & 1 deletion mne/tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_basics():
sfreq = 100.
info = create_info(ch_names=['MEG1', 'MEG2'], ch_types=['grad'] * 2,
sfreq=sfreq)
info['meas_date'] = np.pi
info['meas_date'] = (np.pi, 0)
raws = []
for first_samp in [12300, 100, 12]:
raw = RawArray(data.copy(), info, first_samp=first_samp)
Expand Down
4 changes: 2 additions & 2 deletions mne/tests/test_epochs.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def test_reject():
onsets[0] = onsets[0] + tmin - 0.499 # tmin < 0
onsets[1] = onsets[1] + tmax - 0.001
first_time = (raw.info['meas_date'][0] + raw.info['meas_date'][1] *
0.000001 + raw.first_samp / sfreq)
1e-6 + raw.first_samp / sfreq)
for orig_time in [None, first_time]:
annot = Annotations(onsets, [0.5, 0.5, 0.5], 'BAD', orig_time)
raw.set_annotations(annot)
Expand Down Expand Up @@ -1940,7 +1940,7 @@ def make_epochs(picks, proj):
assert_allclose(data1, data2, atol=1e-25)

epochs_meg2 = epochs_meg.copy()
epochs_meg2.info['meas_date'] += 10
epochs_meg2.info['meas_date'] = (0, 0)
add_channels_epochs([epochs_meg2, epochs_eeg])

epochs_meg2 = epochs_meg.copy()
Expand Down
3 changes: 1 addition & 2 deletions mne/viz/tests/test_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,7 @@ def test_plot_raw():

plt.close('all')
# test if meas_date has only one element
raw.info['meas_date'] = np.array([raw.info['meas_date'][0]],
dtype=np.int32)
raw.info['meas_date'] = (raw.info['meas_date'][0], 0)
annot = Annotations([1 + raw.first_samp / raw.info['sfreq']],
[5], ['bad'])
with pytest.warns(RuntimeWarning, match='outside data range'):
Expand Down

0 comments on commit d1e2cc8

Please sign in to comment.