feat(lidar): add RAVNOVES field review

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 10:26:07 +03:00
parent 2dfb34ef21
commit 3333e9ac0f
13 changed files with 2775 additions and 14 deletions
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import subprocess
import tempfile
from pathlib import Path
from k1link.compute import (
PATCHWORKPP_SOURCE_COMMIT,
PATCHWORKPP_SOURCE_TAG,
RAVNOVES00_CENTRAL_WINDOWS,
E10LidarFieldSource,
GroundBenchmarkProfile,
LidarFieldReviewV1,
PatchworkPPGroundSegmenter,
build_lidar_field_review,
)
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Build accumulated RAVNOVES00 central-urban LiDAR ground review "
"with content-bound camera context previews."
)
)
parser.add_argument("source_pack", type=Path)
parser.add_argument("camera_epoch", type=Path)
parser.add_argument("output_root", type=Path)
parser.add_argument("--ffmpeg", default="ffmpeg")
parser.add_argument("--patchwork-module", default="pypatchworkpp")
parser.add_argument("--patchwork-source-tag", default=PATCHWORKPP_SOURCE_TAG)
parser.add_argument(
"--patchwork-source-commit",
default=PATCHWORKPP_SOURCE_COMMIT,
)
parser.add_argument("--sensor-height-m", type=float, default=1.27)
parser.add_argument("--map-vertical-origin-offset-m", type=float, default=1.27)
return parser.parse_args()
def _preview_segments(epoch: Path) -> dict[str, Path]:
resolved = epoch.expanduser().resolve(strict=True)
init = resolved / "init.mp4"
index_path = resolved / "index.jsonl"
if not init.is_file() or init.is_symlink() or not index_path.is_file():
raise RuntimeError("Camera epoch is incomplete")
required = {
window.preview_source_frame_index + 1: window.key for window in RAVNOVES00_CENTRAL_WINDOWS
}
found: dict[str, Path] = {}
with index_path.open(encoding="utf-8") as stream:
for line in stream:
value = json.loads(line)
sequence = value.get("sequence") if isinstance(value, dict) else None
if sequence not in required:
continue
relative = value.get("path")
expected_sha256 = value.get("sha256")
expected_length = value.get("length")
if (
value.get("schema_version") != "missioncore.camera-recording-index/v1"
or relative != f"segments/{sequence}.m4s"
or not isinstance(expected_sha256, str)
or not isinstance(expected_length, int)
):
raise RuntimeError("Camera preview segment index is invalid")
segment = resolved / relative
if (
segment.is_symlink()
or not segment.is_file()
or segment.stat().st_size != expected_length
or _sha256(segment) != expected_sha256
):
raise RuntimeError("Camera preview segment failed integrity")
found[required[sequence]] = segment
if set(found) != set(required.values()):
raise RuntimeError("Camera preview segments are incomplete")
return found
def _extract_previews(
ffmpeg: str,
epoch: Path,
output: Path,
) -> dict[str, Path]:
init = epoch.expanduser().resolve(strict=True) / "init.mp4"
segments = _preview_segments(epoch)
previews: dict[str, Path] = {}
for key, segment in segments.items():
target = output / f"{key}.jpg"
subprocess.run(
[
ffmpeg,
"-hide_banner",
"-loglevel",
"error",
"-i",
f"concat:{init}|{segment}",
"-frames:v",
"1",
"-vf",
"scale=640:480",
"-q:v",
"3",
str(target),
],
check=True,
)
if not target.is_file() or target.stat().st_size < 1_000:
raise RuntimeError("Camera preview extraction failed")
previews[key] = target
return previews
def main() -> int:
arguments = _arguments()
profile = GroundBenchmarkProfile(
profile_id="ravnoves00-central-urban-operator-height-ground-review/v1",
patchwork_sensor_height_proxy_m=arguments.sensor_height_m,
patchwork_map_vertical_origin_offset_m=(arguments.map_vertical_origin_offset_m),
patchwork_height_evidence="operator-estimated",
)
source = E10LidarFieldSource(arguments.source_pack)
try:
patchwork = PatchworkPPGroundSegmenter.load(
profile=profile,
module_name=arguments.patchwork_module,
source_tag=arguments.patchwork_source_tag,
source_commit=arguments.patchwork_source_commit,
)
with tempfile.TemporaryDirectory(prefix="missioncore-lidar-field-review-") as value:
previews = _extract_previews(
arguments.ffmpeg,
arguments.camera_epoch,
Path(value),
)
output = build_lidar_field_review(
source,
arguments.output_root,
patchwork=patchwork,
profile=profile,
preview_paths=previews,
)
finally:
source.close()
review = LidarFieldReviewV1(output)
try:
print(
json.dumps(
{
"review_id": review.review_id,
"display_name": review.report["display_name"],
"session_id": review.report["session_id"],
"source": review.report["source"],
"selection": review.report["selection"],
"windows": review.report["windows"],
"metrics": review.report["metrics"],
"decision": review.report["decision"],
},
ensure_ascii=False,
indent=2,
)
)
finally:
review.close()
return 0
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
if __name__ == "__main__":
raise SystemExit(main())