def check_wheel_move_before_feedback(data, **_): """ Check that the wheel does move within 100ms of the feedback onset (error sound or valve). Metric: M = (w_t - 0.05) - (w_t + 0.05), where t = feedback_times Criterion: M != 0 Units: radians :param data: dict of trial data with keys ('wheel_timestamps', 'wheel_position', 'choice', 'intervals', 'feedback_times') """ # Get tuple of wheel times and positions within 100ms of feedback traces = traces_by_trial( data["wheel_timestamps"], data["wheel_position"], start=data["feedback_times"] - 0.05, end=data["feedback_times"] + 0.05, ) metric = np.zeros_like(data["feedback_times"]) # For each trial find the displacement for i, trial in enumerate(traces): pos = trial[1] if pos.size > 1: metric[i] = pos[-1] - pos[0] # except no-go trials metric[data["choice"] == 0] = np.nan nans = np.isnan(metric) passed = np.zeros_like(metric) * np.nan passed[~nans] = (metric[~nans] != 0).astype(float) assert data["intervals"].shape[0] == len(metric) == len(passed) return metric, passed
def load_wheel_move_before_feedback(trial_data, wheel_data): """ Wheel should move within 100ms of feedback Variable name: wheel_move_before_feedback Metric: (w_t - 0.05) - (w_t + 0.05) where t = feedback_time Criterion: != 0 for 99% of non-NoGo trials """ # Get tuple of wheel times and positions within 100ms of feedback traces = traces_by_trial( wheel_data["re_ts"], wheel_data["re_pos"], start=trial_data["feedback_times"] - 0.05, end=trial_data["feedback_times"] + 0.05, ) metric = np.zeros_like(trial_data["feedback_times"]) # For each trial find the displacement for i, trial in enumerate(traces): pos = trial[1] if pos.size > 1: metric[i] = pos[-1] - pos[0] # except no-go trials metric[trial_data["choice"] == 0] = np.nan nans = np.isnan(metric) passed = np.zeros_like(metric) * np.nan passed[~nans] = (metric[~nans] != 0).astype(np.float) assert len(trial_data["intervals_0"]) == len(metric) == len(passed) return metric, passed
def _wheel_move_during_closed_loop(re_ts, re_pos, data, wheel_gain=None, tol=1, **_): """ Check that the wheel moves by approximately 35 degrees during the closed-loop period on trials where a feedback (error sound or valve) is delivered. Metric: M = abs(w_resp - w_t0) - threshold_displacement, where w_resp = position at response time, w_t0 = position at go cue time, threshold_displacement = displacement required to move 35 visual degrees Criterion: displacement < tol visual degree Units: degrees angle of wheel turn :param re_ts: extarcted wheel timestamps in seconds :param re_pos: extracted wheel positions in radians :param data: a dict with the keys (goCueTrigger_times, response_times, feedback_times, position, choice, intervals) :param wheel_gain: the 'STIM_GAIN' task setting :param tol: the criterion in visual degrees """ if wheel_gain is None: _log.warning("No wheel_gain input in function call, returning None") return None, None # Get tuple of wheel times and positions over each trial's closed-loop period traces = traces_by_trial(re_ts, re_pos, start=data["goCueTrigger_times"], end=data["response_times"]) metric = np.zeros_like(data["feedback_times"]) # For each trial find the absolute displacement for i, trial in enumerate(traces): t, pos = trial if pos.size != 0: # Find the position of the preceding sample and subtract it idx = np.abs(re_ts - t[0]).argmin() - 1 origin = re_pos[idx] metric[i] = np.abs(pos - origin).max() # Load wheel_gain and thresholds for each trial wheel_gain = np.array([wheel_gain] * len(data["position"])) thresh = data["position"] # abs displacement, s, in mm required to move 35 visual degrees s_mm = np.abs(thresh / wheel_gain) # don't care about direction criterion = cm_to_rad( s_mm * 1e-1) # convert abs displacement to radians (wheel pos is in rad) metric = metric - criterion # difference should be close to 0 rad_per_deg = cm_to_rad(1 / wheel_gain * 1e-1) passed = (np.abs(metric) < rad_per_deg * tol).astype( float) # less than 1 visual degree off metric[data["choice"] == 0] = passed[data["choice"] == 0] = np.nan # except no-go trials assert data["intervals"].shape[0] == len(metric) == len(passed) return metric, passed
def test_traces_by_trial(self): t, pos = self.test_data[0][0] start = self.trials['stimOn_times'] end = self.trials['feedback_times'] traces = wheel.traces_by_trial(t, pos, start=start, end=end) # Check correct number of tuples returned self.assertEqual(len(traces), start.size) expected_ids = ([144, 60143], [74944, 84943], [99944, 102943], [119944, 129943], [163944, 187943]) for trace, ind in zip(traces, expected_ids): trace_t, trace_pos = trace np.testing.assert_array_equal(trace_t[[0, -1]], t[ind]) np.testing.assert_array_equal(trace_pos[[0, -1]], pos[ind])
def load_wheel_move_during_closed_loop(trial_data, wheel_data, wheel_gain): """ Wheel should move a sufficient amount during the closed-loop period Variable name: wheel_move_during_closed_loop Metric: abs(w_resp - w_t0) - threshold_displacement, where w_resp = position at response time, w_t0 = position at go cue time, threshold_displacement = displacement required to move 35 visual degrees Criterion: displacement < 1 visual degree for 99% of non-NoGo trials """ if wheel_gain is None: log.warning("No wheel_gain input in function call, retruning None") return None # Get tuple of wheel times and positions over each trial's closed-loop period traces = traces_by_trial( wheel_data["re_ts"], wheel_data["re_pos"], start=trial_data["goCueTrigger_times"], end=trial_data["response_times"], ) metric = np.zeros_like(trial_data["feedback_times"]) # For each trial find the absolute displacement for i, trial in enumerate(traces): t, pos = trial if pos.size == 0: metric[i] = np.nan else: # Find the position of the preceding sample and subtract it origin = wheel_data["re_pos"][wheel_data["re_ts"] <= t[0]][-1] metric[i] = np.abs(pos - origin).max() # Load wheel_gain and thresholds for each trial wheel_gain = np.array([wheel_gain] * len(trial_data["position"])) thresh = trial_data["position"] # abs displacement, s, in mm required to move 35 visual degrees s_mm = np.abs(thresh / wheel_gain) # don't care about direction criterion = cm_to_rad( s_mm * 1e-1) # convert abs displacement to radians (wheel pos is in rad) metric = metric - criterion # difference should be close to 0 rad_per_deg = cm_to_rad(1 / wheel_gain * 1e-1) passed = (np.abs(metric) < rad_per_deg).astype( np.float) # less than 1 visual degree off metric[trial_data["choice"] == 0] = np.nan # except no-go trials passed[trial_data["choice"] == 0] = np.nan # except no-go trials assert len(trial_data["intervals_0"]) == len(metric) == len(passed) return metric, passed
def load_wheel_freeze_during_quiescence(trial_data, wheel_data): """ Wheel should not move more than 2 ticks each direction for at least 0.2 + 0.2-0.6 amount of time (quiescent period; exact value in bpod['quiescence']) before go cue Variable name: wheel_freeze_during_quiescence Metric: abs(min(W - w_t0), max(W - w_t0)) where W is wheel pos over interval np.max(Metric) to get highest displaceente in any direction interval = [goCueTrigger_time-quiescent_duration,goCueTrigger_time] Criterion: <2 degrees for 99% of trials """ assert np.all(np.diff(wheel_data["re_ts"]) > 0) assert trial_data["quiescence"].size == trial_data[ "goCueTrigger_times"].size # Get tuple of wheel times and positions over each trial's quiescence period qevt_start_times = trial_data["goCueTrigger_times"] - trial_data[ "quiescence"] traces = traces_by_trial( wheel_data["re_ts"], wheel_data["re_pos"], start=qevt_start_times, end=trial_data["goCueTrigger_times"], ) # metric = np.zeros_like(trial_data['quiescence']) # for i, trial in enumerate(traces): # pos = trial[1] # if pos.size > 1: # metric[i] = np.abs(pos.max() - pos.min()) # -OR- metric = np.zeros( (len(trial_data["quiescence"]), 2)) # (n_trials, n_directions) for i, trial in enumerate(traces): t, pos = trial # Get the last position before the period began if pos.size > 1: # Find the position of the preceding sample and subtract it origin = wheel_data["re_pos"][wheel_data["re_ts"] < t[0]][-1] # Find the absolute min and max relative to the last sample metric[i, :] = np.abs([np.min(pos - origin), np.max(pos - origin)]) # Reduce to the largest displacement found in any direction metric = np.max(metric, axis=1) metric = 180 * metric / np.pi # convert to degrees from radians criterion = 2 # Position shouldn't change more than 2 in either direction passed = (metric < criterion).astype(np.float) assert len(trial_data["intervals_0"]) == len(metric) == len(passed) return metric, passed
def check_wheel_freeze_during_quiescence(data, **_): """ Check that the wheel does not move more than 2 ticks each direction for at least 0.2 + 0.2-0.6 amount of time (quiescent period; exact value in bpod['quiescence']) before the go cue tone. Metric: M = |max(W) - min(W)| where W is wheel pos over quiescence interval interval = [goCueTrigger_time - quiescent_duration, goCueTrigger_time] Criterion: M < 2 degrees Units: degrees angle of wheel turn :param data: dict of trial data with keys ('wheel_timestamps', 'wheel_position', 'quiescence', 'intervals', 'stimOnTrigger_times') """ assert np.all(np.diff(data["wheel_timestamps"]) > 0) assert data["quiescence"].size == data["stimOnTrigger_times"].size # Get tuple of wheel times and positions over each trial's quiescence period qevt_start_times = data["stimOnTrigger_times"] - data["quiescence"] traces = traces_by_trial( data["wheel_timestamps"], data["wheel_position"], start=qevt_start_times, end=data["stimOnTrigger_times"] ) metric = np.zeros((len(data["quiescence"]), 2)) # (n_trials, n_directions) for i, trial in enumerate(traces): t, pos = trial # Get the last position before the period began if pos.size > 0: # Find the position of the preceding sample and subtract it idx = np.abs(data["wheel_timestamps"] - t[0]).argmin() - 1 origin = data["wheel_position"][idx if idx != -1 else 0] # Find the absolute min and max relative to the last sample metric[i, :] = np.abs([np.min(pos - origin), np.max(pos - origin)]) # Reduce to the largest displacement found in any direction metric = np.max(metric, axis=1) metric = 180 * metric / np.pi # convert to degrees from radians criterion = 2 # Position shouldn't change more than 2 in either direction passed = metric < criterion assert data["intervals"].shape[0] == len(metric) == len(passed) return metric, passed
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 starts = trial_data['intervals'][trial_ids, 0] ends = trial_data['intervals'][trial_ids, 1] # Cut up the wheel vectors Fs = 1000 pos, t = wh.interpolate_position(wheel.timestamps, wheel.position, freq=Fs) vel, acc = wh.velocity_smoothed(pos, Fs) traces = wh.traces_by_trial(t, pos, start=starts, end=ends) zipped = zip(traces, axs, goCues, responses, trial_ids) for (trace, ax, go, resp, n) in zipped: ax.plot(trace[0], trace[1], 'k-') ax.axvline(x=go, color='g', label='go cue', linestyle=':') ax.axvline(x=resp, color='r', label='threshold', linestyle=':') ax.set_title('Trial #%s' % n) # Turn off tick labels ax.set_yticklabels([]) ax.set_xticklabels([]) # Add labels to first axs[0].set_xlabel('time / sec') axs[0].set_ylabel('position / rad')