Esempio n. 1
0
def sync_rotary_encoder(session_path, bpod_data=None, re_events=None):
    if not bpod_data:
        bpod_data = raw.load_data(session_path)
    evt = re_events or raw.load_encoder_events(session_path)
    # we work with stim_on (2) and closed_loop (3) states for the synchronization with bpod
    tre = evt.re_ts.values / 1e6  # convert to seconds
    # the first trial on the rotary encoder is a dud
    rote = {'stim_on': tre[evt.sm_ev == 2][:-1],
            'closed_loop': tre[evt.sm_ev == 3][:-1]}
    bpod = {
        'stim_on': np.array([tr['behavior_data']['States timestamps']
                             ['stim_on'][0][0] for tr in bpod_data]),
        'closed_loop': np.array([tr['behavior_data']['States timestamps']
                                 ['closed_loop'][0][0] for tr in bpod_data]),
    }
    if rote['closed_loop'].size <= 1:
        raise err.SyncBpodWheelException("Not enough Rotary Encoder events to perform wheel"
                                         " synchronization. Wheel data not extracted")
    # bpod bug that spits out events in ms instead of us
    if np.diff(bpod['closed_loop'][[-1, 0]])[0] / np.diff(rote['closed_loop'][[-1, 0]])[0] > 900:
        _logger.error("Rotary encoder stores values in ms instead of us. Wheel timing inaccurate")
        rote['stim_on'] *= 1e3
        rote['closed_loop'] *= 1e3
    # just use the closed loop for synchronization
    # handle different sizes in synchronization:
    sz = min(rote['closed_loop'].size, bpod['closed_loop'].size)
    # if all the sample are contiguous and first samples match
    diff_first_match = np.diff(rote['closed_loop'][:sz]) - np.diff(bpod['closed_loop'][:sz])
    # if all the sample are contiguous and last samples match
    diff_last_match = np.diff(rote['closed_loop'][-sz:]) - np.diff(bpod['closed_loop'][-sz:])
    # 99% of the pulses match for a first sample lock
    DIFF_THRESHOLD = 0.005
    if np.mean(np.abs(diff_first_match) < DIFF_THRESHOLD) > 0.99:
        re = rote['closed_loop'][:sz]
        bp = bpod['closed_loop'][:sz]
        indko = np.where(np.abs(diff_first_match) >= DIFF_THRESHOLD)[0]
    # 99% of the pulses match for a last sample lock
    elif np.mean(np.abs(diff_last_match) < DIFF_THRESHOLD) > 0.99:
        re = rote['closed_loop'][-sz:]
        bp = bpod['closed_loop'][-sz:]
        indko = np.where(np.abs(diff_last_match) >= DIFF_THRESHOLD)[0]
    # last resort is to use ad-hoc sync function
    else:
        bp, re = raw.sync_trials_robust(bpod['closed_loop'], rote['closed_loop'],
                                        diff_threshold=DIFF_THRESHOLD, max_shift=5)
        indko = np.array([])
        # raise ValueError("Can't sync bpod and rotary encoder: non-contiguous sync pulses")
    # remove faulty indices due to missing or bad syncs
    indko = np.int32(np.unique(np.r_[indko + 1, indko]))
    re = np.delete(re, indko)
    bp = np.delete(bp, indko)
    # check the linear drift
    assert bp.size > 1
    poly = np.polyfit(bp, re, 1)
    assert np.all(np.abs(np.polyval(poly, bp) - re) < 0.002)
    return interpolate.interp1d(re, bp, fill_value="extrapolate")
Esempio n. 2
0
def align_with_bpod(session_path):
    """
    Reads in trials.intervals ALF dataset from bpod and fpga.
    Asserts consistency between datasets and compute the median time difference

    :param session_path:
    :return: dt: median time difference of trial start times (fpga - bpod)
    """
    ITI_DURATION = 0.5
    # check consistency
    output_path = Path(session_path) / 'alf'
    trials = alf.io.load_object(output_path, '_ibl_trials')
    if alf.io.check_dimensions(trials) != 0:
        # patching things up if the bpod and FPGA don't have the same recording span
        _logger.warning(
            "BPOD/FPGA synchronization: Bpod and FPGA don't have the same amount of"
            " trial start events. Patching alf files.")
        _, _, ibpod, ifpga = raw.sync_trials_robust(
            trials['intervals_bpod'][:, 0],
            trials['intervals'][:, 0],
            return_index=True)
        if ibpod.size == 0:
            raise err.SyncBpodFpgaException(
                'Can not sync BPOD and FPGA - no matching sync pulses '
                'found.')
        for k in trials:
            if 'bpod' in k:
                trials[k] = trials[k][ibpod]
            else:
                trials[k] = trials[k][ibpod]
        alf.io.save_object_npy(output_path, trials, '_ibl_trials')
    assert (alf.io.check_dimensions(trials) == 0)
    tlen = (np.diff(trials['intervals_bpod']) -
            np.diff(trials['intervals']))[:-1] - ITI_DURATION
    assert (np.all(np.abs(tlen[np.invert(np.isnan(tlen))])[:-1] < 5 * 1e-3))
    # dt is the delta to apply to bpod times in order to be on the ephys clock
    dt = trials['intervals'][:, 0] - trials['intervals_bpod'][:, 0]
    # compute the clock drift bpod versus dt
    ppm = np.polyfit(trials['intervals'][:, 0], dt, 1)[0] * 1e6
    if ppm > BPOD_FPGA_DRIFT_THRESHOLD_PPM:
        _logger.warning(
            'BPOD/FPGA synchronization shows values greater than 150 ppm')
        # plt.plot(trials['intervals'][:, 0], dt, '*')
    # so far 2 datasets concerned: goCueTrigger_times_bpod  and response_times_bpod
    for k in trials:
        if not k.endswith('_times_bpod'):
            continue
        np.save(output_path.joinpath(f'_ibl_trials.{k[:-5]}.npy'),
                trials[k] + dt)
    return interpolate.interp1d(trials['intervals_bpod'][:, 0],
                                trials['intervals'][:, 0],
                                fill_value="extrapolate")
Esempio n. 3
0
def bpod_fpga_sync(bpod_intervals=None,
                   ephys_intervals=None,
                   iti_duration=None):
    """
    Computes synchronization function from bpod to fpga
    :param bpod_intervals
    :param ephys_intervals
    :return: interpolation function
    """
    if iti_duration is None:
        iti_duration = 0.5
    # check consistency
    if bpod_intervals.size != ephys_intervals.size:
        # patching things up if the bpod and FPGA don't have the same recording span
        _logger.warning(
            "BPOD/FPGA synchronization: Bpod and FPGA don't have the same amount of"
            " trial start events. Patching alf files.")
        _, _, ibpod, ifpga = raw_data_loaders.sync_trials_robust(
            bpod_intervals[:, 0], ephys_intervals[:, 0], return_index=True)
        if ibpod.size == 0:
            raise err.SyncBpodFpgaException(
                'Can not sync BPOD and FPGA - no matching sync pulses '
                'found.')
        bpod_intervals = bpod_intervals[ibpod, :]
        ephys_intervals = ephys_intervals[ifpga, :]
    else:
        ibpod, ifpga = [
            np.arange(bpod_intervals.shape[0]) for _ in np.arange(2)
        ]
    tlen = (np.diff(bpod_intervals) -
            np.diff(ephys_intervals))[:-1] - iti_duration
    assert np.all(np.abs(tlen[np.invert(np.isnan(tlen))])[:-1] < 5 * 1e-3)
    # dt is the delta to apply to bpod times in order to be on the ephys clock
    dt = bpod_intervals[:, 0] - ephys_intervals[:, 0]
    # compute the clock drift bpod versus dt
    ppm = np.polyfit(bpod_intervals[:, 0], dt, 1)[0] * 1e6
    if ppm > BPOD_FPGA_DRIFT_THRESHOLD_PPM:
        _logger.warning(
            'BPOD/FPGA synchronization shows values greater than %i ppm',
            BPOD_FPGA_DRIFT_THRESHOLD_PPM)
        # plt.plot(trials['intervals'][:, 0], dt, '*')
    # so far 2 datasets concerned: goCueTrigger_times_bpod  and response_times_bpod
    fcn_bpod2fpga = interpolate.interp1d(bpod_intervals[:, 0],
                                         ephys_intervals[:, 0],
                                         fill_value="extrapolate")
    return ibpod, ifpga, fcn_bpod2fpga