diff --git a/config/deployment/mission-core-worker-shadow-v1.template.json b/config/deployment/mission-core-worker-shadow-v1.template.json index 18c83c9..9769a5d 100644 --- a/config/deployment/mission-core-worker-shadow-v1.template.json +++ b/config/deployment/mission-core-worker-shadow-v1.template.json @@ -139,7 +139,7 @@ }, "wheel": { "name": "nodedc_mission_core-0.1.0-py3-none-any.whl", - "sha256": "df756938f2c212fb6d1770c83569e5366c708d70d74e0434895a21be55a16102" + "sha256": "4158784fd40b70c7213b629ed39d664fd2f12eab1c9242ec9a6ebbb366e3dd5a" } }, "rollback": { diff --git a/config/perception/m4-reuse-inventory-v1.json b/config/perception/m4-reuse-inventory-v1.json index 07b9765..93d2805 100644 --- a/config/perception/m4-reuse-inventory-v1.json +++ b/config/perception/m4-reuse-inventory-v1.json @@ -28,9 +28,9 @@ "admission": "reuse" }, { - "module": "k1link.compute.yolox_object_detector", - "role": "frozen source-neutral YOLOX preprocess, Triton transport and postprocess", - "admission": "adapt-behind-provider" + "module": "k1link.perception.yolox_object_detector", + "role": "product-owned frozen YOLOX preprocess, Triton transport and postprocess", + "admission": "product-owned" } ], "historical_wrappers": [ diff --git a/docs/22_MILESTONE_4_OBJECT_CENTRIC_RECORDED_REALTIME_CV.md b/docs/22_MILESTONE_4_OBJECT_CENTRIC_RECORDED_REALTIME_CV.md index 25087b6..d04ef9e 100644 --- a/docs/22_MILESTONE_4_OBJECT_CENTRIC_RECORDED_REALTIME_CV.md +++ b/docs/22_MILESTONE_4_OBJECT_CENTRIC_RECORDED_REALTIME_CV.md @@ -686,7 +686,7 @@ duplicate development server or Worker 006 mutation was introduced. The product detector seam is implemented without importing the immutable E46J wrapper: -- `k1link.compute.yolox_object_detector` contains only the frozen raw-KB4 +- `k1link.perception.yolox_object_detector` contains only the frozen raw-KB4 valid-FOV fill, top-left bilinear letterbox, Triton V2 binary HTTP transport, standard YOLOX decode, fixed score/class-wise NMS and FOV/box validation; - `k1link.perception.detector.FrozenYoloxDetectorProvider` turns exactly one diff --git a/scripts/build_m4_worker_shadow_artifact.py b/scripts/build_m4_worker_shadow_artifact.py index 174b63b..4d91376 100644 --- a/scripts/build_m4_worker_shadow_artifact.py +++ b/scripts/build_m4_worker_shadow_artifact.py @@ -25,7 +25,7 @@ WHEEL_NAME = "nodedc_mission_core-0.1.0-py3-none-any.whl" RUNNER_NAME = RUNNER.name PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$") EXPECTED_BASELINE_SHA256 = "ea10359339e6cce31b5780a2710299771cab7cc0c1c2a2b56a1621f786b31fa8" -EXPECTED_WHEEL_SHA256 = "df756938f2c212fb6d1770c83569e5366c708d70d74e0434895a21be55a16102" +EXPECTED_WHEEL_SHA256 = "4158784fd40b70c7213b629ed39d664fd2f12eab1c9242ec9a6ebbb366e3dd5a" PAYLOAD_FILES = ( RUNNER_NAME, WHEEL_NAME, diff --git a/src/k1link/perception/__init__.py b/src/k1link/perception/__init__.py index 7d58a38..8dc0815 100644 --- a/src/k1link/perception/__init__.py +++ b/src/k1link/perception/__init__.py @@ -1,213 +1,6 @@ -"""Object-centric, source-neutral Mission Core perception product boundary.""" +"""Object-centric, source-neutral Mission Core perception product boundary. -from .adapters import ( - PerceptionAdapterError, - observations_from_track_geometry, - temporal_obstacles_from_e34_projection, -) -from .baseline import ( - BASELINE_PROFILE_ID, - BASELINE_SCHEMA, - BaselineContractError, - BaselineProfile, - BaselineVerification, - load_m4_baseline, - validate_reuse_inventory, - verify_m4_baseline, - verify_m4_execution_source, -) -from .contracts import ( - LOCAL_OBSTACLE_MAP_SCHEMA, - OBJECT_PROPOSAL_SCHEMA, - OBSTACLE_OBSERVATION_SCHEMA, - SOURCE_ENVELOPE_SCHEMA, - TEMPORAL_OBSTACLE_SCHEMA, - THREAT_ASSESSMENT_SCHEMA, - BoundingRegion2D, - ClockBasis, - CorridorIntersection, - EvidenceBasis, - EvidenceCurrentness, - FalseAuthority, - GridCell, - HistorySample, - LocalObstacleMap, - MetricGeometry, - ModalityOutcome, - ModalityStatus, - MotionState, - ObjectProposal2D, - ObstacleObservation, - PerceptionContractError, - QualificationState, - SourceAccounting, - SourceEnvelope, - TemporalObstacle, - TemporalState, - ThreatAssessment, - ThreatAuthority, - ThreatDecision, - TimestampBundle, - validate_exclusive_point_ownership, -) -from .detector import ( - FROZEN_YOLOX_MODEL_ID, - FROZEN_YOLOX_PREPROCESS_ID, - FROZEN_YOLOX_PROVIDER_ID, - DetectorProviderError, - DetectorProviderSnapshot, - FrozenYoloxDetectorProvider, - proposals_from_detections, -) -from .detector_replay import run_detector_replay -from .detector_replay_result import ( - DETECTOR_REPLAY_FRAME_SCHEMA, - DETECTOR_REPLAY_RESULT_SCHEMA, - DETECTOR_RUNTIME_IDENTITY_SCHEMA, - M4_DETECTOR_REPLAY_GATE, - DetectorReplayFrame, - DetectorReplayGate, - DetectorReplayMetrics, - DetectorReplayResult, - DetectorReplayResultError, - DetectorRuntimeIdentity, - read_detector_replay_result, - require_m4_detector_replay_acceptance, -) -from .graph import ( - GRAPH_RESULT_SCHEMA, - REFERENCE_GRAPH_ID, - TERMINAL_OUTCOME_SCHEMA, - DeliveredFrame, - GraphExecutionError, - GraphRunResult, - GraphState, - ReferencePerceptionGraphV1, - TerminalOutcome, - TerminalOutcomeType, -) -from .providers import ( - REFERENCE_GRAPH_CONFIG_SCHEMA, - DetectorProvider, - GeometryAssociationProvider, - GraphAuthority, - MotionProvider, - ProviderContractError, - ProviderPin, - ProviderRole, - QueuePolicy, - ReferencePerceptionGraphConfig, - SourcePacket, - SourceProvider, - TemporalStateProvider, - ThreatProvider, -) -from .recorded_source import ( - DecodedRecordedSource, - LiveSourceAdapter, - PyAvRecordedImageDecoder, - RecordedFrameReference, - RecordedImageDecoder, - RecordedRavnoves00Source, - RecordedSourceError, - ReplayPacing, -) - -__all__ = [ - "BASELINE_PROFILE_ID", - "BASELINE_SCHEMA", - "BaselineContractError", - "BaselineProfile", - "BaselineVerification", - "load_m4_baseline", - "validate_reuse_inventory", - "verify_m4_execution_source", - "verify_m4_baseline", - "PerceptionAdapterError", - "observations_from_track_geometry", - "temporal_obstacles_from_e34_projection", - "LOCAL_OBSTACLE_MAP_SCHEMA", - "OBJECT_PROPOSAL_SCHEMA", - "OBSTACLE_OBSERVATION_SCHEMA", - "SOURCE_ENVELOPE_SCHEMA", - "TEMPORAL_OBSTACLE_SCHEMA", - "THREAT_ASSESSMENT_SCHEMA", - "BoundingRegion2D", - "ClockBasis", - "CorridorIntersection", - "EvidenceBasis", - "EvidenceCurrentness", - "FalseAuthority", - "GridCell", - "HistorySample", - "LocalObstacleMap", - "MetricGeometry", - "ModalityOutcome", - "ModalityStatus", - "MotionState", - "ObjectProposal2D", - "ObstacleObservation", - "PerceptionContractError", - "QualificationState", - "SourceAccounting", - "SourceEnvelope", - "TemporalObstacle", - "TemporalState", - "ThreatAssessment", - "ThreatAuthority", - "ThreatDecision", - "TimestampBundle", - "validate_exclusive_point_ownership", - "FROZEN_YOLOX_MODEL_ID", - "FROZEN_YOLOX_PREPROCESS_ID", - "FROZEN_YOLOX_PROVIDER_ID", - "DetectorProviderError", - "DetectorProviderSnapshot", - "FrozenYoloxDetectorProvider", - "proposals_from_detections", - "DETECTOR_REPLAY_FRAME_SCHEMA", - "DETECTOR_REPLAY_RESULT_SCHEMA", - "DETECTOR_RUNTIME_IDENTITY_SCHEMA", - "M4_DETECTOR_REPLAY_GATE", - "DetectorReplayFrame", - "DetectorReplayGate", - "DetectorReplayMetrics", - "DetectorReplayResult", - "DetectorReplayResultError", - "DetectorRuntimeIdentity", - "read_detector_replay_result", - "require_m4_detector_replay_acceptance", - "run_detector_replay", - "REFERENCE_GRAPH_CONFIG_SCHEMA", - "DetectorProvider", - "GeometryAssociationProvider", - "GraphAuthority", - "MotionProvider", - "ProviderContractError", - "ProviderPin", - "ProviderRole", - "QueuePolicy", - "ReferencePerceptionGraphConfig", - "SourcePacket", - "SourceProvider", - "TemporalStateProvider", - "ThreatProvider", - "GRAPH_RESULT_SCHEMA", - "REFERENCE_GRAPH_ID", - "TERMINAL_OUTCOME_SCHEMA", - "DeliveredFrame", - "GraphExecutionError", - "GraphRunResult", - "GraphState", - "ReferencePerceptionGraphV1", - "TerminalOutcome", - "TerminalOutcomeType", - "LiveSourceAdapter", - "DecodedRecordedSource", - "PyAvRecordedImageDecoder", - "RecordedFrameReference", - "RecordedImageDecoder", - "RecordedRavnoves00Source", - "RecordedSourceError", - "ReplayPacing", -] +The package initializer is deliberately side-effect free. Runtime code imports +the concrete contract, provider, graph or source module it needs; importing the +product namespace must not initialize legacy compute, device, web or MQTT code. +""" diff --git a/src/k1link/perception/detector.py b/src/k1link/perception/detector.py index cac7202..bf82271 100644 --- a/src/k1link/perception/detector.py +++ b/src/k1link/perception/detector.py @@ -12,7 +12,9 @@ from typing import Final import numpy as np from numpy.typing import NDArray -from k1link.compute.yolox_object_detector import ( +from .contracts import BoundingRegion2D, ObjectProposal2D +from .providers import SourcePacket +from .yolox_object_detector import ( FROZEN_YOLOX_CONFIG, YOLOX_MODEL_ID, YOLOX_MODEL_VERSION, @@ -24,9 +26,6 @@ from k1link.compute.yolox_object_detector import ( preprocess_raw_kb4, ) -from .contracts import BoundingRegion2D, ObjectProposal2D -from .providers import SourcePacket - FROZEN_YOLOX_PROVIDER_ID: Final = "triton-yolox-s-raw-kb4/v1" FROZEN_YOLOX_MODEL_ID: Final = f"{YOLOX_MODEL_ID}:{YOLOX_MODEL_VERSION}" FROZEN_YOLOX_PREPROCESS_ID: Final = "raw-kb4-valid-fov-letterbox/v1" diff --git a/src/k1link/perception/detector_replay_cli.py b/src/k1link/perception/detector_replay_cli.py index 460b535..4e235d0 100644 --- a/src/k1link/perception/detector_replay_cli.py +++ b/src/k1link/perception/detector_replay_cli.py @@ -9,11 +9,6 @@ import urllib.parse from pathlib import Path from threading import Event -from k1link.compute.yolox_object_detector import ( - TritonHttpInferenceBackend, - load_valid_fov_mask, -) - from .baseline import load_m4_baseline, verify_m4_execution_source from .detector import FrozenYoloxDetectorProvider from .detector_replay import run_detector_replay @@ -28,6 +23,10 @@ from .recorded_source import ( RecordedRavnoves00Source, ReplayPacing, ) +from .yolox_object_detector import ( + TritonHttpInferenceBackend, + load_valid_fov_mask, +) def _arguments() -> argparse.Namespace: diff --git a/src/k1link/perception/detector_replay_contracts.py b/src/k1link/perception/detector_replay_contracts.py index c098bba..6f85f21 100644 --- a/src/k1link/perception/detector_replay_contracts.py +++ b/src/k1link/perception/detector_replay_contracts.py @@ -8,12 +8,6 @@ from dataclasses import dataclass from pathlib import Path from typing import Final -from k1link.compute.yolox_object_detector import ( - YOLOX_CONFIG_SHA256, - YOLOX_MODEL_SHA256, - YOLOX_VALID_FOV_SHA256, -) - from .contracts import ObjectProposal2D, SourceEnvelope from .detector import ( FROZEN_YOLOX_MODEL_ID, @@ -21,6 +15,11 @@ from .detector import ( FROZEN_YOLOX_PROVIDER_ID, DetectorProviderSnapshot, ) +from .yolox_object_detector import ( + YOLOX_CONFIG_SHA256, + YOLOX_MODEL_SHA256, + YOLOX_VALID_FOV_SHA256, +) DETECTOR_REPLAY_RESULT_SCHEMA: Final = "missioncore.perception-detector-replay-result/v1" DETECTOR_REPLAY_RECEIPT_SCHEMA: Final = "missioncore.perception-detector-replay-receipt/v1" diff --git a/src/k1link/perception/detector_replay_result.py b/src/k1link/perception/detector_replay_result.py index 4c3f7fb..893a3b0 100644 --- a/src/k1link/perception/detector_replay_result.py +++ b/src/k1link/perception/detector_replay_result.py @@ -10,14 +10,6 @@ import uuid from datetime import UTC, datetime from pathlib import Path -from k1link.compute.yolox_object_detector import ( - FROZEN_YOLOX_CONFIG, - YOLOX_CONFIG_SHA256, - YOLOX_MODEL_SHA256, - YOLOX_MODEL_VERSION, - YOLOX_VALID_FOV_SHA256, -) - from .baseline import ( BASELINE_CAMERA_STREAM_SHA256, BASELINE_FILE_SHA256, @@ -57,6 +49,13 @@ from .detector_replay_validation import ( ) from .detector_replay_validation import validate_frame_accounting from .recorded_source import RECORDED_SOURCE_PROVIDER_ID +from .yolox_object_detector import ( + FROZEN_YOLOX_CONFIG, + YOLOX_CONFIG_SHA256, + YOLOX_MODEL_SHA256, + YOLOX_MODEL_VERSION, + YOLOX_VALID_FOV_SHA256, +) def seal_detector_replay_result( diff --git a/src/k1link/perception/detector_replay_validation.py b/src/k1link/perception/detector_replay_validation.py index ab1099b..f886a33 100644 --- a/src/k1link/perception/detector_replay_validation.py +++ b/src/k1link/perception/detector_replay_validation.py @@ -6,14 +6,6 @@ import hashlib import json from pathlib import Path -from k1link.compute.yolox_object_detector import ( - FROZEN_YOLOX_CONFIG, - YOLOX_CONFIG_SHA256, - YOLOX_MODEL_SHA256, - YOLOX_MODEL_VERSION, - YOLOX_VALID_FOV_SHA256, -) - from .baseline import ( BASELINE_CAMERA_STREAM_SHA256, BASELINE_FILE_SHA256, @@ -53,6 +45,13 @@ from .detector_replay_contracts import ( detector_replay_accepted, ) from .recorded_source import RECORDED_SOURCE_PROVIDER_ID +from .yolox_object_detector import ( + FROZEN_YOLOX_CONFIG, + YOLOX_CONFIG_SHA256, + YOLOX_MODEL_SHA256, + YOLOX_MODEL_VERSION, + YOLOX_VALID_FOV_SHA256, +) def read_detector_replay_result(root: Path) -> DetectorReplayResult: diff --git a/src/k1link/compute/yolox_object_detector.py b/src/k1link/perception/yolox_object_detector.py similarity index 99% rename from src/k1link/compute/yolox_object_detector.py rename to src/k1link/perception/yolox_object_detector.py index 5b338b8..4dbc5b8 100644 --- a/src/k1link/compute/yolox_object_detector.py +++ b/src/k1link/perception/yolox_object_detector.py @@ -1,4 +1,4 @@ -"""Source-neutral frozen YOLOX preprocessing, inference transport and postprocessing.""" +"""Perception-owned frozen YOLOX preprocessing, transport and postprocessing.""" from __future__ import annotations diff --git a/tests/test_detector_replay.py b/tests/test_detector_replay.py index 9521eb0..cac6982 100644 --- a/tests/test_detector_replay.py +++ b/tests/test_detector_replay.py @@ -11,11 +11,6 @@ import numpy as np import pytest from numpy.typing import NDArray -from k1link.compute.yolox_object_detector import ( - YOLOX_CONFIG_SHA256, - YOLOX_MODEL_SHA256, - YOLOX_VALID_FOV_SHA256, -) from k1link.perception.contracts import ( ClockBasis, ModalityOutcome, @@ -34,6 +29,11 @@ from k1link.perception.detector_replay_result import ( require_m4_detector_replay_acceptance, ) from k1link.perception.providers import SourcePacket +from k1link.perception.yolox_object_detector import ( + YOLOX_CONFIG_SHA256, + YOLOX_MODEL_SHA256, + YOLOX_VALID_FOV_SHA256, +) class _Source: diff --git a/tests/test_perception_architecture.py b/tests/test_perception_architecture.py index 3820b10..6d3657b 100644 --- a/tests/test_perception_architecture.py +++ b/tests/test_perception_architecture.py @@ -19,6 +19,20 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[1] PERCEPTION_ROOT = REPOSITORY_ROOT / "src" / "k1link" / "perception" BASELINE_PATH = REPOSITORY_ROOT / "config" / "perception" / "m4-recorded-realtime-baseline-v1.json" REUSE_PATH = REPOSITORY_ROOT / "config" / "perception" / "m4-reuse-inventory-v1.json" +DETECTOR_RUNTIME_MODULES = ( + "__init__.py", + "baseline.py", + "contracts.py", + "detector.py", + "detector_replay.py", + "detector_replay_cli.py", + "detector_replay_contracts.py", + "detector_replay_result.py", + "detector_replay_validation.py", + "providers.py", + "recorded_source.py", + "yolox_object_detector.py", +) def _imports(path: Path) -> set[str]: @@ -151,6 +165,22 @@ def test_perception_contracts_import_no_compute_lab_graph_or_web_module() -> Non assert forbidden == set() +def test_perception_package_initializer_is_side_effect_free() -> None: + assert _imports(PERCEPTION_ROOT / "__init__.py") == set() + + +def test_detector_runtime_closure_imports_no_legacy_compute_package() -> None: + violations = { + name: sorted( + module + for module in _imports(PERCEPTION_ROOT / name) + if module.startswith("k1link.compute") + ) + for name in DETECTOR_RUNTIME_MODULES + } + assert {name: modules for name, modules in violations.items() if modules} == {} + + def test_new_perception_boundary_has_no_experiment_specific_imports() -> None: violations: dict[str, str] = {} for path in PERCEPTION_ROOT.glob("*.py"): diff --git a/tests/test_yolox_detector_provider.py b/tests/test_yolox_detector_provider.py index d492d5b..515df1c 100644 --- a/tests/test_yolox_detector_provider.py +++ b/tests/test_yolox_detector_provider.py @@ -9,14 +9,6 @@ import numpy as np import pytest from numpy.typing import NDArray -from k1link.compute.yolox_object_detector import ( - FrozenYoloxConfig, - TritonHttpInferenceBackend, - YoloxDetection, - YoloxDetectorError, - postprocess_yolox, - preprocess_raw_kb4, -) from k1link.perception.contracts import ( ClockBasis, ModalityOutcome, @@ -31,6 +23,14 @@ from k1link.perception.detector import ( proposals_from_detections, ) from k1link.perception.providers import SourcePacket +from k1link.perception.yolox_object_detector import ( + FrozenYoloxConfig, + TritonHttpInferenceBackend, + YoloxDetection, + YoloxDetectorError, + postprocess_yolox, + preprocess_raw_kb4, +) REPOSITORY_ROOT = Path(__file__).resolve().parents[1] E46J_FRAMES = (