fix(perception): bound vegetation load decoding

This commit is contained in:
DCCONSTRUCTIONS
2026-08-28 11:55:54 +03:00
parent 722e60e5ef
commit 0848cefc89
4 changed files with 56 additions and 44 deletions
@@ -50,9 +50,12 @@
"reference_graph_parameters_unchanged": true, "reference_graph_parameters_unchanged": true,
"tgs_parameters_unchanged": true, "tgs_parameters_unchanged": true,
"ddrnet_parameters_unchanged": true, "ddrnet_parameters_unchanged": true,
"vegetation_source_buffer_bounded": true,
"vegetation_full_route_rgb_prefetch_allowed": false,
"ppliteseg_concurrent_run_allowed": false, "ppliteseg_concurrent_run_allowed": false,
"camera_semantics_can_clear_rigid_geometry": false, "camera_semantics_can_clear_rigid_geometry": false,
"canonical_triton_mutation_allowed": false, "canonical_triton_mutation_allowed": false,
"runtime_shared_source_frame_target": true,
"gauss_or_playcanvas_in_scope": false "gauss_or_playcanvas_in_scope": false
}, },
"authority": { "authority": {
@@ -288,24 +288,6 @@ foreach ($name in $containers) {
$started = [DateTimeOffset]::UtcNow $started = [DateTimeOffset]::UtcNow
try { try {
if ($VegetationLoadGate) {
$vegetationFrames = Join-Path $runOutput "vegetation\input-frames"
$null = New-Item -ItemType Directory -Path $vegetationFrames
& ffmpeg -hide_banner -loglevel error -i $source.Video -map 0:v:0 -fps_mode passthrough (
Join-Path $vegetationFrames "frame-%06d.png"
)
Assert-LastExitCode "RAVNOVES full-video frame extraction"
$extractedFrames = @(
Get-ChildItem -LiteralPath $vegetationFrames -File -Filter "frame-*.png" |
Sort-Object Name
)
if (
$extractedFrames.Count -ne 4489 -or
$extractedFrames[0].Name -cne "frame-000001.png" -or
$extractedFrames[-1].Name -cne "frame-004489.png"
) { throw "RAVNOVES full-video frame sequence changed" }
}
& docker run --rm --name $prepareName --network none --cpus 8 --memory 16g ` & docker run --rm --name $prepareName --network none --cpus 8 --memory 16g `
--entrypoint python3 ` --entrypoint python3 `
--volume ((Convert-ToDockerPath $source.SourcePack) + ":/source/lidar-pack.npz:ro") ` --volume ((Convert-ToDockerPath $source.SourcePack) + ":/source/lidar-pack.npz:ro") `
@@ -414,6 +396,7 @@ try {
--volume ($dockerRun + ":/shared:rw") ` --volume ($dockerRun + ":/shared:rw") `
--volume ($dockerVegetationDataset + ":/data/goose:ro") ` --volume ($dockerVegetationDataset + ":/data/goose:ro") `
--volume ($dockerVegetationCheckpoint + ":/models/candidate.pth:ro") ` --volume ($dockerVegetationCheckpoint + ":/models/candidate.pth:ro") `
--volume ((Convert-ToDockerPath $source.Video) + ":/source/right.mp4:ro") `
$VegetationImageTag run --no-capture-output --name goose python ` $VegetationImageTag run --no-capture-output --name goose python `
/release/run_vegetation_integrated_load.py ` /release/run_vegetation_integrated_load.py `
--config /release/lab-v1-goose-vegetation-benchmark-v1.json ` --config /release/lab-v1-goose-vegetation-benchmark-v1.json `
@@ -421,7 +404,7 @@ try {
--provider-map /release/lab-v1-vegetation-provider-label-map-v1.json ` --provider-map /release/lab-v1-vegetation-provider-label-map-v1.json `
--checkpoint /models/candidate.pth ` --checkpoint /models/candidate.pth `
--dataset-root /data/goose ` --dataset-root /data/goose `
--frames-root /shared/vegetation/input-frames ` --video /source/right.mp4 `
--source-rate-hz $rate ` --source-rate-hz $rate `
--minimum-effective-fps 11.209069 ` --minimum-effective-fps 11.209069 `
--maximum-completion-p95-ms 125.0 ` --maximum-completion-p95-ms 125.0 `
@@ -582,10 +565,6 @@ try {
Assert-LastExitCode "M49 integrated vegetation evidence gate" Assert-LastExitCode "M49 integrated vegetation evidence gate"
} }
} finally { } finally {
$vegetationFrames = Join-Path $runOutput "vegetation\input-frames"
if (Test-Path -LiteralPath $vegetationFrames -PathType Container) {
Remove-Item -LiteralPath $vegetationFrames -Recurse -Force
}
foreach ($name in $containers) { Remove-ExactContainer $name } foreach ($name in $containers) { Remove-ExactContainer $name }
$canonicalAfter = Get-Container "ndc-mission-core-triton" $canonicalAfter = Get-Container "ndc-mission-core-triton"
if ( if (
@@ -12,6 +12,7 @@ import time
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import cv2
import torch import torch
from PIL import Image from PIL import Image
from run_goose_vegetation_benchmark import ( from run_goose_vegetation_benchmark import (
@@ -51,7 +52,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--provider-map", type=Path, required=True) parser.add_argument("--provider-map", type=Path, required=True)
parser.add_argument("--checkpoint", type=Path, required=True) parser.add_argument("--checkpoint", type=Path, required=True)
parser.add_argument("--dataset-root", type=Path, required=True) parser.add_argument("--dataset-root", type=Path, required=True)
parser.add_argument("--frames-root", type=Path, required=True) parser.add_argument("--video", type=Path, required=True)
parser.add_argument("--source-rate-hz", type=float, required=True) parser.add_argument("--source-rate-hz", type=float, required=True)
parser.add_argument("--minimum-effective-fps", type=float, required=True) parser.add_argument("--minimum-effective-fps", type=float, required=True)
parser.add_argument("--maximum-completion-p95-ms", type=float, required=True) parser.add_argument("--maximum-completion-p95-ms", type=float, required=True)
@@ -86,14 +87,30 @@ def distribution(values: list[float]) -> dict[str, float]:
} }
def exact_frames(root: Path) -> list[Path]: def open_video(path: Path, expected_size: tuple[int, int]) -> cv2.VideoCapture:
if root.is_symlink() or not root.is_dir(): if path.is_symlink() or not path.is_file():
raise IntegratedLoadError("RAVNOVES frame root is unavailable") raise IntegratedLoadError("RAVNOVES video is unavailable")
frames = sorted(root.glob("frame-*.png")) capture = cv2.VideoCapture(str(path))
expected = [f"frame-{sequence + 1:06d}.png" for sequence in range(FRAME_COUNT)] if not capture.isOpened():
if len(frames) != FRAME_COUNT or [frame.name for frame in frames] != expected: raise IntegratedLoadError("RAVNOVES video decoder did not open")
raise IntegratedLoadError("RAVNOVES full-video frame sequence changed") metadata = (
return frames round(capture.get(cv2.CAP_PROP_FRAME_COUNT)),
round(capture.get(cv2.CAP_PROP_FRAME_WIDTH)),
round(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)),
)
if metadata != (FRAME_COUNT, expected_size[0], expected_size[1]):
capture.release()
raise IntegratedLoadError("RAVNOVES video metadata changed")
return capture
def decode_source(capture: cv2.VideoCapture, expected_size: tuple[int, int]) -> Image.Image:
available, bgr = capture.read()
if not available or bgr is None:
raise IntegratedLoadError("RAVNOVES video ended before the frozen frame count")
if (bgr.shape[1], bgr.shape[0]) != expected_size:
raise IntegratedLoadError("RAVNOVES decoded frame dimensions changed")
return Image.fromarray(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB), mode="RGB")
def validate_sha256(value: str, label: str) -> None: def validate_sha256(value: str, label: str) -> None:
@@ -130,14 +147,20 @@ def run() -> int:
raise IntegratedLoadError("DDRNet checkpoint digest changed") raise IntegratedLoadError("DDRNet checkpoint digest changed")
mapping_path = args.dataset_root / config["dataset"]["mapping_relative_path"] mapping_path = args.dataset_root / config["dataset"]["mapping_relative_path"]
load_mapping(mapping_path, config["dataset"]["mapping_sha256"]) load_mapping(mapping_path, config["dataset"]["mapping_sha256"])
frames = exact_frames(args.frames_root) expected_size = (
config["ravnoves"]["expected_width"],
config["ravnoves"]["expected_height"],
)
warmup_capture = open_video(args.video, expected_size)
warmup_source = decode_source(warmup_capture, expected_size)
warmup_capture.release()
torch.cuda.empty_cache() torch.cuda.empty_cache()
model, model_name, architecture_failures = load_model("ddrnet", args.checkpoint) model, model_name, architecture_failures = load_model("ddrnet", args.checkpoint)
with Image.open(frames[0]) as image: warmup_tensor, _ = preprocess(warmup_source)
warmup_tensor, _ = preprocess(image.convert("RGB"))
warmup_latencies_ms = [infer(model, warmup_tensor)[1] for _ in range(3)] warmup_latencies_ms = [infer(model, warmup_tensor)[1] for _ in range(3)]
torch.cuda.reset_peak_memory_stats() torch.cuda.reset_peak_memory_stats()
source_capture = open_video(args.video, expected_size)
wait_for_shared_start( wait_for_shared_start(
args.shared_start_ready_file, args.shared_start_ready_file,
args.shared_start_file, args.shared_start_file,
@@ -153,19 +176,13 @@ def run() -> int:
late_deadline_count = 0 late_deadline_count = 0
args.frame_ledger.parent.mkdir(parents=True, exist_ok=True) args.frame_ledger.parent.mkdir(parents=True, exist_ok=True)
with args.frame_ledger.open("x", encoding="utf-8") as ledger: with args.frame_ledger.open("x", encoding="utf-8") as ledger:
for sequence, frame in enumerate(frames): for sequence in range(FRAME_COUNT):
scheduled_ns = start_ns + round(sequence * interval_ns) scheduled_ns = start_ns + round(sequence * interval_ns)
remaining_ns = scheduled_ns - time.monotonic_ns() remaining_ns = scheduled_ns - time.monotonic_ns()
if remaining_ns > 0: if remaining_ns > 0:
time.sleep(remaining_ns / 1_000_000_000.0) time.sleep(remaining_ns / 1_000_000_000.0)
admitted_ns = time.monotonic_ns() admitted_ns = time.monotonic_ns()
with Image.open(frame) as image: source = decode_source(source_capture, expected_size)
source = image.convert("RGB")
if source.size != (
config["ravnoves"]["expected_width"],
config["ravnoves"]["expected_height"],
):
raise IntegratedLoadError("RAVNOVES video frame dimensions changed")
tensor, _ = preprocess(source) tensor, _ = preprocess(source)
_, inference_ms = infer(model, tensor) _, inference_ms = infer(model, tensor)
completed_ns = time.monotonic_ns() completed_ns = time.monotonic_ns()
@@ -181,7 +198,7 @@ def run() -> int:
row = { row = {
"schema_version": FRAME_SCHEMA, "schema_version": FRAME_SCHEMA,
"sequence": sequence, "sequence": sequence,
"frame_name": frame.name, "frame_name": f"frame-{sequence + 1:06d}",
"scheduled_monotonic_ns": scheduled_ns, "scheduled_monotonic_ns": scheduled_ns,
"admitted_monotonic_ns": admitted_ns, "admitted_monotonic_ns": admitted_ns,
"completed_monotonic_ns": completed_ns, "completed_monotonic_ns": completed_ns,
@@ -192,6 +209,10 @@ def run() -> int:
ledger.write(json.dumps(row, sort_keys=True, separators=(",", ":")) + "\n") ledger.write(json.dumps(row, sort_keys=True, separators=(",", ":")) + "\n")
if sequence % 64 == 0: if sequence % 64 == 0:
ledger.flush() ledger.flush()
extra_available, _ = source_capture.read()
source_capture.release()
if extra_available:
raise IntegratedLoadError("RAVNOVES video contains frames beyond the frozen timeline")
completed_ns = time.monotonic_ns() completed_ns = time.monotonic_ns()
wall_seconds = (completed_ns - start_ns) / 1_000_000_000.0 wall_seconds = (completed_ns - start_ns) / 1_000_000_000.0
@@ -231,6 +252,12 @@ def run() -> int:
"frame_count": FRAME_COUNT, "frame_count": FRAME_COUNT,
"capacity_drop_count": 0, "capacity_drop_count": 0,
"deadline_miss_count": late_deadline_count, "deadline_miss_count": late_deadline_count,
"source_decode": {
"mode": "bounded-sequential-h264/v1",
"full_route_rgb_prefetch": False,
"candidate_local_decoder": True,
"runtime_target": "shared-source-frame",
},
"frame_ledger": { "frame_ledger": {
"path": args.frame_ledger.name, "path": args.frame_ledger.name,
"rows": FRAME_COUNT, "rows": FRAME_COUNT,
@@ -236,6 +236,9 @@ def test_worker_gate_reuses_shared_barrier_and_keeps_canonical_triton_unchanged(
wrapper = POWERSHELL_PATH.read_text(encoding="utf-8") wrapper = POWERSHELL_PATH.read_text(encoding="utf-8")
assert '"source-paced-integrated-shadow/v1"' in runner assert '"source-paced-integrated-shadow/v1"' in runner
assert "wait_for_shared_start(" in runner assert "wait_for_shared_start(" in runner
assert '"bounded-sequential-h264/v1"' in runner
assert '"full_route_rgb_prefetch": False' in runner
assert "decode_source(source_capture, expected_size)" in runner
assert '"camera_semantics_can_clear_rigid_geometry": False' in runner assert '"camera_semantics_can_clear_rigid_geometry": False' in runner
assert "$VegetationLoadGate" in wrapper assert "$VegetationLoadGate" in wrapper
assert '"vegetation"' in wrapper assert '"vegetation"' in wrapper