348 lines
9.5 KiB
Markdown
348 lines
9.5 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
cd examples
|
|
python3 calibrate_touch.py
|
|
```
|
|
|
|
This will:
|
|
1. Display calibration targets (circles) at known positions
|
|
2. Wait for you to touch each target
|
|
3. Compute the transformation matrix
|
|
4. 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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
python3 calibrate_touch.py --output /path/to/calibration.json
|
|
```
|
|
|
|
Then specify the same path when initializing the touch driver:
|
|
|
|
```python
|
|
touch = FT5xx6TouchDriver(
|
|
calibration_file="/path/to/calibration.json"
|
|
)
|
|
```
|
|
|
|
### Display Dimensions
|
|
|
|
If your display is not the default 800x1200:
|
|
|
|
```bash
|
|
python3 calibrate_touch.py --width 1024 --height 768
|
|
```
|
|
|
|
## Testing Calibration
|
|
|
|
Use the test script to verify calibration quality:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
# 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:
|
|
|
|
```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:
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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`:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
# 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:
|
|
|
|
```python
|
|
# 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](src/dreader_hal/calibration.py) for full API documentation.
|
|
|
|
Key classes:
|
|
- **`TouchCalibration`**: Main calibration class
|
|
- **`CalibrationData`**: Calibration dataset and matrix
|
|
- **`CalibrationPoint`**: Single calibration point pair
|
|
|
|
Key methods:
|
|
- `generate_target_positions()`: Create calibration target grid
|
|
- `add_calibration_point()`: Record calibration point
|
|
- `compute_calibration()`: Calculate transformation matrix
|
|
- `transform()`: Apply calibration to coordinates
|
|
- `save()` / `load()`: Persist calibration data
|
|
- `is_calibrated()`: Check if calibration is loaded
|
|
- `get_calibration_quality()`: Get quality assessment
|