Extending Tobii-Pytracker
Tobii-Pytracker is designed to be extensible, allowing users to add new features and customize existing functionality. This guide provides an overview of how to extend Tobii-Pytracker.
Extending CustomDataset
Create a dataset class by inheriting from CustomDataset. A custom dataset is
responsible for loading its source files and converting them into the sample
format used by the GUI and runtime models.
The constructor receives the active configuration object. Call the parent
constructor so that config, dataset_path, classes, data, and
calculate_bboxes are initialized. The two methods that define the dataset
behavior are:
prepare_data()loads the dataset and returns a list of samples.draw_stimulus(window, sample)renders one sample and returns a dictionary with optional stimulus metadata, such as bounding boxes.
Each sample should normally contain class, data, and id. The value
of data may be a path, text, array, or another representation understood by
draw_stimulus. Set self.classes to the available labels, including
"none" when that class is useful for the experiment.
from tobii_pytracker.datasets import CustomDataset
from psychopy import visual
class CsvDataset(CustomDataset):
def __init__(self, config, calculate_bboxes=False):
super().__init__(config, calculate_bboxes)
self.data = self.prepare_data()
def prepare_data(self):
# Load config.get_dataset_path() and return sample dictionaries.
return [{"class": "example", "data": "text to show", "id": "1"}]
def draw_stimulus(self, window: visual.Window, sample):
visual.TextStim(window, text=str(sample["data"]), pos=(0, 0)).draw()
window.flip()
return {"bboxes": []}
If bounding boxes are needed, return them in the metadata dictionary using the
format expected by the consuming code. For image datasets, the built-in
implementation returns image_bboxes containing dictionaries with
class, conf, and a centered bbox. Dataset-specific configuration
accessors can be used when the format needs validation or additional options.
Extending CustomModel
Custom models are loaded with CustomModel(config, dataset). Inherit from
CustomModel and implement all three abstract methods:
prepare_model()loads weights and initializes the inference backend.predict(input_data)runs one prediction using the prepared model.process(path)converts backend output into the project format.
CustomModel provides config, dataset_class_names, is_text, and a
logger. The dataset class names allow a detector to discard predictions that do
not belong to the current experiment. For image bounding-box models,
process should return tuples of (class_name, confidence, bbox) where
bbox is (x_min, y_min, x_max, y_max) in top-left image coordinates.
from tobii_pytracker.runtime_models import CustomModel
class ExampleModel(CustomModel):
def prepare_model(self):
self.model = load_my_model(self.config)
def predict(self, input_data):
return self.model(input_data)
def process(self, path):
predictions = self.predict(path)
return [
(label, float(score), (x_min, y_min, x_max, y_max))
for label, score, x_min, y_min, x_max, y_max in predictions
if label in self.dataset_class_names
]
The model should do backend-specific parsing in process rather than in the
dataset. The built-in image dataset rescales the returned box to the configured
area of interest and converts it to centered coordinates. Keep model loading in
prepare_model so initialization failures are reported when the model is
constructed, not during a later trial.
Extending BaseAnalyzer
Analyzers operate on flattened pandas data. Inherit from BaseAnalyzer and
call super().__init__(output_folder). The required public methods are:
analyze(background_data, ...)computes metrics and assigns the resulting DataFrame toself.results.plot_analysis(...)visualizes the analysis, optionally saving a figure.
save_results() is already implemented by BaseAnalyzer and writes
self.results as JSON. An analyzer can override it when it needs additional
formats or nested-data handling, as VoiceTranscription does.
from pathlib import Path
import pandas as pd
from tobii_pytracker.analyze.models import BaseAnalyzer
class DwellTimeAnalyzer(BaseAnalyzer):
def analyze(self, background_data: pd.DataFrame, per="slide"):
results = (background_data
.groupby(["set_name", "slide_index"])
.agg(samples=("system_time", "count"))
.reset_index())
self.results = results
return results
def plot_analysis(self, background_data, screenshot_path, show=True,
save_path=None, **kwargs):
# Create a matplotlib visualization and optionally save it.
pass
Use the column names produced by DataLoader or validate required columns
at the beginning of analyze. Grouping should follow the analyzer’s chosen
per convention (usually global, set, or slide), and plotting
should accept a filtered subset when a screenshot represents one slide. Keep
analysis independent of file loading where possible so it can be reused with
CSV data, notebooks, and other analyzers.