Exemplo n.º 1
0
def events(vhdr_path=None):
    if vhdr_path is None:
        vhdr_path = ui.ask_file("Pick a Brain Vision EEG Header File", 
                                "Pick a Brain Vision EEG Header File",
                                ext=[('vhdr', 'Brain Vision Header File')])
        if not vhdr_path:
            return
    
    hdr = vhdr(vhdr_path)
    if hdr.markerfile is None:
        raise IOError("No marker file referenced in %r" % vhdr_path)
    elif hdr.DataType == 'FREQUENCYDOMAIN':
        raise NotImplementedError
    
    txt = open(hdr.markerfile).read()
    m = marker_re.findall(txt)
    m = np.array(m)
    
    name, _ = os.path.split(os.path.basename(hdr.path))
    ds = dataset(name=name)
    ds['Mk'] = var(np.array(m[:,0], dtype=int))
    ds['event_type'] = factor(m[:,1])
    ds['event_ID'] = var(np.array(m[:,3], dtype=int))
    ds['i_start'] = var(np.array(m[:,4], dtype=int))
    ds['points'] = var(np.array(m[:,5], dtype=int))
    ds['channel'] = var(np.array(m[:,6], dtype=int))
    
    ds.info['hdr'] = hdr
    ds.info['samplingrate'] = hdr.samplingrate
    return ds
Exemplo n.º 2
0
 def export_durs(self):
     ds = dataset()
     idx = self.words == 'sp'
     words = self.words[~idx]
     durs = self.word_durs[~idx]
     ds['words'] = factor([first.lower() + second.lower() for first, second in 
                           zip(words[::2], words[1::2])])
     ds['c1_dur'] = var(durs[::2])
     ds['c2_dur'] = var(durs[1::2])
     return ds
Exemplo n.º 3
0
def _resample(Y, unit=None, replacement=True, samples=1000):
    """
    Generator function to resample a dependent variable (Y) multiple times
    
    unit: factor specdifying unit of measurement (e.g. subject). If unit is 
          specified, resampling proceeds by first resampling the categories of 
          unit (with or without replacement) and then shuffling the values 
          within unites (no replacement). 
    replacement: whether random samples should be drawn with replacement or 
                 without
    samples: number of samples to yield
    
    """
    if isvar(Y):
        Yout = Y.copy('_resampled')
        Y
    else:
        Y = var(Y)
        Yout = var(Y.copy(), name="Y resampled")
    
    if unit:
        ct = celltable(Y, unit)
        unit_data = ct.get_data(out=list)
        unit_indexes = ct.data_indexes.values()
        x_out = Yout.x
        
        if replacement:
            n = len(ct.indexes)
            for sample in xrange(samples):
                source_ids = np.random.randint(n, size=n)
                for index, source_index in zip(unit_indexes, source_ids):
                    data = unit_data[source_index]
                    np.random.shuffle(data)
                    x_out[index] = data
                yield Yout
            
        else:
            for sample in xrange(samples):
                random.shuffle(unit_data)
                for index, data in zip(unit_indexes, unit_data):
                    np.random.shuffle(data)
                    x_out[index] = data
                yield Yout
            
    else:
        if replacement:
            N = Y.N
            for i in xrange(samples):
                index = np.random.randint(N)
                Yout.x = Y.x[index]
                yield Yout
        else:
            for i in xrange(samples):
                np.random.shuffle(Yout.x)
                yield Yout
Exemplo n.º 4
0
 def export_durs(self):
     ds = dataset()
     idx = self.words == 'sp'
     words = self.words[~idx]
     durs = self.word_durs[~idx]
     ds['words'] = factor([
         first.lower() + second.lower()
         for first, second in zip(words[::2], words[1::2])
     ])
     ds['c1_dur'] = var(durs[::2])
     ds['c2_dur'] = var(durs[1::2])
     return ds
Exemplo n.º 5
0
    def add_T_to(self, ds, Id='eventID', t_edf='t_edf'):
        """
        Add edf trigger times as a variable to dataset ds.
        These can then be used for Edf.add_by_T(ds) after ds hads been
        decimated.

        Parameters
        ----------
        ds : dataset
            The dataset to which the variable is added
        Id : str | var | None
            variable (or its name in the dataset) containing event IDs. Values
            in this variable are checked against the events in the EDF file,
            and an error is raised if there is a mismatch. This test can be
            skipped by setting Id=None.
        t_edf : str
            Name for the target variable holding the edf trigger times.

        """
        if Id:
            self.assert_Id_match(ds=ds, Id=Id)
            if isinstance(Id, str):
                Id = ds[Id]

        ds[t_edf] = var(self.triggers['T'])
Exemplo n.º 6
0
def fiff_mne(ds, fwd='{fif}*fwd.fif', cov='{fif}*cov.fif', label=None, name=None,
             tstart= -0.1, tstop=0.6, baseline=(None, 0)):
    """
    adds data from one label as

    """
    if name is None:
        if label:
            _, lbl = os.path.split(label)
            lbl, _ = os.path.splitext(lbl)
            name = lbl.replace('-', '_')
        else:
            name = 'stc'

    info = ds.info['info']

    raw = ds.info['raw']
    fif_name = raw.info['filename']
    fif_name, _ = os.path.splitext(fif_name)
    if fif_name.endswith('raw'):
        fif_name = fif_name[:-3]

    fwd = fwd.format(fif=fif_name)
    if '*' in fwd:
        d, n = os.path.split(fwd)
        names = fnmatch.filter(os.listdir(d), n)
        if len(names) == 1:
            fwd = os.path.join(d, names[0])
        else:
            raise IOError("No unique fwd file matching %r" % fwd)

    cov = cov.format(fif=fif_name)
    if '*' in cov:
        d, n = os.path.split(cov)
        names = fnmatch.filter(os.listdir(d), n)
        if len(names) == 1:
            cov = os.path.join(d, names[0])
        else:
            raise IOError("No unique cov file matching %r" % cov)

    fwd = mne.read_forward_solution(fwd, force_fixed=False, surf_ori=True)
    cov = mne.Covariance(cov)
    inv = _mn.make_inverse_operator(info, fwd, cov, loose=0.2, depth=0.8)
    epochs = mne_Epochs(ds, tstart=tstart, tstop=tstop, baseline=baseline)

    # mne example:
    snr = 3.0
    lambda2 = 1.0 / snr ** 2

    if label is not None:
        label = mne.read_label(label)
    stcs = _mn.apply_inverse_epochs(epochs, inv, lambda2, dSPM=False, label=label)

    x = np.vstack(s.data.mean(0) for s in stcs)
    s = stcs[0]
    dims = ('case', var(s.times, 'time'),)
    ds[name] = ndvar(x, dims, properties=None, info='')

    return stcs
Exemplo n.º 7
0
 def _get_vessel_for_Y(self, Ydata):
     Ydata = np.array([self._cfunc(data, axis=0) for data in Ydata])
     T = np.arange(self._tw.tstart, self._tw.tend, 1 / self._samplingrate)
     time = _vsl.var(T, name='time')
     dims = ('case', time)
     properties = {'samplingrate': self._samplingrate}
     Y = _vsl.ndvar(Ydata, dims, properties=properties, name=self._name)
     return Y
Exemplo n.º 8
0
    def get_triggers(self, Id='Id', T='t_edf'):
        """
        Returns a dataset with trigger Ids and corresponding Edf time values

        """
        ds = dataset()
        ds[Id] = var(self.triggers['Id'])
        ds[T] = self.get_T(name=T)
        return ds
Exemplo n.º 9
0
def evoked_ndvar(evoked, name='MEG'):
    "Convert an mne Evoked object of a list thereof to an ndvar"
    if isinstance(evoked, mne.fiff.Evoked):
        x = evoked.data
        dims = ()
    else:
        x = np.array([e.data for e in evoked])
        dims = ('case',)
        evoked = evoked[0]
    sensor = sensor_net(evoked)
    time = var(evoked.times, name='time')
    properties = {'colorspace': _cs.get_MEG(2e-13)}
    return ndvar(x, dims + (sensor, time), properties=properties, name=name)
Exemplo n.º 10
0
    def mark(self, ds, tstart= -0.1, tstop=0.6, good=None, bad=False,
             use=['ESACC', 'EBLINK'], T='t_edf', target='accept'):
        """
        Mark events in ds as acceptable or not. ds needs to contain edf trigger
        times in a variable whose name is specified by the ``T`` argument.

        Parameters
        ----------
        ds : dataset
            dataset that contains the data to work with.
        tstart : scalar
            start of the time window relevant for rejection.
        tstop : scalar
            stop of the time window relevant for rejection.
        good : bool | None
            vale assigned to epochs that should be retained based on
            the eye-tracker data.
        bad : bool | None
            value that is assigned to epochs that should be rejected
            based on the eye-tracker data.
        use : list of str
            Artifact categories to include
        T : var
            variable providing the trigger time values
        target : var
            variable to which the good/bad values are assigned (if it does not
            exist, a new variable will be created with all values True
            initially)

        """
        if isinstance(target, str):
            if target in ds:
                target = ds[target]
            else:
                ds[target] = target = var(np.ones(ds.n_cases, dtype=bool))

        if isinstance(T, str):
            T = ds[T]

        accept = self.get_accept(T, tstart=tstart, tstop=tstop, use=use)
        if good is not None:
            target[accept] = good
        if bad is not None:
            target[np.invert(accept)] = bad
Exemplo n.º 11
0
def stc_ndvar(stc, subject='fsaverage', name=None, check=True):
    """
    create an ndvar object from an mne SourceEstimate object

    stc : SourceEstimate | list of SourceEstimates
        The source estimate object(s).
    subject : str
        MRI subject (used for loading MRI in PySurfer plotting)
    name : str | None
        Ndvar name.
    check : bool
        If multiple stcs are provided, check if all stcs have the same times
        and vertices.

    """
    if isinstance(stc, mne.SourceEstimate):
        case = False
        x = stc.data
    else:
        case = True
        stcs = stc
        stc = stcs[0]
        if check:
            vert_lh, vert_rh = stc.vertno
            times = stc.times
            for stc_ in stcs[1:]:
                assert np.all(times == stc_.times)
                lh, rh = stc_.vertno
                assert np.all(vert_lh == lh)
                assert np.all(vert_rh == rh)
        x = np.array([s.data for s in stcs])

    time = var(stc.times, name='time')
    ss = source_space(stc.vertno, subject=subject)
    if case:
        dims = ('case', ss, time)
    else:
        dims = (ss, time)

    return ndvar(x, dims, name=name)
Exemplo n.º 12
0
    def get_dataset(self):
        "get a dataframe containing Y and covariates"
        Y, covs = self._collect_data()
        
        indexes = Y.keys()
        Ydata = [Y[index] for index in indexes]
        Y = self._get_vessel_for_Y(Ydata)
        ds = _vsl.dataset(Y)

        # create _data objects
        for var, valdict in covs.iteritems():
            X = [valdict[index] for index in indexes]
            
            if var.dict_enabled:
                Y = _vsl.factor(X, name=var.name, random=var.random,
                                labels=var.dictionary, colors=var._color_dict)
            else:
                Y = _vsl.var(X, name=var.name)
            
            ds.add(Y)
        
        return ds
Exemplo n.º 13
0
def var(path=None, name=None):
    """
    Loads a ``var`` object from a text file by splitting at white-spaces.

    path : str(path) | None
        Source file. If None, a system file dialog is opened.
    name : str | None
        Name for the var.

    """
    if path is None:
        path = ui.ask_file("Select var File", "()")

    FILE = open(path)
    lines = FILE.read().split()
    FILE.close()
    is_bool = all(line in ['True', 'False'] for line in lines)

    if is_bool:
        x = np.genfromtxt(path, dtype=bool)
    else:
        x = np.loadtxt(path)

    return _data.var(x, name=None)
Exemplo n.º 14
0
    def __init__(self, dataset, data='MEG', target='accept',
                 nplots=(6, 6), plotsize=(3, 1.5),
                 mean=True, topo=True, ylim=None, fill=True, aa=False, dpi=50):
        """
        Plots all cases in the collection segment and allows visual selection
        of cases. The selection can be retrieved through the get_selection
        Method.

        Arguments
        ----------

        dataset : dataset
            dataset on which to perform the selection.

        nplots : 1 | 2 | (i, j)
            Number of plots (including topo plots and mean).

        mean : bool
            Plot the page mean on each page.

        topo : bool
            Show a dynamically updated topographic plot at the bottom right.

        ylim : scalar
            y-limit of the butterfly plots.

        fill : bool
            Only show extrema in the butterfly plots, instead of all traces.
            This is faster for data with many channels.

        aa : bool
            Antialiasing (matplolibt parameter).


        Example::

            >>> select_cases_butterfly(my_dataset)
            [... visual selection of cases ...]
            >>> cases = my_dataset['reject'] == False
            >>> pruned_dataset = my_dataset[cases]

        """
    # interpret plotting args
        # variable keeping track of selection
        if isinstance(data, basestring):
            data = dataset[data]
        self._data = data

        if np.prod(nplots) == 1:
            nplots = (1, 1)
            mean = False
            topo = False
        elif np.prod(nplots) == 2:
            mean = False
            if nplots == 2:
                nplots = (1, 2)

        if isinstance(target, basestring):
            try:
                target = dataset[target]
            except KeyError:
                x = np.ones(dataset.N, dtype=bool)
                target = _data.var(x, name=target)
                dataset.add(target)
        self._target = target
        self._plot_mean = mean
        self._plot_topo = topo

    # prepare segments
        self._nplots = nplots
        n_per_page = self._n_per_page = np.prod(nplots) - bool(topo) - bool(mean)
        n_pages = dataset.N // n_per_page + bool(dataset.N % n_per_page)
        self._n_pages = n_pages

        # get a list of IDS for each page
        self._segs_by_page = []
        for i in xrange(n_pages):
            start = i * n_per_page
            stop = min((i + 1) * n_per_page, dataset.N)
            self._segs_by_page.append(range(start, stop))

    # init wx frame
        title = "select_cases_butterfly -> %r" % target.name
        figsize = (plotsize[0] * nplots[0], plotsize[1] * nplots[1])
        super(self.__class__, self).__init__(title=title, figsize=figsize, dpi=dpi)

    # setup figure
        self.figure.subplots_adjust(left=.01, right=.99, bottom=.05, top=.95,
                                    hspace=.5)
        # connect canvas
        self.canvas.mpl_connect('button_press_event', self._on_click)
        if self._is_wx:
            self.canvas.mpl_connect('axes_leave_event', self._on_leave_axes)

    # compile plot kwargs:
        self._bfly_kwargs = {'extrema': fill}
        if ylim is None:
            ylim = data.properties.get('ylim', None)
        if ylim:
            self._bfly_kwargs['ylim'] = ylim

    # finalize
        self._dataset = dataset
        self.show_page(0)
        self._frame.store_canvas()
        self._show()
Exemplo n.º 15
0
 def get_T(self, name='t_edf'):
     "returns all trigger times in the dataset"
     return var(self.triggers['T'], name=name)
Exemplo n.º 16
0
 def _get_vessel_for_Y(self, Ydata):
     Ydata = [self._cfunc(data) for data in Ydata]
     Y = _vsl.var(Ydata, name=self._name)
     return Y
Exemplo n.º 17
0
def epochs_ndvar(epochs, name='MEG', meg=True, eeg=False, exclude='bads',
                 mult=1, unit='T', properties=None, sensors=None):
    """
    Convert an mne.Epochs object to an ndvar.

    Parameters
    ----------
    epoch : mne.Epochs
        The epochs object
    name : None | str
        Name for the ndvar.
    meg : bool or string
        MEG channels to include (:func:`mne.fiff.pick_types` kwarg).
        If True include all MEG channels. If False include None
        If string it can be 'mag' or 'grad' to select only gradiometers
        or magnetometers. It can also be 'ref_meg' to get CTF
        reference channels.
    eeg : bool
        If True include EEG channels (:func:`mne.fiff.pick_types` kwarg).
    exclude : list of string | str
        Channels to exclude (:func:`mne.fiff.pick_types` kwarg).
        If 'bads' (default), exclude channels in info['bads'].
        If empty do not exclude any.
    mult : scalar
        multiply all data by a constant. If used, the ``unit`` kwarg should
        specify the target unit, not the source unit.
    unit : str
        Unit of the data (default is 'T').
    target : str
        name for the new ndvar containing the epoch data
    reject : None | scalar
        Threshold for rejecting epochs (peak to peak). Requires a for of
        mne-python which implements the Epochs.model['index'] variable.
    raw : None | mne.fiff.Raw
        Raw file providing the data; if ``None``, ``ds.info['raw']`` is used.
    sensors : None | eelbrain.vessels.sensors.sensor_net
        The default (``None``) reads the sensor locations from the fiff file.
        If the fiff file contains incorrect sensor locations, a different
        sensor_net can be supplied through this kwarg.

    """
    props = {'proj': 'z root',
             'unit': unit,
             'ylim': 2e-12 * mult,
             'summary_ylim': 3.5e-13 * mult,
             'colorspace': _cs.get_MEG(2e-12 * mult),
             'summary_colorspace': _cs.get_MEG(2e-13 * mult),
             'samplingrate': epochs.info['sfreq'],
             }

    if properties:
        props.update(properties)

    picks = mne.fiff.pick_types(epochs.info, meg=meg, eeg=eeg, stim=False,
                                eog=False, include=[], exclude=exclude)
    x = epochs.get_data()[:, picks]
    if mult != 1:
        x *= mult

    if sensors is None:
        sensor = sensor_net(epochs, picks=picks)
    else:
        sensor = sensors
    time = var(epochs.times, name='time')
    return ndvar(x, ('case', sensor, time), properties=props, name=name)
Exemplo n.º 18
0
def events(raw=None, merge= -1, proj=False, name=None,
           stim_channel='STI 014',
           stim_channel_bl=0, verbose=False):
    """
    Read events from a raw fiff file.

    Use :func:`fiff_epochs` to load MEG data corresponding to those events.

    Parameters
    ----------
    raw : str(path) | None | mne.fiff.Raw
        The raw fiff file from which to extract events (if ``None``, a file
        dialog will be displayed).
    merge : int
        use to merge events lying in neighboring samples. The integer value
        indicates over how many samples events should be merged, and the sign
        indicates in which direction they should be merged (negative means
        towards the earlier event, positive towards the later event).
    proj : bool | str
        Path to the projections file that will be loaded with the raw file.
        ``'{raw}'`` will be expanded to the raw file's path minus extension.
        With ``proj=True``, ``'{raw}_*proj.fif'`` will be used,
        looking for any projection file starting with the raw file's name.
        If multiple files match the pattern, a ValueError will be raised.
    name : str | None
        A name for the dataset. If ``None``, the raw filename will be used.
    stim_channel : str
        Name of the trigger channel.
    stim_channel_bl : int
        For corrupted event channels:
        After kit2fiff conversion of sqd files with unused trigger channels,
        the resulting fiff file's event channel can contain a baseline other
        than 0. This interferes with normal event extraction. If the baseline
        value is provided as parameter, the events can still be extracted.

    Returns
    -------
    events : dataset
        A dataset with the following variables:
         - *i_start*: the index of the event in the raw file.
         - *eventID*: the event value.
        The dataset's info dictionary contains the following values:
         - *raw*: the mne Raw object.
         - *samplingrate*: the samplingrate of the raw file.

    """
    if raw is None or isinstance(raw, basestring):
        raw = Raw(raw, proj=proj, verbose=verbose)

    if name is None:
        raw_file = raw.info['filename']
        name = os.path.basename(raw_file)

    if stim_channel_bl:
        pick = mne.event.pick_channels(raw.info['ch_names'], include=stim_channel)
        data, _ = raw[pick, :]
        idx = np.where(np.abs(np.diff(data[0])) > 0)[0]

        # find baseline NULL-events
        values = data[0, idx + 1]
        valid_events = np.where(values != stim_channel_bl)[0]
        idx = idx[valid_events]
        values = values[valid_events]

        N = len(values)
        events = np.empty((N, 3), dtype=np.int32)
        events[:, 0] = idx
        events[:, 1] = np.zeros_like(idx)
        events[:, 2] = values
    else:
        events = mne.find_events(raw, verbose=verbose, stim_channel=stim_channel)

    if len(events) == 0:
        raise ValueError("No events found!")

    if merge:
        index = np.ones(len(events), dtype=bool)
        diff = np.diff(events[:, 0])
        where = np.where(diff <= abs(merge))[0]

        if merge > 0:
            # drop the earlier event
            index[where] = False
        else:
            # drop the later event
            index[where + 1] = False
            # move the trigger value to the earlier event
            for w in reversed(where):
                i1 = w
                i2 = w + 1
                events[i1, 2] = events[i2, 2]

        events = events[index]

    istart = var(events[:, 0], name='i_start')
    event = var(events[:, 2], name='eventID')
    info = {'raw': raw,
            'samplingrate': raw.info['sfreq'],
            'info': raw.info}
    return dataset(event, istart, name=name, info=info)
Exemplo n.º 19
0
def tsv(path=None, names=True, types='auto', empty='nan', delimiter=None,
        skiprows=0):
    """
    returns a ``dataset`` with data from a tab-separated values file.


    Parameters
    ----------

    names : list of str | bool
        * ``['name1', ...]`` use these names
        * ``True``: look for names on the first line of the file
        * ``False``: use "v1", "v2", ...
    types : 'auto' | list of int
        * ``'auto'`` -> import as var if all values can be converted float,
          otherwise as factor
        * list of 0=auto, 1=factor, 2=var. e.g. ``[0,1,1,0]``
    empty :
        value to substitute for empty cells
    delimiter : str
        value delimiting cells in the input file (None = any whitespace;
        e.g., ``'\\t'``)
    skiprows : int
        Skip so many rows at the beginning of the file (for tsv files with
        headers). Column names (if names==True) are expected to come after
        the skipped rows.

    """
    if path is None:
        path = ui.ask_file("Select file to import as dataframe",
                           "Select file to import as dataframe")
        if not path:
            return

    with open(path) as f:
        for i in xrange(skiprows):
            f.readline()

        # read / create names
        if names == True:
            names = f.readline().split(delimiter)
            names = [n.strip().strip('"') for n in names]

        lines = []
        for line in f:
            values = []
            for v in line.split(delimiter):
                v = v.strip()
                if not v:
                    v = empty
                values.append(v)
            lines.append(values)

    n_vars = len(lines[0])

    if not names:
        names = ['v%i' % i for i in xrange(n_vars)]

    n = len(names)
    # decide whether to drop first column
    if n_vars == n:
        start = 0
    elif n_vars == n + 1:
        start = 1
    else:
        raise ValueError("number of header different from number of data")

    if types in ['auto', None, False, True]:
        types = [0] * n
    else:
        assert len(types) == n

    # prepare for reading data
    data = []
    for _ in xrange(n):
        data.append([])

    # read rest of the data
    for line in lines:
        for i, v in enumerate(line[start:]):
            for str_del in ["'", '"']:
                if v[0] == str_del:
                    v = v.strip(str_del)
                    types[i] = 1
            data[i].append(v)

    ds = _data.dataset(name=os.path.basename(path))

    for name, values, force_type in zip(names, data, types):
        v = np.array(values)
        if force_type in [0, 2]:
            try:
                v = v.astype(float)
                f = _data.var(v, name=name)
            except:
                f = _data.factor(v, name=name)
        else:
            f = _data.factor(v, name=name)
        ds.add(f)

    return ds
Exemplo n.º 20
0
def _ax_im_array(ax, layers, x='time',  # vmax=None,
                 xlabel=True, ylabel=True, title=None, tick_spacing=.3):
    """
    plots segment data as im

    define a colorspace by supplying one of those kwargs: ``colorspace`` OR
    ``p`` OR ``vmax``

    """
    handles = []
    epoch = layers[0]

    xdim = epoch.get_dim(x)
    if epoch.ndim == 2:
        xdim_i = epoch.dimnames.index(x)
        ydim_i = {1:0, 0:1}[xdim_i]
        y = epoch.dimnames[ydim_i]
    else:
        err = ("Need 2 dimensions, got %i" % epoch.ndim)
        raise ValueError(err)

    ydim = epoch.get_dim(y)
    if y == 'sensor':
        ydim = _dta.var(np.arange(len(ydim)), y)

    map_kwargs = {'extent': [xdim[0], xdim[-1], ydim[0], ydim[-1]],
                  'aspect': 'auto'}

    # plot
    for l in layers:
        h = _plt_im_array(ax, l, dims=(y, x), **map_kwargs)
        handles.append(h)

    if xlabel:
        if xlabel is True:
            xlabel = xdim.name
        ax.set_xlabel(xlabel)

    if ylabel:
        if ylabel is True:
            ylabel = ydim.name
        ax.set_ylabel(ylabel)

    # x-ticks
    tickstart = math.ceil(xdim[0] / tick_spacing) * tick_spacing
    tickend = xdim[-1] + tick_spacing / 1e4
    ticklabels = np.arange(tickstart, tickend, tick_spacing)
    ax.xaxis.set_ticks(ticklabels)
    ax.x_fmt = "t = %.3f s"

    # y-ticks
    if y == 'sensor':  # make sure y-ticklabels are all integers
        locs = ax.yaxis.get_ticklocs()
        if any(locs != locs.round()):
            idx = np.where(locs == locs.round())[0]
            locs = locs[idx]
            labels = map(lambda x: str(int(x)), locs)
            ax.yaxis.set_ticks(locs)
            ax.yaxis.set_ticklabels(labels)

    # title
    if title is None:
        if plt.rcParams['text.usetex']:
            title = fmtxt.texify(epoch.name)
        else:
            title = epoch.name
    ax.set_title(title)

    return handles