Skip to content
Scaling & Monitoring

Sensor Calibration at Scale with TofuPilot

Learn how to automate sensor calibration workflows, track calibration certificates, and manage multi-point calibration data with TofuPilot.

JJulien Buteau
intermediate11 min readMarch 14, 2026

Sensor Calibration at Scale with TofuPilot

Calibrating 10 sensors is a manual job. Calibrating 10,000 requires a system. You need multi-point reference measurements, pass/fail against tolerance bands, calibration certificates, and traceability back to reference standards.

TofuPilot handles the data side so you can focus on the calibration procedure itself.

What Sensor Calibration Involves

Production sensor calibration typically follows this flow:

  1. Apply known reference stimuli (temperature, pressure, force, etc.)
  2. Read the sensor output at each reference point
  3. Calculate error, linearity, and hysteresis
  4. Apply correction factors if needed
  5. Verify corrected output meets specifications
  6. Generate a calibration certificate

Prerequisites

  • Python 3.8+ with openhtf and tofupilot installed
  • A reference standard (calibrated source or reference sensor)
  • A data acquisition system or instrument to read sensor output

Step 1: Define Multi-Point Calibration Measurements

A typical calibration uses 5 to 11 reference points across the sensor's range. Define measurements for each point:

sensor_cal.py
import openhtf as htf
from openhtf.util import units

CAL_POINTS = [0, 25, 50, 75, 100]  # Percent of full scale

measures = []
for pct in CAL_POINTS:
    measures.append(
        htf.Measurement(f"error_at_{pct}pct")
            .with_units(units.PERCENT)
            .in_range(-0.5, 0.5)
            .doc(f"Measurement error at {pct}% of full scale")
    )

measures.append(
    htf.Measurement("max_linearity_error")
        .with_units(units.PERCENT)
        .at_most(0.25)
        .doc("Maximum linearity deviation from best-fit line")
)

measures.append(
    htf.Measurement("hysteresis")
        .with_units(units.PERCENT)
        .at_most(0.1)
        .doc("Maximum hysteresis between up and down sweep")
)

@htf.measures(*measures)
def calibration_test(test, reference_source, sensor_reader):
    """Run multi-point calibration with up and down sweep."""
    full_scale = 100.0  # Adjust for your sensor range
    up_readings = {}
    down_readings = {}

    # Up sweep
    for pct in CAL_POINTS:
        ref_value = full_scale * pct / 100.0
        reference_source.set_output(ref_value)
        time.sleep(2.0)  # Settle time

        reading = sensor_reader.read()
        up_readings[pct] = reading
        error = (reading - ref_value) / full_scale * 100
        setattr(test.measurements, f"error_at_{pct}pct", error)

    # Down sweep for hysteresis
    for pct in reversed(CAL_POINTS):
        ref_value = full_scale * pct / 100.0
        reference_source.set_output(ref_value)
        time.sleep(2.0)

        reading = sensor_reader.read()
        down_readings[pct] = reading

    # Hysteresis: max difference between up and down readings
    max_hyst = max(
        abs(up_readings[p] - down_readings[p]) / full_scale * 100
        for p in CAL_POINTS
    )
    test.measurements.hysteresis = max_hyst

    # Linearity: deviation from best-fit line
    import numpy as np
    ref_vals = [full_scale * p / 100.0 for p in CAL_POINTS]
    read_vals = [up_readings[p] for p in CAL_POINTS]
    coeffs = np.polyfit(ref_vals, read_vals, 1)
    fit_vals = np.polyval(coeffs, ref_vals)
    linearity_errors = [(r - f) / full_scale * 100 for r, f in zip(read_vals, fit_vals)]
    test.measurements.max_linearity_error = max(abs(e) for e in linearity_errors)

Step 2: Store Calibration Curves as Multi-Dimensional Data

Capture the full calibration curve, not just pass/fail:

cal_curve_capture.py
import openhtf as htf
from openhtf.util import units

@htf.measures(
    htf.Measurement("calibration_curve")
        .with_dimensions(units.PERCENT)
        .doc("Full calibration curve: reference input vs sensor output"),
    htf.Measurement("correction_curve")
        .with_dimensions(units.PERCENT)
        .doc("Correction factors at each calibration point"),
)
def capture_calibration_curve(test, reference_source, sensor_reader):
    """Capture dense calibration curve for post-processing."""
    full_scale = 100.0

    for pct in range(0, 101, 5):  # 5% increments
        ref_value = full_scale * pct / 100.0
        reference_source.set_output(ref_value)
        time.sleep(1.0)

        reading = sensor_reader.read()
        test.measurements.calibration_curve[pct] = reading

        # Correction factor: what to add to get the true value
        correction = ref_value - reading
        test.measurements.correction_curve[pct] = correction

TofuPilot stores the full curve. You can compare curves across sensors, detect batch variations, and track calibration drift over time.

Step 3: Track Reference Standard Traceability

Every calibration is only as good as its reference. Track which reference standard was used:

cal_traceability.py
from tofupilot import TofuPilotClient

client = TofuPilotClient()

result = client.create_run(
    procedure_id="pressure-sensor-cal",
    unit_under_test={
        "serial_number": sensor_sn,
        "part_number": "PS-500-A",
    },
    run_passed=True,
    properties={
        "reference_standard": "FLUKE-8845A-SN12345",
        "reference_cal_date": "2026-01-15",
        "reference_cal_due": "2027-01-15",
        "reference_cert_number": "CAL-2026-0042",
        "ambient_temp_c": 23.1,
        "ambient_humidity_pct": 45,
        "operator": "OP-003",
    },
)

When an auditor asks about your traceability chain, every calibration run links to the reference standard, its own calibration certificate, and the environmental conditions during the procedure.

Step 4: Manage Recalibration Schedules

Sensors drift. Track when each unit was last calibrated and when it's due:

recal_tracking.py
from tofupilot import TofuPilotClient
from datetime import date, timedelta

client = TofuPilotClient()

# Get all calibration runs for a sensor type
cal_runs = client.get_runs(
    procedure_id="pressure-sensor-cal",
    limit=5000,
)

# Find latest calibration per serial number
latest_cal = {}
for run in cal_runs:
    sn = run.unit.serial_number
    if sn not in latest_cal or run.started_at > latest_cal[sn].started_at:
        latest_cal[sn] = run

# Check for overdue recalibrations (12-month interval)
cal_interval = timedelta(days=365)
today = date.today()

overdue = []
upcoming = []

for sn, run in latest_cal.items():
    cal_date = run.started_at.date()
    due_date = cal_date + cal_interval

    if due_date < today:
        overdue.append((sn, due_date))
    elif due_date < today + timedelta(days=30):
        upcoming.append((sn, due_date))

print(f"Overdue: {len(overdue)} sensors")
print(f"Due within 30 days: {len(upcoming)} sensors")

Step 5: Detect Calibration Drift

Compare calibration results over time to catch sensors that are drifting toward their tolerance limits:

drift_detection.py
from tofupilot import TofuPilotClient

client = TofuPilotClient()

# Get calibration history for a specific sensor
cal_history = client.get_runs(
    procedure_id="pressure-sensor-cal",
    unit_serial_number="PS-500-A-0042",
    limit=10,
)

print(f"Calibration history for PS-500-A-0042:")
print(f"{'Date':<12} {'Error@50%':<12} {'Linearity':<12} {'Status'}")
print("-" * 48)

for run in reversed(cal_history):
    error_50 = run.measurements.get("error_at_50pct", {}).get("value", "N/A")
    linearity = run.measurements.get("max_linearity_error", {}).get("value", "N/A")
    status = "PASS" if run.passed else "FAIL"
    print(f"{run.started_at.date()!s:<12} {error_50:<12} {linearity:<12} {status}")

If the error at 50% is 0.1% this year and was 0.05% last year, you know which direction it's heading. Replace or adjust before it fails.

More Guides

Put this guide into practice