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

# Install from PyPI (recommended)
pip install thermal-tyre-driver

# --- or --- #

# 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 thermal_tyre_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 thermal_tyre_driver import SensorConfig, TyreThermalSensor

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

# Create sensors for all four tyres
sensors = {
    "FL": TyreThermalSensor("FL", config=config, mux_address=0x70, mux_channel=0, i2c_bus=i2c_bus),
    "FR": TyreThermalSensor("FR", config=config, mux_address=0x70, mux_channel=1, i2c_bus=i2c_bus),
    "RL": TyreThermalSensor("RL", config=config, mux_address=0x70, mux_channel=2, i2c_bus=i2c_bus),
    "RR": TyreThermalSensor("RR", config=config, 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.1.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.1-py3-none-any.whl (37.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: thermal_tyre_driver-0.1.1.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.1.tar.gz
Algorithm Hash digest
SHA256 e0582e2c70d05d22ac685c8fc0cd828ead1f90cbbb25e21aedbb424b113e1227
MD5 df334597a3e0173b6dc8e2d2f28b3a92
BLAKE2b-256 802c13182b39054196ecdd87a5bfe781c986c665b99a95dfade55b26035030e5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for thermal_tyre_driver-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f7313f0fcfa1566bc97cd6c8072d4b289f78194aae5ed63fb5c6715e523894b0
MD5 48ef914e748279f427113b8033afef72
BLAKE2b-256 aaeb2b10b608a7383b6cad0c6dd34ad886e656fdb0b0aec1e4b642e4d4a3a803

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