step.calibration.setup.session ============================== .. py:module:: step.calibration.setup.session .. autoapi-nested-parse:: Robot service that accumulates setup-time calibration evidence. The service is a passive ledger: :class:`CollectDrive` / :class:`CollectIrSet` record opportunistic samples into it during the setup mission, and :class:`CalibrationGate` reads them back to finalize the persisted compensation layers. It never changes the active odometry source or applies a trim itself — that is the gate's job. Classes ------- .. autoapisummary:: step.calibration.setup.session.CalibrationAxis step.calibration.setup.session.DriveCalibrationSample step.calibration.setup.session.IrCalibrationSet step.calibration.setup.session.SetupCalibrationSession Module Contents --------------- .. py:class:: CalibrationAxis(*args, **kwds) Bases: :py:obj:`enum.Enum` Create a collection of name/value pairs. Example enumeration: >>> class Color(Enum): ... RED = 1 ... BLUE = 2 ... GREEN = 3 Access them by: - attribute access: >>> Color.RED - value lookup: >>> Color(1) - name lookup: >>> Color['RED'] Enumerations can be iterated over, and know how many members they have: >>> len(Color) 3 >>> list(Color) [, , ] Methods can be added to enumerations, and members can have their own attributes -- see the documentation for details. .. py:attribute:: FORWARD :value: 'forward' .. py:attribute:: LATERAL :value: 'lateral' .. py:class:: DriveCalibrationSample .. py:attribute:: axis :type: CalibrationAxis .. py:attribute:: odom_distance_m :type: float .. py:attribute:: ground_truth_distance_m :type: float .. py:attribute:: source :type: str .. py:property:: scale :type: float Absolute distance-trim scale this sample implies (``odom / ground_truth``). The trim is applied as ``commanded * scale`` and the gate assigns this value directly via ``MotionTrimService.set_axis_scale`` (it is the target scale, not a delta — so it is set, never composed; composing would diverge geometrically on repeated calibration). ``odom_distance`` and ``ground_truth`` are measured over the same physical drive, so the ratio is independent of whatever scale was active during the drive. Direction: the internal odometry regulates the drive, so when it over-reports (``odom > ground_truth``, e.g. 56.7cm odom for 53.0cm actually travelled) a commanded distance under-shoots physically. The scale is then ``> 1`` to lengthen the commanded target back onto the real distance — this is verified against hardware (a ``< 1`` scale here drives the robot SHORT). A distance trim is a magnitude correction, so it is inherently positive. Absolute distances are used because the calibration board's pose frame has a constant 180° mounting offset — its projected displacement may carry the opposite sign of the internal odometry even though both measure the same forward travel, and a signed ratio would yield a bogus negative scale. .. py:class:: IrCalibrationSet .. py:attribute:: sensor_ports :type: set[int] .. py:attribute:: samples_by_port :type: dict[int, list[float]] .. py:method:: add(port: int, samples: list[float]) -> None .. py:method:: has_minimum_samples(min_samples: int) -> bool .. py:class:: SetupCalibrationSession(robot: raccoon.robot.api.GenericRobot) Accumulates setup-time calibration evidence and applies a final trim. .. py:property:: board_available :type: bool .. py:property:: gate_completed :type: bool .. py:method:: mark_pending() -> None .. py:method:: require_axis(axis: CalibrationAxis) -> None .. py:method:: require_ir_set(set_name: str) -> None .. py:method:: axes_to_finalize(requested: list[CalibrationAxis] | None) -> list[CalibrationAxis] .. py:method:: ir_sets_to_finalize(requested: list[str] | None) -> list[str] .. py:method:: add_drive_sample(sample: DriveCalibrationSample) -> None .. py:method:: get_drive_samples(axis: CalibrationAxis) -> list[DriveCalibrationSample] .. py:method:: clear_drive_samples(axis: CalibrationAxis) -> None Drop all accumulated samples for an axis (operator asked to re-drive). .. py:method:: add_ir_samples(set_name: str, sensor_ports: list[int], samples_by_port: dict[int, list[float]]) -> None .. py:method:: get_ir_set(set_name: str) -> IrCalibrationSet .. py:method:: finish_gate() -> None .. py:method:: median_axis_scale(axis: CalibrationAxis) -> float Median distance-trim scale across this axis' samples (1.0 if none). .. py:method:: median_axis_sample(axis: CalibrationAxis) -> DriveCalibrationSample | None The sample whose scale is the median, for a representative summary. Returns ``None`` when the axis has no samples. The applied trim is always :meth:`median_axis_scale`; this just picks a real ``(odom, ground_truth)`` pair to display alongside it. .. py:method:: ensure_board_probe(robot: raccoon.robot.api.GenericRobot, log_step) -> None .. py:method:: capture_internal_snapshot(robot: raccoon.robot.api.GenericRobot) -> _PoseSnapshot :staticmethod: .. py:method:: capture_reference_snapshot(robot: raccoon.robot.api.GenericRobot) -> _PoseSnapshot :staticmethod: .. py:method:: reference_pose(robot: raccoon.robot.api.GenericRobot) :staticmethod: Read the calibration-board pose directly, without changing the active source. .. py:method:: axis_distance_m(start: _PoseSnapshot, end_pose, axis: CalibrationAxis) -> float :staticmethod: .. py:method:: detect_axis(start: _PoseSnapshot, end_pose) -> CalibrationAxis :staticmethod: Infer the driven axis from the dominant internal-odometry displacement.