def test_extract_first_movement_times(self):
     test_data = self.test_data[1]
     wheel_moves = ephys_fpga.extract_wheel_moves(test_data[0][0], test_data[0][1])
     first, is_final, ind = extract_first_movement_times(wheel_moves, self.trials)
     np.testing.assert_allclose(first, [162.48462599, 105.62562599, np.nan])
     np.testing.assert_array_equal(is_final, [False, True, False])
     np.testing.assert_array_equal(ind, [46, 18])
    def _extract(self, sync=None, chmap=None, **kwargs):
        """Extracts ephys trials by combining Bpod and FPGA sync pulses"""
        # extract the behaviour data from bpod
        if sync is None or chmap is None:
            _sync, _chmap = get_main_probe_sync(self.session_path, bin_exists=False)
            sync = sync or _sync
            chmap = chmap or _chmap
        # load the bpod data and performs a biased choice world training extraction
        bpod_raw = raw_data_loaders.load_data(self.session_path)
        assert bpod_raw is not None, "No task trials data in raw_behavior_data - Exit"
        bpod_trials, _ = biased_trials.extract_all(
            session_path=self.session_path, save=False, bpod_trials=bpod_raw)
        bpod_trials['intervals_bpod'] = np.copy(bpod_trials['intervals'])
        fpga_trials = extract_behaviour_sync(sync=sync, chmap=chmap, bpod_trials=bpod_trials,
                                             tmax=bpod_trials['intervals'][-1, -1] + 60)
        # checks consistency and compute dt with bpod
        self.bpod2fpga, drift_ppm, ibpod, ifpga = dsp.utils.sync_timestamps(
            bpod_trials['intervals_bpod'][:, 0], fpga_trials.pop('intervals')[:, 0],
            return_indices=True)
        nbpod = bpod_trials['intervals_bpod'].shape[0]
        npfga = fpga_trials['feedback_times'].shape[0]
        nsync = len(ibpod)
        _logger.info(f"N trials: {nbpod} bpod, {npfga} FPGA, {nsync} merged, sync {drift_ppm} ppm")
        if drift_ppm > BPOD_FPGA_DRIFT_THRESHOLD_PPM:
            _logger.warning('BPOD/FPGA synchronization shows values greater than %i ppm',
                            BPOD_FPGA_DRIFT_THRESHOLD_PPM)
        # those fields get directly in the output
        bpod_fields = ['feedbackType', 'choice', 'rewardVolume', 'intervals_bpod']
        # those fields have to be resynced
        bpod_rsync_fields = ['intervals', 'response_times', 'goCueTrigger_times',
                             'stimOnTrigger_times', 'stimOffTrigger_times',
                             'stimFreezeTrigger_times', 'errorCueTrigger_times']

        # build trials output
        out = OrderedDict()
        out.update({k: bpod_trials[k][ibpod] for k in bpod_fields})
        out.update({k: self.bpod2fpga(bpod_trials[k][ibpod]) for k in bpod_rsync_fields})
        out.update({k: fpga_trials[k][ifpga] for k in sorted(fpga_trials.keys())})

        # extract the wheel data
        from ibllib.io.extractors.training_wheel import extract_first_movement_times
        ts, pos = extract_wheel_sync(sync=sync, chmap=chmap)
        moves = extract_wheel_moves(ts, pos)
        settings = raw_data_loaders.load_settings(session_path=self.session_path)
        min_qt = settings.get('QUIESCENT_PERIOD', None)
        first_move_onsets, *_ = extract_first_movement_times(moves, out, min_qt=min_qt)
        out.update({'firstMovement_times': first_move_onsets})

        assert tuple(filter(lambda x: 'wheel' not in x, self.var_names)) == tuple(out.keys())
        return [out[k] for k in out] + [ts, pos, moves['intervals'], moves['peakAmplitude']]
Exemple #3
0
def load_wheel_reaction_times(eid, one=None):
    """
    Return the calculated reaction times for session.  Reaction times are defined as the time
    between the go cue (onset tone) and the onset of the first substantial wheel movement.   A
    movement is considered sufficiently large if its peak amplitude is at least 1/3rd of the
    distance to threshold (~0.1 radians).

    Negative times mean the onset of the movement occurred before the go cue.  Nans may occur if
    there was no detected movement withing the period, or when the goCue_times or feedback_times
    are nan.

    Parameters
    ----------
    eid : str
        Session UUID
    one : oneibl.ONE
        An instance of ONE for loading data.  If None a new one is instantiated using the defaults.

    Returns
    ----------
    array-like
        reaction times
    """
    if one is None:
        one = ONE()

    trials = one.load_object(eid, 'trials')
    # If already extracted, load and return
    if trials and 'firstMovement_times' in trials:
        return trials['firstMovement_times'] - trials['goCue_times']
    # Otherwise load wheelMoves object and calculate
    moves = one.load_object(eid, 'wheelMoves')
    # Re-extract wheel moves if necessary
    if not moves or 'peakAmplitude' not in moves:
        wheel = one.load_object(eid, 'wheel')
        moves = extract_wheel_moves(wheel['timestamps'], wheel['position'])
    assert trials and moves, 'unable to load trials and wheelMoves data'
    firstMove_times, is_final_movement, ids = extract_first_movement_times(
        moves, trials)
    return firstMove_times - trials['goCue_times']
one = ONE()

sns.set_style('whitegrid')
device_info = (
    'The wheel diameter is {} cm and the number of ticks is {} per revolution'.
    format(wh.WHEEL_DIAMETER, wh.ENC_RES))
print(device_info)

eid = one.search(subject='dop_12', date_range='2020-12-11')[0]
wheel = one.load_object(eid, 'wheel')
wheel_moves = one.load_object(eid, 'wheelMoves')
rt = load_wheel_reaction_times(eid)
trial_data = one.load_object(eid, 'trials')

firstMove_times, is_final_movement, ids = extract_first_movement_times(
    wheel_moves, trial_data)
print('Trials where mouse sticked to one movement:  {}%'.format(
    np.sum(is_final_movement) / len(is_final_movement)))

# Plot some random trials
n_trials = 3  # Number of trials to plot
# Randomly select the trials to plot
trial_ids = np.random.randint(trial_data['choice'].size, size=n_trials)
fig, axs = plt.subplots(1, n_trials, figsize=(8.5, 2.5))
plt.tight_layout()

# Plot go cue and response times
goCues = trial_data['goCue_times'][trial_ids]
responses = trial_data['response_times'][trial_ids]

# Plot traces between trial intervals
Exemple #5
0
    def _extract(self, sync=None, chmap=None):
        # extracts trials
        # extract the behaviour data from bpod
        if sync is None or chmap is None:
            _sync, _chmap = _get_main_probe_sync(self.session_path,
                                                 bin_exists=False)
            sync = sync or _sync
            chmap = chmap or _chmap
        bpod_raw = raw_data_loaders.load_data(self.session_path)
        tmax = bpod_raw[-1]['behavior_data']['States timestamps'][
            'exit_state'][0][-1] + 60
        bpod_trials, _ = biased_trials.extract_all(
            session_path=self.session_path, save=False, bpod_trials=bpod_raw)
        bpod_trials['intervals_bpod'] = np.copy(bpod_trials['intervals'])
        fpga_trials = extract_behaviour_sync(sync=sync, chmap=chmap, tmax=tmax)
        # checks consistency and compute dt with bpod
        ibpod, ifpga, fcn_bpod2fpga = bpod_fpga_sync(
            bpod_trials['intervals_bpod'], fpga_trials['intervals'])
        # those fields get directly in the output
        bpod_fields = [
            'feedbackType', 'choice', 'rewardVolume', 'intervals_bpod'
        ]
        # those fields have to be resynced
        bpod_rsync_fields = [
            'intervals', 'response_times', 'goCueTrigger_times'
        ]
        # ephys fields to save in the output
        fpga_fields = [
            'stimOn_times', 'stimOff_times', 'goCue_times', 'feedback_times'
        ]
        # get ('probabilityLeft', 'contrastLeft', 'contrastRight') from the custom ephys extractors
        pclcr, _ = ProbaContrasts(self.session_path).extract(
            bpod_trials=bpod_raw, save=False)
        # build trials output
        out = OrderedDict()
        out.update({
            k: pclcr[i][ifpga]
            for i, k in enumerate(ProbaContrasts.var_names)
        })
        out.update({k: bpod_trials[k][ibpod] for k in bpod_fields})
        out.update({
            k: fcn_bpod2fpga(bpod_trials[k][ibpod])
            for k in bpod_rsync_fields
        })
        out.update({k: fpga_trials[k][ifpga] for k in fpga_fields})

        # extract the wheel data
        from ibllib.io.extractors.training_wheel import extract_first_movement_times
        ts, pos = extract_wheel_sync(sync=sync, chmap=chmap)
        moves = extract_wheel_moves(ts, pos)
        settings = raw_data_loaders.load_settings(
            session_path=self.session_path)
        min_qt = settings.get('QUIESCENT_PERIOD', None)
        first_move_onsets, *_ = extract_first_movement_times(moves,
                                                             out,
                                                             min_qt=min_qt)
        out.update({'firstMovement_times': first_move_onsets})

        assert tuple(filter(lambda x: 'wheel' not in x,
                            self.var_names)) == tuple(out.keys())
        return [out[k] for k in out
                ] + [ts, pos, moves['intervals'], moves['peakAmplitude']]
Exemple #6
0
    def make(self, key, one=None):
        # Log eid and task version
        eid, ver = (acquisition.Session & key).fetch1('session_uuid', 'task_protocol')
        logger.info('MovementTimes for session %s, %s', str(eid), ver)

        # Get required data from wheel moves and trials tables, each as a dict of numpy arrays
        fields = ('move_id', 'movement_onset', 'movement_offset', 'movement_amplitude')
        wheel_move_data = {k: v.values for k, v in (
            (
                (WheelMoveSet.Move & key)
                .proj(*fields)
                .fetch(order_by='move_id', format='frame')
                .reset_index()
                .drop(['subject_uuid', 'session_start_time'], axis=1)
                .rename(columns={'movement_amplitude': 'peakAmplitude'})
                .iteritems()
            )
        )}

        fields = ('trial_response_choice', 'trial_response_time', 'trial_stim_on_time',
                  'trial_go_cue_time', 'trial_feedback_time', 'trial_start_time')
        trial_data = {k: v.values for k, v in (
            (
                (behavior.TrialSet.Trial & key)
                .proj(*fields)
                .fetch(order_by='trial_id', format='frame')
                .reset_index()
                .drop(['subject_uuid', 'session_start_time'], axis=1)
                .iteritems()
            )
        )}

        if trial_data['trial_id'].size == 0 or wheel_move_data['move_id'].size == 0:
            logger.warning('Missing DJ trial or move data')
            return

        # Get minimum quiescent period for session
        try:
            one = one or ONE()
            task_params = one.load_object(str(eid), '_iblrig_taskSettings.raw')
            min_qt = task_params['raw']['QUIESCENT_PERIOD']
        except Exception:
            logger.warning('failed to load min quiescent time')
            min_qt = None

        # Many of the timestamps are missing for sessions, therefore will patch together the approximate
        # closed-loop periods by taking the minimum of go_cue and stim_on, response and feedback.
        start = np.nanmin(np.c_[trial_data['trial_stim_on_time'], trial_data['trial_go_cue_time']], axis=1)
        end = np.nanmin(np.c_[trial_data['trial_response_time'], trial_data['trial_feedback_time']], axis=1)

        # Check we have times for at least some trials
        nan_trial = np.isnan(np.c_[start, end]).any(axis=1)
        assert ~nan_trial.all(), 'no reliable trials times for session'

        assert (((start < end) | nan_trial).all() and
                ((np.diff(start) > 0) | np.isnan(np.diff(start))).all()), 'timestamps not increasing'
        go_trial = trial_data['trial_response_choice'] != 'No Go'

        # Rename data for the firstMovement_times extractor function
        wheel_move_data['intervals'] = np.c_[
            wheel_move_data['movement_onset'], wheel_move_data['movement_offset']
        ]
        trial_data = {'goCue_times': start, 'feedback_times': end, 'trial_id': trial_data['trial_id']}

        # Find first significant movement for each trial.  To be counted, the movement must
        # occur between go cue / stim on and before feedback / response time.  The movement
        # onset is sometimes just before the cue (occurring in the gap between quiescence end and
        # cue start, or during the quiescence period but sub-threshold).  The movement is
        # sufficiently large if it is greater than or equal to THRESH
        onsets, final_movement, ids = extract_first_movement_times(wheel_move_data, trial_data, min_qt)
        move_ids = np.full_like(onsets, np.nan)
        move_ids[~np.isnan(onsets)] = ids

        # Check if any movements failed to be detected
        n_nan = np.count_nonzero(np.isnan(onsets[go_trial]))
        if n_nan > 0:
            logger.warning('failed to detect movement on %i go trials', n_nan)

        # Create matrix of values for insertion into table
        movement_data = np.c_[
            trial_data['trial_id'],  # trial_id
            move_ids,  # wheel_move_id
            onsets - start,  # reaction_time
            final_movement,  # final_movement
            end - onsets,  # movement_time
            end - start,  # response_time
            onsets  # movement_onset
        ]
        data = []
        for row in movement_data:
            if ~np.isnan(row).any():  # insert row
                data.append(tuple([*key.values(), *list(row)]))
        self.insert(data)
Exemple #7
0
    def _extract(self, sync=None, chmap=None, **kwargs):
        """Extracts ephys trials by combining Bpod and FPGA sync pulses"""
        # extract the behaviour data from bpod
        if sync is None or chmap is None:
            _sync, _chmap = get_main_probe_sync(self.session_path,
                                                bin_exists=False)
            sync = sync or _sync
            chmap = chmap or _chmap
        # load the bpod data and performs a biased choice world training extraction
        bpod_raw = raw_data_loaders.load_data(self.session_path)
        assert bpod_raw is not None, "No task trials data in raw_behavior_data - Exit"

        bpod_trials = self._extract_bpod(bpod_raw, save=False)
        # Explode trials table df
        trials_table = alfio.AlfBunch.from_df(bpod_trials.pop('table'))
        table_columns = trials_table.keys()
        bpod_trials.update(trials_table)
        # synchronize
        bpod_trials['intervals_bpod'] = np.copy(bpod_trials['intervals'])
        fpga_trials = extract_behaviour_sync(sync=sync,
                                             chmap=chmap,
                                             bpod_trials=bpod_trials)
        # checks consistency and compute dt with bpod
        self.bpod2fpga, drift_ppm, ibpod, ifpga = neurodsp.utils.sync_timestamps(
            bpod_trials['intervals_bpod'][:, 0],
            fpga_trials.pop('intervals')[:, 0],
            return_indices=True)
        nbpod = bpod_trials['intervals_bpod'].shape[0]
        npfga = fpga_trials['feedback_times'].shape[0]
        nsync = len(ibpod)
        _logger.info(
            f"N trials: {nbpod} bpod, {npfga} FPGA, {nsync} merged, sync {drift_ppm} ppm"
        )
        if drift_ppm > BPOD_FPGA_DRIFT_THRESHOLD_PPM:
            _logger.warning(
                'BPOD/FPGA synchronization shows values greater than %i ppm',
                BPOD_FPGA_DRIFT_THRESHOLD_PPM)
        out = OrderedDict()
        out.update({k: bpod_trials[k][ibpod] for k in self.bpod_fields})
        out.update({
            k: self.bpod2fpga(bpod_trials[k][ibpod])
            for k in self.bpod_rsync_fields
        })
        out.update(
            {k: fpga_trials[k][ifpga]
             for k in sorted(fpga_trials.keys())})
        # extract the wheel data
        wheel, moves = get_wheel_positions(sync=sync, chmap=chmap)
        from ibllib.io.extractors.training_wheel import extract_first_movement_times
        settings = raw_data_loaders.load_settings(
            session_path=self.session_path)
        min_qt = settings.get('QUIESCENT_PERIOD', None)
        first_move_onsets, *_ = extract_first_movement_times(moves,
                                                             out,
                                                             min_qt=min_qt)
        out.update({'firstMovement_times': first_move_onsets})
        # Re-create trials table
        trials_table = alfio.AlfBunch({x: out.pop(x) for x in table_columns})
        out['table'] = trials_table.to_df()

        out = {k: out[k] for k in self.var_names if k in out}  # Reorder output
        assert tuple(filter(lambda x: 'wheel' not in x,
                            self.var_names)) == tuple(out.keys())
        return [out[k] for k in out] + [
            wheel['timestamps'], wheel['position'], moves['intervals'],
            moves['peakAmplitude']
        ]