Choosing what to estimate in a calibration¶
A calibration's result depends on which parameters you estimate and how as much as on the measurements. FIGAROH does not make that choice for you: it provides several methods, diagnostics to compare them, and this guide. The methods themselves (what each does, what it needs, pros and cons) are described in the reference page Calibration estimation methods.
This guide is the workflow: start from a baseline, decide what you know, configure, read the diagnostics, and check the choice on data the fit did not see.
1. Know why the choice matters¶
With an estimated base frame and tool point, some joint errors are not identifiable at all (the frames absorb them), and others are identifiable but barely excited by your postures. Estimating the latter freely fits noise: the training residual drops, prediction on new postures gets worse.
On the TIAGo mocap reference (37 training postures, position only), a free
full_params fit with 31 parameters overfits even a synthetic robot whose
true errors are exactly of that form; at realistic noise it predicts new
postures two to three times worse than the methods that account for
excitation. See the table in the
reference page.
2. Run the baseline first¶
Keep the default (structural) and fit once. It is the behaviour of earlier
FIGAROH versions and the reference every other choice is compared with.
calibrator = TiagoCalibration(robot, "config/tiago_unified_config.yaml")
calibrator.initialize()
calibrator.solve(plotting=False)
print(len(calibrator.calib_config["param_name"]), "parameters")
print(calibrator.calib_config.get("absorbed_param_name")) # dropped: frames absorb them
Note the number of parameters against the number of measurements
(NbSample × measured components). Few measurements per parameter is the
first sign that the method matters.
3. Decide what you know¶
| You know… | Consider | Because |
|---|---|---|
| Nothing beyond the URDF | map_cv, cv_subset |
They choose the prior scale or the set size by cross-validation on your postures |
| Typical error sizes (vendor tolerances, earlier calibrations of the same model) | map, excitation |
They use those sizes directly; map for prediction, excitation for a short list |
| You need a short, named list of corrections (for review, a URDF diff, a runtime file) | excitation, cv_subset |
They drop parameters instead of shrinking them |
| You need results comparable with an earlier run | structural |
Unchanged behaviour |
None of these is right in general. If unsure, run several (step 6).
4. Configure¶
In the calibration task of the unified config:
parameters:
calibration_level: full_params
estimation:
method: map_cv
Optional keys and their defaults are listed in the reference page. The ones you are most likely to set:
priors— expected error sizes per group (translationm,rotationrad,joint_offsetrad,prismatic_offsetm). Used bymapandexcitation;map_cvonly uses their ratios.noise_std— measurement noise in the units of your cost function's residuals. Leavenullto estimate it from the data (recommended unless you have a reason to trust a sensor specification more than your residuals).cv_folds,cv_seed— cross-validation over your training postures.cv_rule: one_se— withcv_subset, prefer the smallest set within one standard error of the best.
The same settings can be set in Python before initialize():
calibrator.calib_config["estimation"] = {"method": "excitation", "excitation_k": 1.0}
5. Read the diagnostics¶
Selection happens in initialize(); its record is
calibrator.estimation_report (also calib_config["estimation_report"]):
r = calibrator.estimation_report
print(r["method"], r["noise_std"], r["noise_source"])
print("not identifiable here:", r["dependent"])
for name, ratio in r.get("removal_order", []): # excitation, cv_subset
print(f"removed {name}: predicted SE {ratio:.1f} x expected size")
for point in r.get("cv_curve", []): # map_cv, cv_subset
print(point)
print(r.get("prior_scale"), r.get("n_joint_chosen"))
After solve(), for map and map_cv, compare each parameter's posterior
standard deviation with its prior: close to the prior means the data did not
inform it, and its value is essentially the nominal one.
from figaroh.calibration import estimation
names = calibrator.calib_config["param_name"]
prior = estimation.prior_std(names, calibrator.model, estimation.settings(calibrator.calib_config)["priors"])
for n, s, p in zip(names, calibrator.std_dev, prior):
if p != float("inf"):
print(f"{n:28s} posterior/prior {s / p:.2f}")
Warning signs:
- the cross-validation curve is flat, or its minimum is at the edge of
cv_multipliers/cv_sizes(extend the grid); cv_subsetchooses very different sizes for differentcv_seedvalues (too few postures for a stable choice);- the estimated noise is much larger than the sensor's (unmodelled effects such as backlash or deflection dominate; no method fixes that).
6. Compare candidates on data the fit did not see¶
Cross-validation inside map_cv and cv_subset uses training postures only.
Judge the final choice on postures from another session (validation_data_file)
or a held-out part of the data, with the same frames and processing.
results = {}
for method in ("structural", "excitation", "map_cv", "cv_subset"):
c = TiagoCalibration(robot, config_path)
c.calib_config["estimation"] = {"method": method}
c.initialize()
c.solve(plotting=False)
results[method] = c.evaluation_metrics # includes validation metrics when configured
Decide which comparison counts before looking at it, and look at the held-out set once. Repeatedly tuning the method on the same held-out set turns it into training data. figaroh-examples' TIAGo held-out protocol shows one way to freeze sets and rules.
7. Check against a known truth when you can¶
A synthetic truth at your postures answers questions real data cannot: do
these postures support this many parameters, and does the method recover
what it should? figaroh-examples'
TIAGo truth fixture
(examples/tiago/calibration_truth.py) draws errors of a chosen size,
simulates your postures with noise, fits, and compares with the truth. Adapt
it to your robot's postures and expected error sizes. It has no model
mismatch, so it complements real held-out data rather than replacing it.
8. Export and report¶
- Write the same joint corrections to the URDF and to PAL's
geometric_calibration:
from figaroh.tools.urdf_exporter import export_urdf
export_urdf("urdf/robot.urdf", calibrator.joint_corrections(), output_path="urdf/robot_calibrated.urdf")
For structural, joint_corrections() lifts the fitted base parameters
onto every joint with your expected error sizes, so a joint whose
correction was represented by another parameter in the fit gets its share
(joint_corrections(lift=False) returns the representatives only). For the
other methods it returns the fitted joint parameters; those left out stay
at nominal. Reload the written URDF and compare its forward kinematics with
the fit before deploying it.
- Frames (base, tool point) are measurement-setup quantities and are not
written into the robot URDF.
- Record in your report: the method and every non-default setting, the
estimated or given noise, the priors, the selection record (removal order or
cross-validation curve and choice), the parameter count, and the held-out
result with its evidence type (training only, temporal block, other session,
synthetic truth).
See also¶
- Calibration estimation methods — reference
- Calibration walkthrough
- Plan, fit and validate — the general decision process
- Optimal experiment design — postures that excite more parameters
- Reporting & verification