feat(perception): run PointPillars on RAVNOVES00
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Deterministic helpers for the L3.1 PointPillars transfer on RAVNOVES00."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
|
||||
class L31PointPillarsRavnovesError(RuntimeError):
|
||||
"""The RAVNOVES transfer input violates the frozen L3.1 contract."""
|
||||
|
||||
|
||||
def nearest_pose_indices(
|
||||
point_times_ns: npt.NDArray[np.int64],
|
||||
pose_times_ns: npt.NDArray[np.int64],
|
||||
) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]]:
|
||||
"""Bind each point frame to the nearest pose on the host monotonic clock."""
|
||||
|
||||
point_times = np.asarray(point_times_ns, dtype=np.int64)
|
||||
pose_times = np.asarray(pose_times_ns, dtype=np.int64)
|
||||
if (
|
||||
point_times.ndim != 1
|
||||
or pose_times.ndim != 1
|
||||
or not point_times.size
|
||||
or not pose_times.size
|
||||
or np.any(np.diff(point_times) < 0)
|
||||
or np.any(np.diff(pose_times) < 0)
|
||||
):
|
||||
raise L31PointPillarsRavnovesError("LiDAR/pose time axes are invalid")
|
||||
|
||||
right = np.searchsorted(pose_times, point_times, side="left")
|
||||
right = np.clip(right, 0, pose_times.size - 1)
|
||||
left = np.clip(right - 1, 0, pose_times.size - 1)
|
||||
right_delta = np.abs(pose_times[right] - point_times)
|
||||
left_delta = np.abs(point_times - pose_times[left])
|
||||
indices = np.where(left_delta <= right_delta, left, right).astype(np.int64)
|
||||
age_ms = (
|
||||
np.abs(pose_times[indices] - point_times).astype(np.float64) / 1_000_000.0
|
||||
)
|
||||
if not np.isfinite(age_ms).all():
|
||||
raise L31PointPillarsRavnovesError("LiDAR/pose binding age is invalid")
|
||||
return indices, age_ms
|
||||
|
||||
|
||||
def sensor_frame_xyzi(
|
||||
points_map_xyz: npt.NDArray[np.float64],
|
||||
intensities: npt.NDArray[np.uint8],
|
||||
*,
|
||||
position_map_xyz: npt.NDArray[np.float64],
|
||||
orientation_map_from_lidar_xyzw: npt.NDArray[np.float64],
|
||||
) -> npt.NDArray[np.float32]:
|
||||
"""Convert verified map-frame K1 points into the model's sensor frame."""
|
||||
|
||||
points = np.asarray(points_map_xyz, dtype=np.float64)
|
||||
intensity = np.asarray(intensities, dtype=np.uint8)
|
||||
position = np.asarray(position_map_xyz, dtype=np.float64)
|
||||
quaternion = np.asarray(orientation_map_from_lidar_xyzw, dtype=np.float64)
|
||||
if (
|
||||
points.ndim != 2
|
||||
or points.shape[1:] != (3,)
|
||||
or intensity.shape != (points.shape[0],)
|
||||
or position.shape != (3,)
|
||||
or quaternion.shape != (4,)
|
||||
or not np.isfinite(points).all()
|
||||
or not np.isfinite(position).all()
|
||||
or not np.isfinite(quaternion).all()
|
||||
):
|
||||
raise L31PointPillarsRavnovesError("K1 point/pose arrays are invalid")
|
||||
norm = float(np.linalg.norm(quaternion))
|
||||
if not math.isfinite(norm) or not 0.99 <= norm <= 1.01:
|
||||
raise L31PointPillarsRavnovesError("K1 pose quaternion is not normalized")
|
||||
x, y, z, w = quaternion / norm
|
||||
rotation_map_from_lidar = np.asarray(
|
||||
[
|
||||
[1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
|
||||
[2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
|
||||
[2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
|
||||
],
|
||||
dtype=np.float64,
|
||||
)
|
||||
sensor_xyz = (points - position) @ rotation_map_from_lidar
|
||||
result = np.empty((points.shape[0], 4), dtype=np.float32)
|
||||
result[:, :3] = sensor_xyz.astype(np.float32)
|
||||
result[:, 3] = intensity.astype(np.float32) / 255.0
|
||||
if not np.isfinite(result).all():
|
||||
raise L31PointPillarsRavnovesError("K1 sensor-frame XYZI is non-finite")
|
||||
return result
|
||||
|
||||
|
||||
def select_visual_frame_indices(
|
||||
vehicle_counts: Sequence[int],
|
||||
total_counts: Sequence[int],
|
||||
*,
|
||||
maximum_frames: int = 18,
|
||||
) -> tuple[int, ...]:
|
||||
"""Select route-wide evidence, preferring frames with Vehicle predictions."""
|
||||
|
||||
vehicles = tuple(vehicle_counts)
|
||||
totals = tuple(total_counts)
|
||||
if (
|
||||
len(vehicles) != len(totals)
|
||||
or not vehicles
|
||||
or isinstance(maximum_frames, bool)
|
||||
or maximum_frames < 1
|
||||
or any(
|
||||
isinstance(value, bool) or not isinstance(value, int) or value < 0
|
||||
for value in (*vehicles, *totals)
|
||||
)
|
||||
or any(vehicle > total for vehicle, total in zip(vehicles, totals, strict=True))
|
||||
):
|
||||
raise L31PointPillarsRavnovesError("L3.1 visual selection input is invalid")
|
||||
|
||||
count = min(maximum_frames, len(vehicles))
|
||||
boundaries = np.linspace(0, len(vehicles), count + 1, dtype=np.int64)
|
||||
selected: list[int] = []
|
||||
for bin_index in range(count):
|
||||
start = int(boundaries[bin_index])
|
||||
stop = int(boundaries[bin_index + 1])
|
||||
if stop <= start:
|
||||
continue
|
||||
center = (start + stop - 1) / 2.0
|
||||
chosen = max(
|
||||
range(start, stop),
|
||||
key=lambda index: (
|
||||
vehicles[index],
|
||||
totals[index],
|
||||
-abs(index - center),
|
||||
-index,
|
||||
),
|
||||
)
|
||||
selected.append(chosen)
|
||||
return tuple(selected)
|
||||
@@ -10,8 +10,6 @@ from typing import Any, Final
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from k1link.web.l3_pointpillars_visual_api import latest_l3_visual_identity
|
||||
|
||||
from k1link.compute.e31_source_qualification import (
|
||||
E31SourceQualification,
|
||||
E31SourceQualificationError,
|
||||
@@ -57,6 +55,8 @@ from k1link.compute.e40_perception_product_gate import (
|
||||
E40PerceptionProductGateError,
|
||||
read_e40_perception_product_gate,
|
||||
)
|
||||
from k1link.web.l3_pointpillars_visual_api import latest_l3_visual_identity
|
||||
from k1link.web.l31_pointpillars_ravnoves_api import latest_l31_identity
|
||||
|
||||
LABORATORY_ADVANCED_CATALOG_SCHEMA: Final = (
|
||||
"missioncore.laboratory-advanced-catalog/v1"
|
||||
@@ -828,6 +828,7 @@ def build_advanced_laboratory_router(
|
||||
e39_root_provider: RootProvider = lambda: None,
|
||||
e40_root_provider: RootProvider = lambda: None,
|
||||
l3_visual_root_provider: RootProvider = lambda: None,
|
||||
l31_ravnoves_root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
|
||||
|
||||
@@ -909,6 +910,15 @@ def build_advanced_laboratory_router(
|
||||
"access": "read-only",
|
||||
}
|
||||
)
|
||||
l31_identity = latest_l31_identity(l31_ravnoves_root_provider)
|
||||
if l31_identity is not None:
|
||||
result["items"].append(
|
||||
{
|
||||
"work_id": "l31-pointpillars-ravnoves",
|
||||
**l31_identity,
|
||||
"access": "read-only",
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@router.get("/e31/results")
|
||||
|
||||
+24
-3
@@ -34,9 +34,6 @@ from k1link.sessions import (
|
||||
SessionStore,
|
||||
)
|
||||
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
|
||||
from k1link.web.l3_pointpillars_visual_api import (
|
||||
build_l3_pointpillars_visual_router,
|
||||
)
|
||||
from k1link.web.artifact_health_api import build_artifact_health_router
|
||||
from k1link.web.compute_contour_api import build_compute_contour_router
|
||||
from k1link.web.device_plugin_composition import load_installed_device_plugins
|
||||
@@ -45,6 +42,12 @@ from k1link.web.e30_human_review_api import build_e30_human_review_router
|
||||
from k1link.web.e30_review_api import build_e30_review_router
|
||||
from k1link.web.e40_case_review_api import build_e40_case_review_router
|
||||
from k1link.web.environment_api import build_environment_router
|
||||
from k1link.web.l3_pointpillars_visual_api import (
|
||||
build_l3_pointpillars_visual_router,
|
||||
)
|
||||
from k1link.web.l31_pointpillars_ravnoves_api import (
|
||||
build_l31_pointpillars_ravnoves_router,
|
||||
)
|
||||
from k1link.web.laboratory_api import build_laboratory_router
|
||||
from k1link.web.lidar_api import build_lidar_router
|
||||
from k1link.web.lidar_local_surface_service import K1LocalSurfaceReadService
|
||||
@@ -609,6 +612,13 @@ app.include_router(
|
||||
/ "l3"
|
||||
/ "visual-audits"
|
||||
),
|
||||
l31_ravnoves_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "pointpillars-ravnoves"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
@@ -622,6 +632,17 @@ app.include_router(
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_l31_pointpillars_ravnoves_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "l3"
|
||||
/ "pointpillars-ravnoves"
|
||||
)
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e30_review_router(
|
||||
materialization_root_provider=lambda: (
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
"""Read-only projection of the sealed L3.1 PointPillars RAVNOVES evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
|
||||
RESULT_SCHEMA: Final = "missioncore.l31-pointpillars-ravnoves/v1"
|
||||
CATALOG_SCHEMA: Final = "missioncore.l31-pointpillars-ravnoves-catalog/v1"
|
||||
VISUAL_FRAME_SCHEMA: Final = (
|
||||
"missioncore.l31-pointpillars-ravnoves-visual-frame/v1"
|
||||
)
|
||||
RESULT_PROJECTION_SCHEMA: Final = (
|
||||
"missioncore.l31-pointpillars-ravnoves-result/v1"
|
||||
)
|
||||
RESULT_CATALOG_SCHEMA: Final = (
|
||||
"missioncore.l31-pointpillars-ravnoves-catalog-results/v1"
|
||||
)
|
||||
RESULT_ID: Final = re.compile(r"^l31-pointpillars-ravnoves-[a-f0-9]{64}$")
|
||||
FRAME_ID: Final = re.compile(r"^[0-9]{6}$")
|
||||
SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
MAX_JSON_BYTES: Final = 16 * 1024 * 1024
|
||||
MAX_CANDIDATES: Final = 16
|
||||
MAX_FRAMES: Final = 18
|
||||
|
||||
|
||||
def build_l31_pointpillars_ravnoves_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/laboratory/l31/pointpillars-ravnoves",
|
||||
tags=["laboratory"],
|
||||
)
|
||||
|
||||
@router.get("/results")
|
||||
def list_results(
|
||||
limit: int = Query(default=1, ge=1, le=10),
|
||||
) -> dict[str, object]:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return _empty_catalog(False)
|
||||
candidates = _candidates(root)
|
||||
items: list[dict[str, object]] = []
|
||||
invalid_total = 0
|
||||
for candidate in candidates:
|
||||
try:
|
||||
items.append(_project_result(candidate))
|
||||
except RuntimeError:
|
||||
invalid_total += 1
|
||||
items.sort(
|
||||
key=lambda item: (
|
||||
str(item["created_at_utc"]),
|
||||
str(item["result_id"]),
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
return {
|
||||
"schema_version": RESULT_CATALOG_SCHEMA,
|
||||
"configured": True,
|
||||
"items": items[:limit],
|
||||
"candidate_total": len(candidates),
|
||||
"invalid_total": invalid_total,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
@router.get("/{result_id}/frames/{frame_id}")
|
||||
def get_frame(result_id: str, frame_id: str) -> dict[str, object]:
|
||||
if not RESULT_ID.fullmatch(result_id) or not FRAME_ID.fullmatch(frame_id):
|
||||
raise HTTPException(status_code=404, detail="L3.1 frame not found")
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
raise HTTPException(status_code=404, detail="L3.1 result not found")
|
||||
candidate = root / result_id
|
||||
try:
|
||||
result = _load_result(candidate)
|
||||
descriptor = next(
|
||||
item
|
||||
for item in result["catalog"]["frames"]
|
||||
if item["frame_id"] == frame_id
|
||||
)
|
||||
relative = descriptor["detail_path"]
|
||||
if relative != f"visual-frames/{frame_id}.json":
|
||||
raise RuntimeError("L3.1 visual path changed")
|
||||
path = candidate / relative
|
||||
payload = _read_json(path)
|
||||
if (
|
||||
payload.get("schema_version") != VISUAL_FRAME_SCHEMA
|
||||
or payload.get("frame_id") != frame_id
|
||||
or descriptor["detail_sha256"] != _sha256(path)
|
||||
or descriptor["detail_byte_length"] != path.stat().st_size
|
||||
or not _valid_visual_payload(payload)
|
||||
):
|
||||
raise RuntimeError("L3.1 visual identity changed")
|
||||
except (RuntimeError, StopIteration):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="L3.1 frame not found",
|
||||
) from None
|
||||
return {**copy.deepcopy(payload), "access": "read-only"}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def latest_l31_identity(
|
||||
root_provider: RootProvider,
|
||||
) -> dict[str, str] | None:
|
||||
root = _configured_root(root_provider)
|
||||
if root is None:
|
||||
return None
|
||||
valid: list[dict[str, object]] = []
|
||||
for candidate in _candidates(root):
|
||||
try:
|
||||
valid.append(_project_result(candidate))
|
||||
except RuntimeError:
|
||||
continue
|
||||
if not valid:
|
||||
return None
|
||||
latest = max(
|
||||
valid,
|
||||
key=lambda item: (
|
||||
str(item["created_at_utc"]),
|
||||
str(item["result_id"]),
|
||||
),
|
||||
)
|
||||
return {
|
||||
"result_id": str(latest["result_id"]),
|
||||
"created_at_utc": str(latest["created_at_utc"]),
|
||||
}
|
||||
|
||||
|
||||
def _project_result(candidate: Path) -> dict[str, object]:
|
||||
result = _load_result(candidate)
|
||||
manifest = result["manifest"]
|
||||
identity = manifest["identity"]
|
||||
return {
|
||||
"schema_version": RESULT_PROJECTION_SCHEMA,
|
||||
"result_id": manifest["result_id"],
|
||||
"created_at_utc": manifest["created_at_utc"],
|
||||
"status": "cross-domain-transfer-measured-visual-review-required",
|
||||
"source_session_id": identity["source_session_id"],
|
||||
"source_pack_id": identity["source_pack_id"],
|
||||
"source_logical_content_sha256": identity[
|
||||
"source_logical_content_sha256"
|
||||
],
|
||||
"model": copy.deepcopy(identity["model"]),
|
||||
"execution": copy.deepcopy(identity["execution"]),
|
||||
"metrics": copy.deepcopy(manifest["metrics"]),
|
||||
"frames": copy.deepcopy(result["catalog"]["frames"]),
|
||||
"limitations": copy.deepcopy(manifest["limitations"]),
|
||||
"authority": copy.deepcopy(manifest["authority"]),
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _load_result(candidate: Path) -> dict[str, Any]:
|
||||
if (
|
||||
not candidate.is_dir()
|
||||
or candidate.is_symlink()
|
||||
or not RESULT_ID.fullmatch(candidate.name)
|
||||
):
|
||||
raise RuntimeError("L3.1 candidate is invalid")
|
||||
manifest = _read_json(candidate / "manifest.json")
|
||||
identity = manifest.get("identity")
|
||||
authority = manifest.get("authority")
|
||||
catalog_descriptor = manifest.get("catalog")
|
||||
limitations = manifest.get("limitations")
|
||||
metrics = manifest.get("metrics")
|
||||
if (
|
||||
manifest.get("schema_version") != RESULT_SCHEMA
|
||||
or manifest.get("result_id") != candidate.name
|
||||
or manifest.get("status")
|
||||
!= "k1-cross-domain-transfer-measured-visual-review-required"
|
||||
or not isinstance(manifest.get("created_at_utc"), str)
|
||||
or not isinstance(identity, dict)
|
||||
or identity.get("source_session_id")
|
||||
!= "20260720T065719Z_viewer_live"
|
||||
or not isinstance(authority, dict)
|
||||
or authority.get("shadow_only") is not True
|
||||
or authority.get("commands_enabled") is not False
|
||||
or authority.get("navigation_or_safety_accepted") is not False
|
||||
or authority.get("accuracy_accepted") is not False
|
||||
or manifest.get("identity_sha256")
|
||||
!= hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
or candidate.name
|
||||
!= f"l31-pointpillars-ravnoves-{manifest.get('identity_sha256')}"
|
||||
or not isinstance(metrics, dict)
|
||||
or metrics.get("frame_count") != 4570
|
||||
or metrics.get("input_admission_fraction") != 1.0
|
||||
or metrics.get("output_schema_valid_fraction") != 1.0
|
||||
or not isinstance(limitations, list)
|
||||
or not limitations
|
||||
or not isinstance(catalog_descriptor, dict)
|
||||
or catalog_descriptor.get("path") != "catalog.json"
|
||||
or catalog_descriptor.get("kind") != "visual-frame-catalog"
|
||||
):
|
||||
raise RuntimeError("L3.1 manifest is invalid")
|
||||
catalog_path = candidate / "catalog.json"
|
||||
if (
|
||||
catalog_descriptor.get("sha256") != _sha256(catalog_path)
|
||||
or catalog_descriptor.get("byte_length") != catalog_path.stat().st_size
|
||||
):
|
||||
raise RuntimeError("L3.1 catalog changed")
|
||||
catalog = _read_json(catalog_path)
|
||||
frames = catalog.get("frames")
|
||||
if (
|
||||
catalog.get("schema_version") != CATALOG_SCHEMA
|
||||
or catalog.get("result_id") != candidate.name
|
||||
or catalog.get("source_session_id") != identity["source_session_id"]
|
||||
or not isinstance(frames, list)
|
||||
or not 1 <= len(frames) <= MAX_FRAMES
|
||||
or catalog.get("frame_count") != len(frames)
|
||||
or len({item.get("frame_id") for item in frames if isinstance(item, dict)})
|
||||
!= len(frames)
|
||||
or any(not _valid_frame_descriptor(item) for item in frames)
|
||||
):
|
||||
raise RuntimeError("L3.1 catalog is invalid")
|
||||
return {"manifest": manifest, "catalog": catalog}
|
||||
|
||||
|
||||
def _valid_frame_descriptor(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
frame_id = value.get("frame_id")
|
||||
class_counts = value.get("class_counts")
|
||||
return (
|
||||
isinstance(frame_id, str)
|
||||
and FRAME_ID.fullmatch(frame_id) is not None
|
||||
and value.get("detail_path") == f"visual-frames/{frame_id}.json"
|
||||
and isinstance(value.get("detail_sha256"), str)
|
||||
and SHA256.fullmatch(value["detail_sha256"]) is not None
|
||||
and isinstance(value.get("detail_byte_length"), int)
|
||||
and 0 < value["detail_byte_length"] <= MAX_JSON_BYTES
|
||||
and isinstance(value.get("frame_index"), int)
|
||||
and value["frame_index"] >= 0
|
||||
and isinstance(value.get("source_point_count"), int)
|
||||
and value["source_point_count"] > 0
|
||||
and isinstance(value.get("prediction_count"), int)
|
||||
and value["prediction_count"] >= 0
|
||||
and isinstance(value.get("session_seconds"), (int, float))
|
||||
and value["session_seconds"] >= 0
|
||||
and isinstance(value.get("inference_ms"), (int, float))
|
||||
and 0 < value["inference_ms"] < 60_000
|
||||
and isinstance(class_counts, dict)
|
||||
and set(class_counts) == {"Vehicle", "Pedestrian", "Cyclist"}
|
||||
and all(
|
||||
isinstance(count, int) and not isinstance(count, bool) and count >= 0
|
||||
for count in class_counts.values()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _valid_visual_payload(value: dict[str, Any]) -> bool:
|
||||
points = value.get("points")
|
||||
boxes = value.get("prediction_boxes")
|
||||
interpretation = value.get("interpretation")
|
||||
if (
|
||||
not isinstance(points, dict)
|
||||
or points.get("layout") != "flat-xyzi"
|
||||
or not isinstance(boxes, list)
|
||||
or len(boxes) > 512
|
||||
or not isinstance(interpretation, dict)
|
||||
or interpretation.get("ground_truth_available") is not False
|
||||
or interpretation.get("boxes_are_model_hypotheses") is not True
|
||||
or interpretation.get("accuracy_claim_allowed") is not False
|
||||
):
|
||||
return False
|
||||
values = points.get("values")
|
||||
sampled = points.get("sampled_point_count")
|
||||
return (
|
||||
isinstance(values, list)
|
||||
and isinstance(sampled, int)
|
||||
and 0 < sampled <= 12_000
|
||||
and len(values) == sampled * 4
|
||||
and all(
|
||||
isinstance(item, (int, float))
|
||||
and not isinstance(item, bool)
|
||||
and -1_000_000 < item < 1_000_000
|
||||
for item in values
|
||||
)
|
||||
and all(_valid_box(box) for box in boxes)
|
||||
)
|
||||
|
||||
|
||||
def _valid_box(value: object) -> bool:
|
||||
if not isinstance(value, dict):
|
||||
return False
|
||||
numeric = (
|
||||
"x_m",
|
||||
"y_m",
|
||||
"z_m",
|
||||
"length_m",
|
||||
"width_m",
|
||||
"height_m",
|
||||
"yaw_rad",
|
||||
"score",
|
||||
)
|
||||
return (
|
||||
value.get("model_class") in {"Vehicle", "Pedestrian", "Cyclist"}
|
||||
and isinstance(value.get("class_id"), int)
|
||||
and value["class_id"] in {0, 1, 2}
|
||||
and all(
|
||||
isinstance(value.get(key), (int, float))
|
||||
and not isinstance(value.get(key), bool)
|
||||
and math_is_finite(float(value[key]))
|
||||
for key in numeric
|
||||
)
|
||||
and value["length_m"] > 0
|
||||
and value["width_m"] > 0
|
||||
and value["height_m"] > 0
|
||||
and 0.1 <= value["score"] <= 1.0
|
||||
)
|
||||
|
||||
|
||||
def math_is_finite(value: float) -> bool:
|
||||
return value == value and value not in (float("inf"), float("-inf"))
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
value = provider()
|
||||
if value is None:
|
||||
return None
|
||||
root = value.expanduser().absolute()
|
||||
if not root.is_dir() or root.is_symlink():
|
||||
return None
|
||||
return root
|
||||
|
||||
|
||||
def _candidates(root: Path) -> list[Path]:
|
||||
candidates = [
|
||||
path
|
||||
for path in root.iterdir()
|
||||
if path.is_dir()
|
||||
and not path.is_symlink()
|
||||
and RESULT_ID.fullmatch(path.name)
|
||||
]
|
||||
if len(candidates) > MAX_CANDIDATES:
|
||||
raise RuntimeError("L3.1 candidate bound exceeded")
|
||||
return candidates
|
||||
|
||||
|
||||
def _empty_catalog(configured: bool) -> dict[str, object]:
|
||||
return {
|
||||
"schema_version": RESULT_CATALOG_SCHEMA,
|
||||
"configured": configured,
|
||||
"items": [],
|
||||
"candidate_total": 0,
|
||||
"invalid_total": 0,
|
||||
"access": "read-only",
|
||||
}
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
if (
|
||||
not path.is_file()
|
||||
or path.is_symlink()
|
||||
or not 0 < path.stat().st_size <= MAX_JSON_BYTES
|
||||
):
|
||||
raise RuntimeError(f"{path.name} is unavailable")
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise RuntimeError(f"{path.name} is invalid") from exc
|
||||
if not isinstance(payload, dict):
|
||||
raise RuntimeError(f"{path.name} is not an object")
|
||||
return payload
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as source:
|
||||
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_json(payload: object) -> bytes:
|
||||
return json.dumps(
|
||||
payload,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
).encode("utf-8")
|
||||
Reference in New Issue
Block a user