Protocol Creation
Project Structure
Training data and code are organized into projects. The project folder structure is created automatically when a new project is initialized.
village_projects/
└── projectName/
├── code/
├── media/
└── data/
code/: All executable Python scripts for tasks and training logic.media/: (Optional) Audio, video, or image assets used by behavioral tasks.data/: All experimental outputs — raw and processed data files and videos.
The first time you run the Training Village, a village-demo-project is created
automatically with working example code you can use as a reference.
Code Organization
A training protocol consists of one or more Python task scripts plus a mandatory
training_protocol.py file. The training protocol runs automatically every time a
subject finishes a session and contains the logic to advance or regress the subject
through the training stages — changing tasks, adjusting parameters, or both.
Typical code structures
# Example 1: Multiple sequential tasks
code/
├── habituation.py
├── lick_teaching.py
├── simple_task.py
├── final_task.py
└── training_protocol.py
# Example 2: One task with progressive difficulty
code/
├── behavioral_task.py
└── training_protocol.py
In Example 1, each script corresponds to a distinct training stage:
habituation— animals get acquainted with the operant box.lick_teaching— animals learn to interact with the behavioral ports.simple_task— a simplified version of the final task.final_task— the full experimental task.
In Example 2, a single task is used throughout, but training_protocol.py adjusts its
parameters after every session to increase difficulty progressively.
Both approaches can be combined freely. In addition to task scripts and the training
protocol, a typical code/ folder contains helper modules for plotting, sound or video
generation, and direct hardware functions:
code/
├── __init__.py
├── habituation.py
├── follow_the_light.py
├── training_protocol.py
├── session_plot.py
├── subject_plot.py
├── online_plot.py
├── direct_functions.py
├── sound_functions.py
├── LICENSE
└── README.md
The Training Protocol
The training protocol must live in a file named exactly training_protocol.py inside
your code/ folder. It defines a class called TrainingProtocol that inherits from
TrainingProtocolBase:
from village.classes.training import TrainingProtocolBase
class TrainingProtocol(TrainingProtocolBase):
"""
Defines the automated training logic for the project.
Runs every time a session ends and determines:
- Which task the subject will run next
- How training parameters are updated based on performance
- How long the subject must wait before its next session (refractory period)
Required methods:
- __init__
- default_training_settings
- update_training_settings
Optional method:
- gui_tabs
"""
def __init__(self) -> None:
super().__init__()
default_training_settings()
This method defines all training variables and their initial values. It is called once
when a new subject is created. The variables defined here are accessible from within
any task via self.settings.<variable_name>.
After creation, a subject’s settings can be modified in three ways: manually from the
SUBJECTS tab or from the TASKS tab when launching a task manually; in real time from
within a running task; or automatically by update_training_settings() at the end of
each session.
Warning
update_training_settings() always starts from the settings the subject had at the
moment the session ended — including any values that were changed manually before or
during the session. Only the variables explicitly reassigned inside
update_training_settings() will be overwritten; all others will retain whatever value
they had when the session finished. Keep this in mind if you change a setting manually
and do not want it to persist: make sure update_training_settings() resets it
explicitly.
def default_training_settings(self) -> None:
"""
Define all initial training parameters for new subjects.
Required parameters:
- next_task (str): Name of the first task the subject will run.
- refractory_period (int): Seconds the subject must wait between sessions.
- minimum_duration (int): Seconds before door 2 opens (subject may leave).
- maximum_duration (int): Seconds before the task stops automatically.
Any additional task-specific parameters can be defined below.
"""
# Required parameters
self.settings.next_task = "Habituation"
self.settings.refractory_period = 3600 * 4 # 4 hours between sessions
self.settings.minimum_duration = 600 # 10 min minimum session length
self.settings.maximum_duration = 900 # 15 min maximum session length
# Task-specific parameters
self.settings.reward_amount_ml = 0.08
self.settings.stage = 1
self.settings.light_intensity_high = 255 # Port light intensity (0–255)
self.settings.light_intensity_low = 50
self.settings.trial_types = ["left_easy", "right_easy",
"left_hard", "right_hard"]
self.settings.punishment_time = 1 # seconds
self.settings.iti_time = 2 # inter-trial interval, seconds
self.settings.response_time = 10 # seconds before timeout
next_task determines the first task run for a newly created subject. refractory_period
controls how long a subject must wait after finishing a session before it is allowed back
into the operant box — important in multi-animal setups to prevent individual animals from
monopolizing access. minimum_duration is when door 2 opens and the animal can choose to
leave; maximum_duration is when the task stops unconditionally and the system waits for
the animal to return home (door 2 is already open at this point, so
maximum_duration ≥ minimum_duration always).
update_training_settings()
This method runs automatically at the end of every session. It receives the subject’s
full session history as a DataFrame and updates whichever settings should change based
on performance. The updated values are stored back into subjects.csv and used in the
subject’s next session.
Available attributes:
self.subject— name of the current subjectself.last_task— name of the task that just finishedself.df— DataFrame with all historical session data for this subject
def update_training_settings(self) -> None:
if self.last_task == "Habituation":
df_habituation = self.df[self.df["task"] == "Habituation"]
if len(df_habituation) >= 2:
trials_last_session = df_habituation.iloc[-1]["trial"].iloc[-1]
if trials_last_session >= 100:
self.settings.next_task = "FollowTheLight"
self.settings.reward_amount_ml = 0.07
elif self.last_task == "FollowTheLight":
df_ftl = self.df[self.df["task"] == "FollowTheLight"]
if len(df_ftl) >= 2:
perf_last = df_ftl.iloc[-1]["correct"].mean()
perf_prev = df_ftl.iloc[-2]["correct"].mean()
trials_last = df_ftl.iloc[-1]["trial"].iloc[-1]
trials_prev = df_ftl.iloc[-2]["trial"].iloc[-1]
if (perf_last >= 0.85 and perf_prev >= 0.85 and
trials_last >= 100 and trials_prev >= 100):
self.settings.stage = 2
self.settings.reward_amount_ml = 0.05
gui_tabs() (optional)
If your protocol has many variables, this method lets you organize them into named tabs
in the GUI panel that appears when launching a task manually. Variables not assigned to
any tab are placed in a default General tab. You can also use the reserved "Hide"
tab name to suppress a variable from the GUI entirely.
You can additionally restrict the allowed values for any variable, which causes a dropdown menu to appear instead of a free-text field.
def gui_tabs(self) -> None:
self.gui_tabs = {
"Port_variables": ["reward_amount_ml",
"light_intensity_high",
"light_intensity_low"],
"Other_variables": ["stage",
"trial_types",
"punishment_time",
"iti_time",
"response_time"],
}
# Restrict allowed values — renders as a dropdown in the GUI
self.gui_tabs_restricted = {
"trial_types": ["left_easy", "right_easy", "left_hard", "right_hard"],
}
Summary
Every TrainingProtocol class must implement three methods:
Method |
When it runs |
Purpose |
|---|---|---|
|
At import |
Initialize the class |
|
When a new subject is created |
Define initial parameter values |
|
After every session ends |
Update parameters based on performance |
The optional gui_tabs method controls how parameters are displayed when launching a
task manually from the GUI.