feat(perception): add persistent lidar motion evidence
This commit is contained in:
@@ -36,12 +36,14 @@ from .jobs import (
|
||||
)
|
||||
from .lab_instances import (
|
||||
PublishedIntegratedLabInstance,
|
||||
PublishedPersistentSupportLabInstance,
|
||||
PublishedTemporalLabInstance,
|
||||
PublishedWorldMotionLabInstance,
|
||||
publish_e21_lab_instance,
|
||||
publish_e22_lab_instance,
|
||||
publish_e23_lab_instance,
|
||||
publish_e24_lab_instance,
|
||||
publish_e25_lab_instance,
|
||||
publish_integrated_lab_instance,
|
||||
)
|
||||
from .live_perception import (
|
||||
@@ -120,6 +122,7 @@ __all__ = [
|
||||
"LivePerceptionIngress",
|
||||
"IntegratedPerceptionOverlayStore",
|
||||
"PublishedIntegratedLabInstance",
|
||||
"PublishedPersistentSupportLabInstance",
|
||||
"PublishedTemporalLabInstance",
|
||||
"PublishedWorldMotionLabInstance",
|
||||
"IntegratedPerceptionResult",
|
||||
@@ -154,6 +157,7 @@ __all__ = [
|
||||
"publish_e22_lab_instance",
|
||||
"publish_e23_lab_instance",
|
||||
"publish_e24_lab_instance",
|
||||
"publish_e25_lab_instance",
|
||||
"publish_integrated_lab_instance",
|
||||
"validate_multirate_perception_qualification_result",
|
||||
"prepare_recorded_qualification_slice",
|
||||
|
||||
@@ -73,7 +73,7 @@ class _PresentedCuboid:
|
||||
track_id: int
|
||||
label: str
|
||||
association_group: str
|
||||
distance_m: float
|
||||
distance_m: float | None
|
||||
support_points: int
|
||||
center: np.ndarray
|
||||
half_size: np.ndarray
|
||||
@@ -126,12 +126,20 @@ class _CuboidPresentationState:
|
||||
raise RecordedPerceptionOverlayError(
|
||||
"integrated perception cuboid track identity is invalid"
|
||||
)
|
||||
distance_value = item.get("distance_smoothed_m")
|
||||
distance_m = (
|
||||
float(distance_value)
|
||||
if isinstance(distance_value, int | float)
|
||||
and not isinstance(distance_value, bool)
|
||||
and np.isfinite(float(distance_value))
|
||||
else None
|
||||
)
|
||||
self._latest[track_id] = _PresentedCuboid(
|
||||
observed_ns=timestamp_ns,
|
||||
track_id=track_id,
|
||||
label=str(item.get("label", "object")),
|
||||
association_group=str(item.get("association_group", "object")),
|
||||
distance_m=float(item["distance_smoothed_m"]),
|
||||
distance_m=distance_m,
|
||||
support_points=int(item["clustered_points"]),
|
||||
center=np.asarray(center).copy(),
|
||||
half_size=np.asarray(half_size).copy(),
|
||||
@@ -160,9 +168,10 @@ class _CuboidPresentationState:
|
||||
color[3] = max(24, round(float(color[3]) * (1.0 - 0.55 * fade)))
|
||||
presented_colors.append(color)
|
||||
age_label = "" if age_ns == 0 else f" · hold {age_ns / 1_000_000:.0f} ms"
|
||||
distance_label = "" if cuboid.distance_m is None else f" · {cuboid.distance_m:.1f} m"
|
||||
labels.append(
|
||||
f"{cuboid.association_group} #{cuboid.track_id} {cuboid.label} · "
|
||||
f"{cuboid.distance_m:.1f} m · {cuboid.support_points} pts{age_label}"
|
||||
f"{cuboid.association_group} #{cuboid.track_id} {cuboid.label}"
|
||||
f"{distance_label} · {cuboid.support_points} pts{age_label}"
|
||||
)
|
||||
return (
|
||||
np.stack([cuboid.center for cuboid in presented]),
|
||||
|
||||
@@ -28,6 +28,10 @@ from .integrated_perception import (
|
||||
validate_integrated_perception_result,
|
||||
)
|
||||
from .jobs import CameraComputeJob, validate_camera_compute_job
|
||||
from .occupancy_motion import (
|
||||
PersistentSupportBuild,
|
||||
build_persistent_support_result,
|
||||
)
|
||||
from .temporal_stability import (
|
||||
TemporalStabilityBuild,
|
||||
_quality_metrics,
|
||||
@@ -59,6 +63,14 @@ class PublishedWorldMotionLabInstance:
|
||||
build: WorldMotionBuild
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PublishedPersistentSupportLabInstance:
|
||||
binding: LabSessionBinding
|
||||
job: CameraComputeJob
|
||||
result: IntegratedPerceptionResult
|
||||
build: PersistentSupportBuild
|
||||
|
||||
|
||||
def publish_integrated_lab_instance(
|
||||
*,
|
||||
repository_root: Path,
|
||||
@@ -590,6 +602,113 @@ def publish_e24_lab_instance(
|
||||
)
|
||||
|
||||
|
||||
def publish_e25_lab_instance(
|
||||
*,
|
||||
repository_root: Path,
|
||||
source_result_root: Path,
|
||||
world_motion_profile_path: Path,
|
||||
profile_path: Path,
|
||||
benchmark_path: Path,
|
||||
lab_session_id: str,
|
||||
lab_id: str,
|
||||
display_name: str,
|
||||
) -> PublishedPersistentSupportLabInstance:
|
||||
"""Derive and publish one bounded persistent-support motion LAB run."""
|
||||
|
||||
root = repository_root.expanduser().resolve(strict=True)
|
||||
jobs_root = root / ".runtime" / "compute-jobs"
|
||||
results_root = root / ".runtime" / "compute-experiments" / "e10" / "worker-results"
|
||||
packs_root = root / ".runtime" / "compute-experiments" / "e10" / "lidar-packs"
|
||||
source_path = source_result_root.expanduser().resolve(strict=True)
|
||||
source_document = _read_object(source_path / "result.json", source_path)
|
||||
identity = source_document.get("identity")
|
||||
if not isinstance(identity, dict) or not isinstance(identity.get("job_id"), str):
|
||||
raise SessionIntegrityError("E25 source has no job identity")
|
||||
source = validate_integrated_perception_result(
|
||||
jobs_root / identity["job_id"],
|
||||
source_path,
|
||||
packs_root,
|
||||
)
|
||||
if not source.accepted:
|
||||
raise SessionIntegrityError("E25 source result is not accepted")
|
||||
|
||||
lab_job = _publish_lab_job(source.job, jobs_root, lab_session_id)
|
||||
lab_pack = _publish_lab_pack(source, lab_job, packs_root, lab_session_id)
|
||||
build = build_persistent_support_result(
|
||||
source=source,
|
||||
lab_job=lab_job,
|
||||
lab_pack=lab_pack,
|
||||
results_root=results_root,
|
||||
world_motion_profile_path=world_motion_profile_path,
|
||||
profile_path=profile_path,
|
||||
benchmark_path=benchmark_path,
|
||||
)
|
||||
validated = validate_integrated_perception_result(
|
||||
lab_job.job_root,
|
||||
build.result_root,
|
||||
packs_root,
|
||||
)
|
||||
if not validated.accepted:
|
||||
failed = [
|
||||
name for name, accepted in build.report["acceptance"]["checks"].items() if not accepted
|
||||
]
|
||||
raise SessionIntegrityError(
|
||||
f"E25 persistent-support artifact acceptance failed: {', '.join(failed)}"
|
||||
)
|
||||
|
||||
store = SessionStore(root)
|
||||
source_lab = store.get_lab_instance(source.job.session_id)
|
||||
source_session_id = (
|
||||
source.job.session_id if source_lab is None else source_lab.source_session_id
|
||||
)
|
||||
publish_lab_replay_cache(
|
||||
store.data_dir,
|
||||
source_session_id=source_session_id,
|
||||
lab_session_id=lab_session_id,
|
||||
timeline_start_ns=round(validated.timeline_start_seconds * 1_000_000_000),
|
||||
timeline_end_ns=round(validated.timeline_end_seconds * 1_000_000_000),
|
||||
)
|
||||
metrics = build.report["metrics"]
|
||||
binding = store.publish_lab_instance(
|
||||
session_id=lab_session_id,
|
||||
source_session_id=source_session_id,
|
||||
display_name=display_name,
|
||||
lab_id=lab_id,
|
||||
result_kind="e25-persistent-support-motion",
|
||||
result_id=validated.result_id,
|
||||
source_result_id=source.result_id,
|
||||
config_sha256=build.profile_sha256,
|
||||
run_created_at_utc=validated.created_at_utc,
|
||||
duration_seconds=(validated.timeline_end_seconds - validated.timeline_start_seconds),
|
||||
include_recorded_media=False,
|
||||
provenance={
|
||||
"schema_version": "missioncore.e25-lab-publication/v1",
|
||||
"storage_mode": "bounded-persistent-support-and-immutable-source-replay",
|
||||
"source_result_id": source.result_id,
|
||||
"source_lab_session_id": (None if source_lab is None else source_lab.session_id),
|
||||
"source_payloads_mutated": False,
|
||||
"coordinate_frame": "k1-map",
|
||||
"measurement": "persistent-object-support-occupancy",
|
||||
"lookahead_frames": 0,
|
||||
"benchmark_sha256": build.benchmark_sha256,
|
||||
"benchmark_passed": metrics["benchmark"]["passed"],
|
||||
"benchmark_passed_events": metrics["benchmark"]["passed_events"],
|
||||
"benchmark_total_events": metrics["benchmark"]["total_events"],
|
||||
"persistent_support_processing_p95_ms": metrics["runtime"][
|
||||
"persistent_support_frame_processing_ms"
|
||||
]["p95"],
|
||||
"peak_tracks": metrics["runtime"]["peak_tracks"],
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
)
|
||||
return PublishedPersistentSupportLabInstance(
|
||||
binding=binding,
|
||||
job=lab_job,
|
||||
result=validated,
|
||||
build=build,
|
||||
)
|
||||
|
||||
|
||||
def _validate_e23_inputs(
|
||||
worker_root: Path,
|
||||
source_report_path: Path,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ from k1link.compute import (
|
||||
publish_e22_lab_instance,
|
||||
publish_e23_lab_instance,
|
||||
publish_e24_lab_instance,
|
||||
publish_e25_lab_instance,
|
||||
publish_integrated_lab_instance,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.analyze import (
|
||||
@@ -656,6 +657,92 @@ def publish_e24_lab(
|
||||
)
|
||||
|
||||
|
||||
@lab_app.command("publish-e25")
|
||||
def publish_e25_lab(
|
||||
result: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Accepted full-session integrated result used as the E25 source.",
|
||||
),
|
||||
],
|
||||
world_profile: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
"--world-profile",
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Bounded E24 world-association profile used before E25 evidence.",
|
||||
),
|
||||
],
|
||||
profile: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Bounded E25 persistent-support profile.",
|
||||
),
|
||||
],
|
||||
benchmark: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Spatially bound E25 motion benchmark.",
|
||||
),
|
||||
],
|
||||
session_id: Annotated[
|
||||
str,
|
||||
typer.Option("--session-id", help="New immutable LAB session id."),
|
||||
],
|
||||
lab_id: Annotated[
|
||||
str,
|
||||
typer.Option("--lab-id", help="LAB marker, for example 'LAB E25.1'."),
|
||||
],
|
||||
display_name: Annotated[
|
||||
str,
|
||||
typer.Option("--display-name", help="Operator-facing saved-session title."),
|
||||
],
|
||||
) -> None:
|
||||
"""Build and publish map-frame persistent-support motion evidence."""
|
||||
|
||||
repository_root = Path(__file__).resolve().parents[4]
|
||||
try:
|
||||
published = publish_e25_lab_instance(
|
||||
repository_root=repository_root,
|
||||
source_result_root=result,
|
||||
world_motion_profile_path=world_profile,
|
||||
profile_path=profile,
|
||||
benchmark_path=benchmark,
|
||||
lab_session_id=session_id,
|
||||
lab_id=lab_id,
|
||||
display_name=display_name,
|
||||
)
|
||||
except (OSError, SessionIntegrityError, RuntimeError, ValueError) as exc:
|
||||
console.print(f"[red]E25 LAB publication failed:[/red] {exc}")
|
||||
raise typer.Exit(code=2) from exc
|
||||
metrics = published.build.report["metrics"]
|
||||
console.print(
|
||||
"[green]E25 LAB instance published.[/green] "
|
||||
f"session={published.binding.session_id}; "
|
||||
f"source={published.binding.source_session_id}; "
|
||||
f"result={published.binding.result_id}; "
|
||||
f"benchmark={metrics['benchmark']['passed_events']}/"
|
||||
f"{metrics['benchmark']['total_events']}; "
|
||||
f"p95={metrics['runtime']['persistent_support_frame_processing_ms']['p95']:.3f}ms; "
|
||||
"source_payloads_mutated=false"
|
||||
)
|
||||
|
||||
|
||||
@app.command("serve")
|
||||
def serve_console(
|
||||
port: Annotated[
|
||||
|
||||
Reference in New Issue
Block a user