Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new terminology: first movement onset time & response time #796

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions brainbox/behavior/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
.. _Appendix 2: https://figshare.com/articles/preprint/A_standardized_and_reproducible_method_to_\
measure_decision-making_in_mice_Appendix_2_IBL_protocol_for_mice_training/11634729

For a detailed description of how to interpret RTs, see `this document`_ and the documentation on `working with wheel data`_.

.. _this document: https://docs.google.com/document/d/1s1huCm6eap2cdI6e-3cEnMpH8fP7KvXd16h_350WCU8/edit
.. _working with wheel data`: https://int-brain-lab.github.io/iblenv/notebooks_external/docs_wheel_moves.html#Finding-reaction-time-and-'determined'-movements

Examples
--------
Plot the psychometric curve for a given session.
Expand All @@ -15,20 +20,20 @@

Compute 'response times', defined as the duration of open-loop for each contrast.

>>> reaction_time, contrasts, n_contrasts = compute_reaction_time(trials)
>>> response time, contrasts, n_contrasts = compute_reaction_time(trials)

Compute 'reaction times', defined as the time between go cue and first detected movement.
NB: These may be negative!
Compute 'first movement onset', defined as the time between go cue and first detected movement.
NB: These may be negative! See also [extract_first_movement_times](https://int-brain-lab.github.io/iblenv/_autosummary/ibllib.io.extractors.training_wheel.html#ibllib.io.extractors.training_wheel.extract_first_movement_times).

>>> reaction_time, contrasts, n_contrasts = compute_reaction_time(
>>> first_movement, contrasts, n_contrasts = compute_reaction_time(
... trials, stim_on_type='goCue_times', stim_off_type='firstMovement_times')

Compute 'response times', defined as the time between first detected movement and response.
Compute 'movement response durations', defined as the time between first detected movement and response.

>>> reaction_time, contrasts, n_contrasts = compute_reaction_time(
>>> movement_response_duration, contrasts, n_contrasts = compute_reaction_time(
... trials, stim_on_type='firstMovement_times', stim_off_type='response_times')

Compute 'movement times', defined as the time between last detected movement and response threshold.
Compute 'final movement duration', defined as the time between last detected movement and response threshold.

>>> import brainbox.behavior.wheel as wh
>>> wheel_moves = ONE().load_object(eid, 'wheeMoves')
Expand Down Expand Up @@ -713,7 +718,7 @@ def compute_median_reaction_time(trials, stim_on_type='stimOn_times', contrast=N
the stimulus onset command), if available, or the 'goCue_times' (the time of the soundcard
output TTL when the audio go cue is played) or the 'goCueTrigger_times' (the time of the
audio go cue command).
- The response/reaction time here is defined as the time between stim on and feedback, i.e. the
- The response time here is defined as the time between stim on and feedback, i.e. the
entire open-loop trial duration.
"""
if signed_contrast is None:
Expand Down Expand Up @@ -765,7 +770,7 @@ def compute_reaction_time(trials, stim_on_type='stimOn_times', stim_off_type='re
Returns
-------
numpy.array
The median response times for each unique signed contrast.
The median RTs for each unique signed contrast.
numpy.array
The set of unique signed contrasts.
numpy.array
Expand All @@ -776,11 +781,11 @@ def compute_reaction_time(trials, stim_on_type='stimOn_times', stim_off_type='re

Notes
-----
- The response/reaction time by default is the time between stim on and response, i.e. the
- The response time by default is the time between stim on and response, i.e. the
entire open-loop trial duration. One could use 'stimOn_times' and 'firstMovement_times' to
get the true reaction time, or 'firstMovement_times' and 'response_times' to get the true
response times, or calculate the last movement onset times and calculate the true movement
times. See module examples for how to calculate this.
get the first movement onset time, or 'firstMovement_times' and 'response_times' to get the movement response duration,
or calculate the last movement onset times and calculate the true movement
times. See module examples for how to calculate this, and [this document](https://docs.google.com/document/d/1s1huCm6eap2cdI6e-3cEnMpH8fP7KvXd16h_350WCU8/edit)

See Also
--------
Expand All @@ -796,7 +801,7 @@ def compute_reaction_time(trials, stim_on_type='stimOn_times', stim_off_type='re
block_idx = trials.probabilityLeft == block

contrasts, n_contrasts = np.unique(signed_contrast[block_idx], return_counts=True)
reaction_time = np.vectorize(lambda x: np.nanmedian((trials[stim_off_type] - trials[stim_on_type])
rt = np.vectorize(lambda x: np.nanmedian((trials[stim_off_type] - trials[stim_on_type])
[(x == signed_contrast) & block_idx]))(contrasts)
if compute_ci:
ci = np.full((contrasts.size, 2), np.nan)
Expand All @@ -806,9 +811,9 @@ def compute_reaction_time(trials, stim_on_type='stimOn_times', stim_off_type='re
ci[i, 0] = bt.confidence_interval.low
ci[i, 1] = bt.confidence_interval.high

return reaction_time, contrasts, n_contrasts, ci
return rt, contrasts, n_contrasts, ci
else:
return reaction_time, contrasts, n_contrasts,
return rt, contrasts, n_contrasts,


def criterion_1a(psych, n_trials, perf_easy):
Expand Down Expand Up @@ -1128,7 +1133,7 @@ def plot_reaction_time(trials, ax=None, title=None, plot_ci=False, ci_alpha=0.32

def plot_reaction_time_over_trials(trials, stim_on_type='stimOn_times', ax=None, title=None, **kwargs):
"""
Function to plot reaction time with trial number a la datajoint webpage.
Function to plot RT with trial number a la datajoint webpage.

Parameters
----------
Expand Down
4 changes: 2 additions & 2 deletions brainbox/io/one.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ def load_passive_rfmap(eid, one=None):

def load_wheel_reaction_times(eid, one=None):
"""
Return the calculated reaction times for session. Reaction times are defined as the time
Return the calculated first movement onset time for a session. These are 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).
Expand All @@ -694,7 +694,7 @@ def load_wheel_reaction_times(eid, one=None):
Returns
----------
array-like
reaction times
first movement onset times
"""
if one is None:
one = ONE()
Expand Down
18 changes: 9 additions & 9 deletions brainbox/task/trials.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def find_trial_ids(trials, side='all', choice='all', order='trial num', sort='id
:param choice: trial choice, options are 'all', 'correct' or 'incorrect'
:param contrast: contrast of stimulus, pass in list/tuple of all contrasts that want to be
considered e.g [1, 0.5] would only look for trials with 100 % and 50 % contrast
:param order: how to order the trials, options are 'trial num' or 'reaction time'
:param order: how to order the trials, options are 'trial num' or 'response time'
:param sort: how to sort the trials, options are 'side' (split left right trials), 'choice'
(split correct incorrect trials), 'choice and side' (split left right and correct incorrect)
:param event: trial event to align to (in order to remove nan trials for this event)
Expand Down Expand Up @@ -46,15 +46,15 @@ def find_trial_ids(trials, side='all', choice='all', order='trial num', sort='id
np.bitwise_and(cont, np.bitwise_and(trials['feedbackType'][idx] == -1,
np.isfinite(trials['contrastLeft'][idx]))))[0]

reaction_time = trials['response_times'][idx] - trials['goCue_times'][idx]
response_time = trials['response_times'][idx] - trials['goCue_times'][idx]

def _order_by(_trials, order):
# Returns subset of trials either ordered by trial number or by reaction time
# Returns subset of trials either ordered by trial number or by RT
sorted_trials = np.sort(_trials)
if order == 'trial num':
return sorted_trials
elif order == 'reaction time':
sorted_reaction = np.argsort(reaction_time[sorted_trials])
elif order == 'response time':
sorted_reaction = np.argsort(response_time[sorted_trials])
return sorted_trials[sorted_reaction]

dividers = []
Expand Down Expand Up @@ -218,7 +218,7 @@ def filter_correct_incorrect_left_right(trials, event_raster, event, contrast, o
:param event: event to align to e.g 'goCue_times', 'stimOn_times'
:param contrast: contrast of stimulus, pass in list/tuple of all contrasts that want to be
considered e.g [1, 0.5] would only look for trials with 100 % and 50 % contrast
:param order: order to sort trials by either 'trial num' or 'reaction time'
:param order: order to sort trials by either 'trial num' or 'response time'
:return:
"""
trials_sorted, div = find_trial_ids(trials, sort='choice and side', event=event, order=order, contrast=contrast)
Expand Down Expand Up @@ -258,7 +258,7 @@ def filter_correct_incorrect(trials, event_raster, event, contrast, order='trial
:param event: event to align to e.g 'goCue_times', 'stimOn_times'
:param contrast: contrast of stimulus, pass in list/tuple of all contrasts that want to be
considered e.g [1, 0.5] would only look for trials with 100 % and 50 % contrast
:param order: order to sort trials by either 'trial num' or 'reaction time'
:param order: order to sort trials by either 'trial num' or 'response time'
:return:
"""
trials_sorted, div = find_trial_ids(trials, sort='choice', event=event, order=order, contrast=contrast)
Expand Down Expand Up @@ -286,7 +286,7 @@ def filter_left_right(trials, event_raster, event, contrast, order='trial num'):
:param event: event to align to e.g 'goCue_times', 'stimOn_times'
:param contrast: contrast of stimulus, pass in list/tuple of all contrasts that want to be
considered e.g [1, 0.5] would only look for trials with 100 % and 50 % contrast
:param order: order to sort trials by either 'trial num' or 'reaction time'
:param order: order to sort trials by either 'trial num' or 'response time'
:return:
"""
trials_sorted, div = find_trial_ids(trials, sort='side', event=event, order=order, contrast=contrast)
Expand Down Expand Up @@ -314,7 +314,7 @@ def filter_trials(trials, event_raster, event, contrast=(1, 0.5, 0.25, 0.125, 0.
:param event: event to align to e.g 'goCue_times', 'stimOn_times'
:param contrast: contrast of stimulus, pass in list/tuple of all contrasts that want to be
considered e.g [1, 0.5] would only look for trials with 100 % and 50 % contrast
:param order: order to sort trials by either 'trial num' or 'reaction time'
:param order: order to sort trials by either 'trial num' or 'response time'
:param sort: how to divide trials options are 'choice' (e.g correct vs incorrect), 'side'
(e.g left vs right') and 'choice and side' (e.g correct vs incorrect and left vs right)
:return:
Expand Down
6 changes: 3 additions & 3 deletions examples/loading_data/loading_trials_data.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@
"# find index for stim right trials ordered by trial number\n",
"trial_id, _ = find_trial_ids(trials, side='right', choice='all', order='trial num')\n",
"\n",
"# find index for correct, stim left, 100% contrast trials ordered by reaction time\n",
"trial_id, _ = find_trial_ids(trials, side='left', choice='correct', contrast=[1], order='reaction time')\n",
"# find index for correct, stim left, 100% contrast trials ordered by RT\n",
"trial_id, _ = find_trial_ids(trials, side='left', choice='correct', contrast=[1], order='response time')\n",
"\n",
"# find index for correct trials ordered by trial number sorted by stimulus side\n",
"trial_id, _ = find_trial_ids(trials, side='left', choice='correct', order='reaction time', sort='side')"
"trial_id, _ = find_trial_ids(trials, side='left', choice='correct', order='response time', sort='side')"
]
},
{
Expand Down
20 changes: 10 additions & 10 deletions ibllib/pipes/training_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ def get_sess_dict(session_path, one, protocol, alf_collections=None, raw_collect
training.compute_psychometric(trials, block=0.8)

sess_dict['performance_easy'] = training.compute_performance_easy(trials)
sess_dict['reaction_time'] = training.compute_median_reaction_time(trials)
sess_dict['response_time'] = training.compute_median_reaction_time(trials)
sess_dict['n_trials'] = training.compute_n_trials(trials)
sess_dict['sess_duration'], sess_dict['n_delay'], sess_dict['location'] = \
compute_session_duration_delay_location(session_path, collections=raw_collections)
Expand Down Expand Up @@ -630,7 +630,7 @@ def get_training_info_for_session(session_paths, one, force=True):
psychs['80'] = training.compute_psychometric(combined_trials, block=0.8)

performance_easy = training.compute_performance_easy(combined_trials)
reaction_time = training.compute_median_reaction_time(combined_trials)
response_time = training.compute_median_response_time(combined_trials)
n_trials = training.compute_n_trials(combined_trials)

sess_duration = np.nansum([s['sess_duration'] for s in sess_dicts])
Expand All @@ -640,7 +640,7 @@ def get_training_info_for_session(session_paths, one, force=True):
sess_dict['combined_performance'] = performance
sess_dict['combined_contrasts'] = contrasts
sess_dict['combined_performance_easy'] = performance_easy
sess_dict['combined_reaction_time'] = reaction_time
sess_dict['combined_response'] = response_time
sess_dict['combined_n_trials'] = n_trials
sess_dict['combined_sess_duration'] = sess_duration
sess_dict['combined_n_delay'] = n_delay
Expand All @@ -665,7 +665,7 @@ def get_training_info_for_session(session_paths, one, force=True):
sess_dict['combined_performance'] = sess_dict['performance']
sess_dict['combined_contrasts'] = sess_dict['contrasts']
sess_dict['combined_performance_easy'] = sess_dict['performance_easy']
sess_dict['combined_reaction_time'] = sess_dict['reaction_time']
sess_dict['combined_response_time'] = sess_dict['response_time']
sess_dict['combined_n_trials'] = sess_dict['n_trials']
sess_dict['combined_sess_duration'] = sess_dict['sess_duration']
sess_dict['combined_n_delay'] = sess_dict['n_delay']
Expand Down Expand Up @@ -732,7 +732,7 @@ def plot_trial_count_and_session_duration(df, subject):
return ax


def plot_performance_easy_median_reaction_time(df, subject):
def plot_performance_easy_median_response_time(df, subject):
df = df.drop_duplicates('date').reset_index(drop=True)

y1 = {'column': 'combined_performance_easy',
Expand All @@ -741,9 +741,9 @@ def plot_performance_easy_median_reaction_time(df, subject):
'color': 'k',
'join': True}

y2 = {'column': 'combined_reaction_time',
'title': 'Median reaction time (s)',
'lim': [0.1, np.nanmax([10, np.nanmax(df.combined_reaction_time.values)])],
y2 = {'column': 'combined_response_time',
'title': 'Median response time (s)',
'lim': [0.1, np.nanmax([10, np.nanmax(df.combined_response_time.values)])],
'color': 'r',
'log': True,
'join': True}
Expand Down Expand Up @@ -989,7 +989,7 @@ def make_plots(session_path, one, df=None, save=False, upload=False, task_collec
return

ax1 = plot_trial_count_and_session_duration(df, subject)
ax2 = plot_performance_easy_median_reaction_time(df, subject)
ax2 = plot_performance_easy_median_response_time(df, subject)
ax3 = plot_heatmap_performance_over_days(df, subject)
ax4 = plot_fit_params(df, subject)
ax5 = plot_psychometric_curve(df, subject, one)
Expand All @@ -1001,7 +1001,7 @@ def make_plots(session_path, one, df=None, save=False, upload=False, task_collec
outputs.append(save_name)
ax1.get_figure().savefig(save_name, bbox_inches='tight')

save_name = save_path.joinpath('subj_performance_easy_reaction_time.png')
save_name = save_path.joinpath('subj_performance_easy_response_time.png')
outputs.append(save_name)
ax2.get_figure().savefig(save_name, bbox_inches='tight')

Expand Down
Loading