Custom Touchscreen Interaction
Note
The touchscreen must be enabled and configured before using it in tasks. See Screen Integration for hardware setup and settings.
Every time a touch event passes the debounce filter, the trigger method of
TouchTriggerBase is called. By default it writes the touch coordinates to the
box camera feed. You can override this behavior to implement task-specific touch
responses.
Creating a custom TouchTrigger
Create a file named touch_trigger inside your project’s code directory and
define a class named TouchTrigger that inherits from TouchTriggerBase. The
system will automatically detect it and use it instead of the default base class.
from village.custom_classes.touch_trigger_base import TouchTriggerBase
class TouchTrigger(TouchTriggerBase):
def __init__(self) -> None:
super().__init__()
def trigger(self, x: int, y: int, timestamp: float) -> None:
"""Called on every touch event that passes the debounce interval.
Args:
x (int): Touch x position in screen pixels.
y (int): Touch y position in screen pixels.
timestamp (float): Monotonic timestamp of the event (seconds).
Available via self.task:
- self.task.cam_box — box camera (write_text, areas, position, …)
- self.task.bpod — Bpod controller (send_softcode_to_bpod, …)
- any attribute defined in the task class
"""
self.task.cam_box.write_text(f"Touch: {x}, {y}")
Example — checking a target area and sending a softcode to Bpod
A common pattern in touchscreen tasks is to define a circular target on the stimulus screen and determine whether the animal’s touch landed inside or outside it. The result is then sent to Bpod as a softcode so that the state machine can branch accordingly.
In the task, define the target position and tolerance as instance attributes
so the TouchTrigger can read them via self.task:
class MyTask(Task):
def start_trial(self):
# Position and radius of the target on the stimulus screen (pixels)
self.target_x: int = 640
self.target_y: int = 360
self.tolerance_px: int = 80
self.touch_correct: bool = False
# ... build and run the state machine ...
In touch_trigger.py, check the distance to the target and send the
appropriate softcode:
from village.custom_classes.touch_trigger_base import TouchTriggerBase
class TouchTrigger(TouchTriggerBase):
def __init__(self) -> None:
super().__init__()
def trigger(self, x: int, y: int, timestamp: float) -> None:
task = self.task
# Compute distance from touch to the current target
dx = x - getattr(task, "target_x", 0)
dy = y - getattr(task, "target_y", 0)
distance = (dx**2 + dy**2) ** 0.5
tolerance = getattr(task, "tolerance_px", 80)
if distance <= tolerance:
task.touch_correct = True
task.cam_box.write_text(f"Correct touch: {x}, {y}")
task.bpod.send_softcode_to_bpod(1) # softcode 1 → correct
else:
task.touch_correct = False
task.cam_box.write_text(f"Wrong touch: {x}, {y}")
task.bpod.send_softcode_to_bpod(2) # softcode 2 → incorrect
The Bpod state machine receives the softcode and can use it to branch:
sma.add_state(
state_name="WaitForTouch",
state_timer=5,
state_change_conditions={
"Tup": "Timeout",
"SoftCode1": "Reward",
"SoftCode2": "Punish",
},
output_actions=[],
)
Note
trigger runs in the touch reader thread, not in the task thread. Keep the
method fast and avoid blocking calls. Writing to cam_box and sending a
softcode are both non-blocking and safe to call from here.