Writing A Pack¶
Metric packs let teams keep domain-specific metrics outside the core RoboMetrics package while still registering them through the same evaluator and CLI discovery surfaces. A pack is a normal importable Python module with a top-level METRIC_PACK list. Each entry describes one metric using the registry contract: name, callable, category, unit, required inputs, optional aliases, optional default keyword arguments, reference, whether the metric is novel, and whether higher values are better. load_pack() imports the module and registers every definition in the global registry unless you pass a custom registry.
Keep pack metrics small and deterministic. They should accept NumPy-like arrays or simple Python values, validate their inputs, and return a scalar float or a MetricResult. Use required_inputs to tell Evaluator what keyword inputs must be present. If a metric needs several optional parameters, put stable defaults in default_kwargs so CLI and evaluator behavior stays reproducible. Always include a reference string, even when the metric is internal; that metadata appears in list and describe output.
# my_robot_metrics.py
from __future__ import annotations
import numpy as np
def final_x_error(prediction, ground_truth) -> float:
pred = np.asarray(prediction, dtype=float)
gt = np.asarray(ground_truth, dtype=float)
if pred.ndim != 2 or gt.ndim != 2:
raise ValueError("prediction and ground_truth must be trajectories")
return float(abs(pred[-1, 0] - gt[-1, 0]))
METRIC_PACK = [
{
"name": "final_x_error",
"fn": final_x_error,
"category": "trajectory",
"unit": "meters",
"required_inputs": ("prediction", "ground_truth"),
"reference": "Team internal longitudinal endpoint error",
"is_novel": True,
"higher_is_better": False,
}
]
Register and use the pack like this:
import numpy as np
from robometrics import Evaluator, load_pack
load_pack("my_robot_metrics")
result = Evaluator().evaluate(
prediction=np.array([[0.0, 0.0], [1.2, 0.0]]),
ground_truth=np.array([[0.0, 0.0], [1.0, 0.0]]),
metrics=["final_x_error"],
)
print(result.to_markdown())
For isolated evaluator instances, load the pack into the same custom registry
you pass to Evaluator:
from robometrics import Evaluator, MetricRegistry, load_pack
custom_registry = MetricRegistry()
load_pack("my_robot_metrics", registry=custom_registry)
result = Evaluator(metric_registry=custom_registry).evaluate(...)
During development, unregister test metrics or run pack tests in a fresh process so global registry state does not leak between tests. For published packs, document the expected inputs and units beside the module.