refactor(perception): isolate detector runtime

This commit is contained in:
DCCONSTRUCTIONS
2026-08-05 16:05:26 +03:00
parent 8e161a197f
commit 8bd632509e
14 changed files with 81 additions and 263 deletions
@@ -139,7 +139,7 @@
}, },
"wheel": { "wheel": {
"name": "nodedc_mission_core-0.1.0-py3-none-any.whl", "name": "nodedc_mission_core-0.1.0-py3-none-any.whl",
"sha256": "df756938f2c212fb6d1770c83569e5366c708d70d74e0434895a21be55a16102" "sha256": "4158784fd40b70c7213b629ed39d664fd2f12eab1c9242ec9a6ebbb366e3dd5a"
} }
}, },
"rollback": { "rollback": {
+3 -3
View File
@@ -28,9 +28,9 @@
"admission": "reuse" "admission": "reuse"
}, },
{ {
"module": "k1link.compute.yolox_object_detector", "module": "k1link.perception.yolox_object_detector",
"role": "frozen source-neutral YOLOX preprocess, Triton transport and postprocess", "role": "product-owned frozen YOLOX preprocess, Triton transport and postprocess",
"admission": "adapt-behind-provider" "admission": "product-owned"
} }
], ],
"historical_wrappers": [ "historical_wrappers": [
@@ -686,7 +686,7 @@ duplicate development server or Worker 006 mutation was introduced.
The product detector seam is implemented without importing the immutable E46J The product detector seam is implemented without importing the immutable E46J
wrapper: 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, valid-FOV fill, top-left bilinear letterbox, Triton V2 binary HTTP transport,
standard YOLOX decode, fixed score/class-wise NMS and FOV/box validation; standard YOLOX decode, fixed score/class-wise NMS and FOV/box validation;
- `k1link.perception.detector.FrozenYoloxDetectorProvider` turns exactly one - `k1link.perception.detector.FrozenYoloxDetectorProvider` turns exactly one
+1 -1
View File
@@ -25,7 +25,7 @@ WHEEL_NAME = "nodedc_mission_core-0.1.0-py3-none-any.whl"
RUNNER_NAME = RUNNER.name RUNNER_NAME = RUNNER.name
PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$") PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
EXPECTED_BASELINE_SHA256 = "ea10359339e6cce31b5780a2710299771cab7cc0c1c2a2b56a1621f786b31fa8" EXPECTED_BASELINE_SHA256 = "ea10359339e6cce31b5780a2710299771cab7cc0c1c2a2b56a1621f786b31fa8"
EXPECTED_WHEEL_SHA256 = "df756938f2c212fb6d1770c83569e5366c708d70d74e0434895a21be55a16102" EXPECTED_WHEEL_SHA256 = "4158784fd40b70c7213b629ed39d664fd2f12eab1c9242ec9a6ebbb366e3dd5a"
PAYLOAD_FILES = ( PAYLOAD_FILES = (
RUNNER_NAME, RUNNER_NAME,
WHEEL_NAME, WHEEL_NAME,
+5 -212
View File
@@ -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 ( The package initializer is deliberately side-effect free. Runtime code imports
PerceptionAdapterError, the concrete contract, provider, graph or source module it needs; importing the
observations_from_track_geometry, product namespace must not initialize legacy compute, device, web or MQTT code.
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",
]
+3 -4
View File
@@ -12,7 +12,9 @@ from typing import Final
import numpy as np import numpy as np
from numpy.typing import NDArray 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, FROZEN_YOLOX_CONFIG,
YOLOX_MODEL_ID, YOLOX_MODEL_ID,
YOLOX_MODEL_VERSION, YOLOX_MODEL_VERSION,
@@ -24,9 +26,6 @@ from k1link.compute.yolox_object_detector import (
preprocess_raw_kb4, 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_PROVIDER_ID: Final = "triton-yolox-s-raw-kb4/v1"
FROZEN_YOLOX_MODEL_ID: Final = f"{YOLOX_MODEL_ID}:{YOLOX_MODEL_VERSION}" FROZEN_YOLOX_MODEL_ID: Final = f"{YOLOX_MODEL_ID}:{YOLOX_MODEL_VERSION}"
FROZEN_YOLOX_PREPROCESS_ID: Final = "raw-kb4-valid-fov-letterbox/v1" FROZEN_YOLOX_PREPROCESS_ID: Final = "raw-kb4-valid-fov-letterbox/v1"
+4 -5
View File
@@ -9,11 +9,6 @@ import urllib.parse
from pathlib import Path from pathlib import Path
from threading import Event 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 .baseline import load_m4_baseline, verify_m4_execution_source
from .detector import FrozenYoloxDetectorProvider from .detector import FrozenYoloxDetectorProvider
from .detector_replay import run_detector_replay from .detector_replay import run_detector_replay
@@ -28,6 +23,10 @@ from .recorded_source import (
RecordedRavnoves00Source, RecordedRavnoves00Source,
ReplayPacing, ReplayPacing,
) )
from .yolox_object_detector import (
TritonHttpInferenceBackend,
load_valid_fov_mask,
)
def _arguments() -> argparse.Namespace: def _arguments() -> argparse.Namespace:
@@ -8,12 +8,6 @@ from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Final 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 .contracts import ObjectProposal2D, SourceEnvelope
from .detector import ( from .detector import (
FROZEN_YOLOX_MODEL_ID, FROZEN_YOLOX_MODEL_ID,
@@ -21,6 +15,11 @@ from .detector import (
FROZEN_YOLOX_PROVIDER_ID, FROZEN_YOLOX_PROVIDER_ID,
DetectorProviderSnapshot, 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_RESULT_SCHEMA: Final = "missioncore.perception-detector-replay-result/v1"
DETECTOR_REPLAY_RECEIPT_SCHEMA: Final = "missioncore.perception-detector-replay-receipt/v1" DETECTOR_REPLAY_RECEIPT_SCHEMA: Final = "missioncore.perception-detector-replay-receipt/v1"
@@ -10,14 +10,6 @@ import uuid
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path 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 ( from .baseline import (
BASELINE_CAMERA_STREAM_SHA256, BASELINE_CAMERA_STREAM_SHA256,
BASELINE_FILE_SHA256, BASELINE_FILE_SHA256,
@@ -57,6 +49,13 @@ from .detector_replay_validation import (
) )
from .detector_replay_validation import validate_frame_accounting from .detector_replay_validation import validate_frame_accounting
from .recorded_source import RECORDED_SOURCE_PROVIDER_ID 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( def seal_detector_replay_result(
@@ -6,14 +6,6 @@ import hashlib
import json import json
from pathlib import Path 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 ( from .baseline import (
BASELINE_CAMERA_STREAM_SHA256, BASELINE_CAMERA_STREAM_SHA256,
BASELINE_FILE_SHA256, BASELINE_FILE_SHA256,
@@ -53,6 +45,13 @@ from .detector_replay_contracts import (
detector_replay_accepted, detector_replay_accepted,
) )
from .recorded_source import RECORDED_SOURCE_PROVIDER_ID 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: def read_detector_replay_result(root: Path) -> DetectorReplayResult:
@@ -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 from __future__ import annotations
+5 -5
View File
@@ -11,11 +11,6 @@ import numpy as np
import pytest import pytest
from numpy.typing import NDArray 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 ( from k1link.perception.contracts import (
ClockBasis, ClockBasis,
ModalityOutcome, ModalityOutcome,
@@ -34,6 +29,11 @@ from k1link.perception.detector_replay_result import (
require_m4_detector_replay_acceptance, require_m4_detector_replay_acceptance,
) )
from k1link.perception.providers import SourcePacket 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: class _Source:
+30
View File
@@ -19,6 +19,20 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
PERCEPTION_ROOT = REPOSITORY_ROOT / "src" / "k1link" / "perception" PERCEPTION_ROOT = REPOSITORY_ROOT / "src" / "k1link" / "perception"
BASELINE_PATH = REPOSITORY_ROOT / "config" / "perception" / "m4-recorded-realtime-baseline-v1.json" BASELINE_PATH = REPOSITORY_ROOT / "config" / "perception" / "m4-recorded-realtime-baseline-v1.json"
REUSE_PATH = REPOSITORY_ROOT / "config" / "perception" / "m4-reuse-inventory-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]: 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() 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: def test_new_perception_boundary_has_no_experiment_specific_imports() -> None:
violations: dict[str, str] = {} violations: dict[str, str] = {}
for path in PERCEPTION_ROOT.glob("*.py"): for path in PERCEPTION_ROOT.glob("*.py"):
+8 -8
View File
@@ -9,14 +9,6 @@ import numpy as np
import pytest import pytest
from numpy.typing import NDArray 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 ( from k1link.perception.contracts import (
ClockBasis, ClockBasis,
ModalityOutcome, ModalityOutcome,
@@ -31,6 +23,14 @@ from k1link.perception.detector import (
proposals_from_detections, proposals_from_detections,
) )
from k1link.perception.providers import SourcePacket 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] REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
E46J_FRAMES = ( E46J_FRAMES = (