Full Experiment: Breath Tracking Task¶
Script: respyra/scripts/breath_tracking_task.py
What it demonstrates: The complete respiratory tracking experiment with calibration, multi-condition trials, and data logging.
How to run¶
# Default configuration
respyra-task
# Built-in config by short name
respyra-task --config demo
respyra-task --config validation_study
# Custom config file
respyra-task --config experiments/my_study.py
To customize the experiment, see the Creating Experiments tutorial.
Architecture overview¶
The experiment is built from composable phase functions in respyra.core.runner:
Belt connection (
connect_belt) — connects before PsychoPy import (Windows BLE requirement)Display setup (
setup_display) — monitor profile, window, pre-created stimuliParticipant dialog (
run_participant_dialog) — collects participant ID and session numberRange calibration (
run_range_calibration) — 15 s of deep breaths to establish breathing rangeTrial loop — for each condition:
run_baseline(10 s) →run_countdown(3 s) →run_tracking(30 s) →show_trial_feedback
Cleanup — belt stop, file close, window close (in
finallyblock)
All phases share state via an ExperimentState dataclass.
Key code patterns¶
Belt-before-PsychoPy import order¶
from respyra.core.runner import connect_belt, setup_display
# 1. Connect belt BEFORE importing PsychoPy
belt = connect_belt(cfg)
# 2. Now safe to import PsychoPy and set up display
win, stimuli = setup_display(cfg)
Configuration-driven setup¶
All parameters come from an ExperimentConfig:
from respyra.configs.experiment_config import load_config
from respyra.core.runner import run_experiment
cfg = load_config("demo") # or a file path, or an ExperimentConfig instance
run_experiment(cfg)
Pre-created stimuli¶
All visual stimuli are created once by setup_display before the frame loop:
win, stimuli = setup_display(cfg)
# stimuli["trace"], stimuli["trace_border"], stimuli["phase_title"],
# stimuli["status_text"], stimuli["countdown_text"], stimuli["target_dot"]
Gain perturbation¶
The displayed waveform is perturbed; the dot color reflects the visual (compensated) error:
from respyra.core.runner import apply_gain
# Display: apply gain to the visual trace
stimuli["trace"].draw(apply_gain(buffer, feedback_gain, range_center))
# Target position uses true force; dot color uses visual error
target_force = target_gen.get_target(tracking_t)
error = target_force - force # true (physical) error
visual_force = center + feedback_gain * (force - center)
compensated_error = target_force - visual_force # visual error (drives dot color)
Incremental logging with DataLogger¶
logger = DataLogger(filepath, columns=cfg.data_columns)
# Inside frame loop:
logger.log_row(
timestamp=round(elapsed, 4),
frame=frame_count,
force_n=round(force, 4),
phase='tracking',
condition=condition_name,
...
)
PsychoPy TrialHandler for condition ordering¶
trial_list = [{'condition': c.name} for c in conditions]
trials = data.TrialHandler(
trialList=trial_list, nReps=cfg.trial.n_reps, method=cfg.trial.method,
)
for trial in trials:
condition_name = trial['condition']
...