Screen Integration
If you plan to present visual stimuli or record touch responses, enable the screen under
SETTINGS → SCREEN SETTINGS.
Choosing the right screen technology
We recommend using an infrared (IR) touch frame rather than a capacitive touchscreen. An IR frame works by projecting an invisible grid of IR beams across the display surface; a touch event is registered whenever an object interrupts one of the beams. This means the animal does not need to physically press the screen — proximity alone triggers a response — which reduces aversion and improves task engagement for rodents.
The IR frame connects via USB and the display via the second micro-HDMI port on the Raspberry Pi.
Dual-screen configuration
The system runs with two display outputs:
HDMI-1: Primary display showing the Training Village GUI (can be virtual — no physical monitor required).
HDMI-2: Secondary display inside the operant box showing behavioral stimuli.
To configure this, you need to tell the system to treat both HDMI ports as active, regardless of whether a physical monitor is connected to HDMI-1.
Step 1 — Edit the boot configuration:
sudo nano /boot/firmware/cmdline.txt
Replace:
vc4.force_hotplug=1 video=HDMI-A-1:1600x900@60D
With:
vc4.force_hotplug=3 video=HDMI-A-1:1600x900@60D video=HDMI-A-2:1280x720@60D
The vc4.force_hotplug flag controls which HDMI ports the system treats as connected:
1 = HDMI-1 only, 2 = HDMI-2 only, 3 = both. The video=HDMI-A-* parameters set
the resolution for each port independently.
Step 2 — Set the resolution in the desktop:
Navigate to Raspberry Pi menu → Preferences → Control Centre → Screens → HDMI-2, set the resolution to 1280×720 or lower, and apply.
Resolution and display latency
Keep the stimulus display at 1280×720 or below. Higher resolutions significantly increase CPU load on the Raspberry Pi, leading to greater stimulus presentation latency.
The system operates at a 60 Hz refresh rate (one frame every 16.6 ms). Because the next frame is preloaded in the buffer while the current one is displayed, the latency from issuing a display command to the frame appearing on screen spans approximately one full frame plus the remaining portion of the current frame — equivalent to roughly 1.5 × frame duration plus 2–3 ms of controller-to-Raspberry Pi communication. Measured latencies: mean = 27.4 ms, SD = 7.5 ms.
Using the screen in tasks
Import the module-level instance, provide a drawing function, and call
start_drawing() / stop_drawing() to control when the stimulus appears.
Displaying a static image:
from village.devices.screen import screen
from PyQt5.QtGui import QPainter
def draw():
with QPainter(screen) as painter:
if screen.image:
painter.drawPixmap(0, 0, screen.image)
screen.load_draw_function(draw, image="stimulus.png")
screen.start_drawing()
# ... trial runs ...
screen.stop_drawing()
Displaying a video:
def draw():
frame = screen.get_video_frame()
with QPainter(screen) as painter:
if frame:
painter.drawImage(0, 0, frame)
screen.load_draw_function(draw, video="movie.mp4")
screen.start_drawing()
Drawing programmatically (shapes, text, time-varying stimuli):
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QColor, QPainter
def draw():
with QPainter(screen) as painter:
painter.fillRect(screen.rect(), QColor("black")) # background
# flash a white square for the first 0.1 s of each second
if screen.elapsed_time % 1.0 < 0.1:
painter.fillRect(QRect(100, 100, 200, 200), QColor("white"))
screen.load_draw_function(draw)
screen.start_drawing()
The drawing function is called once per frame (60 Hz) with no arguments. Two attributes are updated automatically before each call:
screen.frame— number of frames rendered sincestart_drawing().screen.elapsed_time— seconds elapsed sincestart_drawing().
Files passed to image= and video= are looked up inside MEDIA_DIRECTORY
(/village_projects/<project_name>/media). Reference them by filename only.
Method reference
Method |
Arguments |
Description |
|---|---|---|
|
|
Sets the drawing function and pre-loads media. |
|
— |
Starts the 60 Hz rendering loop. |
|
— |
Stops rendering and blanks the screen. |
|
|
Loads an image from |
|
|
Prepares a video for playback (started by |
|
— |
Returns the current video frame as a |
Configuring the touchscreen settings
Use these settings when the screen is configured as a touchscreen to both display stimuli and register touch responses from the animals.
Three settings control touchscreen behaviour:
Setting |
Description |
Example |
|---|---|---|
|
Exact device name as shown in |
|
|
Digitizer coordinate range: |
|
|
Minimum seconds between two registered touches (debounce) |
|
Note
TOUCH_RESOLUTION is the internal coordinate range of the touch digitizer hardware —
it is independent of and usually different from the display pixel resolution.
The system uses both values to convert raw touch coordinates into screen pixel positions.
The device is looked up by name at startup, so the /dev/input/eventX path (which
can change after a reboot or USB reconnect) does not need to be configured.
Finding the device name and digitizer resolution
Step 1 — List all input devices:
cat /proc/bus/input/devices
Look for the entry that has B: ABS in its capabilities (indicating absolute position
events). The value you need is the N: Name= field:
I: Bus=0003 Vendor=0b1e Product=000f Version=0110
N: Name="USB Touch USB Touch"
...
H: Handlers=mouse0 event5
B: EV=1b
B: ABS=3
Copy the name exactly as it appears (without quotes) into TOUCHSCREEN_DEVICE (in this case USB Touch USB Touch).
Step 2 — Find the digitizer resolution:
Use the Handlers path from Step 1 (e.g. event5) to inspect the coordinate range:
sudo evtest /dev/input/event5
In the output, look for the EV_ABS section:
Event type 3 (EV_ABS)
Event code 0 (ABS_X)
Min 0
Max 4095
Event code 1 (ABS_Y)
Min 0
Max 4095
Set TOUCH_RESOLUTION to [Max_X + 1, Max_Y + 1] — in this example [4096, 4096].
Note
If evtest is not installed: sudo apt install evtest
Step 3 — Prevent the touchscreen from controlling the desktop:
When Training Village is running, Python grabs the touchscreen device exclusively
via evdev (device.grab()), so touch events never reach the graphical layer of
the OS — the animals cannot accidentally interact with the desktop. However, when
Training Village is not running the device is released and libinput (the input
layer used by the desktop compositor) picks it up again.
To prevent this permanently — regardless of whether Training Village is running —
create a udev rule that tells libinput to always ignore the device. Replace
USB Touch USB Touch with the exact name you found in Step 1.
Create the file /etc/udev/rules.d/99-touchscreen-grab.rules:
sudo nano /etc/udev/rules.d/99-touchscreen-grab.rules
Add the following line, replacing USB Touch USB Touch with the exact device name
found in Step 1:
SUBSYSTEM=="input", ATTRS{name}=="USB Touch USB Touch", ENV{LIBINPUT_IGNORE_DEVICE}="1"
Save and close the file (Ctrl+O, Enter, Ctrl+X).
This makes libinput ignore the device completely. It remains accessible at
/dev/input/eventX so Training Village can still read it via evdev, but the OS
will never treat it as a pointer device.
Apply the rule without rebooting:
sudo udevadm control --reload-rules
sudo udevadm trigger