Skip to content

Commit

Permalink
Merge branch 'release/2.25.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
oliche committed Aug 25, 2023
2 parents 184a540 + a5facc9 commit e31bafb
Show file tree
Hide file tree
Showing 103 changed files with 9,911 additions and 4,234 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ include ibllib/atlas/cosmos.npy
include ibllib/atlas/swanson.npy
include ibllib/atlas/mappings.pqt
include ibllib/io/extractors/extractor_types.json
include ibllib/io/extractors/task_extractor_map.json
include brainbox/tests/wheel_test.p
recursive-include brainbox/tests/fixtures *
recursive-include ibllib/qc/reference *
graft ibllib/tests/extractors/data
graft ibllib/io/extractors/ephys_sessions
graft ibllib/io/extractors/mesoscope
graft ibllib/tests/fixtures
recursive-include oneibl/tests/fixtures *
1 change: 1 addition & 0 deletions brainbox/behavior/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Behaviour analysis functions for the IBL task."""
2 changes: 1 addition & 1 deletion brainbox/behavior/dlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from scipy.stats import zscore

from neurodsp.smooth import smooth_interpolate_savgol
from brainbox.processing import bincount2D
from iblutil.numerical import bincount2D
import brainbox.behavior.wheel as bbox_wheel

logger = logging.getLogger('ibllib')
Expand Down
16 changes: 15 additions & 1 deletion brainbox/behavior/pyschofit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
The psychofit toolbox contains tools to fit two-alternative psychometric
(DEPRECATED) The psychofit toolbox contains tools to fit two-alternative psychometric
data. The fitting is done using maximal likelihood estimation: one
assumes that the responses of the subject are given by a binomial
distribution whose mean is given by the psychometric function.
Expand All @@ -16,14 +16,28 @@
For more info, see:
- Examples: Examples of use of psychofit toolbox
Matteo Carandini, 2000-2015
NB: USE THE PSYCHOFIT PIP PACKAGE INSTEAD.
"""

import functools
import warnings
import traceback
import logging

import numpy as np
import scipy.optimize
from scipy.special import erf


for line in traceback.format_stack():
print(line.strip())

msg = 'brainbox.behavior.pyschofit has been deprecated. Install psychofit via pip. See stack above'
warnings.warn(msg, DeprecationWarning)
logging.getLogger(__name__).warning(msg)


def mle_fit_psycho(data, P_model='weibull', parstart=None, parmin=None, parmax=None, nfits=5):
"""
Maximumum likelihood fit of psychometric function.
Expand Down
18 changes: 9 additions & 9 deletions brainbox/behavior/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from one.api import ONE
from one.alf.exceptions import ALFObjectNotFound

import brainbox.behavior.pyschofit as psy
import psychofit as psy

_logger = logging.getLogger('ibllib')

Expand Down Expand Up @@ -123,9 +123,9 @@ def get_subject_training_status(subj, date=None, details=True, one=None):

def get_sessions(subj, date=None, one=None):
"""
Download and load in training data for a specfied subject. If a date is given it will load data
from the three (or as many are available) previous sessions up to the specified date, if not it
will load data from the last three training sessions that have data available
Download and load in training data for a specified subject. If a date is given it will load
data from the three (or as many as are available) previous sessions up to the specified date.
If not it will load data from the last three training sessions that have data available.
:param subj: subject nickname (must match the name registered on Alyx)
:type subj: string
Expand Down Expand Up @@ -227,7 +227,7 @@ def get_training_status(trials, task_protocol, ephys_sess_dates, n_delay):
"""
Compute training status of a subject from three consecutive training datasets
:param trials: dict containing trials objects from three consective training sessions
:param trials: dict containing trials objects from three consecutive training sessions
:type trials: Bunch
:param task_protocol: task protocol used for the three training session, can be 'training',
'biased' or 'ephys'
Expand Down Expand Up @@ -385,7 +385,7 @@ def compute_training_info(trials, trials_all):
"""
Compute all relevant performance metrics for when subject is on trainingChoiceWorld
:param trials: dict containing trials objects from three consective training sessions,
:param trials: dict containing trials objects from three consecutive training sessions,
keys are session dates
:type trials: Bunch
:param trials_all: trials object with data concatenated over three training sessions
Expand All @@ -410,7 +410,7 @@ def compute_bias_info(trials, trials_all):
"""
Compute all relevant performance metrics for when subject is on biasedChoiceWorld
:param trials: dict containing trials objects from three consective training sessions,
:param trials: dict containing trials objects from three consecutive training sessions,
keys are session dates
:type trials: Bunch
:param trials_all: trials object with data concatenated over three training sessions
Expand Down Expand Up @@ -667,7 +667,7 @@ def criterion_delay(n_trials, perf_easy):

def plot_psychometric(trials, ax=None, title=None, plot_ci=False, ci_aplha=0.32, **kwargs):
"""
Function to plot pyschometric curve plots a la datajoint webpage
Function to plot psychometric curve plots a la datajoint webpage
:param trials:
:return:
"""
Expand Down Expand Up @@ -730,7 +730,7 @@ def plot_psychometric(trials, ax=None, title=None, plot_ci=False, ci_aplha=0.32,

def plot_reaction_time(trials, ax=None, title=None, plot_ci=False, ci_alpha=0.32, **kwargs):
"""
Function to plot reaction time against contrast a la datajoint webpage (inversed for some reason??)
Function to plot reaction time against contrast a la datajoint webpage (inverted for some reason??)
:param trials:
:return:
"""
Expand Down
107 changes: 92 additions & 15 deletions brainbox/behavior/wheel.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
"""
Set of functions to handle wheel data
Set of functions to handle wheel data.
"""
import logging
import warnings
import traceback

import numpy as np
from numpy import pi
from iblutil.numerical import between_sorted
import scipy.interpolate as interpolate
import scipy.signal
from scipy.linalg import hankel
Expand All @@ -13,11 +18,11 @@
__all__ = ['cm_to_deg',
'cm_to_rad',
'interpolate_position',
'last_movement_onset',
'get_movement_onset',
'movements',
'samples_to_cm',
'traces_by_trial',
'velocity_smoothed']
'velocity_filtered']

# Define some constants
ENC_RES = 1024 * 4 # Rotary encoder resolution, assumes X4 encoding
Expand Down Expand Up @@ -49,6 +54,8 @@ def interpolate_position(re_ts, re_pos, freq=1000, kind='linear', fill_gaps=None
Timestamps of interpolated positions
"""
t = np.arange(re_ts[0], re_ts[-1], 1 / freq) # Evenly resample at frequency
if t[-1] > re_ts[-1]:
t = t[:-1] # Occasionally due to precision errors the last sample may be outside of range.
yinterp = interpolate.interp1d(re_ts, re_pos, kind=kind)(t)

if fill_gaps:
Expand All @@ -63,7 +70,7 @@ def interpolate_position(re_ts, re_pos, freq=1000, kind='linear', fill_gaps=None

def velocity(re_ts, re_pos):
"""
Compute wheel velocity from non-uniformly sampled wheel data. Returns the velocity
(DEPRECATED) Compute wheel velocity from non-uniformly sampled wheel data. Returns the velocity
at the same samples locations as the position through interpolation.
Parameters
Expand All @@ -78,6 +85,13 @@ def velocity(re_ts, re_pos):
np.ndarray
numpy array of velocities
"""
for line in traceback.format_stack():
print(line.strip())

msg = 'brainbox.behavior.wheel.velocity has been deprecated. Use velocity_filtered instead.'
warnings.warn(msg, DeprecationWarning)
logging.getLogger(__name__).warning(msg)

dp = np.diff(re_pos)
dt = np.diff(re_ts)
# Compute raw velocity
Expand All @@ -92,12 +106,23 @@ def velocity(re_ts, re_pos):

def velocity_filtered(pos, fs, corner_frequency=20, order=8):
"""
Compute wheel velocity from uniformly sampled wheel data
Compute wheel velocity from uniformly sampled wheel data.
pos: array_like
Vector of uniformly sampled wheel positions.
fs : float
Frequency in Hz of the sampling frequency.
corner_frequency : float
Corner frequency of low-pass filter.
order : int
Order of Butterworth filter.
:param pos: vector of uniformly sampled wheel positions
:param fs: scalar, sampling frequency
:param corner_frequency: scalar, corner frequency of low-pass filter
:param order: scalar, order of Butterworth filter
Returns
-------
vel : np.ndarray
Array of velocity values.
acc : np.ndarray
Array of acceleration values.
"""
sos = scipy.signal.butter(**{'N': order, 'Wn': corner_frequency / fs * 2, 'btype': 'lowpass'}, output='sos')
vel = np.insert(np.diff(scipy.signal.sosfiltfilt(sos, pos)), 0, 0) * fs
Expand All @@ -107,7 +132,7 @@ def velocity_filtered(pos, fs, corner_frequency=20, order=8):

def velocity_smoothed(pos, freq, smooth_size=0.03):
"""
Compute wheel velocity from uniformly sampled wheel data
(DEPRECATED) Compute wheel velocity from uniformly sampled wheel data.
Parameters
----------
Expand All @@ -125,6 +150,13 @@ def velocity_smoothed(pos, freq, smooth_size=0.03):
acc : np.ndarray
Array of acceleration values
"""
for line in traceback.format_stack():
print(line.strip())

msg = 'brainbox.behavior.wheel.velocity_smoothed has been deprecated. Use velocity_filtered instead.'
warnings.warn(msg, DeprecationWarning)
logging.getLogger(__name__).warning(msg)

# Define our smoothing window with an area of 1 so the units won't be changed
std_samps = np.round(smooth_size * freq) # Standard deviation relative to sampling frequency
N = std_samps * 6 # Number of points in the Gaussian covering +/-3 standard deviations
Expand All @@ -141,15 +173,24 @@ def velocity_smoothed(pos, freq, smooth_size=0.03):

def last_movement_onset(t, vel, event_time):
"""
Find the time at which movement started, given an event timestamp that occurred during the
movement. Movement start is defined as the first sample after the velocity has been zero
for at least 50ms. Wheel inputs should be evenly sampled.
(DEPRECATED) Find the time at which movement started, given an event timestamp that occurred during the
movement.
Movement start is defined as the first sample after the velocity has been zero for at least 50ms.
Wheel inputs should be evenly sampled.
:param t: numpy array of wheel timestamps in seconds
:param vel: numpy array of wheel velocities
:param event_time: timestamp anywhere during movement of interest, e.g. peak velocity
:return: timestamp of movement onset
"""
for line in traceback.format_stack():
print(line.strip())

msg = 'brainbox.behavior.wheel.last_movement_onset has been deprecated. Use get_movement_onset instead.'
warnings.warn(msg, DeprecationWarning)
logging.getLogger(__name__).warning(msg)

# Look back from timestamp
threshold = 50e-3
mask = t < event_time
Expand All @@ -166,6 +207,42 @@ def last_movement_onset(t, vel, event_time):
return t


def get_movement_onset(intervals, event_times):
"""
Find the time at which movement started, given an event timestamp that occurred during the
movement.
Parameters
----------
intervals : numpy.array
The wheel movement intervals.
event_times : numpy.array
Sorted event timestamps anywhere during movement of interest, e.g. peak velocity, feedback
time.
Returns
-------
numpy.array
An array the length of event_time of intervals.
Examples
--------
Find the last movement onset before each trial response time
>>> trials = one.load_object(eid, 'trials')
>>> wheelMoves = one.load_object(eid, 'wheelMoves')
>>> onsets = last_movement_onset(wheelMoves.intervals, trials.response_times)
"""
if not np.all(np.diff(event_times) > 0):
raise ValueError('event_times must be in ascending order.')
onsets = np.full(event_times.size, np.nan)
for i in np.arange(intervals.shape[0]):
onset = between_sorted(event_times, intervals[i, :])
if np.any(onset):
onsets[onset] = intervals[i, 0]
return onsets


def movements(t, pos, freq=1000, pos_thresh=8, t_thresh=.2, min_gap=.1, pos_thresh_onset=1.5,
min_dur=.05, make_plots=False):
"""
Expand Down Expand Up @@ -296,7 +373,7 @@ def movements(t, pos, freq=1000, pos_thresh=8, t_thresh=.2, min_gap=.1, pos_thre
if make_plots:
fig, axes = plt.subplots(nrows=2, sharex='all')
indices = np.sort(np.hstack((onset_samps, offset_samps))) # Points to split trace
vel, acc = velocity_smoothed(pos, freq, 0.015)
vel, acc = velocity_filtered(pos, freq)

# Plot the wheel position and velocity
for ax, y in zip(axes, (pos, vel)):
Expand Down Expand Up @@ -440,6 +517,6 @@ def to_mask(a, b):
return [(cuts[n][0, :], cuts[n][1, :]) for n in range(len(cuts))] if separate else cuts


if __name__ == "__main__":
if __name__ == '__main__':
import doctest
doctest.testmod()
3 changes: 2 additions & 1 deletion brainbox/ephys_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import matplotlib.pyplot as plt
from brainbox.plot_base import (ImagePlot, ScatterPlot, ProbePlot, LinePlot, plot_line,
plot_image, plot_probe, plot_scatter, arrange_channels2banks)
from brainbox.processing import bincount2D, compute_cluster_average
from brainbox.processing import compute_cluster_average
from iblutil.numerical import bincount2D
from ibllib.atlas.regions import BrainRegions


Expand Down
Loading

0 comments on commit e31bafb

Please sign in to comment.