mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
9d298304c3
Implement vision annotation observations, inference and preferences through a dedicated runtime owner.
170 lines
5.9 KiB
Python
170 lines
5.9 KiB
Python
"""Exact-asset CPU wrapper with deterministic NV12/mask and fake-network tests."""
|
|
|
|
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from openpilot.starpilot.spot_monitor import inference
|
|
from openpilot.starpilot.spot_monitor.policy import decode_annotation
|
|
|
|
|
|
class FakeNet:
|
|
def __init__(self):
|
|
self.blob = None
|
|
self.output = np.array([[0.05, 0.95, 0.0]], dtype=np.float32)
|
|
self.backend = None
|
|
self.target = None
|
|
|
|
def setPreferableBackend(self, backend):
|
|
self.backend = backend
|
|
|
|
def setPreferableTarget(self, target):
|
|
self.target = target
|
|
|
|
def setInput(self, blob):
|
|
self.blob = blob
|
|
|
|
def forward(self):
|
|
return self.output
|
|
|
|
|
|
class FakeDnn:
|
|
DNN_BACKEND_OPENCV = 7
|
|
DNN_TARGET_CPU = 8
|
|
|
|
def __init__(self, net):
|
|
self.net = net
|
|
self.loaded = None
|
|
|
|
def readNetFromONNX(self, raw):
|
|
self.loaded = raw
|
|
return self.net
|
|
|
|
|
|
class FakeCV2:
|
|
COLOR_YUV2RGB_NV12 = 1
|
|
INTER_LINEAR = 2
|
|
|
|
def __init__(self, net):
|
|
self.dnn = FakeDnn(net)
|
|
self.mask: np.ndarray | None = None
|
|
self.threads = None
|
|
|
|
def setNumThreads(self, count):
|
|
self.threads = count
|
|
|
|
def cvtColor(self, nv12, code):
|
|
assert code == self.COLOR_YUV2RGB_NV12
|
|
height = nv12.shape[0] * 2 // 3
|
|
return np.repeat(nv12[:height, :, None], 3, axis=2)
|
|
|
|
def fillPoly(self, mask, polygons, value):
|
|
points = polygons[0]
|
|
for y in range(mask.shape[0]):
|
|
for x in range(mask.shape[1]):
|
|
# A strict interior raster is enough to expose the wrapper's mask
|
|
# application without requiring OpenCV in the host test environment.
|
|
inside = False
|
|
for a, b in zip(points, np.roll(points, -1, axis=0), strict=True):
|
|
if (a[1] > y + 0.5) != (b[1] > y + 0.5):
|
|
boundary = a[0] + (y + 0.5 - a[1]) * (b[0] - a[0]) / (b[1] - a[1])
|
|
if x + 0.5 < boundary:
|
|
inside = not inside
|
|
if inside:
|
|
mask[y, x] = value
|
|
self.mask = mask.copy()
|
|
|
|
def bitwise_and(self, left, right, *, mask):
|
|
return np.where(mask[:, :, None] != 0, left, 0).astype(np.uint8)
|
|
|
|
def resize(self, image, size, *, interpolation):
|
|
assert interpolation == self.INTER_LINEAR
|
|
width, height = size
|
|
ys = (np.arange(height) * image.shape[0] // height).astype(int)
|
|
xs = (np.arange(width) * image.shape[1] // width).astype(int)
|
|
return image[np.ix_(ys, xs)]
|
|
|
|
|
|
@pytest.fixture
|
|
def tiny_model(tmp_path: Path, monkeypatch):
|
|
raw = b"small reviewed ONNX fixture"
|
|
monkeypatch.setattr(inference, "MODEL_SIZE_BYTES", len(raw))
|
|
monkeypatch.setattr(inference, "MODEL_SHA256", hashlib.sha256(raw).hexdigest())
|
|
path = tmp_path / "model.onnx"
|
|
path.write_bytes(raw)
|
|
return path
|
|
|
|
|
|
def test_exact_asset_cpu_mask_crop_class_one_and_loaded_bytes_pinned(tiny_model, monkeypatch):
|
|
net = FakeNet()
|
|
cv2 = FakeCV2(net)
|
|
model = inference.VASMInference(tiny_model, cv2_module=cv2)
|
|
assert model.load()
|
|
assert cv2.threads == 1
|
|
assert model.loaded_sha256 == inference.MODEL_SHA256
|
|
assert cv2.dnn.loaded == tiny_model.read_bytes()
|
|
assert (net.backend, net.target) == (cv2.dnn.DNN_BACKEND_OPENCV, cv2.dnn.DNN_TARGET_CPU)
|
|
annotation = decode_annotation(b'{"version":1,"width":32,"height":32,"poly_left":[[4,4],[20,4],[4,20]],"poly_right":[]}')
|
|
frame = np.full((48, 32), 128, dtype=np.uint8)
|
|
frame[:32, :] = 255
|
|
assert model.infer_nv12(frame, width=32, height=32, annotation=annotation, side="left") == pytest.approx(0.95)
|
|
assert cv2.mask is not None
|
|
assert cv2.mask[2, 2] == 255
|
|
assert cv2.mask[-2, -2] == 0
|
|
assert net.blob.shape == (1, 3, 352, 352)
|
|
assert net.blob.dtype == np.float32
|
|
assert net.blob.max() == 1.0 and net.blob.min() == 0.0
|
|
tiny_model.write_bytes(b"small tampered ONNX fixture")
|
|
def fail_reopen(_):
|
|
raise AssertionError("infer reopened model source")
|
|
|
|
monkeypatch.setattr(inference, "_verified_model_bytes", fail_reopen)
|
|
assert model.infer_nv12(frame, width=32, height=32, annotation=annotation, side="left") == pytest.approx(0.95)
|
|
assert model.loaded_sha256 == inference.MODEL_SHA256
|
|
|
|
|
|
def test_changed_source_rejected_on_explicit_reload(tiny_model):
|
|
model = inference.VASMInference(tiny_model, cv2_module=FakeCV2(FakeNet()))
|
|
assert model.load()
|
|
tiny_model.write_bytes(b"small tampered ONNX fixture")
|
|
assert not model.load()
|
|
assert model.net is None and model.loaded_sha256 is None
|
|
|
|
|
|
def test_asset_missing_wrong_size_symlink_and_bad_output_fail_unavailable(tiny_model, tmp_path):
|
|
cv2 = FakeCV2(FakeNet())
|
|
missing = inference.VASMInference(tmp_path / "missing.onnx", cv2_module=cv2)
|
|
assert not missing.load()
|
|
alias = tmp_path / "alias.onnx"
|
|
alias.symlink_to(tiny_model)
|
|
assert not inference.VASMInference(alias, cv2_module=cv2).load()
|
|
tiny_model.write_bytes(b"wrong")
|
|
assert not inference.VASMInference(tiny_model, cv2_module=cv2).load()
|
|
tiny_model.write_bytes(b"small reviewed ONNX fixture")
|
|
model = inference.VASMInference(tiny_model, cv2_module=cv2)
|
|
assert model.load()
|
|
frame = np.zeros((48, 32), dtype=np.uint8)
|
|
annotation = decode_annotation(b'{"version":1,"width":32,"height":32,"poly_left":[[4,4],[20,4],[4,20]],"poly_right":[]}')
|
|
cv2.dnn.net.output = np.array([[float("nan"), 0.95, 0.0]], dtype=np.float32)
|
|
assert model.infer_nv12(frame, width=32, height=32, annotation=annotation, side="left") is None
|
|
assert model.net is None
|
|
|
|
|
|
def test_fifo_asset_refused_without_waiting_for_writer(tmp_path):
|
|
fifo = tmp_path / "model.fifo"
|
|
os.mkfifo(fifo)
|
|
assert not inference.VASMInference(fifo, cv2_module=FakeCV2(FakeNet())).load()
|
|
|
|
|
|
def test_wrong_frame_and_unconfigured_side_fail_without_model_output(tiny_model):
|
|
net = FakeNet()
|
|
model = inference.VASMInference(tiny_model, cv2_module=FakeCV2(net))
|
|
assert model.load()
|
|
annotation = decode_annotation(b'{"version":1,"width":32,"height":32,"poly_left":[[4,4],[20,4],[4,20]],"poly_right":[]}')
|
|
assert model.infer_nv12(np.zeros((32, 32), dtype=np.uint8), width=32, height=32,
|
|
annotation=annotation, side="left") is None
|
|
assert net.blob is None
|