From a1e8752084dbefb666a08bfd74f60bb17a0598d9 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 3 May 2016 11:14:59 +0300 Subject: [PATCH 01/85] wip: add elekta averager --- mne/event.py | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index 527a41830ab..a2f4a07cf34 100644 --- a/mne/event.py +++ b/mne/event.py @@ -18,7 +18,7 @@ from .io.open import fiff_open from .io.write import write_int, start_block, start_file, end_block, end_file from .io.pick import pick_channels - +from .epochs import Epochs def pick_events(events, include=None, exclude=None, step=False): """Select some events @@ -885,3 +885,205 @@ def concatenate_events(events, first_samps, last_samps): events_out = np.concatenate((events_out, e2), axis=0) return events_out + + +class Elekta_event(object): + """ Represents a trigger event in Elekta/Neuromag system. """ + + # original dacq variable names + vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', 'OldMask', 'Delay', 'Comment'] + + def __init__(self, name, channel, newbits, oldbits, newmask, oldmask, delay, comment): + self.name = name # short name for the event + self.channel = channel # stimulus channel, e.g. STI101 + self.newbits = int(newbits) # state after trigger transition + self.oldbits = int(oldbits) # state before trigger transition + self.newmask = int(newmask) # bitmask for post-transition state. zero bits are not considered. + self.oldmask = int(oldmask) # bitmask for pre-transition state + self.delay = float(delay) # delay to stimulus (s) + self.comment = comment # verbose comment for the event + # non-dacq vars + self.in_use = False # whether event is in use, i.e. referred to by a category + + def __repr__(self): + return ''.format( + self.name, self.comment, self.newbits, self.oldbits, self.newmask, self.oldmask, self.delay) + + +class Elekta_category(object): + """ Represents an averaging category in Elekta/Neuromag system. """ + + # original dacq variable names + vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', 'Nave', 'ReqEvent', + 'ReqWhen', 'ReqWithin', 'SubAve'] + + def __init__(self, comment, display, start, state, end, event, nave, reqevent, reqwhen, reqwithin, subave): + self.comment = comment + self.display = True if display==u'1' else False # whether do display online in dacq + self.state = True if state==u'1' else False # enabled or not + self.start = float(start) # epoch start + self.end = float(end) # epoch end + self.nave = int(nave) # desired n of averages + self.event = int(event) # the reference event (index to event list) + self.reqevent = int(reqevent) # additional required event (index to event list) + # TODO: check reqwhen + self.reqwhen = int(reqwhen) # 1=before, 0=after ref. event + self.reqwithin = float(reqwithin) # time window before or after ref. event (s) + self.subave = int(subave) # subaverage size + # non-dacq vars + self.times = None # list of t=0 times (in samples) for corresponding epochs + + def __repr__(self): + return ''.format( + self.comment, self.event, self.reqevent, self.reqwhen, self.reqwithin, self.start, self.end) + + +class Elekta_averager(object): + """ Represents averager settings of Elekta TRIUX/Vectorview systems, including all defined + trigger events and categories. + """ + + # these are DACQ averager related variable names without preceding 'ERF' + vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', + 'megSlope', 'megSpike', 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', + 'ecgMax', 'ncateg', 'nevent', 'stimSource', 'triggerMap', 'update', 'version', + 'artefIgnore', 'averUpdate'] + + acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] # dacq variable names start with one of these + + def __init__(self, acq_pars): + """ acq_pars usually is obtained as data.info['acq_pars'], where data can be + instance of Raw, Epochs or Evoked. """ + self.acq_dict = Elekta_averager._acqpars_dict(acq_pars) + # sets instance variables (lowercase versions of dacq variable names), to avoid a lot of boilerplate code + for var in Elekta_averager.vars: + val = self.acq_dict['ERF'+var] + if var[:3] in ['mag','meg','eeg','eog','ecg']: # rejection criteria are floats + val = float(val) + elif var in ['ncateg','nevent']: + val = int(val) + setattr(self, var.lower(), val) + # TODO: check + self.stimsource = u'Internal' if self.stimsource == u'1' else u'External' + # collect events and categories as instance dicts + self.events = self._events_from_acq_pars() + self.categories = self._categories_from_acq_pars() + # tag the events that are actually in use + + for cat in self.categories.values(): + if cat.event: + self.events[cat.event].in_use = True + if cat.reqevent: + self.events[cat.reqevent].in_use = True + + def __repr__(self): + return ''.format( + self.version, self.ncateg, self.nevent, self.stimsource) + + @staticmethod + def _acqpars_gen(acq_pars): + """ Yields key/value pairs from a string of acquisition parameters. """ + for line in acq_pars.split(): + if any([line.startswith(x) for x in Elekta_averager.acq_var_magic]): + key = line + val = '' + else: + val += ' ' + line if val else line # DACQ splits items with spaces into multiple lines + yield key, val + + @staticmethod + def _acqpars_dict(acq_pars): + """ Makes a dict from a string of acquisition parameters, which is info['acq_pars'] + for Elekta Vectorview/TRIUX systems. """ + return dict(Elekta_averager._acqpars_gen(acq_pars)) + + def _events_from_acq_pars(self): + """ Collects DACQ defined events into a dict. """ + events = {} + for evnum in [str(x).zfill(2) for x in range(1,self.ncateg+1)]: # '01', '02', etc. + evdi = {} + for var in Elekta_event.vars: + acq_key = 'ERFevent'+var+evnum # name of dacq variable, e.g. 'ERFeventNewBits01' + class_key = var.lower() # corresponding instance variable, e.g. 'newbits' + evdi[class_key] = self.acq_dict[acq_key] + events[int(evnum)] = Elekta_event(**evdi) # events are keyed by number starting from 1 + return events + + def _categories_from_acq_pars(self, all_categories=False): + """ Collects DACQ averaging categories into a dict. """ + cats = {} + for catnum in [str(x).zfill(2) for x in range(1,self.nevent+1)]: # '01', '02', etc. + catdi = {} + for var in Elekta_category.vars: + acq_key = 'ERFcat'+var+catnum + class_key = var.lower() + catdi[class_key] = self.acq_dict[acq_key] + if int(catdi['state']) == 1 or all_categories: # category enabled + cats[catdi['comment']] = Elekta_category(**catdi) + return cats + + def _mne_events_to_dacq(self, mne_events): + """ Creates list of dacq events based on mne trigger transitions list. + mne_events is typically given by mne.find_events (use consecutive=True + to get all transitions). Output consists of rows in the form [t, 0, event_code] + where t is time in samples and event_code is all events compatible with the transition + bitwise anded together. """ + events_ = mne_events.copy() + events_[:,1] = 0 + events_[:,2] = 0 + for n,ev in self.events.iteritems(): + if ev.in_use: + pre_ok = np.bitwise_and(ev.oldmask, mne_events[:,1]) == ev.oldbits + post_ok = np.bitwise_and(ev.newbits, mne_events[:,2]) == ev.newbits + ok_ind = np.where(pre_ok & post_ok) + if np.all(events_[ok_ind,2] == 0): + events_[ok_ind,2] |= 1 << (n - 1) # switch on the bit corresponding to event number + else: + # code can handle this, but not clear if it's intended to be possible + raise Exception('Multiple dacq events match trigger transition') + return events_ + + def _mne_events_to_category_times(self, cat, mne_events, sfreq): + """ Translate mne events to times when epochs for a given category should + be collected. """ + events = self._mne_events_to_dacq(mne_events) + times = events[:,0] + refEvents_inds = np.where(events[:,2] & (1 << cat.event-1))[0] # indices of times where ref. event occurs + refEvents_t = times[refEvents_inds] + if cat.reqevent: + reqEvents_inds = np.where(events[:,2] & (1 << cat.reqevent-1))[0] # indices of times where req. event occurs + reqEvents_t = times[reqEvents_inds] + # relative (to refevent) time window (in samples) where req. event must occur (e.g. [0 200]) + win = np.round(np.array(sorted([0, (-1)**(cat.reqwhen)*cat.reqwithin]))*sfreq) + refEvents_wins = refEvents_t[:,None] + win + req_acc = np.zeros(refEvents_inds.shape, dtype=bool) + for t in reqEvents_t: + # mark time windows where req. condition is satisfied + reqEvent_in_win = np.logical_and(t >= refEvents_wins[:,0], t <= refEvents_wins[:,1]) + req_acc |= reqEvent_in_win + # drop ref. events where req. event condition is not satisfied + refEvents_inds = refEvents_inds[np.where(req_acc)] + refEvents_t = times[refEvents_inds] + # adjust for trigger-stimulus delay by delaying the ref. event + refEvents_t += np.round(self.events[cat.event].delay * sfreq) + return refEvents_t + + def get_mne_rejection_dict(self): + """ Makes a mne rejection dict based on the averager parameters. Result + can be used e.g. with mne.Epochs """ + return {'grad':self.megmax, 'mag':self.magmax, 'eeg':self.eegmax, + 'eog':self.eogmax, 'ecg':self.ecgmax} + + def get_epochs(self, raw, catname, picks=None, reject=None, stim_channel=None, mask=0): + """ Get mne.Epochs instance corresponding to the given category. """ + cat = self.categories[catname] + mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, consecutive=True) + sfreq = raw.info['sfreq'] + # create array of category averaging times (t0) and corresponding event_id for mne.Epochs + cat_t = self._mne_events_to_category_times(cat, mne_events, sfreq) + catev = np.c_[cat_t, np.zeros(cat_t.shape), np.ones(cat_t.shape)].astype(np.uint32) + id = {cat.comment: 1} + return Epochs(raw, catev, event_id=id, reject=reject, tmin=cat.start, tmax=cat.end, baseline=None, + picks=picks, preload=True) + + From dfae40407d197d65f75a525000b4181920fe1239 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 3 May 2016 16:11:08 +0300 Subject: [PATCH 02/85] wip: Elekta averager [ci skip] --- mne/event.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index a2f4a07cf34..2db125e314f 100644 --- a/mne/event.py +++ b/mne/event.py @@ -11,6 +11,7 @@ import numpy as np from os.path import splitext +from . import Epochs from .utils import check_fname, logger, verbose, _get_stim_channel, warn from .io.constants import FIFF from .io.tree import dir_tree_find @@ -18,7 +19,7 @@ from .io.open import fiff_open from .io.write import write_int, start_block, start_file, end_block, end_file from .io.pick import pick_channels -from .epochs import Epochs + def pick_events(events, include=None, exclude=None, step=False): """Select some events From e195482b98ec9665989778d1bc8ebc85a57e526c Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 4 May 2016 14:55:46 +0300 Subject: [PATCH 03/85] wip: fix import and staticmethods [ci skip] --- mne/event.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/mne/event.py b/mne/event.py index 2db125e314f..8b6950a2fab 100644 --- a/mne/event.py +++ b/mne/event.py @@ -11,7 +11,7 @@ import numpy as np from os.path import splitext -from . import Epochs + from .utils import check_fname, logger, verbose, _get_stim_channel, warn from .io.constants import FIFF from .io.tree import dir_tree_find @@ -888,6 +888,8 @@ def concatenate_events(events, first_samps, last_samps): return events_out + + class Elekta_event(object): """ Represents a trigger event in Elekta/Neuromag system. """ @@ -955,7 +957,7 @@ class Elekta_averager(object): def __init__(self, acq_pars): """ acq_pars usually is obtained as data.info['acq_pars'], where data can be instance of Raw, Epochs or Evoked. """ - self.acq_dict = Elekta_averager._acqpars_dict(acq_pars) + self.acq_dict = _acqpars_dict(acq_pars) # sets instance variables (lowercase versions of dacq variable names), to avoid a lot of boilerplate code for var in Elekta_averager.vars: val = self.acq_dict['ERF'+var] @@ -981,22 +983,6 @@ def __repr__(self): return ''.format( self.version, self.ncateg, self.nevent, self.stimsource) - @staticmethod - def _acqpars_gen(acq_pars): - """ Yields key/value pairs from a string of acquisition parameters. """ - for line in acq_pars.split(): - if any([line.startswith(x) for x in Elekta_averager.acq_var_magic]): - key = line - val = '' - else: - val += ' ' + line if val else line # DACQ splits items with spaces into multiple lines - yield key, val - - @staticmethod - def _acqpars_dict(acq_pars): - """ Makes a dict from a string of acquisition parameters, which is info['acq_pars'] - for Elekta Vectorview/TRIUX systems. """ - return dict(Elekta_averager._acqpars_gen(acq_pars)) def _events_from_acq_pars(self): """ Collects DACQ defined events into a dict. """ @@ -1066,7 +1052,7 @@ def _mne_events_to_category_times(self, cat, mne_events, sfreq): refEvents_inds = refEvents_inds[np.where(req_acc)] refEvents_t = times[refEvents_inds] # adjust for trigger-stimulus delay by delaying the ref. event - refEvents_t += np.round(self.events[cat.event].delay * sfreq) + refEvents_t += int(np.round(self.events[cat.event].delay * sfreq)) return refEvents_t def get_mne_rejection_dict(self): @@ -1077,6 +1063,7 @@ def get_mne_rejection_dict(self): def get_epochs(self, raw, catname, picks=None, reject=None, stim_channel=None, mask=0): """ Get mne.Epochs instance corresponding to the given category. """ + from .epochs import Epochs cat = self.categories[catname] mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, consecutive=True) sfreq = raw.info['sfreq'] @@ -1087,4 +1074,19 @@ def get_epochs(self, raw, catname, picks=None, reject=None, stim_channel=None, m return Epochs(raw, catev, event_id=id, reject=reject, tmin=cat.start, tmax=cat.end, baseline=None, picks=picks, preload=True) +def _acqpars_dict(acq_pars): + """ Makes a dict from a string of acquisition parameters, which is info['acq_pars'] + for Elekta Vectorview/TRIUX systems. """ + return dict(_acqpars_gen(acq_pars)) + +def _acqpars_gen(acq_pars): + """ Yields key/value pairs from a string of acquisition parameters. """ + for line in acq_pars.split(): + if any([line.startswith(x) for x in Elekta_averager.acq_var_magic]): + key = line + val = '' + else: + val += ' ' + line if val else line # DACQ splits items with spaces into multiple lines + yield key, val + From 3f92941288f78415d40d438e71a92e243eea724d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 4 May 2016 15:26:20 +0300 Subject: [PATCH 04/85] pep8 ok --- mne/event.py | 188 +++++++++++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 82 deletions(-) diff --git a/mne/event.py b/mne/event.py index 8b6950a2fab..d8ff9e4196f 100644 --- a/mne/event.py +++ b/mne/event.py @@ -888,91 +888,97 @@ def concatenate_events(events, first_samps, last_samps): return events_out - - class Elekta_event(object): """ Represents a trigger event in Elekta/Neuromag system. """ - # original dacq variable names - vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', 'OldMask', 'Delay', 'Comment'] - - def __init__(self, name, channel, newbits, oldbits, newmask, oldmask, delay, comment): + vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', + 'OldMask', 'Delay', 'Comment'] + + def __init__(self, name, channel, newbits, oldbits, newmask, + oldmask, delay, comment): self.name = name # short name for the event self.channel = channel # stimulus channel, e.g. STI101 self.newbits = int(newbits) # state after trigger transition self.oldbits = int(oldbits) # state before trigger transition - self.newmask = int(newmask) # bitmask for post-transition state. zero bits are not considered. + self.newmask = int(newmask) # bitmask for post-transition state self.oldmask = int(oldmask) # bitmask for pre-transition state self.delay = float(delay) # delay to stimulus (s) self.comment = comment # verbose comment for the event # non-dacq vars - self.in_use = False # whether event is in use, i.e. referred to by a category + self.in_use = False # whether event is referred to by a category def __repr__(self): - return ''.format( - self.name, self.comment, self.newbits, self.oldbits, self.newmask, self.oldmask, self.delay) + return ''.format( + self.name, self.comment, self.newbits, self.oldbits, self.newmask, + self.oldmask, self.delay) class Elekta_category(object): """ Represents an averaging category in Elekta/Neuromag system. """ - # original dacq variable names - vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', 'Nave', 'ReqEvent', - 'ReqWhen', 'ReqWithin', 'SubAve'] + # original dacq variable names + vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', + 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve'] - def __init__(self, comment, display, start, state, end, event, nave, reqevent, reqwhen, reqwithin, subave): + def __init__(self, comment, display, start, state, end, event, nave, + reqevent, reqwhen, reqwithin, subave): self.comment = comment - self.display = True if display==u'1' else False # whether do display online in dacq - self.state = True if state==u'1' else False # enabled or not + self.display = True if display == u'1' else False # show online + self.state = True if state == u'1' else False # enabled or not self.start = float(start) # epoch start self.end = float(end) # epoch end self.nave = int(nave) # desired n of averages self.event = int(event) # the reference event (index to event list) - self.reqevent = int(reqevent) # additional required event (index to event list) - # TODO: check reqwhen + self.reqevent = int(reqevent) # required event (index to event list) + # TODO: check reqwhen self.reqwhen = int(reqwhen) # 1=before, 0=after ref. event - self.reqwithin = float(reqwithin) # time window before or after ref. event (s) + # time window before or after ref. event (s) + self.reqwithin = float(reqwithin) self.subave = int(subave) # subaverage size # non-dacq vars - self.times = None # list of t=0 times (in samples) for corresponding epochs - + # list of t=0 times (in samples) for corresponding epochs + self.times = None + def __repr__(self): - return ''.format( - self.comment, self.event, self.reqevent, self.reqwhen, self.reqwithin, self.start, self.end) - + return ''.format( + self.comment, self.event, self.reqevent, self.reqwhen, + self.reqwithin, self.start, self.end) + class Elekta_averager(object): - """ Represents averager settings of Elekta TRIUX/Vectorview systems, including all defined - trigger events and categories. - """ + """ Represents averager settings of Elekta TRIUX/Vectorview systems """ # these are DACQ averager related variable names without preceding 'ERF' - vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', - 'megSlope', 'megSpike', 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', - 'ecgMax', 'ncateg', 'nevent', 'stimSource', 'triggerMap', 'update', 'version', - 'artefIgnore', 'averUpdate'] + vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', + 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', + 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', + 'nevent', 'stimSource', 'triggerMap', 'update', 'version', + 'artefIgnore', 'averUpdate'] - acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] # dacq variable names start with one of these + # DACQ variable names start with one of these + acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] def __init__(self, acq_pars): - """ acq_pars usually is obtained as data.info['acq_pars'], where data can be - instance of Raw, Epochs or Evoked. """ + """ acq_pars usually is obtained as data.info['acq_pars'], where data + can be instance of Raw, Epochs or Evoked. """ self.acq_dict = _acqpars_dict(acq_pars) - # sets instance variables (lowercase versions of dacq variable names), to avoid a lot of boilerplate code + # sets instance variables (lowercase versions of dacq variable names) for var in Elekta_averager.vars: val = self.acq_dict['ERF'+var] - if var[:3] in ['mag','meg','eeg','eog','ecg']: # rejection criteria are floats + if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: val = float(val) - elif var in ['ncateg','nevent']: + elif var in ['ncateg', 'nevent']: val = int(val) setattr(self, var.lower(), val) # TODO: check - self.stimsource = u'Internal' if self.stimsource == u'1' else u'External' + self.stimsource = ( + u'Internal' if self.stimsource == u'1' else u'External') # collect events and categories as instance dicts self.events = self._events_from_acq_pars() self.categories = self._categories_from_acq_pars() # tag the events that are actually in use - for cat in self.categories.values(): if cat.event: self.events[cat.event].in_use = True @@ -980,26 +986,30 @@ def __init__(self, acq_pars): self.events[cat.reqevent].in_use = True def __repr__(self): - return ''.format( - self.version, self.ncateg, self.nevent, self.stimsource) - + return ''.format( + self.version, self.ncateg, self.nevent, self.stimsource) def _events_from_acq_pars(self): """ Collects DACQ defined events into a dict. """ events = {} - for evnum in [str(x).zfill(2) for x in range(1,self.ncateg+1)]: # '01', '02', etc. + # evnum = '01', '02' etc. + for evnum in [str(x).zfill(2) for x in range(1, self.ncateg+1)]: evdi = {} for var in Elekta_event.vars: - acq_key = 'ERFevent'+var+evnum # name of dacq variable, e.g. 'ERFeventNewBits01' - class_key = var.lower() # corresponding instance variable, e.g. 'newbits' + # name of dacq variable, e.g. 'ERFeventNewBits01' + acq_key = 'ERFevent'+var+evnum + # corresponding instance variable, e.g. 'newbits' + class_key = var.lower() evdi[class_key] = self.acq_dict[acq_key] - events[int(evnum)] = Elekta_event(**evdi) # events are keyed by number starting from 1 + # events are keyed by number starting from 1 + events[int(evnum)] = Elekta_event(**evdi) return events def _categories_from_acq_pars(self, all_categories=False): """ Collects DACQ averaging categories into a dict. """ cats = {} - for catnum in [str(x).zfill(2) for x in range(1,self.nevent+1)]: # '01', '02', etc. + for catnum in [str(x).zfill(2) for x in range(1, self.nevent+1)]: catdi = {} for var in Elekta_category.vars: acq_key = 'ERFcat'+var+catnum @@ -1008,49 +1018,58 @@ def _categories_from_acq_pars(self, all_categories=False): if int(catdi['state']) == 1 or all_categories: # category enabled cats[catdi['comment']] = Elekta_category(**catdi) return cats - + def _mne_events_to_dacq(self, mne_events): """ Creates list of dacq events based on mne trigger transitions list. mne_events is typically given by mne.find_events (use consecutive=True - to get all transitions). Output consists of rows in the form [t, 0, event_code] - where t is time in samples and event_code is all events compatible with the transition - bitwise anded together. """ + to get all transitions). Output consists of rows in the form + [t, 0, event_code] where t is time in samples and event_code is all + events compatible with the transition, bitwise anded together. """ events_ = mne_events.copy() - events_[:,1] = 0 - events_[:,2] = 0 - for n,ev in self.events.iteritems(): + events_[:, 1] = 0 + events_[:, 2] = 0 + for n, ev in self.events.iteritems(): if ev.in_use: - pre_ok = np.bitwise_and(ev.oldmask, mne_events[:,1]) == ev.oldbits - post_ok = np.bitwise_and(ev.newbits, mne_events[:,2]) == ev.newbits + pre_ok = ( + np.bitwise_and(ev.oldmask, mne_events[:, 1]) == ev.oldbits) + post_ok = ( + np.bitwise_and(ev.newbits, mne_events[:, 2]) == ev.newbits) ok_ind = np.where(pre_ok & post_ok) - if np.all(events_[ok_ind,2] == 0): - events_[ok_ind,2] |= 1 << (n - 1) # switch on the bit corresponding to event number + if np.all(events_[ok_ind, 2] == 0): + # switch on the bit corresponding to event number + events_[ok_ind, 2] |= 1 << (n - 1) else: - # code can handle this, but not clear if it's intended to be possible - raise Exception('Multiple dacq events match trigger transition') + # TODO: is this ok? + raise Exception('Multiple dacq events' + 'match trigger transition') return events_ def _mne_events_to_category_times(self, cat, mne_events, sfreq): """ Translate mne events to times when epochs for a given category should be collected. """ events = self._mne_events_to_dacq(mne_events) - times = events[:,0] - refEvents_inds = np.where(events[:,2] & (1 << cat.event-1))[0] # indices of times where ref. event occurs + times = events[:, 0] + # indices of times where ref. event occurs + refEvents_inds = np.where(events[:, 2] & (1 << cat.event-1))[0] refEvents_t = times[refEvents_inds] if cat.reqevent: - reqEvents_inds = np.where(events[:,2] & (1 << cat.reqevent-1))[0] # indices of times where req. event occurs + # indices of times where req. event occurs + reqEvents_inds = np.where(events[:, 2] & (1 << cat.reqevent-1))[0] reqEvents_t = times[reqEvents_inds] - # relative (to refevent) time window (in samples) where req. event must occur (e.g. [0 200]) - win = np.round(np.array(sorted([0, (-1)**(cat.reqwhen)*cat.reqwithin]))*sfreq) - refEvents_wins = refEvents_t[:,None] + win + # relative (to refevent) time window (in samples) where req. event + # must occur (e.g. [0 200]) + win = np.round(np.array( + sorted([0, (-1)**(cat.reqwhen) * cat.reqwithin])) * sfreq) + refEvents_wins = refEvents_t[:, None] + win req_acc = np.zeros(refEvents_inds.shape, dtype=bool) for t in reqEvents_t: # mark time windows where req. condition is satisfied - reqEvent_in_win = np.logical_and(t >= refEvents_wins[:,0], t <= refEvents_wins[:,1]) - req_acc |= reqEvent_in_win + reqEvent_in_win = np.logical_and( + t >= refEvents_wins[:, 0], t <= refEvents_wins[:, 1]) + req_acc |= reqEvent_in_win # drop ref. events where req. event condition is not satisfied refEvents_inds = refEvents_inds[np.where(req_acc)] - refEvents_t = times[refEvents_inds] + refEvents_t = times[refEvents_inds] # adjust for trigger-stimulus delay by delaying the ref. event refEvents_t += int(np.round(self.events[cat.event].delay * sfreq)) return refEvents_t @@ -1058,27 +1077,33 @@ def _mne_events_to_category_times(self, cat, mne_events, sfreq): def get_mne_rejection_dict(self): """ Makes a mne rejection dict based on the averager parameters. Result can be used e.g. with mne.Epochs """ - return {'grad':self.megmax, 'mag':self.magmax, 'eeg':self.eegmax, - 'eog':self.eogmax, 'ecg':self.ecgmax} + return {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, + 'eog': self.eogmax, 'ecg': self.ecgmax} - def get_epochs(self, raw, catname, picks=None, reject=None, stim_channel=None, mask=0): + def get_epochs(self, raw, catname, picks=None, reject=None, + stim_channel=None, mask=0): """ Get mne.Epochs instance corresponding to the given category. """ from .epochs import Epochs cat = self.categories[catname] - mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, consecutive=True) + mne_events = find_events(raw, stim_channel=stim_channel, + mask=mask, consecutive=True) sfreq = raw.info['sfreq'] - # create array of category averaging times (t0) and corresponding event_id for mne.Epochs + # create array of category averaging times (t0) + # and corresponding event_id for mne.Epochs cat_t = self._mne_events_to_category_times(cat, mne_events, sfreq) - catev = np.c_[cat_t, np.zeros(cat_t.shape), np.ones(cat_t.shape)].astype(np.uint32) + catev = np.c_[cat_t, np.zeros(cat_t.shape), + np.ones(cat_t.shape)].astype(np.uint32) id = {cat.comment: 1} - return Epochs(raw, catev, event_id=id, reject=reject, tmin=cat.start, tmax=cat.end, baseline=None, - picks=picks, preload=True) + return Epochs(raw, catev, event_id=id, reject=reject, tmin=cat.start, + tmax=cat.end, baseline=None, picks=picks, preload=True) + def _acqpars_dict(acq_pars): - """ Makes a dict from a string of acquisition parameters, which is info['acq_pars'] - for Elekta Vectorview/TRIUX systems. """ + """ Makes a dict from a string of acquisition parameters, which is + info['acq_pars'] for Elekta Vectorview/TRIUX systems. """ return dict(_acqpars_gen(acq_pars)) + def _acqpars_gen(acq_pars): """ Yields key/value pairs from a string of acquisition parameters. """ for line in acq_pars.split(): @@ -1086,7 +1111,6 @@ def _acqpars_gen(acq_pars): key = line val = '' else: - val += ' ' + line if val else line # DACQ splits items with spaces into multiple lines + # DACQ splits items with spaces into multiple lines + val += ' ' + line if val else line yield key, val - - From 4df920d3508264f9c65a88788f2e3a40f860b313 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 9 May 2016 10:56:38 +0300 Subject: [PATCH 05/85] wip: passes flake8 --- mne/event.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mne/event.py b/mne/event.py index d8ff9e4196f..77af37b8967 100644 --- a/mne/event.py +++ b/mne/event.py @@ -966,7 +966,7 @@ def __init__(self, acq_pars): self.acq_dict = _acqpars_dict(acq_pars) # sets instance variables (lowercase versions of dacq variable names) for var in Elekta_averager.vars: - val = self.acq_dict['ERF'+var] + val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: val = float(val) elif var in ['ncateg', 'nevent']: @@ -988,17 +988,17 @@ def __init__(self, acq_pars): def __repr__(self): return ''.format( - self.version, self.ncateg, self.nevent, self.stimsource) + self.version, self.ncateg, self.nevent, self.stimsource) def _events_from_acq_pars(self): """ Collects DACQ defined events into a dict. """ events = {} # evnum = '01', '02' etc. - for evnum in [str(x).zfill(2) for x in range(1, self.ncateg+1)]: + for evnum in [str(x).zfill(2) for x in range(1, self.ncateg + 1)]: evdi = {} for var in Elekta_event.vars: # name of dacq variable, e.g. 'ERFeventNewBits01' - acq_key = 'ERFevent'+var+evnum + acq_key = 'ERFevent' + var + evnum # corresponding instance variable, e.g. 'newbits' class_key = var.lower() evdi[class_key] = self.acq_dict[acq_key] @@ -1009,10 +1009,10 @@ def _events_from_acq_pars(self): def _categories_from_acq_pars(self, all_categories=False): """ Collects DACQ averaging categories into a dict. """ cats = {} - for catnum in [str(x).zfill(2) for x in range(1, self.nevent+1)]: + for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = {} for var in Elekta_category.vars: - acq_key = 'ERFcat'+var+catnum + acq_key = 'ERFcat' + var + catnum class_key = var.lower() catdi[class_key] = self.acq_dict[acq_key] if int(catdi['state']) == 1 or all_categories: # category enabled @@ -1050,11 +1050,12 @@ def _mne_events_to_category_times(self, cat, mne_events, sfreq): events = self._mne_events_to_dacq(mne_events) times = events[:, 0] # indices of times where ref. event occurs - refEvents_inds = np.where(events[:, 2] & (1 << cat.event-1))[0] + refEvents_inds = np.where(events[:, 2] & (1 << cat.event - 1))[0] refEvents_t = times[refEvents_inds] if cat.reqevent: # indices of times where req. event occurs - reqEvents_inds = np.where(events[:, 2] & (1 << cat.reqevent-1))[0] + reqEvents_inds = np.where(events[:, 2] & ( + 1 << cat.reqevent - 1))[0] reqEvents_t = times[reqEvents_inds] # relative (to refevent) time window (in samples) where req. event # must occur (e.g. [0 200]) From 9a5b82fcaeef6b292e6c7b576a755107902cd069 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 9 May 2016 16:24:11 +0300 Subject: [PATCH 06/85] wip: get onsets and offsets, clean repr --- mne/event.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/mne/event.py b/mne/event.py index 77af37b8967..81b60beb212 100644 --- a/mne/event.py +++ b/mne/event.py @@ -908,8 +908,8 @@ def __init__(self, name, channel, newbits, oldbits, newmask, self.in_use = False # whether event is referred to by a category def __repr__(self): - return ''.format( + return ('').format( self.name, self.comment, self.newbits, self.oldbits, self.newmask, self.oldmask, self.delay) @@ -941,8 +941,8 @@ def __init__(self, comment, display, start, state, end, event, nave, self.times = None def __repr__(self): - return ''.format( + return ('').format( self.comment, self.event, self.reqevent, self.reqwhen, self.reqwithin, self.start, self.end) @@ -986,8 +986,8 @@ def __init__(self, acq_pars): self.events[cat.reqevent].in_use = True def __repr__(self): - return ''.format( + return ('').format( self.version, self.ncateg, self.nevent, self.stimsource) def _events_from_acq_pars(self): @@ -1035,13 +1035,7 @@ def _mne_events_to_dacq(self, mne_events): post_ok = ( np.bitwise_and(ev.newbits, mne_events[:, 2]) == ev.newbits) ok_ind = np.where(pre_ok & post_ok) - if np.all(events_[ok_ind, 2] == 0): - # switch on the bit corresponding to event number - events_[ok_ind, 2] |= 1 << (n - 1) - else: - # TODO: is this ok? - raise Exception('Multiple dacq events' - 'match trigger transition') + events_[ok_ind, 2] |= 1 << (n - 1) return events_ def _mne_events_to_category_times(self, cat, mne_events, sfreq): @@ -1087,7 +1081,7 @@ def get_epochs(self, raw, catname, picks=None, reject=None, from .epochs import Epochs cat = self.categories[catname] mne_events = find_events(raw, stim_channel=stim_channel, - mask=mask, consecutive=True) + mask=mask, output='step', consecutive=True) sfreq = raw.info['sfreq'] # create array of category averaging times (t0) # and corresponding event_id for mne.Epochs From f008597e1f295c61f15d3a9f8c371825d33219d8 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 10 May 2016 15:58:35 +0300 Subject: [PATCH 07/85] wip: beautify repr and docstrings [ci skip] --- mne/event.py | 64 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/mne/event.py b/mne/event.py index 81b60beb212..86aecdafb70 100644 --- a/mne/event.py +++ b/mne/event.py @@ -908,10 +908,15 @@ def __init__(self, name, channel, newbits, oldbits, newmask, self.in_use = False # whether event is referred to by a category def __repr__(self): - return ('').format( - self.name, self.comment, self.newbits, self.oldbits, self.newmask, - self.oldmask, self.delay) + s = '').format( - self.comment, self.event, self.reqevent, self.reqwhen, - self.reqwithin, self.start, self.end) + s = '').format( - self.version, self.ncateg, self.nevent, self.stimsource) + s = ' Date: Tue, 10 May 2016 16:08:10 +0300 Subject: [PATCH 08/85] wip: further beautify [ci skip] --- mne/event.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/mne/event.py b/mne/event.py index 86aecdafb70..79a4dd71a65 100644 --- a/mne/event.py +++ b/mne/event.py @@ -890,7 +890,7 @@ def concatenate_events(events, first_samps, last_samps): class Elekta_event(object): """ Represents a trigger event in Elekta/Neuromag system. """ - # original dacq variable names + # original DACQ variable names vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', 'OldMask', 'Delay', 'Comment'] @@ -904,7 +904,7 @@ def __init__(self, name, channel, newbits, oldbits, newmask, self.oldmask = int(oldmask) # bitmask for pre-transition state self.delay = float(delay) # delay to stimulus (s) self.comment = comment # verbose comment for the event - # non-dacq vars + # non-DACQ vars self.in_use = False # whether event is referred to by a category def __repr__(self): @@ -922,7 +922,7 @@ def __repr__(self): class Elekta_category(object): """ Represents an averaging category in Elekta/Neuromag system. """ - # original dacq variable names + # original DACQ variable names vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve'] @@ -940,7 +940,7 @@ def __init__(self, comment, display, start, state, end, event, nave, # time window before or after ref. event (s) self.reqwithin = float(reqwithin) self.subave = int(subave) # subaverage size - # non-dacq vars + # non-DACQ vars # list of t=0 times (in samples) for corresponding epochs self.times = None @@ -976,7 +976,7 @@ def __init__(self, acq_pars): can be instance of Raw, Epochs or Evoked. """ self._events_in_use = set() self.acq_dict = _acqpars_dict(acq_pars) - # sets instance variables (lowercase versions of dacq variable names) + # sets instance variables (lowercase versions of DACQ variable names) for var in Elekta_averager.vars: val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: @@ -1016,7 +1016,7 @@ def _events_from_acq_pars(self): for evnum in [str(x).zfill(2) for x in range(1, self.ncateg + 1)]: evdi = {} for var in Elekta_event.vars: - # name of dacq variable, e.g. 'ERFeventNewBits01' + # name of DACQ variable, e.g. 'ERFeventNewBits01' acq_key = 'ERFevent' + var + evnum # corresponding instance variable, e.g. 'newbits' class_key = var.lower() @@ -1039,11 +1039,13 @@ def _categories_from_acq_pars(self, all_categories=False): return cats def _mne_events_to_dacq(self, mne_events): - """ Creates list of dacq events based on mne trigger transitions list. + """ Creates list of DACQ events based on mne trigger transitions list. mne_events is typically given by mne.find_events (use consecutive=True to get all transitions). Output consists of rows in the form - [t, 0, event_code] where t is time in samples and event_code is all - events compatible with the transition, bitwise anded together. """ + [t, 0, event_codes] where t is time in samples and event_codes is all + events compatible with the transition, bitwise ORed together: + e.g. [t1, 0, 5] means that events 1 and 3 occurred at time t1, + as 2**(1 - 1) + 2**(3 - 1) = 5. """ events_ = mne_events.copy() events_[:, 1] = 0 events_[:, 2] = 0 @@ -1090,7 +1092,7 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): def get_mne_rejection_dict(self): """ Makes a mne rejection dict based on the averager parameters. Result - can be used e.g. with mne.Epochs """ + can be used e.g. with mne.Epochs. """ return {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, 'ecg': self.ecgmax} From 5bdf8eb1ea63871f17f3235c6ceb09bacc41752d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 10 May 2016 16:22:52 +0300 Subject: [PATCH 09/85] wip: tests, refactor [ci skip] --- mne/event.py | 89 ++++++++++++++++++++++++++++------------- mne/tests/test_event.py | 18 ++++++++- 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/mne/event.py b/mne/event.py index 79a4dd71a65..e904856d474 100644 --- a/mne/event.py +++ b/mne/event.py @@ -910,7 +910,7 @@ def __init__(self, name, channel, newbits, oldbits, newmask, def __repr__(self): s = ' Date: Fri, 27 May 2016 13:47:16 +0300 Subject: [PATCH 10/85] wip: fix indexing [ci skip] --- mne/event.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mne/event.py b/mne/event.py index e904856d474..39ecf11018a 100644 --- a/mne/event.py +++ b/mne/event.py @@ -905,10 +905,12 @@ def __init__(self, name, channel, newbits, oldbits, newmask, self.delay = float(delay) # delay to stimulus (s) self.comment = comment # verbose comment for the event # non-DACQ vars + self.index = None # index of event in DACQ list self.in_use = False # whether event is referred to by a category def __repr__(self): s = ' Date: Fri, 27 May 2016 14:13:09 +0300 Subject: [PATCH 11/85] wip: test for no acq params [ci skip] --- mne/event.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/event.py b/mne/event.py index 39ecf11018a..741e20a4009 100644 --- a/mne/event.py +++ b/mne/event.py @@ -987,6 +987,8 @@ class Elekta_averager(object): def __init__(self, acq_pars): """ acq_pars is usually obtained as data.info['acq_pars'], where data can be instance of Raw, Epochs or Evoked. """ + if not acq_pars: + raise ValueError('No acquisition parameters') self.acq_dict = _acqpars_dict(acq_pars) # sets instance variables (lowercase versions of DACQ variable names) for var in Elekta_averager.vars: From 9adb032c56f70ca1468222d2657922e9fd6a7aa3 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 27 May 2016 14:16:54 +0300 Subject: [PATCH 12/85] wip: print event more nicely [ci skip] --- mne/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index 741e20a4009..60c079fe2b2 100644 --- a/mne/event.py +++ b/mne/event.py @@ -911,7 +911,7 @@ def __init__(self, name, channel, newbits, oldbits, newmask, def __repr__(self): s = ' Date: Mon, 30 May 2016 17:29:55 +0300 Subject: [PATCH 13/85] wip: unit test --- mne/tests/test_event.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 61ba5e53f7d..0fb99f45e3e 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -4,11 +4,12 @@ from nose.tools import assert_true, assert_raises import numpy as np from numpy.testing import (assert_array_almost_equal, assert_array_equal, - assert_equal) + assert_equal, assert_allclose) import warnings from mne import (read_events, write_events, make_fixed_length_events, - find_events, pick_events, find_stim_steps, pick_channels) + find_events, pick_events, find_stim_steps, pick_channels, + read_evokeds) from mne.io import read_raw_fif from mne.tests.common import assert_naming from mne.utils import _TempDir, run_tests_if_main @@ -24,7 +25,7 @@ fname_txt_1 = op.join(base_dir, 'test-eve-1.eve') # for testing Elekta averager -fname_raw_elekta = op.join(base_dir, 'test_elekta_raw.fif') +fname_raw_elekta = op.join(base_dir, 'trigtest_3ch_raw.fif') fname_ave_elekta = op.join(base_dir, 'test_elekta_ave.fif') # using mne_process_raw --raw test_raw.fif --eventsout test-mpr-eve.eve: @@ -420,10 +421,21 @@ def test_elekta_averager(): raw = read_raw_fif(fname_raw_elekta) eav = Elekta_averager(raw.info['acq_pars']) for cat in eav.categories: - eps = cat.epochs(raw) + eps = cat.epochs(raw, baseline=(-.05, 0)) ev = eps.average() - ev_ref = read_evokeds(fname_ave_elekta, cat.comment, baseline=None, - proj=False) - assert_array_almost_equal(ev.data, ev_ref.data) + ev_ref = read_evokeds(fname_ave_elekta, cat.comment, + baseline=(-.05, 0), proj=False) + ev_mag = ev.copy() + ev_mag.pick_channels(['MEG0111']) + ev_grad = ev.copy() + ev_grad.pick_channels(['MEG2643', 'MEG1622']) + ev_ref_mag = ev_ref.copy() + ev_ref_mag.pick_channels(['MEG0111']) + ev_ref_grad = ev_ref.copy() + ev_ref_grad.pick_channels(['MEG2643', 'MEG1622']) + assert_allclose(ev_mag.data, ev_ref_mag.data, + rtol=0, atol=1e-15) # tol = 1 fT + assert_allclose(ev_grad.data, ev_ref_grad.data, + rtol=0, atol=1e-13) # tol = 1 fT/cm run_tests_if_main() From abffaabbb2fc265a9f11e62a7d5cc89cb8388783 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 30 May 2016 17:32:30 +0300 Subject: [PATCH 14/85] wip: comment [ci skip] --- mne/tests/test_event.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 0fb99f45e3e..84ea4bc5486 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -421,6 +421,8 @@ def test_elekta_averager(): raw = read_raw_fif(fname_raw_elekta) eav = Elekta_averager(raw.info['acq_pars']) for cat in eav.categories: + # XXX datasets match only when baseline is applied to both, + # not sure where relative dc shift comes from eps = cat.epochs(raw, baseline=(-.05, 0)) ev = eps.average() ev_ref = read_evokeds(fname_ave_elekta, cat.comment, From 2a273e484052306bd37e167d4e4196adee3b4914 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 30 May 2016 17:58:57 +0300 Subject: [PATCH 15/85] wip: test for dacq <3.4 --- mne/event.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mne/event.py b/mne/event.py index 60c079fe2b2..98eda151a47 100644 --- a/mne/event.py +++ b/mne/event.py @@ -990,6 +990,9 @@ def __init__(self, acq_pars): if not acq_pars: raise ValueError('No acquisition parameters') self.acq_dict = _acqpars_dict(acq_pars) + if 'ERFversion' not in self.acq_dict: + raise ValueError('The file may be from DACQ <3.4 which is ' + 'not supported yet') # sets instance variables (lowercase versions of DACQ variable names) for var in Elekta_averager.vars: val = self.acq_dict['ERF' + var] From dc7c589fb7dc9ef99b6abb5bb1439e0c06a74a9a Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 26 Aug 2016 14:20:55 +0200 Subject: [PATCH 16/85] wip: update location of test data [ci skip] --- mne/tests/test_event.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 84ea4bc5486..fc7d802da09 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -14,6 +14,7 @@ from mne.tests.common import assert_naming from mne.utils import _TempDir, run_tests_if_main from mne.event import define_target_events, merge_events, Elekta_averager +from mne.datasets import testing warnings.simplefilter('always') @@ -25,8 +26,9 @@ fname_txt_1 = op.join(base_dir, 'test-eve-1.eve') # for testing Elekta averager -fname_raw_elekta = op.join(base_dir, 'trigtest_3ch_raw.fif') -fname_ave_elekta = op.join(base_dir, 'test_elekta_ave.fif') +elekta_base_dir = op.join(testing.data_path(download=False), 'misc') +fname_raw_elekta = op.join(elekta_base_dir, 'test_elekta_3ch_raw.fif') +fname_ave_elekta = op.join(elekta_base_dir, 'test_elekta-ave.fif') # using mne_process_raw --raw test_raw.fif --eventsout test-mpr-eve.eve: fname_txt_mpr = op.join(base_dir, 'test-mpr-eve.eve') From e7bb90c5c791c87570dd360d111da5ff14b1f033 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 30 Aug 2016 11:59:53 +0300 Subject: [PATCH 17/85] wip: some renames + refactor [ci skip] --- mne/event.py | 28 +++++++++++++--------------- mne/tests/test_event.py | 8 ++++---- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/mne/event.py b/mne/event.py index 98eda151a47..a85f89df7a4 100644 --- a/mne/event.py +++ b/mne/event.py @@ -970,20 +970,20 @@ def epochs(self, raw, picks=None, reject=None, mask=mask) -class Elekta_averager(object): +class ElektaAverager(object): """ Represents averager settings of Elekta TRIUX/Vectorview systems. """ - # these are DACQ averager related variable names without preceding 'ERF' + # DACQ variable names always start with one of these + acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] + + # these are averager related DACQ variable names (without preceding 'ERF') vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', 'nevent', 'stimSource', 'triggerMap', 'update', 'version', 'artefIgnore', 'averUpdate'] - # DACQ variable names start with one of these - acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] - def __init__(self, acq_pars): """ acq_pars is usually obtained as data.info['acq_pars'], where data can be instance of Raw, Epochs or Evoked. """ @@ -994,7 +994,7 @@ def __init__(self, acq_pars): raise ValueError('The file may be from DACQ <3.4 which is ' 'not supported yet') # sets instance variables (lowercase versions of DACQ variable names) - for var in Elekta_averager.vars: + for var in ElektaAverager.vars: val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: val = float(val) @@ -1002,7 +1002,7 @@ def __init__(self, acq_pars): val = int(val) setattr(self, var.lower(), val) self.stimsource = ( - u'Internal' if self.stimsource == u'1' else u'External') + 'Internal' if self.stimsource == '1' else 'External') # collect all events and categories as instance dicts self._events = self._events_from_acq_pars() self._categories = self._categories_from_acq_pars() @@ -1016,9 +1016,13 @@ def __init__(self, acq_pars): [cat for cat in self._categories.values() if cat.state]) self._events_in_use = ( [ev for ev in self._events.values() if ev.in_use]) + # make a mne rejection dict based on the averager parameters + self.reject = {'grad': self.megmax, 'mag': self.magmax, + 'eeg': self.eegmax, 'eog': self.eogmax, + 'ecg': self.ecgmax} def __repr__(self): - s = ' Date: Tue, 30 Aug 2016 12:21:54 +0300 Subject: [PATCH 18/85] =?UTF-8?q?wip:=20start=20class->dict=20refactor?= =?UTF-8?q?=C2=A0[ci=20skip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mne/event.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/mne/event.py b/mne/event.py index a85f89df7a4..6858719a32d 100644 --- a/mne/event.py +++ b/mne/event.py @@ -984,6 +984,14 @@ class ElektaAverager(object): 'nevent', 'stimSource', 'triggerMap', 'update', 'version', 'artefIgnore', 'averUpdate'] + # event-related variables + event_vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', + 'OldMask', 'Delay', 'Comment'] + + # category-related variables + cat_vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', + 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve'] + def __init__(self, acq_pars): """ acq_pars is usually obtained as data.info['acq_pars'], where data can be instance of Raw, Epochs or Evoked. """ @@ -1036,19 +1044,26 @@ def __getitem__(self, items): return self._categories[items] def _events_from_acq_pars(self): - """ Collects DACQ defined events into a dict. """ + """ Collects DACQ defined events into a dict, keyed by number + starting from 1. Each event is itself a dict containing the event + definitions. """ events = {} # evnum = '01', '02' etc. for evnum in [str(x).zfill(2) for x in range(1, self.ncateg + 1)]: evdi = {} - for var in Elekta_event.vars: + for var in self.event_vars: # name of DACQ variable, e.g. 'ERFeventNewBits01' acq_key = 'ERFevent' + var + evnum - # corresponding instance variable, e.g. 'newbits' - class_key = var.lower() - evdi[class_key] = self.acq_dict[acq_key] + # corresponding dict key, e.g. 'newbits' + dict_key = var.lower() + val = self.acq_dict[acq_key] + if dict_key in ['newbits', 'oldbits', 'newmask', 'oldmask']: + val = int(val) + elif dict_key in ['delay']: + val = float(val) + evdi[dict_key] = val # events are keyed by number starting from 1 - events[int(evnum)] = Elekta_event(**evdi) + events[int(evnum)] = evdi events[int(evnum)].index = int(evnum) return events @@ -1057,7 +1072,7 @@ def _categories_from_acq_pars(self, all_categories=False): cats = {} for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = {} - for var in Elekta_category.vars: + for var in self.cat_vars: acq_key = 'ERFcat' + var + catnum class_key = var.lower() catdi[class_key] = self.acq_dict[acq_key] From c7c9d7836a3c54f7aff8b5630371eb00989b2c52 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 30 Aug 2016 14:07:08 +0300 Subject: [PATCH 19/85] wip: refactor classes away [ci skip] --- mne/event.py | 76 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/mne/event.py b/mne/event.py index 6858719a32d..f2a62199b0c 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1016,14 +1016,14 @@ def __init__(self, acq_pars): self._categories = self._categories_from_acq_pars() # collect events and categories that are used by this setup for cat in self._categories.values(): - if cat.event: - self._events[cat.event].in_use = True - if cat.reqevent: - self._events[cat.reqevent].in_use = True + if cat['event']: + self._events[cat['event']]['in_use'] = True + if cat['reqevent']: + self._events[cat['reqevent']]['in_use'] = True self._categories_in_use = ( - [cat for cat in self._categories.values() if cat.state]) + [cat for cat in self._categories.values() if cat['state']]) self._events_in_use = ( - [ev for ev in self._events.values() if ev.in_use]) + [ev for ev in self._events.values() if ev['in_use']]) # make a mne rejection dict based on the averager parameters self.reject = {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, @@ -1063,24 +1063,47 @@ def _events_from_acq_pars(self): val = float(val) evdi[dict_key] = val # events are keyed by number starting from 1 - events[int(evnum)] = evdi - events[int(evnum)].index = int(evnum) + key = int(evnum) + evdi['index'] = key + events[key] = evdi return events def _categories_from_acq_pars(self, all_categories=False): - """ Collects DACQ averaging categories into a dict. """ + """ Collects DACQ averaging categories into a dict. Categories + are keyed by the comment defined in DACQ setup. Each category is + a dict containing the category definitions. """ cats = {} for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = {} for var in self.cat_vars: acq_key = 'ERFcat' + var + catnum class_key = var.lower() - catdi[class_key] = self.acq_dict[acq_key] + val = self.acq_dict[acq_key] + catdi[class_key] = val if int(catdi['state']) == 1 or all_categories: # category enabled - cat = Elekta_category(**catdi) - cat.index = int(catnum) - cat._parent = self - cats[catdi['comment']] = cat + # show online? + catdi['display'] = True if catdi['display'] == '1' else False + # enabled? + catdi['state'] = True if catdi['state'] == '1' else False + catdi['start'] = float(catdi['start']) # epoch start + catdi['end'] = float(catdi['end']) # epoch end + catdi['nave'] = int(catdi['nave']) # desired n of averages + # the reference event (index to event list) + catdi['event'] = int(catdi['event']) + # required event (index to event list) + catdi['reqevent'] = int(catdi['reqevent']) + # 1=before, 2=after ref. event + catdi['reqwhen'] = int(catdi['reqwhen']) + # time window before or after ref. event (s) + catdi['reqwithin'] = float(catdi['reqwithin']) + catdi['subave'] = int(catdi['subave']) # subaverage size + # non-DACQ vars + # list of t=0 times (in samples) for corresponding epochs + catdi['times'] = None + # index of category in DACQ list + catdi['index'] = int(catnum) + catdi['parent'] = self # owning ElektaAverager instance + cats[catdi['comment']] = catdi return cats def _events_mne_to_dacq(self, mne_events): @@ -1095,11 +1118,11 @@ def _events_mne_to_dacq(self, mne_events): events_[:, 1] = 0 events_[:, 2] = 0 for n, ev in self._events.iteritems(): - if ev.in_use: + if ev['in_use']: pre_ok = ( - np.bitwise_and(ev.oldmask, mne_events[:, 1]) == ev.oldbits) + np.bitwise_and(ev['oldmask'], mne_events[:, 1]) == ev['oldbits']) post_ok = ( - np.bitwise_and(ev.newbits, mne_events[:, 2]) == ev.newbits) + np.bitwise_and(ev['newbits'], mne_events[:, 2]) == ev['newbits']) ok_ind = np.where(pre_ok & post_ok) events_[ok_ind, 2] |= 1 << (n - 1) return events_ @@ -1107,20 +1130,23 @@ def _events_mne_to_dacq(self, mne_events): def _mne_events_to_category_t0(self, cat, mne_events, sfreq): """ Translate mne_events to reference times (t0) for epochs in a given DACQ averaging category cat. """ + cat_ev = cat['event'] + cat_reqev = cat['reqevent'] + events = self._events_mne_to_dacq(mne_events) times = events[:, 0] # indices of times where ref. event occurs - refEvents_inds = np.where(events[:, 2] & (1 << cat.event - 1))[0] + refEvents_inds = np.where(events[:, 2] & (1 << cat_ev - 1))[0] refEvents_t = times[refEvents_inds] - if cat.reqevent: + if cat_reqev: # indices of times where req. event occurs reqEvents_inds = np.where(events[:, 2] & ( - 1 << cat.reqevent - 1))[0] + 1 << cat_reqev - 1))[0] reqEvents_t = times[reqEvents_inds] # relative (to refevent) time window (in samples) where req. event # must occur (e.g. [0 200]) win = np.round(np.array( - sorted([0, (-1)**(cat.reqwhen) * cat.reqwithin])) * sfreq) + sorted([0, (-1)**(cat['reqwhen']) * cat['reqwithin']])) * sfreq) refEvents_wins = refEvents_t[:, None] + win req_acc = np.zeros(refEvents_inds.shape, dtype=bool) for t in reqEvents_t: @@ -1132,7 +1158,7 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): refEvents_inds = refEvents_inds[np.where(req_acc)] refEvents_t = times[refEvents_inds] # adjust for trigger-stimulus delay by delaying the ref. event - refEvents_t += int(np.round(self._events[cat.event].delay * sfreq)) + refEvents_t += int(np.round(self._events[cat_ev].delay * sfreq)) return refEvents_t @property @@ -1140,14 +1166,14 @@ def categories(self): """ Return list of categories in DACQ defined order. Only returns categories marked active in DACQ. """ return sorted(self._categories_in_use, - key=lambda cat: cat.index) + key=lambda cat: cat['index']) @property def events(self): """ Return list of events in DACQ order. Only returns events that are referred to by a DACQ category. """ return sorted(self._events_in_use, - key=lambda ev: ev.index) + key=lambda ev: ev['index']) def _get_epochs(self, raw, category, picks=None, reject=None, baseline=(None, 0), stim_channel=None, mask=0): @@ -1162,7 +1188,7 @@ def _get_epochs(self, raw, category, picks=None, reject=None, cat_t = self._mne_events_to_category_t0(category, mne_events, sfreq) catev = np.c_[cat_t, np.zeros(cat_t.shape), np.ones(cat_t.shape)].astype(np.uint32) - id = {category.comment: 1} + id = {category['comment']: 1} return Epochs(raw, catev, event_id=id, reject=reject, tmin=category.start, tmax=category.end, baseline=baseline, detrend=None, picks=picks, From 6ea7a59984bdf90df97a039e9bc625a8b8b024e8 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 30 Aug 2016 16:30:27 +0300 Subject: [PATCH 20/85] wip: refactored [ci skip] --- mne/event.py | 26 +++++++++++++++----------- mne/tests/test_event.py | 4 ++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/mne/event.py b/mne/event.py index f2a62199b0c..d31f7319933 100644 --- a/mne/event.py +++ b/mne/event.py @@ -971,13 +971,13 @@ def epochs(self, raw, picks=None, reject=None, class ElektaAverager(object): - """ Represents averager settings of Elekta TRIUX/Vectorview systems. - """ + """ Handles events and averaging categories for Elekta TRIUX/Vectorview + systems.""" # DACQ variable names always start with one of these acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] - # these are averager related DACQ variable names (without preceding 'ERF') + # averager related DACQ variable names (without preceding 'ERF') vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', @@ -1057,11 +1057,13 @@ def _events_from_acq_pars(self): # corresponding dict key, e.g. 'newbits' dict_key = var.lower() val = self.acq_dict[acq_key] + # convert numeric values if dict_key in ['newbits', 'oldbits', 'newmask', 'oldmask']: val = int(val) elif dict_key in ['delay']: val = float(val) evdi[dict_key] = val + evdi['in_use'] = False # events are keyed by number starting from 1 key = int(evnum) evdi['index'] = key @@ -1080,7 +1082,8 @@ def _categories_from_acq_pars(self, all_categories=False): class_key = var.lower() val = self.acq_dict[acq_key] catdi[class_key] = val - if int(catdi['state']) == 1 or all_categories: # category enabled + # create only categories that are enabled + if int(catdi['state']) == 1 or all_categories: # show online? catdi['display'] = True if catdi['display'] == '1' else False # enabled? @@ -1120,9 +1123,11 @@ def _events_mne_to_dacq(self, mne_events): for n, ev in self._events.iteritems(): if ev['in_use']: pre_ok = ( - np.bitwise_and(ev['oldmask'], mne_events[:, 1]) == ev['oldbits']) + np.bitwise_and(ev['oldmask'], + mne_events[:, 1]) == ev['oldbits']) post_ok = ( - np.bitwise_and(ev['newbits'], mne_events[:, 2]) == ev['newbits']) + np.bitwise_and(ev['newbits'], + mne_events[:, 2]) == ev['newbits']) ok_ind = np.where(pre_ok & post_ok) events_[ok_ind, 2] |= 1 << (n - 1) return events_ @@ -1132,7 +1137,6 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): DACQ averaging category cat. """ cat_ev = cat['event'] cat_reqev = cat['reqevent'] - events = self._events_mne_to_dacq(mne_events) times = events[:, 0] # indices of times where ref. event occurs @@ -1158,7 +1162,7 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): refEvents_inds = refEvents_inds[np.where(req_acc)] refEvents_t = times[refEvents_inds] # adjust for trigger-stimulus delay by delaying the ref. event - refEvents_t += int(np.round(self._events[cat_ev].delay * sfreq)) + refEvents_t += int(np.round(self._events[cat_ev]['delay'] * sfreq)) return refEvents_t @property @@ -1175,8 +1179,8 @@ def events(self): return sorted(self._events_in_use, key=lambda ev: ev['index']) - def _get_epochs(self, raw, category, picks=None, reject=None, - baseline=(None, 0), stim_channel=None, mask=0): + def get_epochs(self, raw, category, picks=None, reject=None, + baseline=(None, 0), stim_channel=None, mask=0): """ Get mne.Epochs instance corresponding to the given category. """ from .epochs import Epochs mne_events = find_events(raw, stim_channel=stim_channel, @@ -1190,7 +1194,7 @@ def _get_epochs(self, raw, category, picks=None, reject=None, np.ones(cat_t.shape)].astype(np.uint32) id = {category['comment']: 1} return Epochs(raw, catev, event_id=id, reject=reject, - tmin=category.start, tmax=category.end, + tmin=category['start'], tmax=category['end'], baseline=baseline, detrend=None, picks=picks, preload=True, verbose=False) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 59b4bf8703f..9325bf26ea5 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -425,9 +425,9 @@ def test_elekta_averager(): for cat in eav.categories: # XXX datasets match only when baseline is applied to both, # not sure where relative dc shift comes from - eps = cat.epochs(raw, baseline=(-.05, 0)) + eps = eav._get_epochs(raw, cat, baseline=(-.05, 0)) ev = eps.average() - ev_ref = read_evokeds(fname_ave_elekta, cat.comment, + ev_ref = read_evokeds(fname_ave_elekta, cat['comment'], baseline=(-.05, 0), proj=False) ev_mag = ev.copy() ev_mag.pick_channels(['MEG0111']) From c0d5afbb950e335613e27d909c69ea6f51c89af5 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 30 Aug 2016 16:41:10 +0300 Subject: [PATCH 21/85] wip: cleanup --- mne/event.py | 86 +---------------------------------------- mne/tests/test_event.py | 2 +- 2 files changed, 3 insertions(+), 85 deletions(-) diff --git a/mne/event.py b/mne/event.py index d31f7319933..20a71a7c894 100644 --- a/mne/event.py +++ b/mne/event.py @@ -888,88 +888,6 @@ def concatenate_events(events, first_samps, last_samps): return events_out -class Elekta_event(object): - """ Represents a trigger event in Elekta/Neuromag system. """ - # original DACQ variable names - vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', - 'OldMask', 'Delay', 'Comment'] - - def __init__(self, name, channel, newbits, oldbits, newmask, - oldmask, delay, comment): - self.name = name # short name for the event - self.channel = channel # stimulus channel, e.g. STI101 - self.newbits = int(newbits) # state after trigger transition - self.oldbits = int(oldbits) # state before trigger transition - self.newmask = int(newmask) # bitmask for post-transition state - self.oldmask = int(oldmask) # bitmask for pre-transition state - self.delay = float(delay) # delay to stimulus (s) - self.comment = comment # verbose comment for the event - # non-DACQ vars - self.index = None # index of event in DACQ list - self.in_use = False # whether event is referred to by a category - - def __repr__(self): - s = ' Date: Wed, 31 Aug 2016 10:33:34 +0300 Subject: [PATCH 22/85] wip: some cleanup [ci skip] --- mne/event.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/mne/event.py b/mne/event.py index 20a71a7c894..d8b53c01d0c 100644 --- a/mne/event.py +++ b/mne/event.py @@ -917,9 +917,9 @@ def __init__(self, acq_pars): raise ValueError('No acquisition parameters') self.acq_dict = _acqpars_dict(acq_pars) if 'ERFversion' not in self.acq_dict: - raise ValueError('The file may be from DACQ <3.4 which is ' - 'not supported yet') - # sets instance variables (lowercase versions of DACQ variable names) + raise ValueError('Cannot parse version. The file may be from' + 'DACQ <3.4 which is not supported yet') + # set instance variables for var in ElektaAverager.vars: val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: @@ -929,10 +929,10 @@ def __init__(self, acq_pars): setattr(self, var.lower(), val) self.stimsource = ( 'Internal' if self.stimsource == '1' else 'External') - # collect all events and categories as instance dicts + # collect all events and categories self._events = self._events_from_acq_pars() self._categories = self._categories_from_acq_pars() - # collect events and categories that are used by this setup + # collect events and categories that are actually used by the setup for cat in self._categories.values(): if cat['event']: self._events[cat['event']]['in_use'] = True @@ -962,39 +962,38 @@ def __getitem__(self, items): return self._categories[items] def _events_from_acq_pars(self): - """ Collects DACQ defined events into a dict, keyed by number - starting from 1. Each event is itself a dict containing the event - definitions. """ + """ Collect DACQ defined events into a dict. Events are keyed by number + starting from 1 (DACQ index of event). Each event is itself represented + by a dict containing the event definitions. """ events = {} - # evnum = '01', '02' etc. - for evnum in [str(x).zfill(2) for x in range(1, self.ncateg + 1)]: + for evnum in range(1, self.ncateg + 1): + evnum_s = str(evnum).zfill(2) # '01', '02' etc. evdi = {} for var in self.event_vars: # name of DACQ variable, e.g. 'ERFeventNewBits01' - acq_key = 'ERFevent' + var + evnum + acq_key = 'ERFevent' + var + evnum_s # corresponding dict key, e.g. 'newbits' dict_key = var.lower() val = self.acq_dict[acq_key] - # convert numeric values + # type convert numeric values if dict_key in ['newbits', 'oldbits', 'newmask', 'oldmask']: val = int(val) elif dict_key in ['delay']: val = float(val) evdi[dict_key] = val - evdi['in_use'] = False - # events are keyed by number starting from 1 - key = int(evnum) - evdi['index'] = key - events[key] = evdi + evdi['in_use'] = False # __init__() will set this + evdi['index'] = evnum + events[evnum] = evdi return events def _categories_from_acq_pars(self, all_categories=False): """ Collects DACQ averaging categories into a dict. Categories - are keyed by the comment defined in DACQ setup. Each category is + are keyed by the comment defined in DACQ. Each category is represented a dict containing the category definitions. """ cats = {} for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = {} + # read category variables for var in self.cat_vars: acq_key = 'ERFcat' + var + catnum class_key = var.lower() From 1c23c04319e7da70a8cd9e2142e65c62b5d0f22a Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 10:49:15 +0300 Subject: [PATCH 23/85] wip: further cleanup [ci skip] --- mne/event.py | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/mne/event.py b/mne/event.py index d8b53c01d0c..7428967c1bd 100644 --- a/mne/event.py +++ b/mne/event.py @@ -986,44 +986,30 @@ def _events_from_acq_pars(self): events[evnum] = evdi return events - def _categories_from_acq_pars(self, all_categories=False): + def _categories_from_acq_pars(self): """ Collects DACQ averaging categories into a dict. Categories are keyed by the comment defined in DACQ. Each category is represented a dict containing the category definitions. """ cats = {} for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = {} - # read category variables + # read all category variables for var in self.cat_vars: acq_key = 'ERFcat' + var + catnum class_key = var.lower() val = self.acq_dict[acq_key] catdi[class_key] = val - # create only categories that are enabled - if int(catdi['state']) == 1 or all_categories: - # show online? - catdi['display'] = True if catdi['display'] == '1' else False - # enabled? - catdi['state'] = True if catdi['state'] == '1' else False - catdi['start'] = float(catdi['start']) # epoch start - catdi['end'] = float(catdi['end']) # epoch end - catdi['nave'] = int(catdi['nave']) # desired n of averages - # the reference event (index to event list) - catdi['event'] = int(catdi['event']) - # required event (index to event list) - catdi['reqevent'] = int(catdi['reqevent']) - # 1=before, 2=after ref. event - catdi['reqwhen'] = int(catdi['reqwhen']) - # time window before or after ref. event (s) - catdi['reqwithin'] = float(catdi['reqwithin']) - catdi['subave'] = int(catdi['subave']) # subaverage size - # non-DACQ vars - # list of t=0 times (in samples) for corresponding epochs - catdi['times'] = None - # index of category in DACQ list - catdi['index'] = int(catnum) - catdi['parent'] = self # owning ElektaAverager instance - cats[catdi['comment']] = catdi + # some type conversions + catdi['display'] = True if catdi['display'] == '1' else False + catdi['state'] = True if catdi['state'] == '1' else False + for key in ['start', 'end', 'reqwithin']: + catdi[key] = float(catdi[key]) + for key in ['nave', 'event', 'reqevent', 'reqwhen', 'subave']: + catdi[key] = int(catdi[key]) + # some convenient extra (non-DACQ) vars + catdi['index'] = int(catnum) # index of category in DACQ list + catdi['parent'] = self # owning class instance + cats[catdi['comment']] = catdi return cats def _events_mne_to_dacq(self, mne_events): @@ -1035,8 +1021,7 @@ def _events_mne_to_dacq(self, mne_events): e.g. [t1, 0, 5] means that events 1 and 3 occurred at time t1, as 2**(1 - 1) + 2**(3 - 1) = 5. """ events_ = mne_events.copy() - events_[:, 1] = 0 - events_[:, 2] = 0 + events_[:, 1:3] = 0 for n, ev in self._events.iteritems(): if ev['in_use']: pre_ok = ( @@ -1064,8 +1049,8 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): reqEvents_inds = np.where(events[:, 2] & ( 1 << cat_reqev - 1))[0] reqEvents_t = times[reqEvents_inds] - # relative (to refevent) time window (in samples) where req. event - # must occur (e.g. [0 200]) + # relative (to refevent) time window where req. event + # must occur (e.g. [0 .2]) twin = [0, (-1)**(cat['reqwhen']) * cat['reqwithin']] win = np.round(np.array(sorted(twin)) * sfreq) # to samples refEvents_wins = refEvents_t[:, None] + win From 602a38a9015db2e5b983bdfec6692d3d19dd5c8c Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 11:08:01 +0300 Subject: [PATCH 24/85] wip: take info as arg [ci skip] --- mne/event.py | 5 +++-- mne/tests/test_event.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mne/event.py b/mne/event.py index 7428967c1bd..d638ae3ae3d 100644 --- a/mne/event.py +++ b/mne/event.py @@ -910,9 +910,10 @@ class ElektaAverager(object): cat_vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve'] - def __init__(self, acq_pars): - """ acq_pars is usually obtained as data.info['acq_pars'], where data + def __init__(self, info): + """ info is usually obtained as data.info, where data can be instance of Raw, Epochs or Evoked. """ + acq_pars = info['acq_pars'] if not acq_pars: raise ValueError('No acquisition parameters') self.acq_dict = _acqpars_dict(acq_pars) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 01df1a6a1cb..420f4363380 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -421,7 +421,7 @@ def test_define_events(): def test_elekta_averager(): """Test averaging according to Elekta DACQ parameters""" raw = io.read_raw_fif(fname_raw_elekta, preload=True) - eav = ElektaAverager(raw.info['acq_pars']) + eav = ElektaAverager(raw.info) for cat in eav.categories: # XXX datasets match only when baseline is applied to both, # not sure where relative dc shift comes from From d24e8c1a02b1f54be69837c8647a062f91b20263 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 11:59:19 +0300 Subject: [PATCH 25/85] wip: use dicts instead of lists --- mne/event.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/mne/event.py b/mne/event.py index d638ae3ae3d..194cac8cf92 100644 --- a/mne/event.py +++ b/mne/event.py @@ -933,16 +933,17 @@ def __init__(self, info): # collect all events and categories self._events = self._events_from_acq_pars() self._categories = self._categories_from_acq_pars() - # collect events and categories that are actually used by the setup + # mark events that are used by some category for cat in self._categories.values(): if cat['event']: self._events[cat['event']]['in_use'] = True if cat['reqevent']: self._events[cat['reqevent']]['in_use'] = True + # collect categories and events that are actually in use self._categories_in_use = ( - [cat for cat in self._categories.values() if cat['state']]) + {k: v for k, v in self._categories.iteritems() if v['state']}) self._events_in_use = ( - [ev for ev in self._events.values() if ev['in_use']]) + {k: v for k, v in self._events.iteritems() if v['in_use']}) # make a mne rejection dict based on the averager parameters self.reject = {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, @@ -1070,17 +1071,23 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): @property def categories(self): - """ Return list of categories in DACQ defined order. Only returns + """ Return list of category names in DACQ defined order. Only returns categories marked active in DACQ. """ - return sorted(self._categories_in_use, + cats = sorted(self._categories_in_use.values(), key=lambda cat: cat['index']) + return cats @property def events(self): - """ Return list of events in DACQ order. Only returns events that - are referred to by a DACQ category. """ - return sorted(self._events_in_use, - key=lambda ev: ev['index']) + """ Lists events in DACQ defined order. Only returns events + that are in use (referred to by a category). """ + evs = sorted(self._events_in_use.values(), + key=lambda ev: ev['index']) + return evs + + @property + def event_names(self): + return [event['comment'] for event in self.events] def get_epochs(self, raw, category, picks=None, reject=None, baseline=(None, 0), stim_channel=None, mask=0): From 8c6a6638a2bc678f71d66cf0d9d9b5b40cb44097 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 12:18:02 +0300 Subject: [PATCH 26/85] wip: tentative example [ci skip] --- examples/io/elekta_averages.py | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/io/elekta_averages.py diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_averages.py new file mode 100644 index 00000000000..f2a8a585143 --- /dev/null +++ b/examples/io/elekta_averages.py @@ -0,0 +1,47 @@ +""" +================================ +Getting averaging info from fiff +================================ + +Get averaging information from a fiff file (for Vectorview/TRIUX systems) +and extract epochs accordingly. +""" +# Author: Jussi Nurminen (jnu@iki.fi) +# +# License: BSD (3-clause) + + +import mne +import os +from mne.datasets import testing +from mne.event import ElektaAverager + +elekta_base_dir = os.path.join(testing.data_path(), 'misc') +fname_raw_elekta = os.path.join(elekta_base_dir, 'test_elekta_3ch_raw.fif') + + +print(__doc__) + +raw = mne.io.read_raw_fif(fname_raw_elekta) + +eav = ElektaAverager(raw.info) + +# check out which averaging categories were defined in DACQ +print eav.categories + +# extract epochs corresponding to a category +cat = eav['Event 1 followed by 2 within 1100 ms'] +eps = eav.get_epochs(raw, cat) + +""" Read all categories, extract corresponding epochs, average and save to new + fiff file. """ +evokeds = [] +for cat in eav.categories: + eps = eav.get_epochs(raw, cat) + evoked = eps.average() + evoked.comment = cat['comment'] + evokeds.append(evoked) + +fn_out = 'eav_evokeds.fif' +mne.write_evokeds(fn_out, evokeds) + From f666a4325a1554d11f45becfeb34f43549ad847b Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 15:46:06 +0300 Subject: [PATCH 27/85] wip: don't copy undefined rejection limits --- examples/io/elekta_averages.py | 16 ++++++++-------- mne/event.py | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_averages.py index f2a8a585143..a01d72ec438 100644 --- a/examples/io/elekta_averages.py +++ b/examples/io/elekta_averages.py @@ -3,8 +3,8 @@ Getting averaging info from fiff ================================ -Get averaging information from a fiff file (for Vectorview/TRIUX systems) -and extract epochs accordingly. +Get averaging information defined in Elekta Vectorview/TRIUX DACQ (data +acquisition). Extract and extract epochs accordingly. """ # Author: Jussi Nurminen (jnu@iki.fi) # @@ -19,21 +19,22 @@ elekta_base_dir = os.path.join(testing.data_path(), 'misc') fname_raw_elekta = os.path.join(elekta_base_dir, 'test_elekta_3ch_raw.fif') - print(__doc__) raw = mne.io.read_raw_fif(fname_raw_elekta) - eav = ElektaAverager(raw.info) # check out which averaging categories were defined in DACQ print eav.categories -# extract epochs corresponding to a category +""" Extract epochs corresponding to a category. Copy supported rejection +limits from DACQ settings. """ cat = eav['Event 1 followed by 2 within 1100 ms'] -eps = eav.get_epochs(raw, cat) +rej = eav.reject +eps = eav.get_epochs(raw, cat, reject=rej) -""" Read all categories, extract corresponding epochs, average and save to new +""" Read all categories, extract corresponding epochs, average, add +comments from to the DACQ categories and save to new fiff file. """ evokeds = [] for cat in eav.categories: @@ -44,4 +45,3 @@ fn_out = 'eav_evokeds.fif' mne.write_evokeds(fn_out, evokeds) - diff --git a/mne/event.py b/mne/event.py index 194cac8cf92..5cf09b680cc 100644 --- a/mne/event.py +++ b/mne/event.py @@ -892,9 +892,6 @@ class ElektaAverager(object): """ Handles events and averaging categories for Elekta TRIUX/Vectorview systems.""" - # DACQ variable names always start with one of these - acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] - # averager related DACQ variable names (without preceding 'ERF') vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', @@ -948,6 +945,7 @@ def __init__(self, info): self.reject = {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, 'ecg': self.ecgmax} + self.reject = {k: float(v) for k, v in self.reject.iteritems() if float(v) > 0} def __repr__(self): s = ' Date: Wed, 31 Aug 2016 16:26:55 +0300 Subject: [PATCH 28/85] wip: specify event by name, fat docstring, flats --- examples/io/elekta_averages.py | 9 ++- mne/event.py | 102 +++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_averages.py index a01d72ec438..528283fe401 100644 --- a/examples/io/elekta_averages.py +++ b/examples/io/elekta_averages.py @@ -27,11 +27,10 @@ # check out which averaging categories were defined in DACQ print eav.categories -""" Extract epochs corresponding to a category. Copy supported rejection +""" Extract epochs corresponding to a category. Copy rejection limits from DACQ settings. """ -cat = eav['Event 1 followed by 2 within 1100 ms'] -rej = eav.reject -eps = eav.get_epochs(raw, cat, reject=rej) +eps = eav.get_epochs(raw, 'Event 1 followed by 2 within 1100 ms', + reject=True, flat=True) """ Read all categories, extract corresponding epochs, average, add comments from to the DACQ categories and save to new @@ -43,5 +42,5 @@ evoked.comment = cat['comment'] evokeds.append(evoked) -fn_out = 'eav_evokeds.fif' +fn_out = 'elekta_evokeds-ave.fif' mne.write_evokeds(fn_out, evokeds) diff --git a/mne/event.py b/mne/event.py index 5cf09b680cc..53388d06bc6 100644 --- a/mne/event.py +++ b/mne/event.py @@ -941,11 +941,16 @@ def __init__(self, info): {k: v for k, v in self._categories.iteritems() if v['state']}) self._events_in_use = ( {k: v for k, v in self._events.iteritems() if v['in_use']}) - # make a mne rejection dict based on the averager parameters + # make mne rejection dicts based on the averager parameters self.reject = {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, 'ecg': self.ecgmax} - self.reject = {k: float(v) for k, v in self.reject.iteritems() if float(v) > 0} + self.reject = {k: float(v) for k, v in self.reject.iteritems() + if float(v) > 0} + self.flat = {'grad': self.megmin, 'mag': self.magmin, + 'eeg': self.eegmin} + self.flat = {k: float(v) for k, v in self.flat.iteritems() + if float(v) > 0} def __repr__(self): s = ' Date: Wed, 31 Aug 2016 16:32:14 +0300 Subject: [PATCH 29/85] wip: some comments --- mne/event.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index 53388d06bc6..755b4be1542 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1022,7 +1022,7 @@ def _events_mne_to_dacq(self, mne_events): mne_events is typically given by mne.find_events (use consecutive=True to get all transitions). Output consists of rows in the form [t, 0, event_codes] where t is time in samples and event_codes is all - events compatible with the transition, bitwise ORed together: + DACQ events compatible with the transition, bitwise ORed together: e.g. [t1, 0, 5] means that events 1 and 3 occurred at time t1, as 2**(1 - 1) + 2**(3 - 1) = 5. """ events_ = mne_events.copy() @@ -1044,7 +1044,9 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): DACQ averaging category cat. """ cat_ev = cat['event'] cat_reqev = cat['reqevent'] + # first convert mne events to dacq event list events = self._events_mne_to_dacq(mne_events) + # next, take req. events and delays into account times = events[:, 0] # indices of times where ref. event occurs refEvents_inds = np.where(events[:, 2] & (1 << cat_ev - 1))[0] From 15b6e31f3ba261e21dacbdfa9136f7aa72b06bef Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 16:52:31 +0300 Subject: [PATCH 30/85] wip: docstring + typo --- examples/io/elekta_averages.py | 2 +- mne/event.py | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_averages.py index 528283fe401..ea57e08bc59 100644 --- a/examples/io/elekta_averages.py +++ b/examples/io/elekta_averages.py @@ -4,7 +4,7 @@ ================================ Get averaging information defined in Elekta Vectorview/TRIUX DACQ (data -acquisition). Extract and extract epochs accordingly. +acquisition). Extract and average epochs accordingly. """ # Author: Jussi Nurminen (jnu@iki.fi) # diff --git a/mne/event.py b/mne/event.py index 755b4be1542..198d42f4959 100644 --- a/mne/event.py +++ b/mne/event.py @@ -889,8 +889,27 @@ def concatenate_events(events, first_samps, last_samps): class ElektaAverager(object): - """ Handles events and averaging categories for Elekta TRIUX/Vectorview - systems.""" + """Handles events and averaging categories defined in DACQ + (data acquisition) software of Elekta TRIUX/Vectorview systems. + + Parameters + ---------- + info : Info + An instance of Info where the DACQ parameters will be taken from. + + Attributes + ---------- + categories : list + List of averaging categories marked active in DACQ. + events : list + List of events that are in use (referenced by some averaging category). + reject : dict + Rejection criteria from DACQ that can be used with mne.Epochs. + Note that mne does not support all DACQ rejection criteria + (e.g. spike, slope) + flat : dict + Flatness criteria from DACQ that can be used with mne.Epochs. + """ # averager related DACQ variable names (without preceding 'ERF') vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', @@ -908,8 +927,6 @@ class ElektaAverager(object): 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve'] def __init__(self, info): - """ info is usually obtained as data.info, where data - can be instance of Raw, Epochs or Evoked. """ acq_pars = info['acq_pars'] if not acq_pars: raise ValueError('No acquisition parameters') @@ -1076,8 +1093,8 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): @property def categories(self): - """ Return list of category names in DACQ defined order. Only returns - categories marked active in DACQ. """ + """ Return list of averaging categories in DACQ defined order. Only + returns categories marked active in DACQ. """ cats = sorted(self._categories_in_use.values(), key=lambda cat: cat['index']) return cats From c4ae3927112f198fb38c66a2266775cc35090b8f Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 31 Aug 2016 17:05:56 +0300 Subject: [PATCH 31/85] wip: linefeeds into repr --- mne/event.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mne/event.py b/mne/event.py index 198d42f4959..e693ed9a10c 100644 --- a/mne/event.py +++ b/mne/event.py @@ -977,7 +977,11 @@ def __repr__(self): s += 'events: %d ' % self.nevent evs_in_use = len(self._events_in_use) s += '(%d in use), ' % evs_in_use - s += 'stim source: %s>' % self.stimsource + s += 'stim source: %s' % self.stimsource + s += '\nCategories:\n' + for cat in self.categories: + s += '%d: "%s"\n' % (cat['index'], cat['comment']) + s += '>' return s def __getitem__(self, items): @@ -1093,7 +1097,7 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): @property def categories(self): - """ Return list of averaging categories in DACQ defined order. Only + """ Return list of averaging categories in DACQ defined order. Only returns categories marked active in DACQ. """ cats = sorted(self._categories_in_use.values(), key=lambda cat: cat['index']) @@ -1101,7 +1105,7 @@ def categories(self): @property def events(self): - """ Lists events in DACQ defined order. Only returns events + """ Return events in DACQ defined order. Only returns events that are in use (referred to by a category). """ evs = sorted(self._events_in_use.values(), key=lambda ev: ev['index']) From be0fd2ab7232b92bf85f45cd6fd9dd666bbf457d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 1 Sep 2016 10:47:38 +0300 Subject: [PATCH 32/85] wip: most of kingjr review --- examples/io/elekta_averages.py | 7 ++--- mne/event.py | 53 +++++++++++++++------------------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_averages.py index ea57e08bc59..5b41d67d430 100644 --- a/examples/io/elekta_averages.py +++ b/examples/io/elekta_averages.py @@ -24,9 +24,6 @@ raw = mne.io.read_raw_fif(fname_raw_elekta) eav = ElektaAverager(raw.info) -# check out which averaging categories were defined in DACQ -print eav.categories - """ Extract epochs corresponding to a category. Copy rejection limits from DACQ settings. """ eps = eav.get_epochs(raw, 'Event 1 followed by 2 within 1100 ms', @@ -42,5 +39,5 @@ evoked.comment = cat['comment'] evokeds.append(evoked) -fn_out = 'elekta_evokeds-ave.fif' -mne.write_evokeds(fn_out, evokeds) +fname_out = 'elekta_evokeds-ave.fif' +mne.write_evokeds(fname_out, evokeds) diff --git a/mne/event.py b/mne/event.py index e693ed9a10c..765bd6e69a8 100644 --- a/mne/event.py +++ b/mne/event.py @@ -912,19 +912,17 @@ class ElektaAverager(object): """ # averager related DACQ variable names (without preceding 'ERF') - vars = ['magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', + dacq_vars = ('magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', 'nevent', 'stimSource', 'triggerMap', 'update', 'version', - 'artefIgnore', 'averUpdate'] + 'artefIgnore', 'averUpdate') - # event-related variables - event_vars = ['Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', - 'OldMask', 'Delay', 'Comment'] + event_vars = ('Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', + 'OldMask', 'Delay', 'Comment') - # category-related variables - cat_vars = ['Comment', 'Display', 'Start', 'State', 'End', 'Event', - 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve'] + cat_vars = ('Comment', 'Display', 'Start', 'State', 'End', 'Event', + 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve') def __init__(self, info): acq_pars = info['acq_pars'] @@ -932,10 +930,10 @@ def __init__(self, info): raise ValueError('No acquisition parameters') self.acq_dict = _acqpars_dict(acq_pars) if 'ERFversion' not in self.acq_dict: - raise ValueError('Cannot parse version. The file may be from' + raise ValueError('Cannot parse version. The file may be from ' 'DACQ <3.4 which is not supported yet') # set instance variables - for var in ElektaAverager.vars: + for var in ElektaAverager.dacq_vars: val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: val = float(val) @@ -947,7 +945,7 @@ def __init__(self, info): # collect all events and categories self._events = self._events_from_acq_pars() self._categories = self._categories_from_acq_pars() - # mark events that are used by some category + # mark events that are used by a category for cat in self._categories.values(): if cat['event']: self._events[cat['event']]['in_use'] = True @@ -991,10 +989,10 @@ def _events_from_acq_pars(self): """ Collect DACQ defined events into a dict. Events are keyed by number starting from 1 (DACQ index of event). Each event is itself represented by a dict containing the event definitions. """ - events = {} + events = dict() for evnum in range(1, self.ncateg + 1): evnum_s = str(evnum).zfill(2) # '01', '02' etc. - evdi = {} + evdi = dict() for var in self.event_vars: # name of DACQ variable, e.g. 'ERFeventNewBits01' acq_key = 'ERFevent' + var + evnum_s @@ -1016,9 +1014,9 @@ def _categories_from_acq_pars(self): """ Collects DACQ averaging categories into a dict. Categories are keyed by the comment defined in DACQ. Each category is represented a dict containing the category definitions. """ - cats = {} + cats = dict() for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: - catdi = {} + catdi = dict() # read all category variables for var in self.cat_vars: acq_key = 'ERFcat' + var + catnum @@ -1026,8 +1024,8 @@ def _categories_from_acq_pars(self): val = self.acq_dict[acq_key] catdi[class_key] = val # some type conversions - catdi['display'] = True if catdi['display'] == '1' else False - catdi['state'] = True if catdi['state'] == '1' else False + catdi['display'] = (catdi['display'] == '1') + catdi['state'] = (catdi['state'] == '1') for key in ['start', 'end', 'reqwithin']: catdi[key] = float(catdi[key]) for key in ['nave', 'event', 'reqevent', 'reqwhen', 'subave']: @@ -1126,22 +1124,19 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, raw : Raw object An instance of Raw. category : string | dict - The category to use. Can be a name (e.g. 'my_category) or - category dict (e.g. eav['my_category'], where eav is an instance + The category to use. Can be a name (e.g. 'my_category') or + category dict (e.g. eav['my_category'], where eav is an instance of ElektaAverager). picks : array-like of int | None (default) Indices of channels to include (if None, all channels are used). reject : True | dict | None Rejection parameters based on peak-to-peak amplitude. Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'. - If reject is None then no rejection is done. Example:: - + If reject is None then no rejection is done. Example: reject = dict(grad=4000e-13, # T / m (gradiometers) mag=4e-12, # T (magnetometers) eeg=40e-6, # V (EEG channels) - eog=250e-6 # V (EOG channels) - ) - + eog=250e-6 # V (EOG channels)) If True, use the rejection limits defined in DACQ. Note that all DACQ rejection criteria are not supported by mne. flat : True | dict | None @@ -1192,21 +1187,19 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, from .epochs import Epochs if isinstance(category, str): category = self[category] - if reject is True: - reject = self.reject - if flat is True: - flat = self.flat + reject = self.reject if reject is True else reject + flat = self.flat if flat is True else flat mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, verbose=False) sfreq = raw.info['sfreq'] - # create array of category reference times (t0), + # create array of category reference times (t0) # and corresponding (fake) event_id for mne.Epochs cat_t = self._mne_events_to_category_t0(category, mne_events, sfreq) catev = np.c_[cat_t, np.zeros(cat_t.shape), np.ones(cat_t.shape)].astype(np.uint32) - id = {category['comment']: 1} + id = {category['comment'] : 1} return Epochs(raw, catev, event_id=id, reject=reject, flat=flat, tmin=category['start'], tmax=category['end'], baseline=baseline, detrend=detrend, picks=picks, From 94a0febba334ce4a5dccb03d0148f56fde56bbb2 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 1 Sep 2016 10:58:13 +0300 Subject: [PATCH 33/85] wip: hide variable lists [ci skip] --- mne/event.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mne/event.py b/mne/event.py index 765bd6e69a8..fc3f519f486 100644 --- a/mne/event.py +++ b/mne/event.py @@ -912,16 +912,16 @@ class ElektaAverager(object): """ # averager related DACQ variable names (without preceding 'ERF') - dacq_vars = ('magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', + _dacq_vars = ('magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', 'nevent', 'stimSource', 'triggerMap', 'update', 'version', 'artefIgnore', 'averUpdate') - event_vars = ('Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', + _event_vars = ('Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', 'OldMask', 'Delay', 'Comment') - cat_vars = ('Comment', 'Display', 'Start', 'State', 'End', 'Event', + _cat_vars = ('Comment', 'Display', 'Start', 'State', 'End', 'Event', 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve') def __init__(self, info): @@ -933,7 +933,7 @@ def __init__(self, info): raise ValueError('Cannot parse version. The file may be from ' 'DACQ <3.4 which is not supported yet') # set instance variables - for var in ElektaAverager.dacq_vars: + for var in ElektaAverager._dacq_vars: val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: val = float(val) @@ -993,7 +993,7 @@ def _events_from_acq_pars(self): for evnum in range(1, self.ncateg + 1): evnum_s = str(evnum).zfill(2) # '01', '02' etc. evdi = dict() - for var in self.event_vars: + for var in self._event_vars: # name of DACQ variable, e.g. 'ERFeventNewBits01' acq_key = 'ERFevent' + var + evnum_s # corresponding dict key, e.g. 'newbits' @@ -1018,7 +1018,7 @@ def _categories_from_acq_pars(self): for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = dict() # read all category variables - for var in self.cat_vars: + for var in self._cat_vars: acq_key = 'ERFcat' + var + catnum class_key = var.lower() val = self.acq_dict[acq_key] From f0d693dc3f7536e9367145ae6d51324af4229aab Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 1 Sep 2016 11:02:54 +0300 Subject: [PATCH 34/85] wip: small naming fix [ci skip] --- mne/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/event.py b/mne/event.py index fc3f519f486..9727b20cae8 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1197,10 +1197,10 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, # create array of category reference times (t0) # and corresponding (fake) event_id for mne.Epochs cat_t = self._mne_events_to_category_t0(category, mne_events, sfreq) - catev = np.c_[cat_t, np.zeros(cat_t.shape), + cat_ev = np.c_[cat_t, np.zeros(cat_t.shape), np.ones(cat_t.shape)].astype(np.uint32) id = {category['comment'] : 1} - return Epochs(raw, catev, event_id=id, reject=reject, flat=flat, + return Epochs(raw, cat_ev, event_id=id, reject=reject, flat=flat, tmin=category['start'], tmax=category['end'], baseline=baseline, detrend=detrend, picks=picks, preload=True, verbose=False) From f42cbf3c7badcb77ca6ddd9f97539f3c7abe4d02 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 1 Sep 2016 14:01:33 +0300 Subject: [PATCH 35/85] try STI101 first, then STI 014 --- mne/event.py | 4 ++-- mne/utils.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mne/event.py b/mne/event.py index 9727b20cae8..bc1f95c6ad7 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1168,8 +1168,8 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, affected by the trigger. If None, the config variables 'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2', etc. are read. If these are not found, it will fall back to - 'STI 014' if present, then fall back to the first channel of type - 'stim', if present. + 'STI101' or 'STI 014' if present, then fall back to the first + channel of type 'stim', if present. mask : int | None The value of the digital mask to apply to the stim channel values. If None (default), no masking is performed. diff --git a/mne/utils.py b/mne/utils.py index bf3e41ab119..d92e3a2ac35 100644 --- a/mne/utils.py +++ b/mne/utils.py @@ -2028,10 +2028,16 @@ def _get_stim_channel(stim_channel, info, raise_error=True): if ch_count > 0: return stim_channel +<<<<<<< HEAD if 'STI101' in info['ch_names']: # newer Elekta systems return ['STI101'] if 'STI 014' in info['ch_names']: # older Elekta systems +======= + if 'STI101' in info['ch_names']: # combination channel for newer systems + return ['STI101'] + if 'STI 014' in info['ch_names']: # for older systems +>>>>>>> try STI101 first, then STI 014 return ['STI 014'] from .io.pick import pick_types From a43754f31e9c4ca5de6a207ce72c937f0039b77d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 1 Sep 2016 14:05:34 +0300 Subject: [PATCH 36/85] Revert "try STI101 first, then STI 014" This reverts commit 58225fdbf4e33b0d32df5b706a9424f9f9d6f14a. --- mne/event.py | 8 ++++---- mne/utils.py | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mne/event.py b/mne/event.py index bc1f95c6ad7..56800e8a438 100644 --- a/mne/event.py +++ b/mne/event.py @@ -506,8 +506,8 @@ def find_events(raw, stim_channel=None, output='onset', affected by the trigger. If None, the config variables 'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2', etc. are read. If these are not found, it will fall back to - 'STI101' or 'STI 014' if present, then fall back to the first channel - of type 'stim', if present. + 'STI 014' if present, then fall back to the first channel of type + 'stim', if present. output : 'onset' | 'offset' | 'step' Whether to report when events start, when events end, or both. consecutive : bool | 'increasing' @@ -1168,8 +1168,8 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, affected by the trigger. If None, the config variables 'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2', etc. are read. If these are not found, it will fall back to - 'STI101' or 'STI 014' if present, then fall back to the first - channel of type 'stim', if present. + 'STI 014' if present, then fall back to the first channel of type + 'stim', if present. mask : int | None The value of the digital mask to apply to the stim channel values. If None (default), no masking is performed. diff --git a/mne/utils.py b/mne/utils.py index d92e3a2ac35..ec22e956441 100644 --- a/mne/utils.py +++ b/mne/utils.py @@ -1993,9 +1993,8 @@ def _get_stim_channel(stim_channel, info, raise_error=True): """Helper to determine the appropriate stim_channel First, 'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2', etc. - are read. If these are not found, it will fall back to 'STI101' or - 'STI 014' if present, then fall back to the first channel of type - 'stim', if present. + are read. If these are not found, it will fall back to 'STI 014' if + present, then fall back to the first channel of type 'stim', if present. Parameters ---------- @@ -2028,6 +2027,7 @@ def _get_stim_channel(stim_channel, info, raise_error=True): if ch_count > 0: return stim_channel +<<<<<<< HEAD <<<<<<< HEAD if 'STI101' in info['ch_names']: # newer Elekta systems return ['STI101'] @@ -2038,6 +2038,9 @@ def _get_stim_channel(stim_channel, info, raise_error=True): return ['STI101'] if 'STI 014' in info['ch_names']: # for older systems >>>>>>> try STI101 first, then STI 014 +======= + if 'STI 014' in info['ch_names']: +>>>>>>> Revert "try STI101 first, then STI 014" return ['STI 014'] from .io.pick import pick_types From 1455fb5973b6409362e11b08251489acfc610e31 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 09:22:47 +0300 Subject: [PATCH 37/85] wip: fix some indents --- mne/event.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/mne/event.py b/mne/event.py index 56800e8a438..de027ec53ff 100644 --- a/mne/event.py +++ b/mne/event.py @@ -912,17 +912,18 @@ class ElektaAverager(object): """ # averager related DACQ variable names (without preceding 'ERF') - _dacq_vars = ('magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', 'megMax', - 'megMin', 'megNoise', 'megSlope', 'megSpike', 'eegMax', 'eegMin', - 'eegNoise', 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', - 'nevent', 'stimSource', 'triggerMap', 'update', 'version', - 'artefIgnore', 'averUpdate') + _dacq_vars = ('magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', + 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', + 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', + 'eogMax', 'ecgMax', 'ncateg', 'nevent', 'stimSource', + 'triggerMap', 'update', 'version', 'artefIgnore', + 'averUpdate') _event_vars = ('Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', - 'OldMask', 'Delay', 'Comment') + 'OldMask', 'Delay', 'Comment') _cat_vars = ('Comment', 'Display', 'Start', 'State', 'End', 'Event', - 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve') + 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve') def __init__(self, info): acq_pars = info['acq_pars'] @@ -1198,8 +1199,8 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, # and corresponding (fake) event_id for mne.Epochs cat_t = self._mne_events_to_category_t0(category, mne_events, sfreq) cat_ev = np.c_[cat_t, np.zeros(cat_t.shape), - np.ones(cat_t.shape)].astype(np.uint32) - id = {category['comment'] : 1} + np.ones(cat_t.shape)].astype(np.uint32) + id = {category['comment']: 1} return Epochs(raw, cat_ev, event_id=id, reject=reject, flat=flat, tmin=category['start'], tmax=category['end'], baseline=baseline, detrend=detrend, picks=picks, From ff70cfb3534f04795e6eaefe6e2d71258afdc9a0 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 09:29:23 +0300 Subject: [PATCH 38/85] wip: rm unneeded property [ci skip] --- examples/io/elekta_averages.py | 2 ++ mne/event.py | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_averages.py index 5b41d67d430..b2eeacb7204 100644 --- a/examples/io/elekta_averages.py +++ b/examples/io/elekta_averages.py @@ -41,3 +41,5 @@ fname_out = 'elekta_evokeds-ave.fif' mne.write_evokeds(fname_out, evokeds) + +""" Modify averaging settings """ diff --git a/mne/event.py b/mne/event.py index de027ec53ff..fc0ba4345e0 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1110,10 +1110,6 @@ def events(self): key=lambda ev: ev['index']) return evs - @property - def event_names(self): - return [event['comment'] for event in self.events] - def get_epochs(self, raw, category, picks=None, reject=None, flat=None, baseline=(None, 0), detrend=None, stim_channel=None, mask=None, uint_cast=None, mask_type=None): From f2db3fedbd1537b6f503af7aeb3c4370dbd011d3 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 09:30:19 +0300 Subject: [PATCH 39/85] wip: rm parent attribute [ci skip] --- mne/event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index fc0ba4345e0..474555f8232 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1033,7 +1033,6 @@ def _categories_from_acq_pars(self): catdi[key] = int(catdi[key]) # some convenient extra (non-DACQ) vars catdi['index'] = int(catnum) # index of category in DACQ list - catdi['parent'] = self # owning class instance cats[catdi['comment']] = catdi return cats From a91aabb6315f278b9710d56b3388dbf546e8cea6 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 09:56:03 +0300 Subject: [PATCH 40/85] wip: get cats/events in use dynamically [ci skip] --- mne/event.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mne/event.py b/mne/event.py index 474555f8232..6f4cede5546 100644 --- a/mne/event.py +++ b/mne/event.py @@ -952,11 +952,6 @@ def __init__(self, info): self._events[cat['event']]['in_use'] = True if cat['reqevent']: self._events[cat['reqevent']]['in_use'] = True - # collect categories and events that are actually in use - self._categories_in_use = ( - {k: v for k, v in self._categories.iteritems() if v['state']}) - self._events_in_use = ( - {k: v for k, v in self._events.iteritems() if v['in_use']}) # make mne rejection dicts based on the averager parameters self.reject = {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, @@ -1109,6 +1104,16 @@ def events(self): key=lambda ev: ev['index']) return evs + @property + def _categories_in_use(self): + return ( + {k: v for k, v in self._categories.iteritems() if v['state']}) + + @property + def _events_in_use(self): + return ( + {k: v for k, v in self._events.iteritems() if v['in_use']}) + def get_epochs(self, raw, category, picks=None, reject=None, flat=None, baseline=(None, 0), detrend=None, stim_channel=None, mask=None, uint_cast=None, mask_type=None): From 14a15669b78b4b8cb0788ecbd473296fdb573f46 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 09:57:57 +0300 Subject: [PATCH 41/85] example: modify averaging parameters --- examples/io/{elekta_averages.py => elekta_average.py} | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) rename examples/io/{elekta_averages.py => elekta_average.py} (82%) diff --git a/examples/io/elekta_averages.py b/examples/io/elekta_average.py similarity index 82% rename from examples/io/elekta_averages.py rename to examples/io/elekta_average.py index b2eeacb7204..e6ed7dca45b 100644 --- a/examples/io/elekta_averages.py +++ b/examples/io/elekta_average.py @@ -42,4 +42,12 @@ fname_out = 'elekta_evokeds-ave.fif' mne.write_evokeds(fname_out, evokeds) -""" Modify averaging settings """ +""" Make a new category using an existing one as a template and extract +corresponding epochs. """ +newcat = eav.categories[0].copy() +newcat['comment'] = 'New category' +newcat['event'] = 1 +newcat['start'] = -.1 +newcat['end'] = .5 + +eps = eav.get_epochs(raw, newcat) From fd0fde8654aebc7098e77519da4cc6bcc8a3ce00 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 11:16:32 +0300 Subject: [PATCH 42/85] enhanced example --- examples/io/elekta_average.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/io/elekta_average.py b/examples/io/elekta_average.py index e6ed7dca45b..93cef11bfd7 100644 --- a/examples/io/elekta_average.py +++ b/examples/io/elekta_average.py @@ -46,8 +46,11 @@ corresponding epochs. """ newcat = eav.categories[0].copy() newcat['comment'] = 'New category' -newcat['event'] = 1 -newcat['start'] = -.1 -newcat['end'] = .5 +newcat['event'] = 1 # reference event +newcat['start'] = -.1 # epoch start rel. to ref. event (in seconds) +newcat['end'] = .5 # epoch end +newcat['reqevent'] = 2 # additional required event; 0 if none +newcat['reqwithin'] = 1.5 # req. event required within 1.5 sec of ref. event +newcat['reqwhen'] = 1 # required before (1) or after (2) ref. event eps = eav.get_epochs(raw, newcat) From 9cfc23276ee149c755f6d4b25e492f116529b3b4 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 13:29:01 +0300 Subject: [PATCH 43/85] add class to rst [ci skip] --- doc/python_reference.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/python_reference.rst b/doc/python_reference.rst index a9109c3c0f7..784607f65b9 100644 --- a/doc/python_reference.rst +++ b/doc/python_reference.rst @@ -535,6 +535,14 @@ Events combine_event_ids equalize_epoch_counts +Classes: + +.. autosummary:: + :toctree: generated/ + :template: class.rst + + ElektaAverager + Sensor Space Data ================= From 5511f6778bf5ffca867e3bd626678781246e823a Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 2 Sep 2016 14:21:32 +0300 Subject: [PATCH 44/85] wip: rm get_epochs method, return params instead --- examples/io/elekta_average.py | 30 +++++++----- mne/event.py | 89 ++++++++++++----------------------- mne/tests/test_event.py | 8 ++-- 3 files changed, 52 insertions(+), 75 deletions(-) diff --git a/examples/io/elekta_average.py b/examples/io/elekta_average.py index 93cef11bfd7..580010183b6 100644 --- a/examples/io/elekta_average.py +++ b/examples/io/elekta_average.py @@ -21,29 +21,33 @@ print(__doc__) +# read raw file and create averager instance raw = mne.io.read_raw_fif(fname_raw_elekta) eav = ElektaAverager(raw.info) -""" Extract epochs corresponding to a category. Copy rejection -limits from DACQ settings. """ -eps = eav.get_epochs(raw, 'Event 1 followed by 2 within 1100 ms', - reject=True, flat=True) +# check DACQ defined averaging categories +print(eav) -""" Read all categories, extract corresponding epochs, average, add -comments from to the DACQ categories and save to new - fiff file. """ +# extract epochs corresponding to a category +(cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, 'Test event 3') +eps = mne.Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax) + +# get epochs corresponding to each category, average and save all averages +# into a new evoked fiff file evokeds = [] for cat in eav.categories: - eps = eav.get_epochs(raw, cat) + (cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, cat) + # copy (supported) rejection parameters from DACQ settings + eps = mne.Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax, + reject=eav.reject, flat=eav.flat) evoked = eps.average() evoked.comment = cat['comment'] evokeds.append(evoked) - fname_out = 'elekta_evokeds-ave.fif' mne.write_evokeds(fname_out, evokeds) -""" Make a new category using an existing one as a template and extract -corresponding epochs. """ +# make a new category using existing one as a template and extract +# corresponding epochs newcat = eav.categories[0].copy() newcat['comment'] = 'New category' newcat['event'] = 1 # reference event @@ -52,5 +56,5 @@ newcat['reqevent'] = 2 # additional required event; 0 if none newcat['reqwithin'] = 1.5 # req. event required within 1.5 sec of ref. event newcat['reqwhen'] = 1 # required before (1) or after (2) ref. event - -eps = eav.get_epochs(raw, newcat) +(cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, newcat) +eps = mne.Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax) diff --git a/mne/event.py b/mne/event.py index 6f4cede5546..d8a88e9d65e 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1106,19 +1106,17 @@ def events(self): @property def _categories_in_use(self): - return ( - {k: v for k, v in self._categories.iteritems() if v['state']}) + return {k: v for k, v in self._categories.iteritems() if v['state']} @property def _events_in_use(self): - return ( - {k: v for k, v in self._events.iteritems() if v['in_use']}) + return {k: v for k, v in self._events.iteritems() if v['in_use']} - def get_epochs(self, raw, category, picks=None, reject=None, flat=None, - baseline=(None, 0), detrend=None, stim_channel=None, - mask=None, uint_cast=None, mask_type=None): - """ Get an mne.Epochs instance corresponding to the given DACQ - (data acquisition) category. + def get_category_t0(self, raw, category, stim_channel=None, mask=None, + uint_cast=None, mask_type=None): + """ Get reference times corresponding to the given DACQ + (data acquisition) category. Output can be used with + the Epochs class to extract corresponding epochs. Parameters ---------- @@ -1128,42 +1126,6 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, The category to use. Can be a name (e.g. 'my_category') or category dict (e.g. eav['my_category'], where eav is an instance of ElektaAverager). - picks : array-like of int | None (default) - Indices of channels to include (if None, all channels are used). - reject : True | dict | None - Rejection parameters based on peak-to-peak amplitude. - Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'. - If reject is None then no rejection is done. Example: - reject = dict(grad=4000e-13, # T / m (gradiometers) - mag=4e-12, # T (magnetometers) - eeg=40e-6, # V (EEG channels) - eog=250e-6 # V (EOG channels)) - If True, use the rejection limits defined in DACQ. - Note that all DACQ rejection criteria are not supported by mne. - flat : True | dict | None - Rejection parameters based on flatness of signal. - Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg', and values - are floats that set the minimum acceptable peak-to-peak amplitude. - If True, use the flatness rejection limits defined in DACQ. - If None, no rejection is done. - baseline : None or tuple of length 2 (default (None, 0)) - The time interval to apply baseline correction. - If None do not apply it. If baseline is (a, b) - the interval is between "a (s)" and "b (s)". - If a is None the beginning of the data is used - and if b is None then b is set to the end of the interval. - If baseline is equal to (None, None) all the time - interval is used. - The baseline (a, b) includes both endpoints, i.e. all - timepoints t such that a <= t <= b. - detrend : int | None - If 0 or 1, the data channels (MEG and EEG) will be detrended when - loaded. 0 is a constant (DC) detrend, 1 is a linear detrend. None - is no detrending. Note that detrending is performed before baseline - correction. If no DC offset is preferred (zeroth order detrending), - either turn off baseline correction, as this may introduce a DC - shift, or set baseline correction to use the entire time interval - (will yield equivalent results but be slower). stim_channel : None | string | list of string Name of the stim channel or all the stim channels affected by the trigger. If None, the config variables @@ -1183,28 +1145,37 @@ def get_epochs(self, raw, category, picks=None, reject=None, flat=None, mask_type: 'and' | 'not_and' The type of operation between the mask and the trigger. Choose 'and' for MNE-C masking behavior. - """ - from .epochs import Epochs + Returns + ------- + cat_t0 : array, shape (n_epochs_out, 3) + List of zero time points (t0) for the epochs matching the + category. Use as the ``events`` parameter to Epochs. + cat_id : dict + Category name and index compatible with cat_events. Should be + passed as the ``event_id`` parameter to Epochs together with + ``cat_t0``. + tmin : float + Epoch starting time relative to t0. Use as the ``tmin`` + parameter to Epochs. + tmax : float + Epoch ending time relative to t0. Can be used as the ``tmax`` + parameter to Epochs. + """ if isinstance(category, str): category = self[category] - reject = self.reject if reject is True else reject - flat = self.flat if flat is True else flat mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, verbose=False) sfreq = raw.info['sfreq'] - # create array of category reference times (t0) - # and corresponding (fake) event_id for mne.Epochs - cat_t = self._mne_events_to_category_t0(category, mne_events, sfreq) - cat_ev = np.c_[cat_t, np.zeros(cat_t.shape), - np.ones(cat_t.shape)].astype(np.uint32) - id = {category['comment']: 1} - return Epochs(raw, cat_ev, event_id=id, reject=reject, flat=flat, - tmin=category['start'], tmax=category['end'], - baseline=baseline, detrend=detrend, picks=picks, - preload=True, verbose=False) + cat_t0_ = self._mne_events_to_category_t0(category, mne_events, sfreq) + # make it compatible with a normal events array + cat_t0 = np.c_[cat_t0_, np.zeros(cat_t0_.shape), + np.ones(cat_t0_.shape)].astype(np.uint32) + cat_id = {category['comment']: 1} + tmin, tmax = category['start'], category['end'] + return (cat_t0, cat_id, tmin, tmax) def _acqpars_dict(acq_pars): diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 420f4363380..4408f19e8d8 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -8,8 +8,8 @@ import warnings from mne import (read_events, write_events, make_fixed_length_events, - find_events, pick_events, find_stim_steps, pick_channels, - read_evokeds, io) + find_events, pick_events, find_stim_steps, io, pick_channels, + read_evokeds, io, Epochs) from mne.io import read_raw_fif from mne.tests.common import assert_naming from mne.utils import _TempDir, run_tests_if_main @@ -425,7 +425,9 @@ def test_elekta_averager(): for cat in eav.categories: # XXX datasets match only when baseline is applied to both, # not sure where relative dc shift comes from - eps = eav.get_epochs(raw, cat, baseline=(-.05, 0)) + (cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, cat) + eps = Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax, + baseline=(-.05, 0)) ev = eps.average() ev_ref = read_evokeds(fname_ave_elekta, cat['comment'], baseline=(-.05, 0), proj=False) From 52136f15e4becea2faf8b00e90730f806190174b Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 7 Sep 2016 10:45:07 +0300 Subject: [PATCH 45/85] wip: wip [ci skip] --- mne/event.py | 83 +++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/mne/event.py b/mne/event.py index d8a88e9d65e..c2d7c85c01f 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1112,20 +1112,22 @@ def _categories_in_use(self): def _events_in_use(self): return {k: v for k, v in self._events.iteritems() if v['in_use']} - def get_category_t0(self, raw, category, stim_channel=None, mask=None, - uint_cast=None, mask_type=None): - """ Get reference times corresponding to the given DACQ - (data acquisition) category. Output can be used with - the Epochs class to extract corresponding epochs. + def get_condition_t0(self, raw, conditions, stim_channel=None, mask=None, + uint_cast=None, mask_type=None): + """ Get reference times corresponding to the given condition + (=averaging category defined in Elekta data acquisition software). + Output can be used with the Epochs class to extract corresponding + epochs. Parameters ---------- raw : Raw object An instance of Raw. - category : string | dict - The category to use. Can be a name (e.g. 'my_category') or - category dict (e.g. eav['my_category'], where eav is an instance - of ElektaAverager). + condition : list + List of conditions, or a condition. Can be names + (e.g. 'Auditory left') or category dicts + (e.g. eav['Auditory left'], where eav is an instance of + ElektaAverager). stim_channel : None | string | list of string Name of the stim channel or all the stim channels affected by the trigger. If None, the config variables @@ -1148,34 +1150,43 @@ def get_category_t0(self, raw, category, stim_channel=None, mask=None, Returns ------- - cat_t0 : array, shape (n_epochs_out, 3) - List of zero time points (t0) for the epochs matching the - category. Use as the ``events`` parameter to Epochs. - cat_id : dict - Category name and index compatible with cat_events. Should be - passed as the ``event_id`` parameter to Epochs together with - ``cat_t0``. - tmin : float - Epoch starting time relative to t0. Use as the ``tmin`` - parameter to Epochs. - tmax : float - Epoch ending time relative to t0. Can be used as the ``tmax`` - parameter to Epochs. + conds_data : list of dicts corresponding to conditions, with keys: + events : array, shape (n_epochs_out, 3) + List of zero time points (t0) for the epochs matching the + condition. Use as the ``events`` parameter to Epochs. Note + that these are not (necessarily) actual trigger events. + event_id : dict + Condition name and index compatible with ``events``. Should be + passed as the ``event_id`` parameter to Epochs. + tmin : float + Epoch starting time relative to t0. Use as the ``tmin`` + parameter to Epochs. + tmax : float + Epoch ending time relative to t0. Can be used as the ``tmax`` + parameter to Epochs. """ - if isinstance(category, str): - category = self[category] - mne_events = find_events(raw, stim_channel=stim_channel, - mask=mask, mask_type=mask_type, output='step', - uint_cast=uint_cast, consecutive=True, - verbose=False) - sfreq = raw.info['sfreq'] - cat_t0_ = self._mne_events_to_category_t0(category, mne_events, sfreq) - # make it compatible with a normal events array - cat_t0 = np.c_[cat_t0_, np.zeros(cat_t0_.shape), - np.ones(cat_t0_.shape)].astype(np.uint32) - cat_id = {category['comment']: 1} - tmin, tmax = category['start'], category['end'] - return (cat_t0, cat_id, tmin, tmax) + if not isinstance(conditions, list): + conditions = list(conditions) + conds_data = list() + for category in conditions: + if isinstance(category, str): + category = self[category] + # TODO: add shortest_event=0 (or 1?) to prevent failures + mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, + mask_type=mask_type, output='step', + uint_cast=uint_cast, consecutive=True, + verbose=False) + sfreq = raw.info['sfreq'] + cat_t0_ = self._mne_events_to_category_t0(category, + mne_events, sfreq) + # make it compatible with a normal events array + cat_t0 = np.c_[cat_t0_, np.zeros(cat_t0_.shape), + np.ones(cat_t0_.shape)].astype(np.uint32) + cat_id = {category['comment']: 1} + tmin, tmax = category['start'], category['end'] + conds_data.append(dict(events=cat_t0, event_id=cat_id, + tmin=tmin, tmax=tmax)) + return conds_data def _acqpars_dict(acq_pars): From 2c16c9d078c38d4397582eb8e30c6366ff226a73 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 7 Sep 2016 14:16:53 +0300 Subject: [PATCH 46/85] wip: return conditions as list of dicts --- examples/io/elekta_average.py | 13 ++++++------- mne/event.py | 27 +++++++++++++-------------- mne/tests/test_event.py | 5 ++--- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/examples/io/elekta_average.py b/examples/io/elekta_average.py index 580010183b6..8f99b410dd7 100644 --- a/examples/io/elekta_average.py +++ b/examples/io/elekta_average.py @@ -29,17 +29,16 @@ print(eav) # extract epochs corresponding to a category -(cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, 'Test event 3') -eps = mne.Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax) +cond = eav.get_condition(raw, 'Test event 3')[0] # always returns a list +eps = mne.Epochs(raw, **cond) # get epochs corresponding to each category, average and save all averages # into a new evoked fiff file evokeds = [] for cat in eav.categories: - (cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, cat) + cond = eav.get_condition(raw, cat)[0] # copy (supported) rejection parameters from DACQ settings - eps = mne.Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax, - reject=eav.reject, flat=eav.flat) + eps = mne.Epochs(raw, reject=eav.reject, flat=eav.flat, **cond) evoked = eps.average() evoked.comment = cat['comment'] evokeds.append(evoked) @@ -56,5 +55,5 @@ newcat['reqevent'] = 2 # additional required event; 0 if none newcat['reqwithin'] = 1.5 # req. event required within 1.5 sec of ref. event newcat['reqwhen'] = 1 # required before (1) or after (2) ref. event -(cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, newcat) -eps = mne.Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax) +cond = eav.get_condition(raw, newcat)[0] +eps = mne.Epochs(raw, **cond) diff --git a/mne/event.py b/mne/event.py index c2d7c85c01f..51e4d7351da 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1112,20 +1112,19 @@ def _categories_in_use(self): def _events_in_use(self): return {k: v for k, v in self._events.iteritems() if v['in_use']} - def get_condition_t0(self, raw, conditions, stim_channel=None, mask=None, - uint_cast=None, mask_type=None): - """ Get reference times corresponding to the given condition - (=averaging category defined in Elekta data acquisition software). - Output can be used with the Epochs class to extract corresponding - epochs. + def get_condition(self, raw, conditions, stim_channel=None, mask=None, + uint_cast=None, mask_type=None): + """ Get data corresponding to the given condition (=averaging category + defined in Elekta data acquisition software). + Output can be used with Epochs to extract corresponding epochs. Parameters ---------- raw : Raw object An instance of Raw. - condition : list - List of conditions, or a condition. Can be names - (e.g. 'Auditory left') or category dicts + condition : list | dict + List of conditions, or a condition. Can be names + (e.g. 'Auditory left') or category dicts (e.g. eav['Auditory left'], where eav is an instance of ElektaAverager). stim_channel : None | string | list of string @@ -1150,11 +1149,11 @@ def get_condition_t0(self, raw, conditions, stim_channel=None, mask=None, Returns ------- - conds_data : list of dicts corresponding to conditions, with keys: + conds_data : list of dict, each with following keys: events : array, shape (n_epochs_out, 3) List of zero time points (t0) for the epochs matching the condition. Use as the ``events`` parameter to Epochs. Note - that these are not (necessarily) actual trigger events. + that these are not (necessarily) actual events. event_id : dict Condition name and index compatible with ``events``. Should be passed as the ``event_id`` parameter to Epochs. @@ -1166,12 +1165,12 @@ def get_condition_t0(self, raw, conditions, stim_channel=None, mask=None, parameter to Epochs. """ if not isinstance(conditions, list): - conditions = list(conditions) + conditions = [conditions] conds_data = list() for category in conditions: if isinstance(category, str): category = self[category] - # TODO: add shortest_event=0 (or 1?) to prevent failures + # TODO: add shortest_event=0 (or 1?) to prevent failures? mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, @@ -1179,7 +1178,7 @@ def get_condition_t0(self, raw, conditions, stim_channel=None, mask=None, sfreq = raw.info['sfreq'] cat_t0_ = self._mne_events_to_category_t0(category, mne_events, sfreq) - # make it compatible with a normal events array + # make it compatible with the usual events array cat_t0 = np.c_[cat_t0_, np.zeros(cat_t0_.shape), np.ones(cat_t0_.shape)].astype(np.uint32) cat_id = {category['comment']: 1} diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 4408f19e8d8..6830875b82b 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -425,9 +425,8 @@ def test_elekta_averager(): for cat in eav.categories: # XXX datasets match only when baseline is applied to both, # not sure where relative dc shift comes from - (cat_ev, cat_id, tmin, tmax) = eav.get_category_t0(raw, cat) - eps = Epochs(raw, cat_ev, cat_id, tmin=tmin, tmax=tmax, - baseline=(-.05, 0)) + cond = eav.get_condition(raw, cat) + eps = Epochs(raw, baseline=(-.05, 0), **cond[0]) ev = eps.average() ev_ref = read_evokeds(fname_ave_elekta, cat['comment'], baseline=(-.05, 0), proj=False) From 4d16864139e5e14068a0872f01edce8a1b9d223e Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 7 Sep 2016 14:26:23 +0300 Subject: [PATCH 47/85] wip: update example comments --- examples/io/elekta_average.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/io/elekta_average.py b/examples/io/elekta_average.py index 8f99b410dd7..ea47b3a7732 100644 --- a/examples/io/elekta_average.py +++ b/examples/io/elekta_average.py @@ -21,19 +21,22 @@ print(__doc__) -# read raw file and create averager instance +############################################################################### +# Read raw file and create averager instance raw = mne.io.read_raw_fif(fname_raw_elekta) eav = ElektaAverager(raw.info) -# check DACQ defined averaging categories +############################################################################### +# Check DACQ defined averaging categories and other info print(eav) -# extract epochs corresponding to a category +############################################################################### +# Extract epochs corresponding to a category cond = eav.get_condition(raw, 'Test event 3')[0] # always returns a list eps = mne.Epochs(raw, **cond) -# get epochs corresponding to each category, average and save all averages -# into a new evoked fiff file +############################################################################### +# Get epochs from all conditions, average, save to an evoked fiff file evokeds = [] for cat in eav.categories: cond = eav.get_condition(raw, cat)[0] @@ -45,8 +48,8 @@ fname_out = 'elekta_evokeds-ave.fif' mne.write_evokeds(fname_out, evokeds) -# make a new category using existing one as a template and extract -# corresponding epochs +############################################################################### +# Make a new category using existing one as a template, extract epochs newcat = eav.categories[0].copy() newcat['comment'] = 'New category' newcat['event'] = 1 # reference event From 79a766f511498cb2fa8ccf43505c05dc2eceebff Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 9 Sep 2016 10:02:34 +0300 Subject: [PATCH 48/85] minor tweak to api, doc fix, comments --- doc/python_reference.rst | 8 +------ examples/io/elekta_average.py | 19 ++++++++------- mne/event.py | 45 +++++++++++++++++++---------------- mne/tests/test_event.py | 3 ++- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/doc/python_reference.rst b/doc/python_reference.rst index 784607f65b9..f5158fa0d6e 100644 --- a/doc/python_reference.rst +++ b/doc/python_reference.rst @@ -29,6 +29,7 @@ Classes io.RawFIF io.RawArray Annotations + ElektaAverager Epochs Evoked SourceSpaces @@ -535,13 +536,6 @@ Events combine_event_ids equalize_epoch_counts -Classes: - -.. autosummary:: - :toctree: generated/ - :template: class.rst - - ElektaAverager Sensor Space Data ================= diff --git a/examples/io/elekta_average.py b/examples/io/elekta_average.py index ea47b3a7732..959821d8f06 100644 --- a/examples/io/elekta_average.py +++ b/examples/io/elekta_average.py @@ -4,7 +4,8 @@ ================================ Get averaging information defined in Elekta Vectorview/TRIUX DACQ (data -acquisition). Extract and average epochs accordingly. +acquisition). Extract and average epochs accordingly. Modify some +averaging parameters and get epochs. """ # Author: Jussi Nurminen (jnu@iki.fi) # @@ -32,17 +33,17 @@ ############################################################################### # Extract epochs corresponding to a category -cond = eav.get_condition(raw, 'Test event 3')[0] # always returns a list -eps = mne.Epochs(raw, **cond) +cond = eav.get_condition(raw, 'Test event 3') +epochs = mne.Epochs(raw, **cond) ############################################################################### # Get epochs from all conditions, average, save to an evoked fiff file evokeds = [] for cat in eav.categories: - cond = eav.get_condition(raw, cat)[0] + cond = eav.get_condition(raw, cat) # copy (supported) rejection parameters from DACQ settings - eps = mne.Epochs(raw, reject=eav.reject, flat=eav.flat, **cond) - evoked = eps.average() + epochs = mne.Epochs(raw, reject=eav.reject, flat=eav.flat, **cond) + evoked = epochs.average() evoked.comment = cat['comment'] evokeds.append(evoked) fname_out = 'elekta_evokeds-ave.fif' @@ -51,12 +52,12 @@ ############################################################################### # Make a new category using existing one as a template, extract epochs newcat = eav.categories[0].copy() -newcat['comment'] = 'New category' +newcat['comment'] = 'My new category' newcat['event'] = 1 # reference event newcat['start'] = -.1 # epoch start rel. to ref. event (in seconds) newcat['end'] = .5 # epoch end newcat['reqevent'] = 2 # additional required event; 0 if none newcat['reqwithin'] = 1.5 # req. event required within 1.5 sec of ref. event newcat['reqwhen'] = 1 # required before (1) or after (2) ref. event -cond = eav.get_condition(raw, newcat)[0] -eps = mne.Epochs(raw, **cond) +cond = eav.get_condition(raw, newcat) +epochs = mne.Epochs(raw, **cond) diff --git a/mne/event.py b/mne/event.py index 51e4d7351da..3a24d3bd942 100644 --- a/mne/event.py +++ b/mne/event.py @@ -956,11 +956,11 @@ def __init__(self, info): self.reject = {'grad': self.megmax, 'mag': self.magmax, 'eeg': self.eegmax, 'eog': self.eogmax, 'ecg': self.ecgmax} - self.reject = {k: float(v) for k, v in self.reject.iteritems() + self.reject = {k: float(v) for k, v in self.reject.items() if float(v) > 0} self.flat = {'grad': self.megmin, 'mag': self.magmin, 'eeg': self.eegmin} - self.flat = {k: float(v) for k, v in self.flat.iteritems() + self.flat = {k: float(v) for k, v in self.flat.items() if float(v) > 0} def __repr__(self): @@ -972,9 +972,9 @@ def __repr__(self): evs_in_use = len(self._events_in_use) s += '(%d in use), ' % evs_in_use s += 'stim source: %s' % self.stimsource - s += '\nCategories:\n' + s += '\nCategories:' for cat in self.categories: - s += '%d: "%s"\n' % (cat['index'], cat['comment']) + s += '\n%d: "%s"' % (cat['index'], cat['comment']) s += '>' return s @@ -1041,7 +1041,7 @@ def _events_mne_to_dacq(self, mne_events): as 2**(1 - 1) + 2**(3 - 1) = 5. """ events_ = mne_events.copy() events_[:, 1:3] = 0 - for n, ev in self._events.iteritems(): + for n, ev in self._events.items(): if ev['in_use']: pre_ok = ( np.bitwise_and(ev['oldmask'], @@ -1106,27 +1106,29 @@ def events(self): @property def _categories_in_use(self): - return {k: v for k, v in self._categories.iteritems() if v['state']} + return {k: v for k, v in self._categories.items() if v['state']} @property def _events_in_use(self): - return {k: v for k, v in self._events.iteritems() if v['in_use']} + return {k: v for k, v in self._events.items() if v['in_use']} - def get_condition(self, raw, conditions, stim_channel=None, mask=None, + def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, uint_cast=None, mask_type=None): - """ Get data corresponding to the given condition (=averaging category - defined in Elekta data acquisition software). - Output can be used with Epochs to extract corresponding epochs. + """ Get parameters corresponding to a category defined in + Elekta DACQ (data acquisition). + Output is designed to be used with the Epochs class to extract the + corresponding epochs. Parameters ---------- raw : Raw object An instance of Raw. - condition : list | dict - List of conditions, or a condition. Can be names - (e.g. 'Auditory left') or category dicts + condition : None | dict | list of dict + Condition or a list of conditions. Conditions can be strings + (DACQ comment field, e.g. 'Auditory left') or category dicts (e.g. eav['Auditory left'], where eav is an instance of - ElektaAverager). + ElektaAverager). If None, get all conditions marked active in + DACQ. stim_channel : None | string | list of string Name of the stim channel or all the stim channels affected by the trigger. If None, the config variables @@ -1155,17 +1157,20 @@ def get_condition(self, raw, conditions, stim_channel=None, mask=None, condition. Use as the ``events`` parameter to Epochs. Note that these are not (necessarily) actual events. event_id : dict - Condition name and index compatible with ``events``. Should be - passed as the ``event_id`` parameter to Epochs. + Name of condition and index compatible with ``events``. + Should be passed as the ``event_id`` parameter to Epochs. tmin : float Epoch starting time relative to t0. Use as the ``tmin`` parameter to Epochs. tmax : float - Epoch ending time relative to t0. Can be used as the ``tmax`` + Epoch ending time relative to t0. Use as the ``tmax`` parameter to Epochs. + """ + if conditions is None: + conditions = self.categories # get all if not isinstance(conditions, list): - conditions = [conditions] + conditions = [conditions] # single cond -> listify conds_data = list() for category in conditions: if isinstance(category, str): @@ -1185,7 +1190,7 @@ def get_condition(self, raw, conditions, stim_channel=None, mask=None, tmin, tmax = category['start'], category['end'] conds_data.append(dict(events=cat_t0, event_id=cat_id, tmin=tmin, tmax=tmax)) - return conds_data + return conds_data[0] if len(conds_data) == 1 else conds_data def _acqpars_dict(acq_pars): diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 6830875b82b..0dc28c4340d 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -418,6 +418,7 @@ def test_define_events(): assert_array_equal(true_lag_nofill, lag_nofill) +@testing.requires_testing_data def test_elekta_averager(): """Test averaging according to Elekta DACQ parameters""" raw = io.read_raw_fif(fname_raw_elekta, preload=True) @@ -426,7 +427,7 @@ def test_elekta_averager(): # XXX datasets match only when baseline is applied to both, # not sure where relative dc shift comes from cond = eav.get_condition(raw, cat) - eps = Epochs(raw, baseline=(-.05, 0), **cond[0]) + eps = Epochs(raw, baseline=(-.05, 0), **cond) ev = eps.average() ev_ref = read_evokeds(fname_ave_elekta, cat['comment'], baseline=(-.05, 0), proj=False) From 32c5a95de6cce5d5151886cb0086c288e96c6751 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 9 Sep 2016 11:02:43 +0300 Subject: [PATCH 49/85] minor doc tweaks --- mne/event.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mne/event.py b/mne/event.py index 3a24d3bd942..e28496709e6 100644 --- a/mne/event.py +++ b/mne/event.py @@ -984,7 +984,7 @@ def __getitem__(self, items): def _events_from_acq_pars(self): """ Collect DACQ defined events into a dict. Events are keyed by number starting from 1 (DACQ index of event). Each event is itself represented - by a dict containing the event definitions. """ + by a dict containing the event parameters. """ events = dict() for evnum in range(1, self.ncateg + 1): evnum_s = str(evnum).zfill(2) # '01', '02' etc. @@ -1008,8 +1008,8 @@ def _events_from_acq_pars(self): def _categories_from_acq_pars(self): """ Collects DACQ averaging categories into a dict. Categories - are keyed by the comment defined in DACQ. Each category is represented - a dict containing the category definitions. """ + are keyed by the comment field in DACQ. Each category is itself + represented a dict containing the category parameters. """ cats = dict() for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = dict() @@ -1100,8 +1100,7 @@ def categories(self): def events(self): """ Return events in DACQ defined order. Only returns events that are in use (referred to by a category). """ - evs = sorted(self._events_in_use.values(), - key=lambda ev: ev['index']) + evs = sorted(self._events_in_use.values(), key=lambda ev: ev['index']) return evs @property @@ -1134,8 +1133,8 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, affected by the trigger. If None, the config variables 'MNE_STIM_CHANNEL', 'MNE_STIM_CHANNEL_1', 'MNE_STIM_CHANNEL_2', etc. are read. If these are not found, it will fall back to - 'STI 014' if present, then fall back to the first channel of type - 'stim', if present. + 'STI101' or 'STI 014' if present, then fall back to the first + channel of type 'stim', if present. mask : int | None The value of the digital mask to apply to the stim channel values. If None (default), no masking is performed. From 1ee72d393a0f057806a29970fdce7c4bbf1d1e76 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 9 Sep 2016 15:15:54 +0300 Subject: [PATCH 50/85] set shortest_event=1 --- mne/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index e28496709e6..d55712ffcad 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1178,7 +1178,7 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, - verbose=False) + verbose=False, shortest_event=1) sfreq = raw.info['sfreq'] cat_t0_ = self._mne_events_to_category_t0(category, mne_events, sfreq) From 5a44e584d6141397a75578749a72d9fbaee10c0d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 12 Sep 2016 10:55:21 +0300 Subject: [PATCH 51/85] Eric comments --- examples/io/elekta_average.py | 14 ++++----- mne/__init__.py | 2 +- mne/event.py | 58 +++++++++++++++++++++-------------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/examples/io/elekta_average.py b/examples/io/elekta_average.py index 959821d8f06..5976fd9559b 100644 --- a/examples/io/elekta_average.py +++ b/examples/io/elekta_average.py @@ -1,7 +1,7 @@ """ -================================ -Getting averaging info from fiff -================================ +====================================== +Getting averaging info from .fif files +====================================== Get averaging information defined in Elekta Vectorview/TRIUX DACQ (data acquisition). Extract and average epochs accordingly. Modify some @@ -15,16 +15,16 @@ import mne import os from mne.datasets import testing -from mne.event import ElektaAverager +from mne import ElektaAverager -elekta_base_dir = os.path.join(testing.data_path(), 'misc') -fname_raw_elekta = os.path.join(elekta_base_dir, 'test_elekta_3ch_raw.fif') +fname_raw = os.path.join(testing.data_path(), 'misc', + 'test_elekta_3ch_raw.fif') print(__doc__) ############################################################################### # Read raw file and create averager instance -raw = mne.io.read_raw_fif(fname_raw_elekta) +raw = mne.io.read_raw_fif(fname_raw) eav = ElektaAverager(raw.info) ############################################################################### diff --git a/mne/__init__.py b/mne/__init__.py index c45db09fab0..db75400e5a7 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -41,7 +41,7 @@ compute_covariance, whiten_evoked, make_ad_hoc_cov) from .event import (read_events, write_events, find_events, merge_events, pick_events, make_fixed_length_events, concatenate_events, - find_stim_steps) + find_stim_steps, ElektaAverager) from .forward import (read_forward_solution, apply_forward, apply_forward_raw, average_forward_solutions, Forward, write_forward_solution, make_forward_solution, diff --git a/mne/event.py b/mne/event.py index d55712ffcad..3695e6ac32c 100644 --- a/mne/event.py +++ b/mne/event.py @@ -889,8 +889,11 @@ def concatenate_events(events, first_samps, last_samps): class ElektaAverager(object): - """Handles events and averaging categories defined in DACQ - (data acquisition) software of Elekta TRIUX/Vectorview systems. + """ Parser for Elekta DACQ averaging categories. + + This class parses events and averaging categories that are defined in the + Elekta TRIUX/VectorView data acquisition software and stored in + ``info['acq_pars']``. Parameters ---------- @@ -982,9 +985,11 @@ def __getitem__(self, items): return self._categories[items] def _events_from_acq_pars(self): - """ Collect DACQ defined events into a dict. Events are keyed by number - starting from 1 (DACQ index of event). Each event is itself represented - by a dict containing the event parameters. """ + """ Collect DACQ events into a dict. + + Events are keyed by number starting from 1 (DACQ index of event). + Each event is itself represented by a dict containing the event + parameters. """ events = dict() for evnum in range(1, self.ncateg + 1): evnum_s = str(evnum).zfill(2) # '01', '02' etc. @@ -1007,9 +1012,10 @@ def _events_from_acq_pars(self): return events def _categories_from_acq_pars(self): - """ Collects DACQ averaging categories into a dict. Categories - are keyed by the comment field in DACQ. Each category is itself - represented a dict containing the category parameters. """ + """ Collect DACQ averaging categories into a dict. + + Categories are keyed by the comment field in DACQ. Each category is + itself represented a dict containing the category parameters. """ cats = dict() for catnum in [str(x).zfill(2) for x in range(1, self.nevent + 1)]: catdi = dict() @@ -1033,6 +1039,7 @@ def _categories_from_acq_pars(self): def _events_mne_to_dacq(self, mne_events): """ Creates list of DACQ events based on mne trigger transitions list. + mne_events is typically given by mne.find_events (use consecutive=True to get all transitions). Output consists of rows in the form [t, 0, event_codes] where t is time in samples and event_codes is all @@ -1054,8 +1061,13 @@ def _events_mne_to_dacq(self, mne_events): return events_ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): - """ Translate mne_events to reference times (t0) for epochs in a given - DACQ averaging category cat. """ + """ Translate mne_events to epoch zero times (t0). + + First mne events (trigger transitions) are converted into DACQ events. + Then the zero times for the epochs are obtained by considering the + reference and conditional (required) events and the delay to stimulus. + """ + cat_ev = cat['event'] cat_reqev = cat['reqevent'] # first convert mne events to dacq event list @@ -1090,16 +1102,19 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): @property def categories(self): - """ Return list of averaging categories in DACQ defined order. Only - returns categories marked active in DACQ. """ + """ Return list of averaging categories in DACQ defined order. + + Only returns categories marked active in DACQ. + """ cats = sorted(self._categories_in_use.values(), key=lambda cat: cat['index']) return cats @property def events(self): - """ Return events in DACQ defined order. Only returns events - that are in use (referred to by a category). """ + """ Return events in DACQ defined order. + + Only returns events that are in use (referred to by a category). """ evs = sorted(self._events_in_use.values(), key=lambda ev: ev['index']) return evs @@ -1112,9 +1127,9 @@ def _events_in_use(self): return {k: v for k, v in self._events.items() if v['in_use']} def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, - uint_cast=None, mask_type=None): - """ Get parameters corresponding to a category defined in - Elekta DACQ (data acquisition). + uint_cast=None, mask_type='and'): + """ Get averaging parameters for a condition (averaging category). + Output is designed to be used with the Epochs class to extract the corresponding epochs. @@ -1150,7 +1165,7 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, Returns ------- - conds_data : list of dict, each with following keys: + conds_data : dict or list of dict, each with following keys: events : array, shape (n_epochs_out, 3) List of zero time points (t0) for the epochs matching the condition. Use as the ``events`` parameter to Epochs. Note @@ -1174,7 +1189,6 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, for category in conditions: if isinstance(category, str): category = self[category] - # TODO: add shortest_event=0 (or 1?) to prevent failures? mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, @@ -1193,14 +1207,12 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, def _acqpars_dict(acq_pars): - """ Makes a dict from a string of acquisition parameters, which is - info['acq_pars'] for Elekta Vectorview/TRIUX systems. """ + """ Parse `` info['acq_pars']`` into a dict. """ return dict(_acqpars_gen(acq_pars)) def _acqpars_gen(acq_pars): - """ Yields key/value pairs from a string containing Elekta Vectorview/TRIUX - acquisition parameters (usually info['acq_pars']) """ + """ Helper function, yields key/value pairs from ``info['acq_pars'])`` """ # DACQ variable names always start with one of these acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] for line in acq_pars.split(): From 676e475d5a996af77f946a212af30f8440af7447 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 12 Sep 2016 12:08:21 +0300 Subject: [PATCH 52/85] docstring fix --- mne/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index 3695e6ac32c..aac6a3ef4fe 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1137,7 +1137,7 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, ---------- raw : Raw object An instance of Raw. - condition : None | dict | list of dict + conditions : None | dict | list of dict Condition or a list of conditions. Conditions can be strings (DACQ comment field, e.g. 'Auditory left') or category dicts (e.g. eav['Auditory left'], where eav is an instance of From d7291fdb36c37f00780f7897fba8be000afa9a64 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 12 Sep 2016 19:48:18 +0300 Subject: [PATCH 53/85] repr fix, rename example --- examples/io/{elekta_average.py => plot_elekta_averager.py} | 0 mne/event.py | 7 ++++--- 2 files changed, 4 insertions(+), 3 deletions(-) rename examples/io/{elekta_average.py => plot_elekta_averager.py} (100%) diff --git a/examples/io/elekta_average.py b/examples/io/plot_elekta_averager.py similarity index 100% rename from examples/io/elekta_average.py rename to examples/io/plot_elekta_averager.py diff --git a/mne/event.py b/mne/event.py index aac6a3ef4fe..3ec4427d550 100644 --- a/mne/event.py +++ b/mne/event.py @@ -975,9 +975,10 @@ def __repr__(self): evs_in_use = len(self._events_in_use) s += '(%d in use), ' % evs_in_use s += 'stim source: %s' % self.stimsource - s += '\nCategories:' - for cat in self.categories: - s += '\n%d: "%s"' % (cat['index'], cat['comment']) + if self.categories: + s += '\nAveraging categories:' + for cat in self.categories: + s += '\n%d: "%s"' % (cat['index'], cat['comment']) s += '>' return s From 0d73eff0210316c67dfd018161959e922d84d9b7 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 12 Sep 2016 22:46:30 +0300 Subject: [PATCH 54/85] since it's full of cats anyway --- mne/event.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mne/event.py b/mne/event.py index 3ec4427d550..d2815a500c6 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1187,21 +1187,20 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, if not isinstance(conditions, list): conditions = [conditions] # single cond -> listify conds_data = list() - for category in conditions: - if isinstance(category, str): - category = self[category] + for cat in conditions: + if isinstance(cat, str): + cat = self[cat] mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, verbose=False, shortest_event=1) sfreq = raw.info['sfreq'] - cat_t0_ = self._mne_events_to_category_t0(category, - mne_events, sfreq) + cat_t0_ = self._mne_events_to_category_t0(cat, mne_events, sfreq) # make it compatible with the usual events array cat_t0 = np.c_[cat_t0_, np.zeros(cat_t0_.shape), np.ones(cat_t0_.shape)].astype(np.uint32) - cat_id = {category['comment']: 1} - tmin, tmax = category['start'], category['end'] + cat_id = {cat['comment']: 1} + tmin, tmax = cat['start'], cat['end'] conds_data.append(dict(events=cat_t0, event_id=cat_id, tmin=tmin, tmax=tmax)) return conds_data[0] if len(conds_data) == 1 else conds_data From e74229bee1a8203d23560985a1a40006ed2474e9 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 13 Sep 2016 11:40:37 +0300 Subject: [PATCH 55/85] improve __getitem__, implement __len__ --- mne/event.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index d2815a500c6..a0faad6887a 100644 --- a/mne/event.py +++ b/mne/event.py @@ -983,7 +983,15 @@ def __repr__(self): return s def __getitem__(self, items): - return self._categories[items] + if not isinstance(items, str): + raise TypeError('Keys must be category names') + if items in self._categories: + return self._categories[items] + else: + raise KeyError('No such category') + + def __len__(self): + return len(self.categories) def _events_from_acq_pars(self): """ Collect DACQ events into a dict. From 4f908650187d10f837e7534ea10a3de478b4912d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 13 Sep 2016 12:59:25 +0300 Subject: [PATCH 56/85] docstring --- mne/event.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mne/event.py b/mne/event.py index a0faad6887a..d1c626d0f17 100644 --- a/mne/event.py +++ b/mne/event.py @@ -892,8 +892,9 @@ class ElektaAverager(object): """ Parser for Elekta DACQ averaging categories. This class parses events and averaging categories that are defined in the - Elekta TRIUX/VectorView data acquisition software and stored in - ``info['acq_pars']``. + Elekta TRIUX/VectorView data acquisition software (DACQ) and stored in + ``info['acq_pars']``. It can be used to reaverage raw data according to + DACQ settings and modify original averaging settings if necessary. Parameters ---------- @@ -912,6 +913,11 @@ class ElektaAverager(object): (e.g. spike, slope) flat : dict Flatness criteria from DACQ that can be used with mne.Epochs. + + Notes + ----- + Any category (also non-active ones) can be accessed by indexing as + ``elekta_averager['category_name']``. """ # averager related DACQ variable names (without preceding 'ERF') From 54ac33baea3f0f95b972fb73c3ca24ca50277321 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 15 Sep 2016 10:28:10 +0300 Subject: [PATCH 57/85] docstring fix --- mne/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index d1c626d0f17..a7cb42ccfd4 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1152,7 +1152,7 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, ---------- raw : Raw object An instance of Raw. - conditions : None | dict | list of dict + conditions : None | str | dict | list of dict Condition or a list of conditions. Conditions can be strings (DACQ comment field, e.g. 'Auditory left') or category dicts (e.g. eav['Auditory left'], where eav is an instance of From 84e4cfe80cb74e7f5f49b6d3ec0101c698f72ca2 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 15 Sep 2016 10:34:15 +0300 Subject: [PATCH 58/85] return category index as event id --- mne/event.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/event.py b/mne/event.py index a7cb42ccfd4..3be5d175b3f 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1212,8 +1212,9 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, cat_t0_ = self._mne_events_to_category_t0(cat, mne_events, sfreq) # make it compatible with the usual events array cat_t0 = np.c_[cat_t0_, np.zeros(cat_t0_.shape), - np.ones(cat_t0_.shape)].astype(np.uint32) - cat_id = {cat['comment']: 1} + cat['index'] * np.ones(cat_t0_.shape) + ].astype(np.uint32) + cat_id = {cat['comment']: cat['index']} tmin, tmax = cat['start'], cat['end'] conds_data.append(dict(events=cat_t0, event_id=cat_id, tmin=tmin, tmax=tmax)) From 271550f59c09faf7be8c6b40085b852ebce90447 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 15 Sep 2016 19:58:53 +0300 Subject: [PATCH 59/85] conditions -> condition, fix rebase mess --- mne/event.py | 12 ++++++------ mne/utils.py | 11 ----------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/mne/event.py b/mne/event.py index 3be5d175b3f..7a557c1562e 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1152,7 +1152,7 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, ---------- raw : Raw object An instance of Raw. - conditions : None | str | dict | list of dict + condition : None | str | dict | list of dict Condition or a list of conditions. Conditions can be strings (DACQ comment field, e.g. 'Auditory left') or category dicts (e.g. eav['Auditory left'], where eav is an instance of @@ -1196,12 +1196,12 @@ def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, parameter to Epochs. """ - if conditions is None: - conditions = self.categories # get all - if not isinstance(conditions, list): - conditions = [conditions] # single cond -> listify + if condition is None: + condition = self.categories # get all + if not isinstance(condition, list): + condition = [condition] # single cond -> listify conds_data = list() - for cat in conditions: + for cat in condition: if isinstance(cat, str): cat = self[cat] mne_events = find_events(raw, stim_channel=stim_channel, mask=mask, diff --git a/mne/utils.py b/mne/utils.py index ec22e956441..eb384e1a829 100644 --- a/mne/utils.py +++ b/mne/utils.py @@ -2027,20 +2027,9 @@ def _get_stim_channel(stim_channel, info, raise_error=True): if ch_count > 0: return stim_channel -<<<<<<< HEAD -<<<<<<< HEAD - if 'STI101' in info['ch_names']: # newer Elekta systems - return ['STI101'] - - if 'STI 014' in info['ch_names']: # older Elekta systems -======= if 'STI101' in info['ch_names']: # combination channel for newer systems return ['STI101'] if 'STI 014' in info['ch_names']: # for older systems ->>>>>>> try STI101 first, then STI 014 -======= - if 'STI 014' in info['ch_names']: ->>>>>>> Revert "try STI101 first, then STI 014" return ['STI 014'] from .io.pick import pick_types From 3a54acdb97aa275734828a65d04b19da1b2ad25e Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 15 Sep 2016 20:00:14 +0300 Subject: [PATCH 60/85] damn --- mne/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index 7a557c1562e..57b6486145f 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1141,7 +1141,7 @@ def _categories_in_use(self): def _events_in_use(self): return {k: v for k, v in self._events.items() if v['in_use']} - def get_condition(self, raw, conditions=None, stim_channel=None, mask=None, + def get_condition(self, raw, condition=None, stim_channel=None, mask=None, uint_cast=None, mask_type='and'): """ Get averaging parameters for a condition (averaging category). From fac700a5e408b29b69b4ad1bb567715b38508f90 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 16 Sep 2016 11:07:52 +0300 Subject: [PATCH 61/85] fix imports --- mne/tests/test_event.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 0dc28c4340d..d3de3a545ae 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -8,8 +8,8 @@ import warnings from mne import (read_events, write_events, make_fixed_length_events, - find_events, pick_events, find_stim_steps, io, pick_channels, - read_evokeds, io, Epochs) + find_events, pick_events, find_stim_steps, pick_channels, + read_evokeds, Epochs) from mne.io import read_raw_fif from mne.tests.common import assert_naming from mne.utils import _TempDir, run_tests_if_main @@ -421,7 +421,7 @@ def test_define_events(): @testing.requires_testing_data def test_elekta_averager(): """Test averaging according to Elekta DACQ parameters""" - raw = io.read_raw_fif(fname_raw_elekta, preload=True) + raw = read_raw_fif(fname_raw_elekta, preload=True) eav = ElektaAverager(raw.info) for cat in eav.categories: # XXX datasets match only when baseline is applied to both, From 275f81a01ee850b10b47d65c6f3066ae4d4dc7a2 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 16 Sep 2016 14:40:34 +0300 Subject: [PATCH 62/85] support for old dacq, delayed lookup --- mne/event.py | 80 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/mne/event.py b/mne/event.py index 57b6486145f..a36c2c94cd7 100644 --- a/mne/event.py +++ b/mne/event.py @@ -921,29 +921,39 @@ class ElektaAverager(object): """ # averager related DACQ variable names (without preceding 'ERF') - _dacq_vars = ('magMax', 'magMin', 'magNoise', 'magSlope', 'magSpike', - 'megMax', 'megMin', 'megNoise', 'megSlope', 'megSpike', - 'eegMax', 'eegMin', 'eegNoise', 'eegSlope', 'eegSpike', - 'eogMax', 'ecgMax', 'ncateg', 'nevent', 'stimSource', - 'triggerMap', 'update', 'version', 'artefIgnore', - 'averUpdate') + # old versions (DACQ < 3.4) + _dacq_vars_compat = ('megMax', 'megMin', 'megNoise', 'megSlope', + 'megSpike', 'eegMax', 'eegMin', 'eegNoise', + 'eegSlope', 'eegSpike', 'eogMax', 'ecgMax', 'ncateg', + 'nevent', 'stimSource', 'triggerMap', 'update', + 'artefIgnore', 'averUpdate') - _event_vars = ('Name', 'Channel', 'NewBits', 'OldBits', 'NewMask', - 'OldMask', 'Delay', 'Comment') + _event_vars_compat = ('Comment', 'Delay') _cat_vars = ('Comment', 'Display', 'Start', 'State', 'End', 'Event', 'Nave', 'ReqEvent', 'ReqWhen', 'ReqWithin', 'SubAve') + # new versions only (DACQ >= 3.4) + _dacq_vars = _dacq_vars_compat + ('magMax', 'magMin', 'magNoise', + 'magSlope', 'magSpike', 'version') + + _event_vars = _event_vars_compat + ('Name', 'Channel', 'NewBits', + 'OldBits', 'NewMask', 'OldMask') + def __init__(self, info): acq_pars = info['acq_pars'] if not acq_pars: raise ValueError('No acquisition parameters') self.acq_dict = _acqpars_dict(acq_pars) - if 'ERFversion' not in self.acq_dict: - raise ValueError('Cannot parse version. The file may be from ' - 'DACQ <3.4 which is not supported yet') + if 'ERFversion' in self.acq_dict: + self.compat = False # DACQ ver >= 3.4 + elif 'ERFncateg' in self.acq_dict: # probably DACQ < 3.4 + self.compat = True + else: + raise ValueError('Cannot parse acquisition parameters') + dacq_vars = self._dacq_vars_compat if self.compat else self._dacq_vars # set instance variables - for var in ElektaAverager._dacq_vars: + for var in dacq_vars: val = self.acq_dict['ERF' + var] if var[:3] in ['mag', 'meg', 'eeg', 'eog', 'ecg']: val = float(val) @@ -962,13 +972,15 @@ def __init__(self, info): if cat['reqevent']: self._events[cat['reqevent']]['in_use'] = True # make mne rejection dicts based on the averager parameters - self.reject = {'grad': self.megmax, 'mag': self.magmax, - 'eeg': self.eegmax, 'eog': self.eogmax, - 'ecg': self.ecgmax} + self.reject = {'grad': self.megmax, 'eeg': self.eegmax, + 'eog': self.eogmax, 'ecg': self.ecgmax} + if not self.compat: + self.reject['mag'] = self.magmax self.reject = {k: float(v) for k, v in self.reject.items() if float(v) > 0} - self.flat = {'grad': self.megmin, 'mag': self.magmin, - 'eeg': self.eegmin} + self.flat = {'grad': self.megmin, 'eeg': self.eegmin} + if not self.compat: + self.flat['mag'] = self.magmin self.flat = {k: float(v) for k, v in self.flat.items() if float(v) > 0} @@ -1005,11 +1017,17 @@ def _events_from_acq_pars(self): Events are keyed by number starting from 1 (DACQ index of event). Each event is itself represented by a dict containing the event parameters. """ + # lookup table for event number -> bits in old DACQ versions + _compat_event_lookup = {1: 1, 2: 2, 3: 4, 4: 8, 5: 16, 6: 32, 7: 3, + 8: 5, 9: 6, 10: 7, 11: 9, 12: 10, 13: 11, + 14: 12, 15: 13, 16: 14, 17: 15} events = dict() - for evnum in range(1, self.ncateg + 1): + for evnum in range(1, self.nevent + 1): evnum_s = str(evnum).zfill(2) # '01', '02' etc. evdi = dict() - for var in self._event_vars: + event_vars = (self._event_vars_compat if self.compat + else self._event_vars) + for var in event_vars: # name of DACQ variable, e.g. 'ERFeventNewBits01' acq_key = 'ERFevent' + var + evnum_s # corresponding dict key, e.g. 'newbits' @@ -1023,6 +1041,12 @@ def _events_from_acq_pars(self): evdi[dict_key] = val evdi['in_use'] = False # __init__() will set this evdi['index'] = evnum + if self.compat: + evdi['name'] = str(evnum) + evdi['oldmask'] = 63 + evdi['newmask'] = 63 + evdi['oldbits'] = 0 + evdi['newbits'] = _compat_event_lookup[evnum] events[evnum] = evdi return events @@ -1069,7 +1093,7 @@ def _events_mne_to_dacq(self, mne_events): np.bitwise_and(ev['oldmask'], mne_events[:, 1]) == ev['oldbits']) post_ok = ( - np.bitwise_and(ev['newbits'], + np.bitwise_and(ev['newmask'], mne_events[:, 2]) == ev['newbits']) ok_ind = np.where(pre_ok & post_ok) events_[ok_ind, 2] |= 1 << (n - 1) @@ -1142,7 +1166,7 @@ def _events_in_use(self): return {k: v for k, v in self._events.items() if v['in_use']} def get_condition(self, raw, condition=None, stim_channel=None, mask=None, - uint_cast=None, mask_type='and'): + uint_cast=None, mask_type='and', delayed_lookup=True): """ Get averaging parameters for a condition (averaging category). Output is designed to be used with the Epochs class to extract the @@ -1177,6 +1201,16 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, mask_type: 'and' | 'not_and' The type of operation between the mask and the trigger. Choose 'and' for MNE-C masking behavior. + delayed_lookup: bool + If True, use the 'delayed lookup' procedure implemented in Elekta + software. When a trigger transition happens, the lookup of + the new trigger value will not happen immediately at the following + sample, but with a delay of one sample. This allows a slight + asynchrony between trigger onsets, when they are intended to be + synchronous. If you have accurate hardware and want to detect + transitions with a resolution of one sample resolution, use + delayed_lookup=False. + Returns ------- @@ -1208,6 +1242,10 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, mask_type=mask_type, output='step', uint_cast=uint_cast, consecutive=True, verbose=False, shortest_event=1) + if delayed_lookup: + ind = np.where(np.diff(mne_events[:, 0]) == 1)[0] + mne_events[ind, 2] = mne_events[ind+1, 2] + mne_events = np.delete(mne_events, ind+1, axis=0) sfreq = raw.info['sfreq'] cat_t0_ = self._mne_events_to_category_t0(cat, mne_events, sfreq) # make it compatible with the usual events array From cbd4971ca26a7fce602251fe38727c47082c84d5 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 16 Sep 2016 14:52:13 +0300 Subject: [PATCH 63/85] TODO --- mne/event.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/event.py b/mne/event.py index a36c2c94cd7..079691d7849 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1244,6 +1244,7 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, verbose=False, shortest_event=1) if delayed_lookup: ind = np.where(np.diff(mne_events[:, 0]) == 1)[0] + # TODO: warn about several subsequent 1-sample transitions? mne_events[ind, 2] = mne_events[ind+1, 2] mne_events = np.delete(mne_events, ind+1, axis=0) sfreq = raw.info['sfreq'] From 804f2cadefcf1d43d1caecb53362373688962e5f Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Fri, 16 Sep 2016 15:44:28 +0300 Subject: [PATCH 64/85] exception for messy trigger data --- examples/io/plot_elekta_averager.py | 8 +++++++- mne/event.py | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/io/plot_elekta_averager.py b/examples/io/plot_elekta_averager.py index 5976fd9559b..95f060fe4aa 100644 --- a/examples/io/plot_elekta_averager.py +++ b/examples/io/plot_elekta_averager.py @@ -14,12 +14,18 @@ import mne import os -from mne.datasets import testing +from mne.datasets import testing, somato from mne import ElektaAverager fname_raw = os.path.join(testing.data_path(), 'misc', 'test_elekta_3ch_raw.fif') +#fname_raw = os.path.join(somato.data_path(), 'MEG', 'somato', +# 'sef_raw_sss.fif') + +# fname_raw = '/Users/hus20664877/Dropbox/jn_multimodal01_raw.fif' + + print(__doc__) ############################################################################### diff --git a/mne/event.py b/mne/event.py index 079691d7849..824f177318d 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1244,9 +1244,15 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, verbose=False, shortest_event=1) if delayed_lookup: ind = np.where(np.diff(mne_events[:, 0]) == 1)[0] - # TODO: warn about several subsequent 1-sample transitions? - mne_events[ind, 2] = mne_events[ind+1, 2] - mne_events = np.delete(mne_events, ind+1, axis=0) + if 1 in np.diff(ind): + raise ValueError('There are several subsequent ' + 'transitions on the trigger channel. ' + 'This may not work well with ' + 'delayed_lookup=True. You may want to ' + 'check your trigger data and possibly ' + 'set delayed_lookup=False.') + mne_events[ind, 2] = mne_events[ind + 1, 2] + mne_events = np.delete(mne_events, ind + 1, axis=0) sfreq = raw.info['sfreq'] cat_t0_ = self._mne_events_to_category_t0(cat, mne_events, sfreq) # make it compatible with the usual events array From b3e62223fd89a2ef1288f57d33eb6d5ee4860831 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Sun, 18 Sep 2016 18:47:56 +0300 Subject: [PATCH 65/85] small comment fixes [ci skip] --- mne/event.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mne/event.py b/mne/event.py index 824f177318d..5f3ac4411f5 100644 --- a/mne/event.py +++ b/mne/event.py @@ -991,8 +991,7 @@ def __repr__(self): s += '(%d in use), ' % cats_in_use s += 'events: %d ' % self.nevent evs_in_use = len(self._events_in_use) - s += '(%d in use), ' % evs_in_use - s += 'stim source: %s' % self.stimsource + s += '(%d in use)' % evs_in_use if self.categories: s += '\nAveraging categories:' for cat in self.categories: @@ -1017,7 +1016,7 @@ def _events_from_acq_pars(self): Events are keyed by number starting from 1 (DACQ index of event). Each event is itself represented by a dict containing the event parameters. """ - # lookup table for event number -> bits in old DACQ versions + # lookup table for event number -> bits for old DACQ versions _compat_event_lookup = {1: 1, 2: 2, 3: 4, 4: 8, 5: 16, 6: 32, 7: 3, 8: 5, 9: 6, 10: 7, 11: 9, 12: 10, 13: 11, 14: 12, 15: 13, 16: 14, 17: 15} @@ -1203,12 +1202,12 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, Choose 'and' for MNE-C masking behavior. delayed_lookup: bool If True, use the 'delayed lookup' procedure implemented in Elekta - software. When a trigger transition happens, the lookup of + software. When a trigger transition occurs, the lookup of the new trigger value will not happen immediately at the following - sample, but with a delay of one sample. This allows a slight + sample, but with a 1-sample delay. This allows a slight asynchrony between trigger onsets, when they are intended to be synchronous. If you have accurate hardware and want to detect - transitions with a resolution of one sample resolution, use + transitions with a resolution of one sample, use delayed_lookup=False. @@ -1247,9 +1246,9 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, if 1 in np.diff(ind): raise ValueError('There are several subsequent ' 'transitions on the trigger channel. ' - 'This may not work well with ' + 'This will not work well with ' 'delayed_lookup=True. You may want to ' - 'check your trigger data and possibly ' + 'check your trigger data and ' 'set delayed_lookup=False.') mne_events[ind, 2] = mne_events[ind + 1, 2] mne_events = np.delete(mne_events, ind + 1, axis=0) From c5ce3400d2e1e24a492c8a01397dba525887f995 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Sun, 18 Sep 2016 18:52:09 +0300 Subject: [PATCH 66/85] ElektaAverager -> AcqParserFIF [ci skip] --- doc/python_reference.rst | 2 +- examples/io/plot_elekta_averager.py | 4 ++-- mne/__init__.py | 2 +- mne/event.py | 6 +++--- mne/tests/test_event.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/python_reference.rst b/doc/python_reference.rst index f5158fa0d6e..2b7292f36e3 100644 --- a/doc/python_reference.rst +++ b/doc/python_reference.rst @@ -29,7 +29,7 @@ Classes io.RawFIF io.RawArray Annotations - ElektaAverager + AcqParserFIF Epochs Evoked SourceSpaces diff --git a/examples/io/plot_elekta_averager.py b/examples/io/plot_elekta_averager.py index 95f060fe4aa..ea8b035dd9a 100644 --- a/examples/io/plot_elekta_averager.py +++ b/examples/io/plot_elekta_averager.py @@ -15,7 +15,7 @@ import mne import os from mne.datasets import testing, somato -from mne import ElektaAverager +from mne import AcqParserFIF fname_raw = os.path.join(testing.data_path(), 'misc', 'test_elekta_3ch_raw.fif') @@ -31,7 +31,7 @@ ############################################################################### # Read raw file and create averager instance raw = mne.io.read_raw_fif(fname_raw) -eav = ElektaAverager(raw.info) +eav = AcqParserFIF(raw.info) ############################################################################### # Check DACQ defined averaging categories and other info diff --git a/mne/__init__.py b/mne/__init__.py index db75400e5a7..73274cd05b9 100644 --- a/mne/__init__.py +++ b/mne/__init__.py @@ -41,7 +41,7 @@ compute_covariance, whiten_evoked, make_ad_hoc_cov) from .event import (read_events, write_events, find_events, merge_events, pick_events, make_fixed_length_events, concatenate_events, - find_stim_steps, ElektaAverager) + find_stim_steps, AcqParserFIF) from .forward import (read_forward_solution, apply_forward, apply_forward_raw, average_forward_solutions, Forward, write_forward_solution, make_forward_solution, diff --git a/mne/event.py b/mne/event.py index 5f3ac4411f5..32b4935f5d0 100644 --- a/mne/event.py +++ b/mne/event.py @@ -888,7 +888,7 @@ def concatenate_events(events, first_samps, last_samps): return events_out -class ElektaAverager(object): +class AcqParserFIF(object): """ Parser for Elekta DACQ averaging categories. This class parses events and averaging categories that are defined in the @@ -985,7 +985,7 @@ def __init__(self, info): if float(v) > 0} def __repr__(self): - s = ' Date: Sun, 18 Sep 2016 19:00:24 +0300 Subject: [PATCH 67/85] helper methods inside class [ci skip] --- mne/event.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/mne/event.py b/mne/event.py index 32b4935f5d0..ddfb1fa0bda 100644 --- a/mne/event.py +++ b/mne/event.py @@ -920,6 +920,9 @@ class AcqParserFIF(object): ``elekta_averager['category_name']``. """ + # DACQ variables always start with one of these + _acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] + # averager related DACQ variable names (without preceding 'ERF') # old versions (DACQ < 3.4) _dacq_vars_compat = ('megMax', 'megMin', 'megNoise', 'megSlope', @@ -944,7 +947,7 @@ def __init__(self, info): acq_pars = info['acq_pars'] if not acq_pars: raise ValueError('No acquisition parameters') - self.acq_dict = _acqpars_dict(acq_pars) + self.acq_dict = self._acqpars_dict(acq_pars) if 'ERFversion' in self.acq_dict: self.compat = False # DACQ ver >= 3.4 elif 'ERFncateg' in self.acq_dict: # probably DACQ < 3.4 @@ -1049,6 +1052,22 @@ def _events_from_acq_pars(self): events[evnum] = evdi return events + def _acqpars_dict(self, acq_pars): + """ Parse `` info['acq_pars']`` into a dict. """ + return dict(self._acqpars_gen(acq_pars)) + + def _acqpars_gen(self, acq_pars): + """ Yields key/value pairs from ``info['acq_pars'])`` """ + # DACQ variable names always start with one of these + for line in acq_pars.split(): + if any([line.startswith(x) for x in self._acq_var_magic]): + key = line + val = '' + else: + # DACQ splits items with spaces into multiple lines + val += ' ' + line if val else line + yield key, val + def _categories_from_acq_pars(self): """ Collect DACQ averaging categories into a dict. @@ -1265,20 +1284,3 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, return conds_data[0] if len(conds_data) == 1 else conds_data -def _acqpars_dict(acq_pars): - """ Parse `` info['acq_pars']`` into a dict. """ - return dict(_acqpars_gen(acq_pars)) - - -def _acqpars_gen(acq_pars): - """ Helper function, yields key/value pairs from ``info['acq_pars'])`` """ - # DACQ variable names always start with one of these - acq_var_magic = ['ERF', 'DEF', 'ACQ', 'TCP'] - for line in acq_pars.split(): - if any([line.startswith(x) for x in acq_var_magic]): - key = line - val = '' - else: - # DACQ splits items with spaces into multiple lines - val += ' ' + line if val else line - yield key, val From 49c81752d5ddede741d47673117c70174fe55cb6 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Sun, 18 Sep 2016 19:08:49 +0300 Subject: [PATCH 68/85] doc fixes, fix broken example --- examples/io/plot_elekta_averager.py | 7 +------ mne/event.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/examples/io/plot_elekta_averager.py b/examples/io/plot_elekta_averager.py index ea8b035dd9a..f592d9c32a7 100644 --- a/examples/io/plot_elekta_averager.py +++ b/examples/io/plot_elekta_averager.py @@ -14,17 +14,12 @@ import mne import os -from mne.datasets import testing, somato +from mne.datasets import testing from mne import AcqParserFIF fname_raw = os.path.join(testing.data_path(), 'misc', 'test_elekta_3ch_raw.fif') -#fname_raw = os.path.join(somato.data_path(), 'MEG', 'somato', -# 'sef_raw_sss.fif') - -# fname_raw = '/Users/hus20664877/Dropbox/jn_multimodal01_raw.fif' - print(__doc__) diff --git a/mne/event.py b/mne/event.py index ddfb1fa0bda..d4fe1c74636 100644 --- a/mne/event.py +++ b/mne/event.py @@ -889,12 +889,13 @@ def concatenate_events(events, first_samps, last_samps): class AcqParserFIF(object): - """ Parser for Elekta DACQ averaging categories. + """ Parser for Elekta DACQ settings. - This class parses events and averaging categories that are defined in the - Elekta TRIUX/VectorView data acquisition software (DACQ) and stored in - ``info['acq_pars']``. It can be used to reaverage raw data according to - DACQ settings and modify original averaging settings if necessary. + This class parses parameters (e.g. events and averaging categories) that + are defined in the Elekta TRIUX/VectorView data acquisition software (DACQ) + and stored in ``info['acq_pars']``. It can be used to reaverage raw data + according to DACQ settings and modify original averaging settings if + necessary. Parameters ---------- @@ -913,11 +914,13 @@ class AcqParserFIF(object): (e.g. spike, slope) flat : dict Flatness criteria from DACQ that can be used with mne.Epochs. + acq_dict : dict + All DACQ parameters. Notes ----- - Any category (also non-active ones) can be accessed by indexing as - ``elekta_averager['category_name']``. + Any averaging category (also non-active ones) can be accessed by indexing + as ``acqparserfif['category_name']``. """ # DACQ variables always start with one of these @@ -1057,7 +1060,7 @@ def _acqpars_dict(self, acq_pars): return dict(self._acqpars_gen(acq_pars)) def _acqpars_gen(self, acq_pars): - """ Yields key/value pairs from ``info['acq_pars'])`` """ + """ Yields key/value pairs from ``info['acq_pars'])``. """ # DACQ variable names always start with one of these for line in acq_pars.split(): if any([line.startswith(x) for x in self._acq_var_magic]): @@ -1283,4 +1286,3 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, tmin=tmin, tmax=tmax)) return conds_data[0] if len(conds_data) == 1 else conds_data - From 99b238bc18030e59673810dac725216983b3058c Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Sun, 18 Sep 2016 20:03:06 +0300 Subject: [PATCH 69/85] include multimodal dataset --- examples/io/plot_elekta_averager.py | 39 ++++++++++++++------------- mne/datasets/__init__.py | 1 + mne/datasets/multimodal/__init__.py | 4 +++ mne/datasets/multimodal/multimodal.py | 30 +++++++++++++++++++++ mne/datasets/utils.py | 7 ++++- mne/utils.py | 1 + 6 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 mne/datasets/multimodal/__init__.py create mode 100644 mne/datasets/multimodal/multimodal.py diff --git a/examples/io/plot_elekta_averager.py b/examples/io/plot_elekta_averager.py index f592d9c32a7..5e3abd7bc04 100644 --- a/examples/io/plot_elekta_averager.py +++ b/examples/io/plot_elekta_averager.py @@ -14,11 +14,10 @@ import mne import os -from mne.datasets import testing +from mne.datasets import multimodal from mne import AcqParserFIF -fname_raw = os.path.join(testing.data_path(), 'misc', - 'test_elekta_3ch_raw.fif') +fname_raw = os.path.join(multimodal.data_path(), 'multimodal_raw.fif') print(__doc__) @@ -26,39 +25,41 @@ ############################################################################### # Read raw file and create averager instance raw = mne.io.read_raw_fif(fname_raw) -eav = AcqParserFIF(raw.info) +ap = AcqParserFIF(raw.info) ############################################################################### # Check DACQ defined averaging categories and other info -print(eav) +print(ap) ############################################################################### # Extract epochs corresponding to a category -cond = eav.get_condition(raw, 'Test event 3') +cond = ap.get_condition(raw, 'Auditory right') epochs = mne.Epochs(raw, **cond) +epochs.average().plot_topo() ############################################################################### # Get epochs from all conditions, average, save to an evoked fiff file evokeds = [] -for cat in eav.categories: - cond = eav.get_condition(raw, cat) +for cat in ap.categories: + cond = ap.get_condition(raw, cat) # copy (supported) rejection parameters from DACQ settings - epochs = mne.Epochs(raw, reject=eav.reject, flat=eav.flat, **cond) + epochs = mne.Epochs(raw, reject=ap.reject, flat=ap.flat, **cond) evoked = epochs.average() evoked.comment = cat['comment'] evokeds.append(evoked) -fname_out = 'elekta_evokeds-ave.fif' -mne.write_evokeds(fname_out, evokeds) +# fname_out = 'elekta_evokeds-ave.fif' +# mne.write_evokeds(fname_out, evokeds) ############################################################################### # Make a new category using existing one as a template, extract epochs -newcat = eav.categories[0].copy() -newcat['comment'] = 'My new category' -newcat['event'] = 1 # reference event -newcat['start'] = -.1 # epoch start rel. to ref. event (in seconds) -newcat['end'] = .5 # epoch end -newcat['reqevent'] = 2 # additional required event; 0 if none -newcat['reqwithin'] = 1.5 # req. event required within 1.5 sec of ref. event +newcat = ap.categories[0].copy() +newcat['comment'] = 'SEF right, longer' +newcat['event'] = 5 # reference event +newcat['start'] = -.5 # epoch start rel. to ref. event (in seconds) +newcat['end'] = 1 # epoch end +newcat['reqevent'] = 0 # additional required event; 0 if none +newcat['reqwithin'] = 0.0 # req. event required within 1.5 sec of ref. event newcat['reqwhen'] = 1 # required before (1) or after (2) ref. event -cond = eav.get_condition(raw, newcat) +cond = ap.get_condition(raw, newcat) epochs = mne.Epochs(raw, **cond) +epochs.average().plot() diff --git a/mne/datasets/__init__.py b/mne/datasets/__init__.py index bc86467eaed..b97da341378 100644 --- a/mne/datasets/__init__.py +++ b/mne/datasets/__init__.py @@ -7,6 +7,7 @@ from . import misc from . import sample from . import somato +from . import multimodal from . import spm_face from . import testing from . import _fake diff --git a/mne/datasets/multimodal/__init__.py b/mne/datasets/multimodal/__init__.py new file mode 100644 index 00000000000..947071e8121 --- /dev/null +++ b/mne/datasets/multimodal/__init__.py @@ -0,0 +1,4 @@ +"""Multimodal dataset +""" + +from .multimodal import data_path, has_multimodal_data, get_version diff --git a/mne/datasets/multimodal/multimodal.py b/mne/datasets/multimodal/multimodal.py new file mode 100644 index 00000000000..d8eb31aa808 --- /dev/null +++ b/mne/datasets/multimodal/multimodal.py @@ -0,0 +1,30 @@ +# Authors: Alexandre Gramfort +# Martin Luessi +# Eric Larson +# License: BSD Style. + +from functools import partial + +from ...utils import verbose +from ..utils import (has_dataset, _data_path, _data_path_doc, + _get_version, _version_doc) + + +has_multimodal_data = partial(has_dataset, name='multimodal') + + +@verbose +def data_path(path=None, force_update=False, update_path=True, download=True, + verbose=None): + return _data_path(path=path, force_update=force_update, + update_path=update_path, name='multimodal', + download=download) + +data_path.__doc__ = _data_path_doc.format(name='multimodal', + conf='MNE_DATASETS_MULTIMODAL_PATH') + + +def get_version(): + return _get_version('multimodal') + +get_version.__doc__ = _version_doc.format(name='multimodal') diff --git a/mne/datasets/utils.py b/mne/datasets/utils.py index 5763b39db9a..1916d6cafa2 100644 --- a/mne/datasets/utils.py +++ b/mne/datasets/utils.py @@ -159,6 +159,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, 'somato': 'MNE_DATASETS_SOMATO_PATH', 'brainstorm': 'MNE_DATASETS_BRAINSTORM_PATH', 'testing': 'MNE_DATASETS_TESTING_PATH', + 'multimodal': 'MNE_DATASETS_MULTIMODAL_PATH', }[name] path = _get_path(path, key, name) @@ -175,6 +176,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, somato='MNE-somato-data.tar.gz', spm='MNE-spm-face.tar.gz', testing='mne-testing-data-%s.tar.gz' % releases['testing'], + multimodal='MNE-multimodal-data.tar.gz', fake='foo.tgz', ) if archive_name is not None: @@ -185,6 +187,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, misc='MNE-misc-data', sample='MNE-sample-data', somato='MNE-somato-data', + multimodal='MNE-multimodal-data', spm='MNE-spm-face', testing='MNE-testing-data', ) @@ -200,6 +203,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, spm='https://mne-tools.s3.amazonaws.com/datasets/%s', testing='https://codeload.github.com/mne-tools/mne-testing-data/' 'tar.gz/%s' % releases['testing'], + multimodal='https://ndownloader.figshare.com/files/5999598', ) hashes = dict( brainstorm=None, @@ -208,7 +212,8 @@ def _data_path(path=None, force_update=False, update_path=True, download=True, sample='1d5da3a809fded1ef5734444ab5bf857', somato='f3e3a8441477bb5bacae1d0c6e0964fb', spm='f61041e3f3f2ba0def8a2ca71592cc41', - testing='217aed43e361c86b622dc0363ae3cef4' + testing='217aed43e361c86b622dc0363ae3cef4', + multimodal='26ec847ae9ab80f58f204d09e2c08367', ) folder_origs = dict( # not listed means None misc='mne-misc-data-%s' % releases['misc'], diff --git a/mne/utils.py b/mne/utils.py index eb384e1a829..16b57c604b9 100644 --- a/mne/utils.py +++ b/mne/utils.py @@ -1464,6 +1464,7 @@ def set_memmap_min_size(memmap_min_size): 'MNE_DATASETS_MISC_PATH', 'MNE_DATASETS_SAMPLE_PATH', 'MNE_DATASETS_SOMATO_PATH', + 'MNE_DATASETS_MULTIMODAL_PATH', 'MNE_DATASETS_SPM_FACE_DATASETS_TESTS', 'MNE_DATASETS_SPM_FACE_PATH', 'MNE_DATASETS_TESTING_PATH', From c6c42a96aa3d5eef32c71d1279521f1fd68ebf36 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Sun, 18 Sep 2016 20:03:55 +0300 Subject: [PATCH 70/85] rename --- examples/io/{plot_elekta_averager.py => plot_elekta_epochs.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/io/{plot_elekta_averager.py => plot_elekta_epochs.py} (100%) diff --git a/examples/io/plot_elekta_averager.py b/examples/io/plot_elekta_epochs.py similarity index 100% rename from examples/io/plot_elekta_averager.py rename to examples/io/plot_elekta_epochs.py From 2a9168eb72a978986e36514f2ac43d0dbf307f7d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 19 Sep 2016 08:46:32 +0300 Subject: [PATCH 71/85] add multimodal to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a60cfd0e536..9f61e5dfaeb 100755 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ 'mne.datasets.brainstorm', 'mne.datasets.testing', 'mne.datasets.tests', + 'mne.datasets.multimodal', 'mne.externals', 'mne.externals.h5io', 'mne.externals.tempita', From 29a6b604f40a4e9ebaa35bb593e5025ff81a5af4 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Mon, 19 Sep 2016 09:57:10 +0300 Subject: [PATCH 72/85] rm empty line --- mne/event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/event.py b/mne/event.py index d4fe1c74636..4e94cb73b83 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1285,4 +1285,3 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, conds_data.append(dict(events=cat_t0, event_id=cat_id, tmin=tmin, tmax=tmax)) return conds_data[0] if len(conds_data) == 1 else conds_data - From 456bb5fad6ed832df8769f70472664a3dd69bfbb Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 20 Sep 2016 11:00:44 +0300 Subject: [PATCH 73/85] fine tune example --- examples/io/plot_elekta_epochs.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/io/plot_elekta_epochs.py b/examples/io/plot_elekta_epochs.py index 5e3abd7bc04..5bf6442208d 100644 --- a/examples/io/plot_elekta_epochs.py +++ b/examples/io/plot_elekta_epochs.py @@ -38,7 +38,7 @@ epochs.average().plot_topo() ############################################################################### -# Get epochs from all conditions, average, save to an evoked fiff file +# Get epochs from all conditions, average evokeds = [] for cat in ap.categories: cond = ap.get_condition(raw, cat) @@ -47,19 +47,21 @@ evoked = epochs.average() evoked.comment = cat['comment'] evokeds.append(evoked) -# fname_out = 'elekta_evokeds-ave.fif' +# save to an evoked fiff file +# fname_out = 'multimodal-ave.fif' # mne.write_evokeds(fname_out, evokeds) ############################################################################### # Make a new category using existing one as a template, extract epochs newcat = ap.categories[0].copy() -newcat['comment'] = 'SEF right, longer' -newcat['event'] = 5 # reference event -newcat['start'] = -.5 # epoch start rel. to ref. event (in seconds) -newcat['end'] = 1 # epoch end +newcat['comment'] = 'Visual lower left, longer epochs' +newcat['event'] = 3 # reference event +newcat['start'] = -.2 # epoch start rel. to ref. event (in seconds) +newcat['end'] = .7 # epoch end newcat['reqevent'] = 0 # additional required event; 0 if none -newcat['reqwithin'] = 0.0 # req. event required within 1.5 sec of ref. event -newcat['reqwhen'] = 1 # required before (1) or after (2) ref. event +newcat['reqwithin'] = .5 # ...required within .5 sec (before or after) +newcat['reqwhen'] = 2 # ...required before (1) or after (2) ref. event + cond = ap.get_condition(raw, newcat) -epochs = mne.Epochs(raw, **cond) +epochs = mne.Epochs(raw, reject=ap.reject, flat=ap.flat, **cond) epochs.average().plot() From 2a87e6b895ed8c6ace106a23c37af35e739cc896 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 20 Sep 2016 11:17:23 +0300 Subject: [PATCH 74/85] create new avg category from scratch --- examples/io/plot_elekta_epochs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/io/plot_elekta_epochs.py b/examples/io/plot_elekta_epochs.py index 5bf6442208d..2b30740d014 100644 --- a/examples/io/plot_elekta_epochs.py +++ b/examples/io/plot_elekta_epochs.py @@ -52,8 +52,8 @@ # mne.write_evokeds(fname_out, evokeds) ############################################################################### -# Make a new category using existing one as a template, extract epochs -newcat = ap.categories[0].copy() +# Make a new averaging category +newcat = dict() newcat['comment'] = 'Visual lower left, longer epochs' newcat['event'] = 3 # reference event newcat['start'] = -.2 # epoch start rel. to ref. event (in seconds) @@ -61,6 +61,7 @@ newcat['reqevent'] = 0 # additional required event; 0 if none newcat['reqwithin'] = .5 # ...required within .5 sec (before or after) newcat['reqwhen'] = 2 # ...required before (1) or after (2) ref. event +newcat['index'] = 9 # can be set freely cond = ap.get_condition(raw, newcat) epochs = mne.Epochs(raw, reject=ap.reject, flat=ap.flat, **cond) From 9d03314850b80c8a4e9b32b3c11ba6b7b16bb7f4 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Tue, 20 Sep 2016 13:20:15 +0300 Subject: [PATCH 75/85] add whatsnew line and fix typo on previous line --- doc/whats_new.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index e2cf6649f7c..c092c58a5bd 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -15,6 +15,8 @@ Current Changelog ~~~~~~~~~ + - Add new class :class:`AcqParserFIF` to parse Elekta/Neuromag MEG acquisition info, allowing e.g. collecting epochs according to acquisition-defined averaging categories by `Jussi Nurminen`_ + - Adds automatic determination of FIR filter parameters ``filter_length``, ``l_trans_bandwidth``, and ``h_trans_bandwidth`` and adds ``phase`` argument in e.g. in :meth:`mne.io.Raw.filter` by `Eric Larson`_ - Adds faster ``n_fft='auto'`` option to :meth:`mne.io.Raw.apply_hilbert` by `Eric Larson`_ @@ -31,7 +33,7 @@ Changelog - Add label center of mass function :func:`mne.Label.center_of_mass` by `Eric Larson`_ - - Added :func:`mne.viz.plot_ica_properties` that allows ploting of independent component properties similar to ``pop_prop`` in EEGLAB. Also :class:`mne.preprocessing.ICA` has :func:`mne.preprocessing.ICA.plot_properties` method now. Added by `Mikołaj Magnuski`_ + - Added :func:`mne.viz.plot_ica_properties` that allows plotting of independent component properties similar to ``pop_prop`` in EEGLAB. Also :class:`mne.preprocessing.ICA` has :func:`mne.preprocessing.ICA.plot_properties` method now. Added by `Mikołaj Magnuski`_ - Add second-order sections (instead of ``(b, a)`` form) IIR filtering for reduced numerical error by `Eric Larson`_ From 355f521d993ec3c53c3e4b5f78a9f1f30af78aaf Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 11:47:59 +0300 Subject: [PATCH 76/85] docs for len and getitem, getitem enh. --- mne/event.py | 66 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/mne/event.py b/mne/event.py index 4e94cb73b83..bf71dc766cc 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1005,15 +1005,65 @@ def __repr__(self): s += '>' return s - def __getitem__(self, items): - if not isinstance(items, str): - raise TypeError('Keys must be category names') - if items in self._categories: - return self._categories[items] - else: - raise KeyError('No such category') + def __getitem__(self, item): + """ Return an averaging category, or list of categories. + + Parameters + ---------- + item : str or list of str + Name of the category (comment field in DACQ). + + Returns + ------- + conds : dict or list of dict, each with following keys: + comment: str + The comment field in DACQ. + state : bool + Whether the category was marked enabled in DACQ. + index : int + The index of the category in DACQ. Indices start from 1. + event : int + Index of the reference event (trigger event, zero time for the + corresponding epochs). + start : float + Start time of epoch relative to the reference event. + end : float + End time of epoch relative to the reference event. + reqevent : int + Index of the required event. + reqwhen : int + Whether the required event is required before (1) or after (2) + the reference event. + reqwithin : float + The time range within which the required event must occur, + before or after the reference event. + display : bool + Whether the category was displayed online in DACQ. + nave : int + Desired number of averages. DACQ stops collecting averages once + this number is reached. + subave : int + Whether to compute normal and alternating subaverages, and + how many epochs to include. See the Elekta data acquisition + manual for details. Currently the class does not offer any + facility for computing subaverages, but it can be done manually + by the user after collecting the epochs. + + """ + if isinstance(item, str): + item = [item] + elif not isinstance(item, list): + raise ValueError('Keys must be category names') + cats = list() + for it in item: + if it in self._categories: + cats.append(self._categories[it]) + else: + raise KeyError('No such category') + return cats[0] if len(cats) == 1 else cats def __len__(self): + """ Return number of averaging categories marked active in DACQ. """ return len(self.categories) def _events_from_acq_pars(self): @@ -1200,7 +1250,7 @@ def get_condition(self, raw, condition=None, stim_channel=None, mask=None, condition : None | str | dict | list of dict Condition or a list of conditions. Conditions can be strings (DACQ comment field, e.g. 'Auditory left') or category dicts - (e.g. eav['Auditory left'], where eav is an instance of + (e.g. acqp['Auditory left'], where acqp is an instance of AcqParserFIF). If None, get all conditions marked active in DACQ. stim_channel : None | string | list of string From a4a551fb71a38a048e3d0bfca29ec18acd7b0576 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 11:53:32 +0300 Subject: [PATCH 77/85] add multimodal to download_all --- mne/datasets/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/datasets/utils.py b/mne/datasets/utils.py index 1916d6cafa2..3ea20c43d58 100644 --- a/mne/datasets/utils.py +++ b/mne/datasets/utils.py @@ -362,12 +362,13 @@ def _download_all_example_data(verbose=True): # verbose=True by default so we get nice status messages # Consider adding datasets from here to CircleCI for PR-auto-build from . import (sample, testing, misc, spm_face, somato, brainstorm, megsim, - eegbci) + eegbci, multimodal) sample.data_path() testing.data_path() misc.data_path() spm_face.data_path() somato.data_path() + multimodal.data_path() sys.argv += ['--accept-brainstorm-license'] try: brainstorm.bst_raw.data_path() From ada6c6eb7c695a463c2b76452f315724aefa9f8d Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 12:02:36 +0300 Subject: [PATCH 78/85] wip: tests --- mne/tests/test_event.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 69a23178e60..8dfef46b1af 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -418,15 +418,21 @@ def test_define_events(): assert_array_equal(true_lag_nofill, lag_nofill) +def test_acqparser(): + raw = read_raw_fif(fname_raw_elekta, preload=True) + acqp = AcqParserFIF(raw.info) + assert_true(repr(acqp)) + + @testing.requires_testing_data -def test_elekta_averager(): +def test_acqparser_averaging(): """Test averaging according to Elekta DACQ parameters""" raw = read_raw_fif(fname_raw_elekta, preload=True) - eav = AcqParserFIF(raw.info) - for cat in eav.categories: + acqp = AcqParserFIF(raw.info) + for cat in acqp.categories: # XXX datasets match only when baseline is applied to both, # not sure where relative dc shift comes from - cond = eav.get_condition(raw, cat) + cond = acqp.get_condition(raw, cat) eps = Epochs(raw, baseline=(-.05, 0), **cond) ev = eps.average() ev_ref = read_evokeds(fname_ave_elekta, cat['comment'], From 1507e36f9637f30db4eb1082870473588d9e9e1c Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 12:51:42 +0300 Subject: [PATCH 79/85] wip: tests --- mne/tests/test_event.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 8dfef46b1af..4af90a30879 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -419,10 +419,16 @@ def test_define_events(): def test_acqparser(): - raw = read_raw_fif(fname_raw_elekta, preload=True) + raw = read_raw_fif(fname_raw_elekta, preload=False) acqp = AcqParserFIF(raw.info) + # test __repr__() assert_true(repr(acqp)) - + # nonexisting category + assert_raises(KeyError, acqp.__getitem__, 'does not exist') + # not a string + assert_raises(ValueError, acqp.__getitem__, 0) + # length + assert_equal(len(acqp), 7) @testing.requires_testing_data def test_acqparser_averaging(): From 2e4472d239741325b67d46c436d6c01c691d69d1 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 13:06:04 +0300 Subject: [PATCH 80/85] more tests --- mne/tests/test_event.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index 4af90a30879..f09d949414c 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -418,6 +418,7 @@ def test_define_events(): assert_array_equal(true_lag_nofill, lag_nofill) +@testing.requires_testing_data def test_acqparser(): raw = read_raw_fif(fname_raw_elekta, preload=False) acqp = AcqParserFIF(raw.info) @@ -425,10 +426,16 @@ def test_acqparser(): assert_true(repr(acqp)) # nonexisting category assert_raises(KeyError, acqp.__getitem__, 'does not exist') - # not a string + assert_raises(KeyError, acqp.get_condition, raw, 'foo') + # category not a string assert_raises(ValueError, acqp.__getitem__, 0) - # length + # number of events / categories assert_equal(len(acqp), 7) + assert_equal(len(acqp.categories), 7) + assert_equal(len(acqp._categories), 32) + assert_equal(len(acqp.events), 6) + assert_equal(len(acqp._events), 32) + @testing.requires_testing_data def test_acqparser_averaging(): From 91c6c13c6dc96df878f4dcc6b18b1b6b2bfef9dc Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 15:16:44 +0300 Subject: [PATCH 81/85] small glitches --- mne/tests/test_event.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index f09d949414c..aac9f28130c 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -420,6 +420,7 @@ def test_define_events(): @testing.requires_testing_data def test_acqparser(): + """ Test AcqParserFIF """ raw = read_raw_fif(fname_raw_elekta, preload=False) acqp = AcqParserFIF(raw.info) # test __repr__() @@ -433,13 +434,13 @@ def test_acqparser(): assert_equal(len(acqp), 7) assert_equal(len(acqp.categories), 7) assert_equal(len(acqp._categories), 32) - assert_equal(len(acqp.events), 6) + assert_equal(len(acqp.events), 6) assert_equal(len(acqp._events), 32) @testing.requires_testing_data def test_acqparser_averaging(): - """Test averaging according to Elekta DACQ parameters""" + """ Test averaging with AcqParserFIF vs. Elekta software """ raw = read_raw_fif(fname_raw_elekta, preload=True) acqp = AcqParserFIF(raw.info) for cat in acqp.categories: From 50429a6067c9be2ba25af17bedc496dbc9d4ce1b Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 15:51:35 +0300 Subject: [PATCH 82/85] couple more tests + checks --- mne/event.py | 3 +++ mne/tests/test_event.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/mne/event.py b/mne/event.py index bf71dc766cc..bd57b4fc513 100644 --- a/mne/event.py +++ b/mne/event.py @@ -1112,11 +1112,14 @@ def _acqpars_dict(self, acq_pars): def _acqpars_gen(self, acq_pars): """ Yields key/value pairs from ``info['acq_pars'])``. """ # DACQ variable names always start with one of these + key, val = '', '' for line in acq_pars.split(): if any([line.startswith(x) for x in self._acq_var_magic]): key = line val = '' else: + if not key: + raise ValueError('Cannot parse acquisition parameters') # DACQ splits items with spaces into multiple lines val += ' ' + line if val else line yield key, val diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index aac9f28130c..b6c1380d272 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -421,10 +421,18 @@ def test_define_events(): @testing.requires_testing_data def test_acqparser(): """ Test AcqParserFIF """ + # no acquisition parameters + assert_raises(ValueError, AcqParserFIF, {'acq_pars': ''}) + # invalid acquisition parameters + assert_raises(ValueError, AcqParserFIF, {'acq_pars': 'baaa'}) + assert_raises(ValueError, AcqParserFIF, {'acq_pars': 'ERFVersion\n1'}) + # check file raw = read_raw_fif(fname_raw_elekta, preload=False) acqp = AcqParserFIF(raw.info) # test __repr__() assert_true(repr(acqp)) + # this file not in compatibility mode + assert_true(not acqp.compat) # nonexisting category assert_raises(KeyError, acqp.__getitem__, 'does not exist') assert_raises(KeyError, acqp.get_condition, raw, 'foo') From 608ddecc55cd3d2e5c0422103fb1d103d2858740 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Wed, 21 Sep 2016 17:04:07 +0300 Subject: [PATCH 83/85] tests on older file --- mne/tests/test_event.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/mne/tests/test_event.py b/mne/tests/test_event.py index b6c1380d272..e1dd4d615db 100644 --- a/mne/tests/test_event.py +++ b/mne/tests/test_event.py @@ -426,12 +426,26 @@ def test_acqparser(): # invalid acquisition parameters assert_raises(ValueError, AcqParserFIF, {'acq_pars': 'baaa'}) assert_raises(ValueError, AcqParserFIF, {'acq_pars': 'ERFVersion\n1'}) - # check file + # test oldish file + raw = read_raw_fif(raw_fname, preload=False) + acqp = AcqParserFIF(raw.info) + # test __repr__() + assert_true(repr(acqp)) + # old file should trigger compat mode + assert_true(acqp.compat) + # count events and categories + assert_equal(len(acqp.categories), 6) + assert_equal(len(acqp._categories), 17) + assert_equal(len(acqp.events), 6) + assert_equal(len(acqp._events), 17) + # get category + assert_true(acqp['Surprise visual']) + # test TRIUX file raw = read_raw_fif(fname_raw_elekta, preload=False) acqp = AcqParserFIF(raw.info) # test __repr__() assert_true(repr(acqp)) - # this file not in compatibility mode + # this file should not be in compatibility mode assert_true(not acqp.compat) # nonexisting category assert_raises(KeyError, acqp.__getitem__, 'does not exist') @@ -444,6 +458,8 @@ def test_acqparser(): assert_equal(len(acqp._categories), 32) assert_equal(len(acqp.events), 6) assert_equal(len(acqp._events), 32) + # get category + assert_true(acqp['Test event 5']) @testing.requires_testing_data From 786ad96187f6d218da0d7b3f6e1af0db25d4530b Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 22 Sep 2016 10:25:18 +0300 Subject: [PATCH 84/85] minor doc tweaks --- mne/event.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mne/event.py b/mne/event.py index bd57b4fc513..e2084d58d52 100644 --- a/mne/event.py +++ b/mne/event.py @@ -889,7 +889,7 @@ def concatenate_events(events, first_samps, last_samps): class AcqParserFIF(object): - """ Parser for Elekta DACQ settings. + """ Parser for Elekta data acquisition settings. This class parses parameters (e.g. events and averaging categories) that are defined in the Elekta TRIUX/VectorView data acquisition software (DACQ) @@ -911,9 +911,9 @@ class AcqParserFIF(object): reject : dict Rejection criteria from DACQ that can be used with mne.Epochs. Note that mne does not support all DACQ rejection criteria - (e.g. spike, slope) + (e.g. spike, slope). flat : dict - Flatness criteria from DACQ that can be used with mne.Epochs. + Flatness rejection criteria from DACQ that can be used with mne.Epochs. acq_dict : dict All DACQ parameters. @@ -1023,14 +1023,15 @@ def __getitem__(self, item): index : int The index of the category in DACQ. Indices start from 1. event : int - Index of the reference event (trigger event, zero time for the - corresponding epochs). + DACQ index of the reference event (trigger event, zero time for + the corresponding epochs). Note that the event indices start + from 1. start : float Start time of epoch relative to the reference event. end : float End time of epoch relative to the reference event. reqevent : int - Index of the required event. + Index of the required (conditional) event. reqwhen : int Whether the required event is required before (1) or after (2) the reference event. @@ -1215,7 +1216,7 @@ def _mne_events_to_category_t0(self, cat, mne_events, sfreq): @property def categories(self): - """ Return list of averaging categories in DACQ defined order. + """ Return list of averaging categories ordered by DACQ index. Only returns categories marked active in DACQ. """ @@ -1225,9 +1226,10 @@ def categories(self): @property def events(self): - """ Return events in DACQ defined order. + """ Return events ordered by DACQ index. - Only returns events that are in use (referred to by a category). """ + Only returns events that are in use (referred to by a category). + """ evs = sorted(self._events_in_use.values(), key=lambda ev: ev['index']) return evs From 3dc657d96efec6b4b8cf2545600b8aa805da2c15 Mon Sep 17 00:00:00 2001 From: Jussi Nurminen Date: Thu, 22 Sep 2016 10:29:06 +0300 Subject: [PATCH 85/85] tweak example comments --- examples/io/plot_elekta_epochs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/io/plot_elekta_epochs.py b/examples/io/plot_elekta_epochs.py index 2b30740d014..de0e0f38c60 100644 --- a/examples/io/plot_elekta_epochs.py +++ b/examples/io/plot_elekta_epochs.py @@ -3,7 +3,7 @@ Getting averaging info from .fif files ====================================== -Get averaging information defined in Elekta Vectorview/TRIUX DACQ (data +Parse averaging information defined in Elekta Vectorview/TRIUX DACQ (data acquisition). Extract and average epochs accordingly. Modify some averaging parameters and get epochs. """ @@ -23,7 +23,7 @@ print(__doc__) ############################################################################### -# Read raw file and create averager instance +# Read raw file and create parser instance raw = mne.io.read_raw_fif(fname_raw) ap = AcqParserFIF(raw.info) @@ -47,7 +47,7 @@ evoked = epochs.average() evoked.comment = cat['comment'] evokeds.append(evoked) -# save to an evoked fiff file +# save all averages to an evoked fiff file # fname_out = 'multimodal-ave.fif' # mne.write_evokeds(fname_out, evokeds)