feat(perception): integrate native RF-DETR shadow provider

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 02:06:16 +03:00
parent 28effdde23
commit b111406cf8
9 changed files with 1489 additions and 30 deletions
+53 -1
View File
@@ -4,7 +4,10 @@ import hashlib
import json
from pathlib import Path
from k1link.perception.detector import RF_DETR_SHADOW_PROVIDER_ID
from k1link.perception.detector import (
RF_DETR_NATIVE_SHADOW_PROVIDER_ID,
RF_DETR_SHADOW_PROVIDER_ID,
)
from k1link.perception.m48s_advisory import (
AdvisoryFamily,
advisory_policy_matrix,
@@ -17,6 +20,10 @@ GRAPH_CONFIG = (
REPOSITORY_ROOT
/ "config/perception/m48s-rf-detr-reference-graph-shadow-v0.json"
)
NATIVE_GRAPH_CONFIG = (
REPOSITORY_ROOT
/ "config/perception/m48n-rf-detr-native-reference-graph-shadow-v0.json"
)
def test_m48s_reference_graph_replaces_only_the_detector_pin() -> None:
@@ -64,6 +71,51 @@ def test_m48s_reference_graph_pins_every_profile_digest() -> None:
assert pins[role].sha256 == hashlib.sha256(payload).hexdigest()
def test_m48n_native_reference_graph_replaces_only_the_detector_pin() -> None:
native = ReferencePerceptionGraphConfigV2.from_dict(
json.loads(NATIVE_GRAPH_CONFIG.read_text("utf-8"))
)
legacy = ReferencePerceptionGraphConfigV2.from_dict(
json.loads(GRAPH_CONFIG.read_text("utf-8"))
)
native_pins = {item.role: item for item in native.providers}
legacy_pins = {item.role: item for item in legacy.providers}
assert native.graph_id == legacy.graph_id == "reference-perception-graph/v2"
assert native.source_profile_id == legacy.source_profile_id
assert native.queues == legacy.queues
assert native.authority == legacy.authority
assert (
native_pins[ProviderRole.DETECTOR].provider_id
== RF_DETR_NATIVE_SHADOW_PROVIDER_ID
)
assert all(
native_pins[role] == legacy_pins[role]
for role in ProviderRole
if role is not ProviderRole.DETECTOR
)
def test_m48n_native_reference_graph_pins_every_profile_digest() -> None:
config = ReferencePerceptionGraphConfigV2.from_dict(
json.loads(NATIVE_GRAPH_CONFIG.read_text("utf-8"))
)
paths = {
ProviderRole.SOURCE: "m4-recorded-realtime-baseline-v1.json",
ProviderRole.DETECTOR: "rf-detr-large-native-kb4-risk-shadow-v0.json",
ProviderRole.GEOMETRY: "m4-geometry-association-v1.json",
ProviderRole.TEMPORAL: "m4-temporal-motion-v1.json",
ProviderRole.MOTION: "m4-temporal-motion-v1.json",
ProviderRole.ROLLING: "m4-rolling-local-map-v1.json",
ProviderRole.THREAT: "m4-replay-threat-v3.json",
}
pins = {item.role: item for item in config.providers}
for role, name in paths.items():
payload = (REPOSITORY_ROOT / "config/perception" / name).read_bytes()
assert pins[role].sha256 == hashlib.sha256(payload).hexdigest()
def test_m48s_advisory_policy_is_bounded_distinct_and_commandless() -> None:
matrix = advisory_policy_matrix()
@@ -0,0 +1,236 @@
from __future__ import annotations
import json
import math
from pathlib import Path
import numpy as np
import pytest
from numpy.typing import NDArray
from k1link.perception.contracts import (
ClockBasis,
ModalityOutcome,
ModalityStatus,
SourceEnvelope,
TimestampBundle,
)
from k1link.perception.detector import (
RF_DETR_NATIVE_SHADOW_MODEL_ID,
RF_DETR_NATIVE_SHADOW_PREPROCESS_ID,
RF_DETR_NATIVE_SHADOW_PROVIDER_ID,
DetectorFrameTiming,
NativeRfDetrShadowDetectorProvider,
)
from k1link.perception.m48s_reference_graph_runtime import _validate_detector_profile
from k1link.perception.providers import SourcePacket
from k1link.perception.rf_detr_native_object_detector import (
RF_DETR_NATIVE_CONFIG,
RF_DETR_NATIVE_ENGINE_SHA256,
NativeRfDetrConfig,
NativeRfDetrDetectorError,
TritonNativeRfDetrHttpInferenceBackend,
postprocess_native_rf_detr,
prepare_raw_kb4_rf_detr_native,
)
from k1link.perception.rf_detr_object_detector import RfDetrRawOutput
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def _status() -> ModalityStatus:
return ModalityStatus(True, ModalityOutcome.AVAILABLE, "test-available")
def _packet(sequence: int, image: object) -> SourcePacket:
return SourcePacket(
envelope=SourceEnvelope(
source_id="RAVNOVES00",
session_id="20260720T065719Z_viewer_live",
frame_id=f"frame-{sequence:06d}",
sequence=sequence,
timestamps=TimestampBundle(
utc_ns=1_000 + sequence,
monotonic_ns=2_000 + sequence,
source_ns=3_000 + sequence,
clock_basis=ClockBasis.RECORDED_HOST,
),
source_age_ns=0,
binding_reason="test-recorded-source",
calibration_id="camera-1-kb4-test",
representation_id="registered-map-increment-v1",
image=_status(),
registered_point_increment=_status(),
pose=_status(),
),
image_payload=image,
registered_point_increment_payload=("points", sequence),
pose_payload=("pose", sequence),
)
def _output() -> RfDetrRawOutput:
boxes = np.zeros((1, 300, 4), dtype=np.float16)
logits = np.full((1, 300, 91), -20.0, dtype=np.float16)
boxes[0, 0] = (0.5, 0.5, 0.25, 0.25)
logits[0, 0, 18] = np.float16(math.log(3.0)) # dog, score 0.75
boxes[0, 1] = (0.25, 0.25, 0.1, 0.2)
logits[0, 1, 1] = np.float16(math.log(4.0)) # person, score 0.80
boxes[0, 2] = (0.75, 0.25, 0.1, 0.2)
logits[0, 2, 62] = np.float16(math.log(9.0)) # chair, non-risk
return RfDetrRawOutput(boxes=boxes, logits=logits)
class _Backend:
def __init__(self, output: RfDetrRawOutput) -> None:
self.output = output
self.calls = 0
def infer(self, tensor: NDArray[np.uint8]) -> RfDetrRawOutput:
assert tensor.shape == (1, 600, 800, 3)
assert tensor.dtype == np.uint8
assert tensor.flags.c_contiguous
self.calls += 1
return self.output
def test_native_prepare_preserves_every_raw_pixel_without_geometric_transform() -> None:
raster = np.arange(600 * 800 * 3, dtype=np.uint8).reshape(600, 800, 3)
non_contiguous = raster[:, ::-1]
tensor = prepare_raw_kb4_rf_detr_native(non_contiguous)
assert tensor.shape == (1, 600, 800, 3)
assert tensor.dtype == np.uint8
assert tensor.flags.c_contiguous
assert tensor.nbytes == 1_440_000
np.testing.assert_array_equal(tensor[0], non_contiguous)
def test_native_postprocess_uses_608_model_canvas_then_clips_to_raw_raster() -> None:
result = postprocess_native_rf_detr(
_output(),
np.ones((600, 800), dtype=np.bool_),
)
assert tuple(item.label for item in result.detections) == ("person", "dog")
assert result.detections[0].score == pytest.approx(0.8, abs=0.001)
assert result.detections[1].score == pytest.approx(0.75, abs=0.001)
assert result.detections[1].bbox_xyxy == pytest.approx(
(300.0, 228.0, 500.0, 380.0),
abs=0.03,
)
assert dict(result.rejected) == {"non-risk-class": 1}
def test_native_shadow_provider_uses_one_raw_pass_and_preserves_identity() -> None:
backend = _Backend(_output())
provider = NativeRfDetrShadowDetectorProvider(
mask=np.ones((600, 800), dtype=np.bool_),
backend=backend,
clock_ns=iter((10, 30)).__next__,
)
proposals = provider.detect(_packet(7, np.zeros((600, 800, 3), dtype=np.uint8)))
assert backend.calls == 1
assert tuple(item.semantic_hint for item in proposals) == ("person", "dog")
assert all(item.provider_id == RF_DETR_NATIVE_SHADOW_PROVIDER_ID for item in proposals)
assert all(item.model_id == RF_DETR_NATIVE_SHADOW_MODEL_ID for item in proposals)
assert all(item.preprocess_id == RF_DETR_NATIVE_SHADOW_PREPROCESS_ID for item in proposals)
assert provider.snapshot().completed_frames == 1
assert provider.snapshot().proposal_count == 2
assert provider.snapshot().core_duration_ns == 20
def test_native_shadow_provider_reports_prepare_transport_and_postprocess_timing() -> None:
observed: list[DetectorFrameTiming] = []
provider = NativeRfDetrShadowDetectorProvider(
mask=np.ones((600, 800), dtype=np.bool_),
backend=_Backend(_output()),
clock_ns=iter((10, 20, 50, 70)).__next__,
timing_observer=observed.append,
)
provider.detect(_packet(7, np.zeros((600, 800, 3), dtype=np.uint8)))
assert [item.to_dict() for item in observed] == [
{
"sequence": 7,
"preprocess_duration_ns": 10,
"inference_transport_duration_ns": 30,
"postprocess_duration_ns": 20,
"total_duration_ns": 60,
}
]
def test_native_shadow_warmup_is_idempotent_and_excluded_from_frame_counts() -> None:
backend = _Backend(_output())
provider = NativeRfDetrShadowDetectorProvider(
mask=np.ones((600, 800), dtype=np.bool_),
backend=backend,
clock_ns=iter((10, 20, 50, 70)).__next__,
)
first = provider.warm_up()
second = provider.warm_up()
assert first is second
assert backend.calls == 1
assert first.total_duration_ns == 60
assert provider.snapshot().input_frames == 0
assert provider.snapshot().completed_frames == 0
def test_native_profile_is_frozen_and_transport_pins_model_version() -> None:
assert RF_DETR_NATIVE_CONFIG.minimum_score == 0.25
with pytest.raises(NativeRfDetrDetectorError, match="cannot be tuned"):
NativeRfDetrConfig(minimum_score=0.5)
backend = TritonNativeRfDetrHttpInferenceBackend("http://127.0.0.1:8100")
try:
assert backend.path == (
"/v2/models/rf_detr_large_native_kb4/versions/1/infer"
)
finally:
backend.close()
with pytest.raises(NativeRfDetrDetectorError, match="explicit HTTP origin"):
TritonNativeRfDetrHttpInferenceBackend("http://user:secret@127.0.0.1:8100")
def test_native_engine_identity_is_pinned() -> None:
assert RF_DETR_NATIVE_ENGINE_SHA256 == (
"b8a40b3580edff001ec9680de68707242294ff590ab296000fae371f1083f695"
)
def test_native_shadow_profile_records_failed_legacy_agreement_without_authority() -> None:
profile = json.loads(
(
REPOSITORY_ROOT
/ "config/perception/rf-detr-large-native-kb4-risk-shadow-v0.json"
).read_text("utf-8")
)
assert profile["provider_id"] == RF_DETR_NATIVE_SHADOW_PROVIDER_ID
assert profile["model"]["worker_006_rtx4090_tensorrt_11_engine_sha256"] == (
RF_DETR_NATIVE_ENGINE_SHA256
)
assert profile["preprocessing"]["geometric_resampling"] is False
assert profile["preprocessing"]["resize"] is False
assert profile["qualification"]["native_pytorch_tensorrt_parity"]["passed"] is True
assert (
profile["qualification"]["full_ravnoves00_native_vs_legacy_704"]
["legacy_box_agreement_gate_passed"]
is False
)
assert profile["status"]["production_accepted"] is False
assert not any(profile["authority"].values())
assert (
_validate_detector_profile(
REPOSITORY_ROOT
/ "config/perception/rf-detr-large-native-kb4-risk-shadow-v0.json"
)
== "native-kb4"
)