Skip to content

Commit

Permalink
Merge branch 'iblrigv8dev' into photometry
Browse files Browse the repository at this point in the history
  • Loading branch information
bimac authored Nov 15, 2024
2 parents 7938783 + 5b0f763 commit 3d8eaa5
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 1 deletion.
3 changes: 2 additions & 1 deletion iblrig/online_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pandas.api.types import CategoricalDtype

import one.alf.io
import one.alf.path
from iblrig.choiceworld import get_subject_training_info
from iblrig.misc import online_std
from iblrig.raw_data_loaders import load_task_jsonable
Expand Down Expand Up @@ -50,7 +51,7 @@ class DataModel:
time_elapsed = 0.0

def __init__(self, settings_file: Path | None):
self.session_path = one.alf.files.get_session_path(settings_file) or ''
self.session_path = one.alf.path.get_session_path(settings_file) or ''
self.last_trials = pd.DataFrame(
columns=['correct', 'signed_contrast', 'stim_on', 'play_tone', 'reward_time', 'error_time', 'response_time'],
index=np.arange(NTRIALS_PLOT),
Expand Down
80 changes: 80 additions & 0 deletions iblrig/raw_data_loaders.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import json
import logging
import re
from pathlib import Path
from typing import Any

import pandas as pd
from pandas.core.dtypes.concat import union_categoricals

log = logging.getLogger(__name__)
RE_PATTERN_EVENT = re.compile(r'^(\D+\d)_?(.+)$')


def load_task_jsonable(jsonable_file: str | Path, offset: int | None = None) -> tuple[pd.DataFrame, list[Any]]:
Expand Down Expand Up @@ -37,3 +40,80 @@ def load_task_jsonable(jsonable_file: str | Path, offset: int | None = None) ->

trials_table = pd.DataFrame(trials_table)
return trials_table, bpod_data


def bpod_session_events_to_dataframe(bpod_data: list[dict[str, Any]], trials: int | list[int] | slice | None = None):
"""
Convert Bpod session data into a single Pandas DataFrame.
Parameters
----------
bpod_data : list of dict
A list of dictionaries as returned by load_task_jsonable, where each dictionary contains data for a single trial.
trials : int, list of int, slice, or None, optional
Specifies which trials to include in the DataFrame. All trials are included by default.
Returns
-------
pd.DataFrame
A Pandas DataFrame containing combined event data from the specified trials, with columns for time, event, channel,
and value.
"""
# define trial index
if trials is None:
trials = range(len(bpod_data))
elif isinstance(trials, int):
return _bpod_trial_events_to_dataframe(bpod_data[trials], trials)
elif isinstance(trials, slice):
trials = range(len(bpod_data))[trials]

# loop over requested trials
dataframes = []
for trial in trials:
dataframes.append(_bpod_trial_events_to_dataframe(bpod_data[trial], trial))

# combine trials into a single dataframe
categories_event = union_categoricals([df['Event'] for df in dataframes])
categories_channel = union_categoricals([df['Channel'] for df in dataframes])
for df in dataframes:
df['Event'] = df['Event'].cat.set_categories(categories_event.categories)
df['Channel'] = df['Channel'].cat.set_categories(categories_channel.categories)
return pd.concat(dataframes, ignore_index=True)


def _bpod_trial_events_to_dataframe(bpod_trial_data: dict[str, Any], trial: int) -> pd.DataFrame:
"""
Convert a single Bpod trial's data into a Pandas DataFrame.
Parameters
----------
bpod_trial_data : dict
A dictionary containing data for a single trial, including timestamps and events.
trial : int
An integer representing the trial index.
Returns
-------
pd.DataFrame
A Pandas DataFrame containing the event data for the specified trial, with columns for time, event, channel, and value.
"""
trial_start = bpod_trial_data['Trial start timestamp']
trial_end = bpod_trial_data['Trial end timestamp']

# convert bpod trial data to list of tuples, containing timestamps and event-names
event_list = [(time, event) for event, times in bpod_trial_data['Events timestamps'].items() for time in times]
event_list = [(0, 'TrialStart')] + sorted(event_list) + [(trial_end - trial_start, 'TrialEnd')]

# create dataframe
df = pd.DataFrame(data=event_list, columns=['Time', 'Event'])
df['Time'] = pd.to_timedelta(df['Time'] + trial_start, unit='seconds')
df['Event'] = df['Event'].astype('category')
df.insert(1, 'Trial', pd.to_numeric(pd.Series(trial, index=df.index), downcast='unsigned'))

# deduce channels and values from event names
df[['Channel', 'Value']] = df['Event'].str.extract(RE_PATTERN_EVENT, expand=True)
df['Channel'] = df['Channel'].astype('category')
df['Value'] = df['Value'].replace({'Low': 0, 'High': 1, 'Out': 0, 'In': 1})
df['Value'] = pd.to_numeric(df['Value'], errors='coerce', downcast='unsigned', dtype_backend='numpy_nullable')

return df

0 comments on commit 3d8eaa5

Please sign in to comment.