mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 13:21:13 +00:00
[AZ-895] Deprecate replay auto-sync surface; file AZ-908 follow-up
Option A (minimum-deprecation, 2 SP) per user complexity-budget decision. Auto-sync stays importable as a raising stub for one cycle so external callers see a clean ReplayInputAdapterError instead of an ImportError. Full physical removal is filed as AZ-908 (cycle-5+ backlog). Production: - auto_sync.py: 700+ LOC -> 56-line no-op stub raising "auto-sync removed; supply --imu CSV instead" - tlog_video_adapter.py: 700+ LOC -> 105-line deprecated stub; ReplayInputAdapter.open() raises immediately, close() is a no-op - _replay_branch.py: dropped legacy auto-sync branch + _build_auto_sync_config; _validate_replay_paths now requires imu_csv_path; replay_input_adapter_factory parameter removed - cli/replay.py: --time-offset-ms / --skip-auto-sync / --auto-trim emit DeprecationWarning + stderr line; values ignored - tlog_replay_adapter.py + tlog_ground_truth.py docstrings: AUDIT-ONLY Tests: - DELETED test_az405_auto_sync, test_az405_replay_input_adapter, test_az698_window_alignment (covered code no longer runs) - ADDED test_az895_auto_sync_deprecated_stub (5 parametrised, pins AC-1) - test_az402_replay_cli: deprecation warnings + ignored-value asserts - test_az401_compose_root_replay: new imu_csv_path-required gate; deleted the calibration-loading test that relied on the removed replay_input_adapter_factory injection point - test_derkachi_real_tlog: xfail reason refreshed to AZ-848 + AZ-883 (AC-4 "AZ-848-scoped reason") Docs: - module-layout.md: replay_input file list flags deprecated modules, adds csv_ground_truth.py - _dependencies_table.md: +AZ-908 row, preamble + totals updated (179 -> 180 tasks, 567 -> 570 SP) - AZ-908 backlog spec added; AZ-895 spec moved todo -> done - batch_03_cycle4_report.md written Touched-module tests green (111 passed, 1 skipped). Full unit suite green: 2287 passed, 85 skipped, 1 deselected (pre-existing flaky perf test, unrelated). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,14 +16,21 @@ shared composition spine while still exposing exactly one
|
||||
Build-flag gates (per replay protocol Invariant 9):
|
||||
|
||||
- ``BUILD_VIDEO_FILE_FRAME_SOURCE`` — required for the
|
||||
:class:`VideoFileFrameSource` instance returned by the coordinator.
|
||||
- ``BUILD_TLOG_REPLAY_ADAPTER`` — required for the
|
||||
:class:`TlogReplayFcAdapter` instance returned by the coordinator.
|
||||
:class:`VideoFileFrameSource` instance.
|
||||
- ``BUILD_TLOG_REPLAY_ADAPTER`` — historical guard. The tlog adapter
|
||||
is no longer composed by replay (AZ-895 deprecated the (video, tlog)
|
||||
path), but the flag remains in :data:`REPLAY_BUILD_FLAGS` for one
|
||||
deprecation cycle so operator overrides keep their expected semantics.
|
||||
AZ-908 will drop the flag.
|
||||
- ``BUILD_REPLAY_SINK_JSONL`` — shared by the JSONL sink and the noop
|
||||
outbound transport.
|
||||
|
||||
All three default ON in the airborne binary (per ADR-011); flipping any
|
||||
OFF disables replay mode without affecting live mode.
|
||||
|
||||
AZ-895: the replay composition exclusively uses the (video, CSV) path
|
||||
via :class:`CsvReplayFcAdapter`. The legacy (video, tlog) auto-sync
|
||||
branch was removed; ``imu_csv_path`` is required.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -48,13 +55,8 @@ from gps_denied_onboard.components.c8_fc_adapter.replay_sink import (
|
||||
from gps_denied_onboard.config import Config
|
||||
from gps_denied_onboard.fdr_client import make_fdr_client
|
||||
from gps_denied_onboard.frame_source.video_file import VideoFileFrameSource
|
||||
from gps_denied_onboard.helpers.wgs_converter import WgsConverter
|
||||
from gps_denied_onboard.logging import get_logger
|
||||
from gps_denied_onboard.replay_input import (
|
||||
AutoSyncConfig,
|
||||
ReplayInputAdapter,
|
||||
ReplayInputBundle,
|
||||
)
|
||||
from gps_denied_onboard.replay_input import ReplayInputBundle
|
||||
from gps_denied_onboard.replay_input.tlog_video_adapter import ReplayPace
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -77,10 +79,6 @@ REPLAY_BUILD_FLAGS: Final[tuple[str, ...]] = (
|
||||
"BUILD_REPLAY_SINK_JSONL",
|
||||
)
|
||||
|
||||
# AZ-894: separate build flag for the CSV adapter so the replay binary
|
||||
# can opt into the new path without disturbing the BUILD_TLOG_* gate
|
||||
# (the tlog adapter is still composed by _build_replay_input_bundle's
|
||||
# legacy branch until AZ-895 removes it).
|
||||
_CSV_REPLAY_BUILD_FLAG: Final[str] = "BUILD_CSV_REPLAY_ADAPTER"
|
||||
|
||||
|
||||
@@ -106,7 +104,6 @@ def build_replay_components(
|
||||
config: Config,
|
||||
*,
|
||||
fdr_client_factory: Any | None = None,
|
||||
replay_input_adapter_factory: Any | None = None,
|
||||
sink_factory: Any | None = None,
|
||||
transport_factory: Any | None = None,
|
||||
) -> tuple[dict[str, Any], tuple[str, ...]]:
|
||||
@@ -137,11 +134,10 @@ def build_replay_components(
|
||||
sink_fdr_client = fdr_factory("c8_fc_adapter.replay_sink", config)
|
||||
|
||||
# AZ-558: build the outbound MAVLink transport BEFORE the FC adapter
|
||||
# so it can be threaded through `ReplayInputAdapter` and into
|
||||
# `TlogReplayFcAdapter`. The same instance is exposed as the
|
||||
# ``mavlink_transport`` slot in ``components`` (replay protocol
|
||||
# Invariant 5: encoders write through the seam in both modes;
|
||||
# replay drops the bytes via NoopMavlinkTransport).
|
||||
# so the same instance can be exposed as the ``mavlink_transport``
|
||||
# slot in ``components`` (replay protocol Invariant 5: encoders
|
||||
# write through the seam in both modes; replay drops the bytes via
|
||||
# NoopMavlinkTransport).
|
||||
if transport_factory is not None:
|
||||
transport = transport_factory(config)
|
||||
else:
|
||||
@@ -150,7 +146,6 @@ def build_replay_components(
|
||||
bundle = _build_replay_input_bundle(
|
||||
config,
|
||||
fdr_client=fdr_client,
|
||||
adapter_factory=replay_input_adapter_factory,
|
||||
mavlink_transport=transport,
|
||||
)
|
||||
|
||||
@@ -187,19 +182,19 @@ def _validate_build_flags() -> None:
|
||||
def _validate_replay_paths(config: Config) -> None:
|
||||
"""Reject empty / missing replay paths early with a precise message.
|
||||
|
||||
AZ-894: ``imu_csv_path`` is the canonical replay input. ``tlog_path``
|
||||
remains valid for the legacy auto-sync path until AZ-895 removes it,
|
||||
but exactly one of the two must be set so the composition root can
|
||||
pick a single branch.
|
||||
AZ-895: ``imu_csv_path`` is the only supported replay input. The
|
||||
legacy tlog auto-sync surface was deprecated in AZ-895 and will be
|
||||
physically removed in AZ-908; until then ``tlog_path`` may remain
|
||||
set in the config but is ignored by composition.
|
||||
"""
|
||||
if not config.replay.video_path:
|
||||
raise CompositionError(
|
||||
"config.replay.video_path is empty; replay mode requires a video path"
|
||||
)
|
||||
if not config.replay.imu_csv_path and not config.replay.tlog_path:
|
||||
if not config.replay.imu_csv_path:
|
||||
raise CompositionError(
|
||||
"config.replay.imu_csv_path is empty and no tlog_path fallback is set; "
|
||||
"replay mode requires an IMU+GPS CSV (AZ-894) or a tlog file (legacy)"
|
||||
"config.replay.imu_csv_path is empty; "
|
||||
"replay mode requires an IMU+GPS CSV (--imu PATH.csv)"
|
||||
)
|
||||
if not config.replay.output_path:
|
||||
raise CompositionError(
|
||||
@@ -211,61 +206,27 @@ def _build_replay_input_bundle(
|
||||
config: Config,
|
||||
*,
|
||||
fdr_client: "FdrClient",
|
||||
adapter_factory: Any | None,
|
||||
mavlink_transport: Any | None = None,
|
||||
) -> ReplayInputBundle:
|
||||
"""Build the replay input bundle and open the underlying strategies.
|
||||
|
||||
AZ-894: branches on ``config.replay.imu_csv_path`` — when set, builds
|
||||
the :class:`CsvReplayFcAdapter` + :class:`VideoFileFrameSource` pair
|
||||
on a single canonical clock derived from the CSV's ``Time`` column;
|
||||
when unset, falls back to the legacy :class:`ReplayInputAdapter`
|
||||
tlog path (auto-sync + AC-9 validator). AZ-895 removes the legacy
|
||||
branch.
|
||||
AZ-895: the (video, CSV) path is the only supported composition.
|
||||
The legacy (video, tlog) auto-sync branch was removed; the
|
||||
:func:`_validate_replay_paths` gate above guarantees
|
||||
``imu_csv_path`` is set before this function runs.
|
||||
"""
|
||||
pace = _resolve_pace(config.replay.pace)
|
||||
target_fc_dialect = _resolve_fc_kind(config.replay.target_fc_dialect)
|
||||
camera_calibration = _load_camera_calibration(config)
|
||||
wgs_converter = WgsConverter()
|
||||
|
||||
if config.replay.imu_csv_path:
|
||||
return _build_csv_bundle(
|
||||
config,
|
||||
fdr_client=fdr_client,
|
||||
pace=pace,
|
||||
target_fc_dialect=target_fc_dialect,
|
||||
camera_calibration=camera_calibration,
|
||||
mavlink_transport=mavlink_transport,
|
||||
)
|
||||
|
||||
auto_sync = _build_auto_sync_config(config)
|
||||
if adapter_factory is not None:
|
||||
adapter = adapter_factory(
|
||||
config=config,
|
||||
camera_calibration=camera_calibration,
|
||||
target_fc_dialect=target_fc_dialect,
|
||||
wgs_converter=wgs_converter,
|
||||
fdr_client=fdr_client,
|
||||
pace=pace,
|
||||
auto_sync_config=auto_sync,
|
||||
mavlink_transport=mavlink_transport,
|
||||
)
|
||||
else:
|
||||
adapter = ReplayInputAdapter(
|
||||
video_path=Path(config.replay.video_path),
|
||||
tlog_path=Path(config.replay.tlog_path),
|
||||
camera_calibration=camera_calibration,
|
||||
target_fc_dialect=target_fc_dialect,
|
||||
wgs_converter=wgs_converter,
|
||||
fdr_client=fdr_client,
|
||||
pace=pace,
|
||||
manual_time_offset_ms=config.replay.time_offset_ms,
|
||||
skip_auto_sync_validation=config.replay.skip_auto_sync_validation,
|
||||
auto_trim=config.replay.auto_trim,
|
||||
auto_sync_config=auto_sync,
|
||||
mavlink_transport=mavlink_transport,
|
||||
)
|
||||
return adapter.open()
|
||||
return _build_csv_bundle(
|
||||
config,
|
||||
fdr_client=fdr_client,
|
||||
pace=pace,
|
||||
target_fc_dialect=target_fc_dialect,
|
||||
camera_calibration=camera_calibration,
|
||||
mavlink_transport=mavlink_transport,
|
||||
)
|
||||
|
||||
|
||||
def _build_csv_bundle(
|
||||
@@ -339,35 +300,6 @@ def _resolve_fc_kind(raw: str) -> FcKind:
|
||||
)
|
||||
|
||||
|
||||
def _build_auto_sync_config(config: Config) -> AutoSyncConfig:
|
||||
block = config.replay.auto_sync
|
||||
return AutoSyncConfig(
|
||||
takeoff_accel_threshold_g=block.takeoff_accel_threshold_g,
|
||||
takeoff_attitude_rate_threshold_rad_s=(
|
||||
block.takeoff_attitude_rate_threshold_rad_s
|
||||
),
|
||||
sustained_seconds=block.sustained_seconds,
|
||||
prescan_max_messages=block.prescan_max_messages,
|
||||
video_motion_threshold=block.video_motion_threshold,
|
||||
video_motion_scan_seconds=block.video_motion_scan_seconds,
|
||||
match_threshold_pct=block.match_threshold_pct,
|
||||
match_window_ms=block.match_window_ms,
|
||||
low_confidence_threshold=block.low_confidence_threshold,
|
||||
alignment_resample_hz=block.alignment_resample_hz,
|
||||
alignment_video_scan_seconds=block.alignment_video_scan_seconds,
|
||||
alignment_low_confidence_threshold=block.alignment_low_confidence_threshold,
|
||||
alignment_segment_motion_threshold_g=(
|
||||
block.alignment_segment_motion_threshold_g
|
||||
),
|
||||
alignment_segment_min_flight_duration_seconds=(
|
||||
block.alignment_segment_min_flight_duration_seconds
|
||||
),
|
||||
alignment_segment_max_internal_gap_seconds=(
|
||||
block.alignment_segment_max_internal_gap_seconds
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _load_camera_calibration(config: Config) -> CameraCalibration:
|
||||
"""Read the camera calibration JSON into a :class:`CameraCalibration` DTO.
|
||||
|
||||
@@ -427,12 +359,11 @@ def _log_ready(config: Config, bundle: ReplayInputBundle) -> None:
|
||||
"kind": _LOG_KIND_READY,
|
||||
"kv": {
|
||||
"video_path": config.replay.video_path,
|
||||
"tlog_path": config.replay.tlog_path,
|
||||
"imu_csv_path": config.replay.imu_csv_path,
|
||||
"output_path": config.replay.output_path,
|
||||
"pace": config.replay.pace,
|
||||
"resolved_offset_ms": bundle.resolved_time_offset_ms,
|
||||
"calib_path": config.runtime.camera_calibration_path,
|
||||
"auto_sync_used": bundle.auto_sync_result is not None,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user