TeslasuitDocumentation
Frameworks

Calibration

Trigger Teslasuit skeleton calibration, score its quality, export the reference frame. Typically used as a gate inside on_start().

Source: fes_framework/calibration.py API reference: api_reference.md → CalibrationAPI


What it is#

CalibrationAPI is a small wrapper around the Teslasuit SDK's calibration routine. It exposes three operations:

  • calibrate() — trigger the SDK's blocking calibration call and capture the resulting reference frame.
  • check_quality() — assess the calibration via three heuristic checks (sensor reporting, left-right symmetry, plausible IMU ranges) and return a structured score.
  • export(path) — write the reference frame to a timestamped CSV file for offline analysis.

The framework instantiates one CalibrationAPI per engine and exposes it as engine.calibration.

Why it exists#

Calibration is essential for accurate motion capture — every joint angle is measured relative to the I-pose reference frame the SDK captures during calibration. Without it, joint angles drift, and any strategy keying on them produces incorrect stimulation.

But raw SDK calibration is just "call this function while the user stands still." The framework adds three things:

  1. A structured result (CalibrationResult) that records success and the reference frame, so subsequent code can decide what to do.
  2. A quality assessment (CalibrationQuality) that catches the common failure modes — dead sensors, wrong I-pose, gross IMU miscalibration — before the user starts a session.
  3. Export to CSV in a single call, for offline reproducibility.

The result is a pattern (calibrate → check quality → gate the run) that's easy to apply consistently across applications.

When to use it#

Almost every application should run calibration before its first control cycle. The standard pattern is to subclass ClosedLoopEngine, override on_start(), and run the calibrate/check_quality/stop sequence:

class CalibratedEngine(ClosedLoopEngine):
    def on_start(self) -> None:
        input("Stand in I-pose. Press ENTER to calibrate...")
        result = self.calibration.calibrate()
        if not result.success:
            print(f"FAIL: {result.message}")
            self.stop()
            return
        quality = self.calibration.check_quality()
        print(f"Quality: {quality.overall_score:.0%}")
        if not quality.is_acceptable:
            print("Quality too low. Reposition suit and retry.")
            self.stop()

Skip calibration only for applications that don't depend on accurate joint angles — pure haptic-feedback apps, foot-contact-only apps, runtime parameter exploration. Even then, the engine logs a warning when run() is called without calibration.


Quality checks#

check_quality() runs three checks and averages their scores:

1. Sensor reporting (~33% weight)#

Counts how many of the 20 body sensors are returning data after calibration. A sensor that's missing or returning all-zero acceleration is flagged. Score = reporting / 20.

Common cause of failure: a suit segment is loose or unplugged. quality.missing_sensors lists which ones.

2. Left-right symmetry (~33% weight)#

For each bilateral pair (LeftUpperLeg/RightUpperLeg, LeftLowerLeg/RightLowerLeg, etc.) the cosine similarity between the two acceleration vectors is computed. In I-pose, gravity should be the only acceleration; bilateral limbs should be near-identical. Score = mean cosine similarity across pairs (clamped to [0, 1]).

Common cause of failure: the user wasn't actually in I-pose (arms crossed, feet apart, leaning).

3. Plausible IMU ranges (~33% weight)#

For a small set of representative bones (Hips, Chest, LeftUpperArm, RightUpperArm), confirm the accelerometer magnitude is plausibly close to 1 G (~9.81 m/s²). Acceptable range: 8.8–11.8 m/s². Score = passed / total.

Common cause of failure: gross sensor miscalibration.

Overall#

overall_score = mean(sensor_score, symmetry_score, range_score), rounded to 3 decimals. is_acceptable = overall_score >= 0.7.

The 0.7 threshold is CalibrationAPI.QUALITY_THRESHOLD. You can tune it for your application — lower for casual demos, higher for clinical precision work.


Result structures#

@dataclass
class CalibrationResult:
    success: bool                       # True if SDK calibration succeeded
    timestamp: float                    # time.time() at completion
    reference_frame: RawData | None     # raw IMU snapshot in I-pose
    message: str = ""                   # human-readable status

@dataclass
class CalibrationQuality:
    overall_score: float                # 0.0..1.0 (mean of three checks)
    is_acceptable: bool                 # overall_score >= QUALITY_THRESHOLD
    sensors_reporting: int              # 0..20
    sensors_expected: int               # always 20
    missing_sensors: list[str]          # bone names of silent sensors
    symmetry_score: float               # 0.0..1.0
    issues: list[str]                   # human-readable issue descriptions

When you display quality to a user, quality.issues is the operator-friendly summary; the numeric scores are for logging.


Minimal example — calibration gate#

from fes_framework.engine import ClosedLoopEngine

class CalibratedEngine(ClosedLoopEngine):
    def on_start(self) -> None:
        result = self.calibration.calibrate()
        if not result.success:
            self.stop()
            return
        quality = self.calibration.check_quality()
        print(f"Calibration: {quality.overall_score:.0%} "
              f"({quality.sensors_reporting}/20 sensors)")
        if not quality.is_acceptable:
            print(f"Issues: {quality.issues}")
            self.stop()
            return
        # OK — continue into the main loop
        self.calibration.export(path="./data")

For a richer pattern (re-calibration on poor quality), see examples/atomic/calibration_gate.py.


Export#

engine.calibration.export(path="./data") writes a CSV with a timestamped filename: data/calibration_<YYYY-MM-DD_HH-MM-SS>.csv. The contents are the raw IMU snapshot taken immediately after calibration — the same data used for quality checks.

Use this for:

  • offline reproducibility (re-run analysis with the same reference),
  • archival of session metadata,
  • debugging post-hoc when a session went unexpectedly.

See also#