Skip to main content

Thermal tyre analysis toolkit for MLX90640 sensors.

Project description

Thermal Tyre Sensor Driver

Python Version License Platform AI Slop

A robust Python driver for thermal tyre temperature monitoring using MLX90640 thermal cameras. Features MAD-based detection algorithms, I2C multiplexer support, and real-time analysis of tyre temperature distribution.

🌟 Features

  • Robust Detection: MAD-based centre-out region growing algorithm
  • Multi-Sensor Support: Built-in I2C multiplexer (TCA9548A) support for up to 8 sensors
  • Real-time Analysis: Three-section tyre temperature analysis (left/centre/right)
  • Confidence Scoring: Detection confidence metrics for reliability assessment
  • Edge Case Handling: Automatic handling of brake heat, cold tyres, and sensor clipping
  • Structured Output: JSON-serializable data structures for easy integration
  • Configurable Parameters: Extensive configuration options for different use cases
  • Production Ready: Comprehensive error handling and temporal smoothing

📋 Requirements

Hardware

  • Raspberry Pi (or compatible SBC with I2C)
  • MLX90640 thermal camera sensor(s)
  • (Optional) TCA9548A I2C multiplexer for multiple sensors
  • I2C pull-up resistors (typically 4.7kΩ)

Software

# Python 3.7+
python3 --version

# Required packages
pip install numpy
pip install scipy
pip install adafruit-circuitpython-mlx90640
pip install adafruit-blinka

🚀 Quick Start

Installation

# Clone the repository
git clone https://github.com/samskjord/thermal-tyre-driver.git
cd thermal-tyre-driver

# Install dependencies
pip install -r requirements.txt

# Enable I2C on Raspberry Pi
sudo raspi-config
# Navigate to: Interfacing Options -> I2C -> Enable

Basic Usage

from thermal_tyre_driver import SensorConfig, TyreThermalSensor

# Configure the sensor
config = SensorConfig(include_raw_frame=False, refresh_rate=4)

# Create sensor instance
sensor = TyreThermalSensor(sensor_id="FRONT_LEFT", config=config)

# Read temperature data
data = sensor.read()

# Access temperature analysis
print(f"Left: {data.analysis.left.avg:.1f}°C")
print(f"Centre: {data.analysis.centre.avg:.1f}°C")
print(f"Right: {data.analysis.right.avg:.1f}°C")
print(f"Confidence: {data.detection.confidence:.0%}")

🧪 Examples

  • examples/basic_usage.py – single-sensor read with explicit configuration and JSON output.
  • examples/multiplexed_usage.py – shared I2C bus with a TCA9548A multiplexer reading all four tyre positions.

Run an example with:

python3 examples/basic_usage.py
python3 examples/multiplexed_usage.py

📖 API Documentation

TyreThermalSensor Class

Constructor

TyreThermalSensor(
    sensor_id: str,
    config: Optional[SensorConfig] = None,
    mux_address: Optional[int] = None,
    mux_channel: Optional[int] = None,
    i2c_bus: Optional[busio.I2C] = None
)
Parameter Type Description
sensor_id str Unique identifier for the sensor (e.g., "FRONT_LEFT")
config SensorConfig Configuration object (uses defaults if None)
mux_address int I2C address of multiplexer (e.g., 0x70)
mux_channel int Multiplexer channel (0-7)
i2c_bus busio.I2C Shared I2C bus instance

Methods

read() -> TyreThermalData

Reads sensor and returns complete analysis.

data = sensor.read()
get_stats() -> Dict

Returns driver statistics.

stats = sensor.get_stats()
print(f"Frames processed: {stats['frame_count']}")
print(f"Average confidence: {stats['average_confidence']:.1%}")
reset()

Resets driver state.

sensor.reset()

Data Structures

TyreThermalData

The main data structure returned by read():

@dataclass
class TyreThermalData:
    timestamp: datetime          # Reading timestamp
    sensor_id: str               # Sensor identifier
    frame_number: int            # Sequential frame count
    analysis: TyreAnalysis       # Temperature analysis
    detection: DetectionInfo     # Detection algorithm info
    temperature_profile: np.ndarray  # 1D temperature array
    raw_frame: Optional[np.ndarray]  # Full 24x32 frame
    warnings: List[str]          # Warning messages

TyreAnalysis

Temperature analysis for tyre sections:

@dataclass
class TyreAnalysis:
    left: TyreSection            # Left third of tyre
    centre: TyreSection          # Centre third
    right: TyreSection           # Right third
    lateral_gradient: float      # Temperature gradient across tyre

TyreSection

Statistics for each section:

@dataclass
class TyreSection:
    avg: float      # Average temperature
    median: float   # Median temperature
    min: float      # Minimum temperature
    max: float      # Maximum temperature
    std: float      # Standard deviation

⚙️ Configuration

SensorConfig Parameters

from tyre_thermal_driver import SensorConfig

config = SensorConfig(
    # Sensor specifications
    sensor_width=32,              # Pixels
    sensor_height=24,             # Pixels
    middle_rows=4,                # Rows to analyze
    
    # Temperature limits
    min_temp=0.0,                 # °C
    max_temp=180.0,               # °C
    brake_temp_threshold=180.0,   # °C
    
    # MAD thresholds
    mad_uniform_threshold=0.5,
    k_floor=5.0,
    k_multiplier=2.0,
    delta_floor=3.0,
    delta_multiplier=1.8,
    
    # Detection parameters
    min_tyre_width=6,             # Pixels
    max_tyre_width=28,            # Pixels
    max_width_change_ratio=0.3,   # ±30% per frame
    
    # Smoothing
    ema_alpha=0.3,                # EMA weight
    persistence_frames=2,          # Frames for stability
    
    # Output options
    include_raw_frame=False       # Include full frame data
)

sensor = TyreThermalSensor("CUSTOM", config=config)

🔧 Advanced Usage

Multiple Sensors with I2C Multiplexer

import busio
import board
from tyre_thermal_driver import TyreThermalSensor

# Share I2C bus for efficiency
i2c_bus = busio.I2C(board.SCL, board.SDA)

# Create sensors for all four tyres
sensors = {
    "FL": TyreThermalSensor("FL", mux_address=0x70, mux_channel=0, i2c_bus=i2c_bus),
    "FR": TyreThermalSensor("FR", mux_address=0x70, mux_channel=1, i2c_bus=i2c_bus),
    "RL": TyreThermalSensor("RL", mux_address=0x70, mux_channel=2, i2c_bus=i2c_bus),
    "RR": TyreThermalSensor("RR", mux_address=0x70, mux_channel=3, i2c_bus=i2c_bus)
}

# Read all tyres
for position, sensor in sensors.items():
    data = sensor.read()
    print(f"{position}: L={data.analysis.left.avg:.1f}°C "
          f"C={data.analysis.centre.avg:.1f}°C "
          f"R={data.analysis.right.avg:.1f}°C "
          f"[{data.detection.confidence:.0%}]")

Data Logging Example

import json
import time
from datetime import datetime

def log_tyre_data(sensor, log_file="tyre_temps.jsonl"):
    """Log tyre temperature data to JSON Lines file"""
    
    while True:
        try:
            data = sensor.read()
            
            # Create log entry
            log_entry = {
                "timestamp": data.timestamp.isoformat(),
                "sensor": data.sensor_id,
                "temps": {
                    "left": data.analysis.left.avg,
                    "centre": data.analysis.centre.avg,
                    "right": data.analysis.right.avg
                },
                "confidence": data.detection.confidence,
                "warnings": data.warnings
            }
            
            # Append to file
            with open(log_file, 'a') as f:
                f.write(json.dumps(log_entry) + '\n')
            
            time.sleep(1.0)
            
        except KeyboardInterrupt:
            break
        except Exception as e:
            print(f"Error: {e}")
            time.sleep(1.0)

Real-time Monitoring

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np

def create_live_plot(sensor):
    """Create live temperature plot"""
    
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
    
    # Temperature history
    history_len = 100
    temps = {'left': [], 'centre': [], 'right': []}
    confidence = []
    
    def update(frame):
        data = sensor.read()
        
        # Update history
        for section in ['left', 'centre', 'right']:
            temp = getattr(data.analysis, section).avg
            temps[section].append(temp)
            if len(temps[section]) > history_len:
                temps[section].pop(0)
        
        confidence.append(data.detection.confidence)
        if len(confidence) > history_len:
            confidence.pop(0)
        
        # Plot temperatures
        ax1.clear()
        x = range(len(temps['left']))
        ax1.plot(x, temps['left'], 'b-', label='Left')
        ax1.plot(x, temps['centre'], 'g-', label='Centre')
        ax1.plot(x, temps['right'], 'r-', label='Right')
        ax1.set_ylabel('Temperature (°C)')
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # Plot confidence
        ax2.clear()
        ax2.plot(range(len(confidence)), confidence, 'k-')
        ax2.set_ylabel('Confidence')
        ax2.set_xlabel('Time (frames)')
        ax2.set_ylim([0, 1])
        ax2.grid(True, alpha=0.3)
        
        plt.tight_layout()
    
    ani = FuncAnimation(fig, update, interval=250)
    plt.show()

🔍 Detection Algorithm

The driver uses a MAD-based (Median Absolute Deviation) centre-out region growing algorithm:

  1. Profile Extraction: Collapses middle sensor rows to 1D temperature profile
  2. Temporal Smoothing: Applies EMA filter to reduce noise
  3. MAD Calculation: Computes global and local temperature variation
  4. Region Growing: Grows from centre pixel using dual thresholds:
    • Local threshold (k): Temperature similarity to seed point
    • Global threshold (Δ): Temperature above/below median
  5. Constraint Application: Enforces geometry and temporal constraints
  6. Confidence Scoring: Evaluates detection quality

Algorithm Parameters

  • k = max(5.0, 2.0 × local_MAD) - Local temperature threshold
  • Δ = max(3.0, 1.8 × global_MAD) - Global temperature threshold
  • Two consecutive failures stop region growth
  • Width constrained to 6-28 pixels
  • ±30% width change limit per frame

📊 Output Examples

JSON Output

{
  "timestamp": "2024-01-15T14:32:15.123456",
  "sensor_id": "FRONT_LEFT",
  "frame_number": 42,
  "analysis": {
    "left": {
      "avg": 45.2,
      "median": 45.1,
      "min": 44.8,
      "max": 45.7,
      "std": 0.3
    },
    "centre": {
      "avg": 48.5,
      "median": 48.6,
      "min": 47.9,
      "max": 49.2,
      "std": 0.4
    },
    "right": {
      "avg": 44.8,
      "median": 44.7,
      "min": 44.3,
      "max": 45.3,
      "std": 0.3
    },
    "lateral_gradient": 4.3
  },
  "detection": {
    "method": "region_growing",
    "span_start": 8,
    "span_end": 24,
    "width": 16,
    "confidence": 0.85,
    "inverted": false,
    "clipped": "none"
  },
  "warnings": [
    "Temperature differential: 3.7°C across tyre"
  ]
}

🐛 Troubleshooting

Common Issues

I2C Device Not Found

# Check I2C devices
sudo i2cdetect -y 1

# Expected output for MLX90640: address 0x33
# Expected output for TCA9548A: address 0x70

Permission Denied

# Add user to i2c group
sudo usermod -a -G i2c $USER

# Logout and login again

Slow Frame Rate

  • Check I2C speed: sudo cat /boot/config.txt | grep i2c
  • Add dtparam=i2c_baudrate=400000 for 400kHz

Low Confidence Readings

  • Ensure sensor is properly aimed at tyre
  • Check for obstructions or reflections
  • Adjust mounting distance (10-30cm typical)
  • Calibrate configuration parameters

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thermal_tyre_driver-0.1.0.tar.gz (54.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thermal_tyre_driver-0.1.0-py3-none-any.whl (37.7 kB view details)

Uploaded Python 3

File details

Details for the file thermal_tyre_driver-0.1.0.tar.gz.

File metadata

  • Download URL: thermal_tyre_driver-0.1.0.tar.gz
  • Upload date:
  • Size: 54.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for thermal_tyre_driver-0.1.0.tar.gz
Algorithm Hash digest
SHA256 057240277aa18945202364ebb9445e0ce11125be47c512d7b43d36246cd38ef3
MD5 79ebd796406f29a9ab986e838e241432
BLAKE2b-256 768b3f518f2a84899053cff8422371771d74e09f6ebbb7d701ebaa4d0c598fcb

See more details on using hashes here.

File details

Details for the file thermal_tyre_driver-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for thermal_tyre_driver-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 325a20b9c94904acfd406d472aaac419a4422230d031cb78754f4c3596a82213
MD5 20dff2a5b60f2c1d0c4f6b9bc2565f4f
BLAKE2b-256 8334dd2dbf6da77525c66f4932c193d3daaa451a1c2afc6311106f56b6a84ffc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page