step.calibration.setup.steps ============================ .. py:module:: step.calibration.setup.steps .. autoapi-nested-parse:: Setup-calibration steps: opportunistic collectors + a finalizing gate. ``collect_drive`` / ``collect_ir_set`` wrap an existing setup motion and, while it runs, record ground-truth distance and IR samples into the :class:`SetupCalibrationSession`. ``calibration_gate`` then folds that evidence into the persisted compensation layers — distance trim via ``MotionTrimService.set_axis_scale`` (the measured scale is the absolute target, so it is assigned, not composed) and IR thresholds via the calibration store — running a measured fallback drive only for axes/sets that never gathered enough samples. Classes ------- .. autoapisummary:: step.calibration.setup.steps.CollectDrive step.calibration.setup.steps.CollectIrSet step.calibration.setup.steps.CalibrationGate Functions --------- .. autoapisummary:: step.calibration.setup.steps.collect_drive step.calibration.setup.steps.collect_ir_set step.calibration.setup.steps.calibration_gate Module Contents --------------- .. py:class:: CollectDrive(step, manual_measurement: bool = True) Bases: :py:obj:`raccoon.ui.step.UIStep` Wrap a setup motion and opportunistically record a distance sample. Runs the wrapped ``step`` unchanged. When a calibration board is connected, it captures the internal-odometry and board-ground-truth displacement across the drive, infers the driven axis from the dominant internal displacement, and records a :class:`DriveCalibrationSample`. Without a usable board ground truth (no board, dropout, or ~0cm reading) the behaviour depends on ``manual_measurement``: when ``True`` (default) it prompts the operator to measure the travelled distance by hand and records that as the sample; when ``False`` it rejects the drive and records nothing (the axis is left for the :class:`CalibrationGate` to handle, or left untrimmed). Users go through :func:`collect_drive`. .. py:class:: CollectIrSet(step, set_name: str, sensors: list[raccoon.sensor_ir.IRSensor] | None = None) Bases: :py:obj:`raccoon.step.base.Step` Wrap a setup motion and sample IR sensors into a named calibration set. Runs the wrapped ``step`` while polling each IR sensor at ~100 Hz, appending the readings to ``set_name`` in the :class:`SetupCalibrationSession`. The :class:`CalibrationGate` later turns the accumulated samples into stored black/white thresholds. Users go through :func:`collect_ir_set`. .. py:class:: CalibrationGate(require_axes: list[step.calibration.setup.session.CalibrationAxis] | None = None, require_ir_sets: list[str] | None = None, forward_fallback_cm: float = _FALLBACK_FORWARD_CM, lateral_fallback_cm: float = _FALLBACK_LATERAL_CM) Bases: :py:obj:`raccoon.ui.step.UIStep` Finalize all accumulated setup-calibration evidence exactly once. For each required drive axis it assigns the median sample scale as the distance trim via ``MotionTrimService.set_axis_scale`` (it is the absolute target scale, so composing it would diverge on repeat), running a measured fallback drive first if the axis gathered no samples — using the calibration board for ground truth when present, otherwise prompting the operator to measure the travelled distance by hand. For each required IR set it applies stored thresholds, driving a fallback sampling pass if needed. Idempotent: once finalized it returns immediately until new evidence arrives. Distance is only marked calibrated when an axis was actually trimmed. Under ``--no-calibrate`` it is a no-op. Users go through :func:`calibration_gate`. .. py:function:: collect_drive(step, manual_measurement: bool = True) -> CollectDrive Wrap a setup motion to opportunistically record a distance sample. The wrapped ``step`` runs unchanged. While it executes, the calibration board (if connected) is read as ground truth alongside the internal odometry, and a per-axis distance sample is stored in the setup calibration session for the :func:`calibration_gate` to fold into the distance trim. Without a usable board ground truth, ``manual_measurement`` decides what happens: when ``True`` (default) the operator is prompted to measure the travelled distance by hand and that becomes the sample; when ``False`` the drive is rejected and no sample is recorded (the axis is left for the gate fallback, or left untrimmed). Prerequisites: A calibration board for ground-truth sampling (optional — see ``manual_measurement``). :param step: Any step (e.g. a ``seq([...])``) whose motion should be measured. :param manual_measurement: When no board ground truth is available, prompt the operator to measure the distance by hand (``True``, default) instead of rejecting the drive (``False``). :returns: A step wrapping ``step``. :rtype: CollectDrive Example:: from raccoon.step.calibration.setup import collect_drive # Ask for a manual measurement if no board is connected (default): collect_drive( seq( [ drive_forward().until(over_line(front.left)), strafe_right().until(over_line(front.left)), ] ), ) # Board-only: silently skip the sample when no board is present. collect_drive(drive_forward().until(over_line(front.left)), manual_measurement=False) .. py:function:: collect_ir_set(step, set_name: str, sensors: list[raccoon.sensor_ir.IRSensor] | None = None) -> CollectIrSet Wrap a setup motion to sample IR sensors into a named calibration set. The wrapped ``step`` runs while every IR sensor is polled at ~100 Hz; the readings accumulate under ``set_name`` in the setup calibration session. The :func:`calibration_gate` later converts the samples into stored black/white thresholds. :param step: Any step whose motion sweeps the sensors over the surface(s). :param set_name: Name of the IR calibration set (e.g. ``"default"``, ``"upper"``). :param sensors: Explicit IR sensors to sample; defaults to all IR sensors in ``robot.defs.analog_sensors``. :returns: A step wrapping ``step``. :rtype: CollectIrSet Example:: from raccoon.step.calibration.setup import collect_ir_set collect_ir_set(drive_backward(cm=50), set_name="upper") .. py:function:: calibration_gate(require_axes: list[step.calibration.setup.session.CalibrationAxis] | None = None, require_ir_sets: list[str] | None = None, fallback_cm: float | None = None, forward_fallback_cm: float = _FALLBACK_FORWARD_CM, lateral_fallback_cm: float = _FALLBACK_LATERAL_CM) -> CalibrationGate Finalize the accumulated setup-calibration evidence exactly once. Assigns each required drive axis' median sample scale as the distance trim via ``MotionTrimService.set_axis_scale`` (it is the absolute target scale, so it is set, not composed — composing would diverge on repeated calibration) and applies each required IR set's thresholds. A drive axis with no samples triggers a measured fallback drive: with a calibration board the board supplies the ground truth, otherwise the operator is prompted to measure the travelled distance by hand. Idempotent and a no-op under ``--no-calibrate``. :param require_axes: Drive axes that must be finalized. ``None`` finalizes every axis that accumulated a sample or was marked required by a collector. :param require_ir_sets: IR set names that must be finalized. ``None`` finalizes every set seen so far. :param fallback_cm: Distance (cm) for the fallback drive, applied to *both* axes. A convenience override — when given it replaces both ``forward_fallback_cm`` and ``lateral_fallback_cm``. ``None`` keeps the per-axis defaults. :param forward_fallback_cm: Distance (cm) of the forward fallback drive when the forward axis has no samples. :param lateral_fallback_cm: Distance (cm) of the lateral fallback strafe. :returns: The finalizing step. :rtype: CalibrationGate Example:: from raccoon.step.calibration.setup import CalibrationAxis, calibration_gate # Default per-axis fallback distances: calibration_gate( require_axes=[CalibrationAxis.FORWARD], require_ir_sets=["default", "upper"], ) # Drive 80 cm for any fallback (both axes): calibration_gate(require_axes=[CalibrationAxis.FORWARD], fallback_cm=80)