Task Methods & Attributes

Full reference for TaskBase — every method you can override or call, and every attribute you can read, from inside your own task. For a walkthrough of building a task from scratch, see the Task Development Guide.


Methods you must override

Method

Called

Use it to

start(self)

Once, before the trial loop begins.

Configure hardware, pre-compute stimuli, open files, etc.

create_trial(self)

Once at the start of every trial.

Build and run the trial.

after_trial(self)

Immediately after each trial finishes.

Score performance, update adaptive parameters, call register_value.

close(self)

Once after the trial loop ends (session finished, forced stop, or error).

Close files, stop hardware, release resources.

create_trial with Bpod

Each call starts with an empty state machine. Add states with self.bpod.add_state — when the method returns, the state machine is sent and run automatically, and after_trial is called once it finishes. Without Bpod, you’re responsible for creating and running the trial logic yourself.

after_trial must register “water”

Always call self.register_value("water", ...) with the amount of water consumed during the trial. The system sums it across the session to get total water consumption — if that total falls below a configurable threshold, an alarm is triggered (adjustable in the Settings tab of the GUI).


Methods you can call

Do not override these — call them from inside your task’s own methods.

Registering controller events

If you’re using Bpod, all four of these are called automatically — you never need to call them yourself. With BEHAVIOR_CONTROLLER set to OTHER, call them with the right timestamps to populate trial_data (see below).

Method

Args

When to call it (OTHER)

register_start_trial

raspberry_timestamp: float, controller_timestamp: float

At the beginning of each trial.

register_end_trial

controller_timestamp: float

At the end of each trial.

register_enter_state

state_name: str, controller_timestamp: float

Whenever you enter a state.

register_controller_event

name: str, controller_timestamp: float

To register any other controller event.

  • raspberry_timestamp: the Raspberry Pi’s own clock — get it with time_utils.now_timestamp().

  • controller_timestamp: read from the microcontroller’s own clock (e.g. an Arduino’s millis(), converted to seconds), if you’re talking to one and it reports its own timing.

  • If you’re not syncing against an external clock (no microcontroller, or one that doesn’t report its own timing), controller_timestamp is exactly the same as raspberry_timestamp.

register_start_trial uses the two timestamps together to compute a clock offset (raspberry_timestamp - controller_timestamp), used to convert every later controller_timestamp back to Raspberry Pi absolute time.

Registering a Raspberry-side event

def register_raspberry_event(self, name: str, raspberry_timestamp: float) -> None

For events generated by the Raspberry Pi itself, asynchronously from the controller — e.g. executing a direct function, or a camera/touchscreen detection. Pass time_utils.now_timestamp() as raspberry_timestamp.

Registering a value

def register_value(self, name: str, value: Any) -> None

Saves a custom value to the current trial row in the CSV — call it inside after_trial.

self.register_value("correct", 1)
self.register_value("response_time", 0.432)

Executing a direct function

def execute_function(self, i: int) -> None

Runs the direct function registered at index i (1-99). See Direct, Audio & Video Functions for how those get registered and the other ways they can be triggered.

self.execute_function(1)  # executes the function registered at index 1

Attributes you can read

Attribute

Type

Description

self.name

str

Name of the task (the class name).

self.subject

str

Name of the subject running the session.

self.system_name

str

Name of the system, as defined in the Settings tab.

self.bpod

BpodController

Bpod interface — state machine construction, sending, etc. Mainly used inside create_trial.

self.arduino

ArduinoController

Arduino interface, for tasks using an Arduino instead of Bpod.

self.settings

Settings

Session parameters defined in the training protocol. Read and write its attributes to implement adaptive training.

self.cam_box

Camera | NullCamera

Camera attached to the operant box (NullCamera if not configured).

self.gpio

Gpio | NullGpio

Output pin control (set_on() / set_off()). See Custom GPIO Interaction for the input-pin trigger hook.

self.current_trial

int

The current trial number, starting from 1.

self.date

str

Date string of the current session, set once when it starts.

self.run_mode

str

"Manual" or "Auto".

self.force_stop

bool

Set to True from your own task logic (e.g. in after_trial) to make the task stop.

self.stop_button_pressed

bool

True once something external (e.g. the STOP TASK button) has asked the task to stop. Read-only — react to it, don’t set it.

self.chrono

Chrono

self.chrono.get_seconds() gives the time in seconds since the task started.

self.info

Human-readable description of the task, set once (usually in __init__). Shown to the user when selecting the task in the GUI.

self.info = """
My Task
-------
Describe what the task does and how it progresses here.
"""

self.calibrations

Holds the task’s calibrations — call its methods to convert between hardware values and real-world units (see Custom Calibrations).

gain = self.calibrations.sound_calibration.get_sound_gain(
    speaker=1, dB=70.0, sound_name="white_noise"
)

self.should_stop (read-only property)

True once any stopping condition is met: the trial/time limit (Manual mode only — Auto mode runs until the subject leaves the corridor, not for a fixed number of trials), force_stop, or stop_button_pressed.

Needed for a task with its own waiting loop

Trial/time limits and force_stop are only checked between trials, by the task’s own run loop — they can’t interrupt a create_trial that’s blocked inside its own waiting loop. If your task doesn’t use Bpod and waits on its own condition inside create_trial, poll self.should_stop so the loop actually exits when asked to:

def create_trial(self):
    while not self.should_stop:
        ...  # wait for your own condition

self.trial_data (dict)

Populated automatically at the end of each trial — available inside after_trial.

Keys always present:

  • "date" (str), "trial" (int), "subject" (str), "task" (str), "system_name" (str)

  • "TRIAL_START" / "TRIAL_END" (float): absolute timestamps (UNIX epoch seconds)

  • "ordered_list_of_events" (list[str]): event names in the order they occurred (e.g. ["Port1In", "Port1Out", "Port1In"])

Keys added per state visited (Bpod or manual):

  • "STATE_<name>_START" / "STATE_<name>_END" (list[float]): timestamps of every entry/exit for that state — a list because the same state can be visited more than once per trial.

Keys added per event type:

  • "<EventName>" (list[float]): timestamps of every occurrence of that event (e.g. "Port1In", "Port1Out").

When using Bpod, all state and event keys are filled in automatically. Without Bpod, call register_enter_state and register_controller_event / register_raspberry_event yourself to populate them.

Keys added by you: any name passed to register_value inside after_trial.

def after_trial(self):
    if self.trial_data.get("correct") == 1:
        self.settings.difficulty += 1
    t_start = self.trial_data["STATE_stimulus_START"][0]
    t_end = self.trial_data["STATE_stimulus_END"][0]
    response_time = t_end - t_start