Ejemplo n.º 1
0
    def test_sync(self):
        # Test casting non-uniformly-sampled data to a evenly-sampled TimeSeries.
        # Begin by defining sampling intervals of random half-normally distributed length
        times = np.cumsum(np.abs(np.random.normal(loc=4., scale=6., size=100)))
        # take sample values as though the value was increasing as a cube of sample time
        samples = times**3
        # Use cubic interpolation to resample to uniform interval
        cubes = core.TimeSeries(times=times, values=samples, columns=('cubic',))
        resamp = processing.sync(0.1, cubes, interp='cubic', fillval='extrapolate')
        # Check that the sync function is returning a new time series object
        self.assertTrue(isinstance(resamp, core.TimeSeries))
        # Test that all returned sample times are uniformly spaced
        # We need to use np.isclose because of floating point arithematic problems instead of ==0.1
        # Since the actual diff returns 0.09999999999999964
        self.assertTrue(np.all(np.isclose(np.diff(resamp.times), 0.1)))
        # Check that we're within a margin of error on the interpolation
        err_margin = 1e-3  # Maximum percent error allowed
        err_percs = np.abs(resamp.times**3 - resamp.values.T) / (resamp.times**3)
        self.assertTrue(np.all(err_percs < err_margin))
        # Make a second timeseries of square-law increasing samples
        times2 = np.cumsum(np.abs(np.random.normal(loc=2., scale=1., size=200)))
        samples2 = times2**2
        squares = core.TimeSeries(times=times2, values=samples2, columns=('square',))
        # Use cubic interpolation again, this time on both timeseries
        resamp2 = processing.sync(0.1, [squares, cubes], interp='cubic', fillval='extrapolate')
        # Check that the new TS has both squares and cubes as keys and attribs
        self.assertTrue(hasattr(resamp2, 'cubic'))
        self.assertTrue(hasattr(resamp2, 'square'))
        # Check that both timeseries are fully contained in the resampled TS
        self.assertTrue(cubes.times.min() >= resamp2.times.min())
        self.assertTrue(cubes.times.max() <= resamp2.times.max())
        self.assertTrue(squares.times.min() >= resamp2.times.min())
        self.assertTrue(squares.times.max() <= resamp2.times.max())
        # Check that all interpolated values are within the margin of error against the known func
        sq_errperc = np.abs(resamp2.times**2 - resamp2.square) / resamp2.times**2
        cu_errperc = np.abs(resamp2.times**3 - resamp2.cubic) / resamp2.times**3
        self.assertTrue(np.all(sq_errperc < err_margin) & np.all(cu_errperc < err_margin))

        # Now check the numpy array behavior of sync.
        # Try running sync on the cubic times and values only.
        resamp = processing.sync(0.1, times=times, values=samples, interp='cubic',
                                 fillval='extrapolate')
        # Do all the tests we did for the instance created using TimeSeries objects
        self.assertTrue(isinstance(resamp, core.TimeSeries))
        self.assertTrue(np.all(np.isclose(np.diff(resamp.times), 0.1)))
        err_margin = 1e-3  # Maximum percent error allowed
        err_percs = np.abs(resamp.times**3 - resamp.values.T) / (resamp.times**3)
        self.assertTrue(np.all(err_percs < err_margin))
        # Try the multiple-arrays case in which we pass two times and two values
        resamp2 = processing.sync(0.1, times=(times, times2), values=(samples, samples2),
                                  interp='cubic', fillval='extrapolate')
        self.assertTrue(times.min() >= resamp2.times.min())
        self.assertTrue(times.max() <= resamp2.times.max())
        self.assertTrue(times2.min() >= resamp2.times.min())
        self.assertTrue(times2.max() <= resamp2.times.max())
Ejemplo n.º 2
0
def load_trials_df(eid,
                   one=None,
                   maxlen=None,
                   t_before=0.,
                   t_after=0.,
                   ret_wheel=False,
                   ret_abswheel=False,
                   wheel_binsize=0.02):
    """
    Generate a pandas dataframe of per-trial timing information about a given session.
    Each row in the frame will correspond to a single trial, with timing values indicating timing
    session-wide (i.e. time in seconds since session start). Can optionally return a resampled
    wheel velocity trace of either the signed or absolute wheel velocity.

    The resulting dataframe will have a new set of columns, trial_start and trial_end, which define
    via t_before and t_after the span of time assigned to a given trial.
    (useful for bb.modeling.glm)

    Parameters
    ----------
    eid : str
        Session UUID string to pass to ONE
    one : oneibl.one.OneAlyx, optional
        one object to use for loading. Will generate internal one if not used, by default None
    maxlen : float, optional
        Maximum trial length for inclusion in df. Trials where feedback - response is longer
        than this value will not be included in the dataframe, by default None
    t_before : float, optional
        Time before stimulus onset to include for a given trial, as defined by the trial_start
        column of the dataframe. If zero, trial_start will be identical to stimOn, by default 0.
    t_after : float, optional
        Time after feedback to include in the trail, as defined by the trial_end
        column of the dataframe. If zero, trial_end will be identical to feedback, by default 0.
    ret_wheel : bool, optional
        Whether to return the time-resampled wheel velocity trace, by default False
    ret_abswheel : bool, optional
        Whether to return the time-resampled absolute wheel velocity trace, by default False
    wheel_binsize : float, optional
        Time bins to resample wheel velocity to, by default 0.02

    Returns
    -------
    pandas.DataFrame
        Dataframe with trial-wise information. Indices are the actual trial order in the original
        data, preserved even if some trials do not meet the maxlen criterion. As a result will not
        have a monotonic index. Has special columns trial_start and trial_end which define start
        and end times via t_before and t_after
    """
    if not one:
        one = ONE()

    if ret_wheel and ret_abswheel:
        raise ValueError('ret_wheel and ret_abswheel cannot both be true.')

    # Define which datatypes we want to pull out
    trialstypes = [
        'trials.choice',
        'trials.probabilityLeft',
        'trials.feedbackType',
        'trials.feedback_times',
        'trials.contrastLeft',
        'trials.contrastRight',
        'trials.goCue_times',
        'trials.stimOn_times',
    ]

    # A quick function to remap probabilities in those sessions where it was not computed correctly
    def remap_trialp(probs):
        # Block probabilities in trial data aren't accurate and need to be remapped
        validvals = np.array([0.2, 0.5, 0.8])
        diffs = np.abs(np.array([x - validvals for x in probs]))
        maps = diffs.argmin(axis=1)
        return validvals[maps]

    starttimes = one.load(eid, dataset_types=['trials.stimOn_times'])[0]
    endtimes = one.load(eid, dataset_types=['trials.feedback_times'])[0]
    tmp = one.load(eid, dataset_types=trialstypes)

    if maxlen is not None:
        with np.errstate(invalid='ignore'):
            keeptrials = (endtimes - starttimes) <= maxlen
    else:
        keeptrials = range(len(starttimes))
    trialdata = {
        x.split('.')[1]: tmp[i][keeptrials]
        for i, x in enumerate(trialstypes)
    }
    trialdata['probabilityLeft'] = remap_trialp(trialdata['probabilityLeft'])
    trialsdf = pd.DataFrame(trialdata)
    if maxlen is not None:
        trialsdf.set_index(np.nonzero(keeptrials)[0], inplace=True)
    trialsdf['trial_start'] = trialsdf['stimOn_times'] - t_before
    trialsdf['trial_end'] = trialsdf['feedback_times'] + t_after
    if not ret_wheel and not ret_abswheel:
        return trialsdf

    wheel = one.load_object(eid, 'wheel')
    whlpos, whlt = wheel.position, wheel.timestamps
    starttimes = trialsdf['trial_start']
    endtimes = trialsdf['trial_end']
    wh_endlast = 0
    trials = []
    for (start, end) in np.vstack((starttimes, endtimes)).T:
        wh_startind = np.searchsorted(whlt[wh_endlast:], start) + wh_endlast
        wh_endind = np.searchsorted(whlt[wh_endlast:], end,
                                    side='right') + wh_endlast + 4
        wh_endlast = wh_endind
        tr_whlpos = whlpos[wh_startind - 1:wh_endind + 1]
        tr_whlt = whlt[wh_startind - 1:wh_endind + 1] - start
        tr_whlt[0] = 0.  # Manual previous-value interpolation
        whlseries = TimeSeries(tr_whlt, tr_whlpos, columns=['whlpos'])
        whlsync = sync(wheel_binsize, timeseries=whlseries, interp='previous')
        trialstartind = np.searchsorted(whlsync.times, 0)
        trialendind = np.ceil((end - start) / wheel_binsize).astype(int)
        trpos = whlsync.values[trialstartind:trialendind + trialstartind]
        whlvel = trpos[1:] - trpos[:-1]
        whlvel = np.insert(whlvel, 0, 0)
        if np.abs((trialendind - len(whlvel))) > 0:
            raise IndexError(
                'Mismatch between expected length of wheel data and actual.')
        if ret_wheel:
            trials.append(whlvel)
        elif ret_abswheel:
            trials.append(np.abs(whlvel))
    trialsdf['wheel_velocity'] = trials
    return trialsdf