9.5 KiB
Touchscreen Calibration
The DReader HAL includes touchscreen calibration support to accurately align touch coordinates with display pixels. This is essential for precise touch interaction on e-ink devices.
Why Calibration is Needed
Touchscreen controllers and display controllers are separate components with their own coordinate systems. Without calibration:
- Touch coordinates may not align precisely with display pixels
- Touches may register at offset positions
- The offset may vary across different areas of the screen
- Linear scaling alone may not account for rotation, skew, or non-linear distortion
Calibration solves this by computing an affine transformation matrix that maps touch coordinates to display coordinates with high precision.
Quick Start
1. Run Calibration
cd examples
python3 calibrate_touch.py
This will:
- Display calibration targets (circles) at known positions
- Wait for you to touch each target
- Compute the transformation matrix
- Save calibration data to
~/.config/dreader/touch_calibration.json
2. Use Calibrated Touch
Once calibration is complete, the touch driver automatically loads and applies the calibration:
from dreader_hal.touch.ft5xx6 import FT5xx6TouchDriver
touch = FT5xx6TouchDriver(width=800, height=1200)
await touch.initialize() # Automatically loads calibration
# All touch events are now calibrated
event = await touch.get_touch_event()
print(f"Touch at ({event.x}, {event.y})") # Calibrated coordinates
Calibration Options
Number of Calibration Points
You can choose between 5-point or 9-point calibration:
# 5-point calibration (corners + center) - faster
python3 calibrate_touch.py --points 5
# 9-point calibration (3x3 grid) - more accurate (default)
python3 calibrate_touch.py --points 9
Recommendation: Use 9-point calibration for best accuracy.
Custom Calibration File Location
python3 calibrate_touch.py --output /path/to/calibration.json
Then specify the same path when initializing the touch driver:
touch = FT5xx6TouchDriver(
calibration_file="/path/to/calibration.json"
)
Display Dimensions
If your display is not the default 800x1200:
python3 calibrate_touch.py --width 1024 --height 768
Testing Calibration
Use the test script to verify calibration quality:
python3 test_calibration.py
This displays an interactive UI where you can:
- Tap anywhere on the screen
- See calibrated coordinates with crosshairs
- View calibration offset and error
- Verify calibration quality
Calibration Quality
The calibration system computes RMS (Root Mean Square) error to assess quality:
| Quality | RMS Error | Description |
|---|---|---|
| Excellent | < 5 pixels | Professional-grade accuracy |
| Good | 5-10 pixels | Suitable for most applications |
| Fair | 10-20 pixels | Acceptable for basic touch |
| Poor | > 20 pixels | Re-calibration recommended |
Quality is displayed during calibration and can be checked programmatically:
quality = touch.calibration.get_calibration_quality()
error = touch.calibration.calibration_data.rms_error
print(f"Calibration: {quality} ({error:.2f}px RMS error)")
How Calibration Works
1. Calibration Point Collection
The calibration process displays targets at known display coordinates and records the raw touch coordinates when you tap each target:
Display Coordinates Touch Coordinates
(100, 100) → (95, 103)
(400, 100) → (392, 105)
(700, 100) → (689, 107)
...
2. Affine Transformation
An affine transformation maps touch coordinates to display coordinates:
x_display = a * x_touch + b * y_touch + c
y_display = d * x_touch + e * y_touch + f
This handles:
- Translation (offset)
- Scaling (different resolutions)
- Rotation (if display is rotated)
- Skew (non-perpendicular axes)
3. Least-Squares Fitting
The calibration algorithm uses least-squares fitting to find the best transformation matrix that minimizes error across all calibration points.
With N calibration points, the system is:
- Over-determined (N > 3 points for 6 unknowns)
- Robust to individual touch errors
- Optimal in the least-squares sense
4. Coordinate Transformation
Once calibrated, all touch coordinates are automatically transformed:
# Raw touch from sensor
raw_x, raw_y = 392, 105
# Apply calibration
calibrated_x, calibrated_y = calibration.transform(raw_x, raw_y)
# Result: (400, 100) - matches display target!
Calibration Data Format
Calibration is saved as JSON:
{
"points": [
{"display_x": 100, "display_y": 100, "touch_x": 95, "touch_y": 103},
{"display_x": 400, "display_y": 100, "touch_x": 392, "touch_y": 105},
...
],
"matrix": [1.05, -0.02, -3.5, 0.01, 0.98, 2.1],
"width": 800,
"height": 1200,
"rms_error": 3.42
}
- points: List of calibration point pairs
- matrix: Affine transformation
[a, b, c, d, e, f] - width/height: Display dimensions
- rms_error: Quality metric in pixels
Programmatic Usage
Manual Calibration
You can implement custom calibration UI:
from dreader_hal.calibration import TouchCalibration
# Create calibration instance
calibration = TouchCalibration(width=800, height=1200, num_points=9)
# Generate target positions
targets = calibration.generate_target_positions(margin=100, target_radius=20)
# For each target
for display_x, display_y in targets:
# Show target on display
# Wait for touch
touch_x, touch_y = get_touch() # Your touch reading code
# Add calibration point
calibration.add_calibration_point(display_x, display_y, touch_x, touch_y)
# Compute transformation
success = calibration.compute_calibration()
if success:
# Save calibration
calibration.save("~/.config/dreader/touch_calibration.json")
print(f"Quality: {calibration.get_calibration_quality()}")
print(f"RMS Error: {calibration.calibration_data.rms_error:.2f}px")
Using Calibration
from dreader_hal.calibration import TouchCalibration
# Load existing calibration
calibration = TouchCalibration(width=800, height=1200)
calibration.load("~/.config/dreader/touch_calibration.json")
# Transform coordinates
display_x, display_y = calibration.transform(raw_x, raw_y)
# Check if calibrated
if calibration.is_calibrated():
print("Calibration active")
When to Re-Calibrate
You should re-calibrate if:
- Initial setup: First time using the device
- Hardware changes: Replaced touchscreen or display
- Poor accuracy: RMS error > 20 pixels
- Display rotation: Changed from portrait to landscape
- Physical damage: Screen damage or loose connections
Troubleshooting
Calibration Fails to Compute
Problem: compute_calibration() returns False
Solutions:
- Ensure at least 3 calibration points were collected
- Check that points are not all collinear
- Verify touch coordinates are valid
Poor Calibration Quality
Problem: High RMS error or "Poor" quality rating
Solutions:
- Re-run calibration, touching targets more precisely
- Use 9-point calibration instead of 5-point
- Check for hardware issues (loose connections, damaged screen)
- Ensure targets are clearly visible on e-ink display
Calibration Not Loading
Problem: "No calibration file found" message
Solutions:
- Check calibration file exists at expected path
- Verify file permissions are readable
- Ensure display dimensions match calibration data
Touches Still Offset After Calibration
Problem: Calibrated touches don't align with targets
Solutions:
- Check calibration quality with
test_calibration.py - Re-run calibration
- Verify calibration file is being loaded (check console output)
- Ensure touch driver is using calibration (not bypassed)
Advanced Topics
Custom Transformation Algorithms
The default calibration uses affine transformation with least-squares fitting. For advanced use cases, you can extend TouchCalibration:
class CustomCalibration(TouchCalibration):
def compute_calibration(self) -> bool:
# Implement custom algorithm
# e.g., polynomial transformation, neural network, etc.
pass
Multi-Display Support
For devices with multiple displays:
# Calibration per display
calibration_main = TouchCalibration(800, 1200)
calibration_main.load("main_display_cal.json")
calibration_secondary = TouchCalibration(400, 600)
calibration_secondary.load("secondary_display_cal.json")
Runtime Calibration Adjustment
You can update calibration without full re-calibration:
# Add new calibration points to existing calibration
calibration.add_calibration_point(x_display, y_display, x_touch, y_touch)
calibration.compute_calibration() # Re-compute with new points
calibration.save(calibration_file)
API Reference
See calibration.py for full API documentation.
Key classes:
TouchCalibration: Main calibration classCalibrationData: Calibration dataset and matrixCalibrationPoint: Single calibration point pair
Key methods:
generate_target_positions(): Create calibration target gridadd_calibration_point(): Record calibration pointcompute_calibration(): Calculate transformation matrixtransform(): Apply calibration to coordinatessave()/load(): Persist calibration datais_calibrated(): Check if calibration is loadedget_calibration_quality(): Get quality assessment