feat(perception): add full TGS shadow runner

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 21:59:39 +03:00
parent 6544d9e918
commit 40c850b167
8 changed files with 1194 additions and 0 deletions
@@ -0,0 +1,187 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseRoot,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
[string]$RunId,
[string]$SourcePackPath = "D:\NDC_MISSIONCORE\runtime\derived\e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b\lidar-pack.npz",
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m49-tgs-full-shadow"
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$TravelImageTag = "ndc/mission-core-m49-t3-travel:20260826"
$TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
$ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130"
$ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Resolve-DDirectory([string]$Path, [string]$Label, [bool]$Create) {
if ($Create -and -not (Test-Path -LiteralPath $Path)) {
$null = New-Item -ItemType Directory -Path $Path
}
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
if (
-not $item.PSIsContainer -or
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
) { throw "$Label must be a real D: directory" }
return $item.FullName
}
function Resolve-DFile([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
if (
$item.PSIsContainer -or
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
) { throw "$Label must be a real D: file" }
return $item.FullName
}
function Convert-ToDockerPath([string]$Path) { return ($Path -replace "\\", "/") }
function Get-Container([string]$Name) {
$rows = @(((& docker inspect $Name) | ConvertFrom-Json))
Assert-LastExitCode "Docker inspection for $Name"
if ($rows.Count -ne 1) { throw "Container identity for $Name is not unique" }
return $rows[0]
}
function Assert-Image([string]$Tag, [string]$ExpectedId) {
$rows = @(((& docker image inspect $Tag) | ConvertFrom-Json))
Assert-LastExitCode "Docker image inspection for $Tag"
if ($rows.Count -ne 1 -or [string]$rows[0].Id -cne $ExpectedId) {
throw "Pinned image identity changed for $Tag"
}
}
function Remove-ExactContainer([string]$Name) {
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$Name$") {
& docker rm --force $Name *> $null
}
}
if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") { throw "M49 TGS full shadow is pinned to Worker 006" }
$release = Resolve-DDirectory $ReleaseRoot "M49 TGS full-shadow release" $false
$payload = Resolve-DDirectory (Join-Path $release "payload") "M49 TGS full-shadow payload" $false
$sourcePack = Resolve-DFile $SourcePackPath "RAVNOVES00 source pack"
$output = Resolve-DDirectory $OutputRoot "M49 TGS full-shadow output root" $true
$runCandidate = Join-Path $output $RunId
if (Test-Path -LiteralPath $runCandidate) { throw "M49 TGS full-shadow output already exists" }
$null = New-Item -ItemType Directory -Path $runCandidate
$runOutput = Resolve-DDirectory $runCandidate "M49 TGS full-shadow run output" $false
$releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json
if (
$releaseDocument.schema_version -cne "missioncore.m49-tgs-full-shadow-worker-release/v1" -or
$releaseDocument.worker_id -cne "worker-006" -or
$releaseDocument.candidate_id -cne "travel-tgs-full-shadow"
) { throw "M49 TGS full-shadow release contract changed" }
foreach ($property in $releaseDocument.files.PSObject.Properties) {
$path = Join-Path $payload $property.Name
$actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLowerInvariant()
if ($actual -cne [string]$property.Value.sha256) {
throw "M49 TGS full-shadow payload digest changed: $($property.Name)"
}
}
$sourcePackSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $sourcePack).Hash.ToLowerInvariant()
if ($sourcePackSha -cne "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944") {
throw "RAVNOVES00 source pack digest changed"
}
$os = Get-CimInstance Win32_OperatingSystem
$freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB
if ($freeMemoryGiB -lt 24.0) {
throw ("M49 TGS full shadow requires 24 GiB free memory; observed {0:N2} GiB" -f $freeMemoryGiB)
}
$tritonBefore = Get-Container "ndc-mission-core-triton"
if (-not $tritonBefore.State.Running -or $tritonBefore.State.Health.Status -cne "healthy") {
throw "Canonical Mission Core Triton must remain healthy during M49 TGS full shadow"
}
Assert-Image $TravelImageTag $TravelImageId
Assert-Image $ParityImageTag $ParityImageId
$prepareName = "ndc-mission-core-m49-tgs-full-prepare-$RunId"
$runName = "ndc-mission-core-m49-tgs-full-run-$RunId"
$analyzeName = "ndc-mission-core-m49-tgs-full-analyze-$RunId"
foreach ($name in @($prepareName, $runName, $analyzeName)) {
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
throw "M49 TGS full-shadow container name already exists: $name"
}
}
$started = [DateTimeOffset]::UtcNow
try {
& docker run --rm --name $prepareName --network none --cpus 8 --memory 16g `
--entrypoint python3 `
--volume ((Convert-ToDockerPath $sourcePack) + ":/source/lidar-pack.npz:ro") `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath $runOutput) + ":/tgs") `
$ParityImageTag /release/prepare_tgs_full_shadow_inputs.py `
--source-pack /source/lidar-pack.npz `
--config /release/m49-tgs-full-shadow-v1.json `
--output-root /tgs/inputs
Assert-LastExitCode "M49 TGS full-shadow input preparation"
& docker run --rm --name $runName --network none --cpus 16 --memory 24g `
--entrypoint /bin/bash `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath $runOutput) + ":/tgs") `
$TravelImageTag /release/run_tgs_full_shadow.sh
Assert-LastExitCode "M49 source-paced TGS full-shadow run"
& docker run --rm --name $analyzeName --network none --cpus 8 --memory 16g `
--entrypoint python3 `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath $runOutput) + ":/tgs") `
$ParityImageTag /release/build_tgs_full_shadow_evidence.py `
--run-root /tgs `
--config /release/m49-tgs-full-shadow-v1.json `
--output-root /tgs/evidence
Assert-LastExitCode "M49 TGS full-shadow evidence analysis"
} finally {
foreach ($name in @($prepareName, $runName, $analyzeName)) { Remove-ExactContainer $name }
}
$completed = [DateTimeOffset]::UtcNow
$resultPath = Join-Path $runOutput "evidence\result.json"
if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) { throw "M49 TGS full-shadow result is missing" }
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
if (
$result.timeline.frame_count -ne 4489 -or
$result.timeline.available_lidar_frame_count -ne 3928 -or
$result.timeline.missing_lidar_frame_count -ne 561 -or
$result.point_accounting.unaccounted -ne 0
) { throw "M49 TGS full-shadow structural acceptance failed" }
$tritonAfter = Get-Container "ndc-mission-core-triton"
if (
-not $tritonAfter.State.Running -or
$tritonAfter.State.Health.Status -cne "healthy" -or
[string]$tritonAfter.Id -cne [string]$tritonBefore.Id
) { throw "Canonical Mission Core Triton changed during M49 TGS full shadow" }
$summary = [ordered]@{
schema_version = "missioncore.m49-tgs-full-shadow-worker-summary/v1"
worker_id = "worker-006"
run_id = $RunId
code_revision = [string]$releaseDocument.code_revision
source_pack_sha256 = $sourcePackSha
travel_image_id = $TravelImageId
parity_image_id = $ParityImageId
started_utc = $started.ToString("o")
wall_seconds = [math]::Round(($completed - $started).TotalSeconds, 6)
free_memory_gib_before = [math]::Round($freeMemoryGiB, 6)
canonical_triton_id = [string]$tritonAfter.Id
canonical_triton_health = [string]$tritonAfter.State.Health.Status
result_status = [string]$result.status
all_timeline_frames_accounted = $true
all_eligible_points_accounted = $true
aos_used = $false
gpu_requested = $false
integrated_graph_performance_accepted = $false
navigation_or_actuation_allowed = $false
}
$summary | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (Join-Path $runOutput "worker-summary.json") -Encoding utf8
$summary | ConvertTo-Json -Depth 3
@@ -0,0 +1,45 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseRoot,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
[string]$RunId
)
$ErrorActionPreference = "Stop"
$taskName = "MissionCore-M49TgsFullShadow"
$release = (Resolve-Path -LiteralPath $ReleaseRoot).Path
$runner = Join-Path $release "payload\Invoke-M49TgsFullShadow.ps1"
if (-not (Test-Path -LiteralPath $runner -PathType Leaf)) { throw "M49 TGS full-shadow runner is missing" }
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($existing -and $existing.State -eq "Running") { throw "$taskName is already running" }
$powerShell = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$arguments = @(
"-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
"-File", "`"$runner`"", "-ReleaseRoot", "`"$release`"", "-RunId", "`"$RunId`""
) -join " "
$userId = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$action = New-ScheduledTaskAction -Execute $powerShell -Argument $arguments -WorkingDirectory $release
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date).AddMinutes(30))
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-ExecutionTimeLimit ([TimeSpan]::FromHours(2))
Register-ScheduledTask `
-TaskName $taskName `
-Action $action `
-Principal $principal `
-Trigger $trigger `
-Settings $settings `
-Description "One-shot CPU-only source-paced TGS full shadow." `
-Force | Out-Null
Start-ScheduledTask -TaskName $taskName
[pscustomobject]@{
task_name = $taskName
run_id = $RunId
release_root = $release
state = (Get-ScheduledTask -TaskName $taskName).State.ToString()
} | ConvertTo-Json -Compress
@@ -0,0 +1,332 @@
#!/usr/bin/env python3
"""Build mmap-friendly evidence for the complete source-paced TGS shadow."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import math
from pathlib import Path
import numpy as np
from build_tgs_fail_closed_evidence import costmap_grid
class FullShadowError(RuntimeError):
"""The complete TGS shadow or its fail-closed contract is invalid."""
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def load_xyzi(path: Path) -> np.ndarray:
if path.is_symlink() or not path.is_file():
raise FullShadowError(f"sealed input is unavailable: {path.name}")
values = np.fromfile(path, dtype=np.float32)
if values.size % 4:
raise FullShadowError(f"sealed XYZI shape changed: {path.name}")
result = values.reshape(-1, 4)
if not np.isfinite(result).all():
raise FullShadowError(f"sealed XYZI is non-finite: {path.name}")
return result
def classify_exact_input(
native: np.ndarray, ground: np.ndarray, nonground: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
ranges = np.linalg.norm(native[:, :2].astype(np.float64), axis=1)
points = np.ascontiguousarray(native[(ranges > 1.0) & (ranges < 80.0), :3])
output = np.ascontiguousarray(np.concatenate((ground[:, :3], nonground[:, :3]), axis=0))
output_states = np.concatenate(
(np.ones(ground.shape[0], dtype=np.uint8), np.full(nonground.shape[0], 2, dtype=np.uint8))
)
key_dtype = np.dtype((np.void, 12))
input_keys = points.view(key_dtype).reshape(-1)
output_keys = output.view(key_dtype).reshape(-1)
input_order = np.argsort(input_keys, kind="stable")
output_order = np.argsort(output_keys, kind="stable")
sorted_input = input_keys[input_order]
sorted_output = output_keys[output_order]
positions = np.searchsorted(sorted_input, sorted_output, side="left")
if sorted_output.size:
group_starts = np.r_[0, np.flatnonzero(sorted_output[1:] != sorted_output[:-1]) + 1]
group_lengths = np.diff(np.r_[group_starts, sorted_output.size])
occurrence = np.arange(sorted_output.size) - np.repeat(group_starts, group_lengths)
targets = positions + occurrence
if (
np.any(targets >= sorted_input.size)
or np.any(sorted_input[targets] != sorted_output)
or np.unique(targets).size != targets.size
):
raise FullShadowError("TGS output is not a multiset subset of its exact input")
else:
targets = np.empty(0, dtype=np.int64)
sorted_states = np.full(points.shape[0], 3, dtype=np.uint8)
sorted_states[targets] = output_states[output_order]
states = np.empty_like(sorted_states)
states[input_order] = sorted_states
return points, states
def rasterize(
points: np.ndarray,
states: np.ndarray,
grid: np.ndarray,
cell_size_m: float,
) -> tuple[np.ndarray, np.ndarray]:
minimum_ix = int(np.min(grid[:, 0]))
maximum_ix = int(np.max(grid[:, 0]))
minimum_iy = int(np.min(grid[:, 1]))
maximum_iy = int(np.max(grid[:, 1]))
lookup = np.full(
(maximum_ix - minimum_ix + 1, maximum_iy - minimum_iy + 1), -1, dtype=np.int32
)
lookup[
grid[:, 0].astype(np.int32) - minimum_ix,
grid[:, 1].astype(np.int32) - minimum_iy,
] = np.arange(grid.shape[0], dtype=np.int32)
cell_xy = np.floor(points[:, :2] / cell_size_m).astype(np.int32)
inside = (
(cell_xy[:, 0] >= minimum_ix)
& (cell_xy[:, 0] <= maximum_ix)
& (cell_xy[:, 1] >= minimum_iy)
& (cell_xy[:, 1] <= maximum_iy)
)
point_indices = np.flatnonzero(inside)
cell_indices = lookup[
cell_xy[inside, 0] - minimum_ix, cell_xy[inside, 1] - minimum_iy
]
valid = cell_indices >= 0
point_indices = point_indices[valid]
cell_indices = cell_indices[valid]
cell_states = np.zeros(grid.shape[0], dtype=np.uint8)
selected_states = states[point_indices]
ground_cells = np.zeros(grid.shape[0], dtype=np.uint8)
rejected_cells = np.zeros(grid.shape[0], dtype=np.uint8)
nonground_cells = np.zeros(grid.shape[0], dtype=np.uint8)
np.maximum.at(ground_cells, cell_indices, (selected_states == 1).astype(np.uint8))
np.maximum.at(rejected_cells, cell_indices, (selected_states == 3).astype(np.uint8))
np.maximum.at(nonground_cells, cell_indices, (selected_states == 2).astype(np.uint8))
cell_states[ground_cells > 0] = 1
cell_states[rejected_cells > 0] = 3
cell_states[nonground_cells > 0] = 2
minimum_z = np.full(grid.shape[0], np.inf, dtype=np.float32)
maximum_z = np.full(grid.shape[0], -np.inf, dtype=np.float32)
np.minimum.at(minimum_z, cell_indices, points[point_indices, 2])
np.maximum.at(maximum_z, cell_indices, points[point_indices, 2])
z_bounds = np.column_stack((minimum_z, maximum_z)).astype(np.float32, copy=False)
z_bounds[~np.isfinite(z_bounds)] = np.nan
return cell_states, z_bounds
def percentile(values: np.ndarray, value: float) -> float:
return float(np.percentile(values.astype(np.float64), value)) if values.size else 0.0
def build(run_root: Path, config_path: Path, output_root: Path) -> dict[str, object]:
if output_root.exists():
raise FullShadowError("full-shadow evidence output already exists")
config = json.loads(config_path.read_text(encoding="utf-8"))
if (
config.get("schema_version") != "missioncore.m49-tgs-full-shadow-profile/v1"
or config.get("invariants", {}).get("aos_allowed") is not False
or config.get("invariants", {}).get("gpu_allowed") is not False
or config.get("invariants", {}).get("missing_lidar_means_unobserved") is not True
):
raise FullShadowError("full-shadow profile changed")
manifest_path = run_root / "inputs" / "input-manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
records = manifest.get("records", [])
if (
manifest.get("schema_version") != "missioncore.m49-tgs-full-shadow-input/v1"
or manifest.get("source_pack_sha256") != config["source"]["source_pack_sha256"]
or manifest.get("config_sha256") != sha256_file(config_path)
or manifest.get("future_frames_used") is not False
or len(records) != 4489
or sum(bool(row["sample_available"]) for row in records) != 3928
):
raise FullShadowError("full-shadow input manifest changed")
with (run_root / "tgs-full-timing.tsv").open("r", encoding="utf-8", newline="") as stream:
timing_rows = list(csv.DictReader(stream, delimiter="\t"))
if len(timing_rows) != 4489:
raise FullShadowError("full-shadow timing frame accounting changed")
cell_size = float(config["costmap"]["cell_size_m"])
radius = float(config["costmap"]["radius_m"])
grid = costmap_grid(radius, cell_size)
output_root.mkdir(parents=True)
np.save(output_root / "costmap-cell-indices-xy.npy", grid[:, :2].astype(np.int32))
np.save(output_root / "costmap-cell-centers-xy-m.npy", grid[:, 2:].astype(np.float32))
states_out = np.lib.format.open_memmap(
output_root / "costmap-states.npy", mode="w+", dtype=np.uint8, shape=(4489, grid.shape[0])
)
z_out = np.lib.format.open_memmap(
output_root / "costmap-z-bounds-m.npy",
mode="w+",
dtype=np.float32,
shape=(4489, grid.shape[0], 2),
)
z_out[:] = np.nan
summaries: list[dict[str, object]] = []
eligible_total = ground_total = nonground_total = rejected_total = 0
available_seen = 0
for frame_index, (record, timing) in enumerate(zip(records, timing_rows, strict=True)):
if int(timing["timeline_frame_index"]) != frame_index:
raise FullShadowError("full-shadow timing order changed")
available = bool(record["sample_available"])
if not available:
if int(timing["sample_available"]) != 0:
raise FullShadowError("missing LiDAR frame was processed")
states_out[frame_index] = 0
summaries.append(
{
"timeline_frame_index": frame_index,
"source_frame_index": int(record["source_frame_index"]),
"session_seconds": float(record["session_seconds"]),
"sample_available": False,
"eligible_point_count": 0,
"ground_point_count": 0,
"nonground_point_count": 0,
"rejected_point_count": 0,
"occupied_cell_count": 0,
}
)
continue
available_seen += 1
native_path = run_root / "inputs" / str(record["relative_path"])
if sha256_file(native_path) != record["sha256"]:
raise FullShadowError("sealed gravity-aligned full-shadow input changed")
output = run_root / "outputs" / "causal_rolling_1s"
ground = load_xyzi(output / f"{frame_index}_ground.bin")
nonground = load_xyzi(output / f"{frame_index}_nonground.bin")
points, point_states = classify_exact_input(load_xyzi(native_path), ground, nonground)
cell_states, z_bounds = rasterize(points, point_states, grid, cell_size)
states_out[frame_index] = cell_states
z_out[frame_index] = z_bounds
ground_count = int(np.count_nonzero(point_states == 1))
nonground_count = int(np.count_nonzero(point_states == 2))
rejected_count = int(np.count_nonzero(point_states == 3))
eligible_total += int(points.shape[0])
ground_total += ground_count
nonground_total += nonground_count
rejected_total += rejected_count
summaries.append(
{
"timeline_frame_index": frame_index,
"source_frame_index": int(record["source_frame_index"]),
"session_seconds": float(record["session_seconds"]),
"sample_available": True,
"eligible_point_count": int(points.shape[0]),
"ground_point_count": ground_count,
"nonground_point_count": nonground_count,
"rejected_point_count": rejected_count,
"occupied_cell_count": int(np.count_nonzero(cell_states == 2)),
}
)
states_out.flush()
z_out.flush()
if available_seen != 3928 or eligible_total != ground_total + nonground_total + rejected_total:
raise FullShadowError("full-shadow eligible point accounting failed")
frames_path = output_root / "frames.ndjson"
frames_path.write_text(
"".join(json.dumps(row, sort_keys=True) + "\n" for row in summaries), encoding="utf-8"
)
available_timings = [row for row in timing_rows if int(row["sample_available"]) == 1]
tgs_ms = np.asarray([float(row["tgs_ms"]) for row in available_timings])
completion_ms = np.asarray([float(row["completion_age_ms"]) for row in timing_rows])
capacity_drops = sum(int(row["capacity_drop"]) for row in timing_rows)
duration = float(records[-1]["session_seconds"]) - float(records[0]["session_seconds"])
effective_fps = (len(records) - 1) / duration
thresholds = config["acceptance"]
acceptance = {
"minimum_effective_timeline_fps": effective_fps >= float(thresholds["minimum_effective_timeline_fps"]),
"candidate_stage_p95_ms": percentile(tgs_ms, 95) <= float(thresholds["candidate_stage_p95_ms_max"]),
"candidate_stage_p99_ms": percentile(tgs_ms, 99) <= float(thresholds["candidate_stage_p99_ms_max"]),
"completion_age_p99_ms": percentile(completion_ms, 99) <= float(thresholds["completion_age_p99_ms_max"]),
"capacity_drop_count": capacity_drops <= int(thresholds["capacity_drop_count_max"]),
"all_frames_accounted": len(summaries) == 4489 and available_seen == 3928,
"all_eligible_points_accounted": eligible_total == ground_total + nonground_total + rejected_total,
}
files = {}
for path in sorted(output_root.iterdir()):
if path.is_file() and path.name != "result.json":
files[path.name] = {"bytes": path.stat().st_size, "sha256": sha256_file(path)}
result = {
"schema_version": "missioncore.m49-tgs-full-shadow-result/v1",
"status": "passed" if all(acceptance.values()) else "failed",
"config_sha256": sha256_file(config_path),
"source_pack_sha256": manifest["source_pack_sha256"],
"input_manifest_sha256": sha256_file(manifest_path),
"timeline": {
"frame_count": 4489,
"available_lidar_frame_count": 3928,
"missing_lidar_frame_count": 561,
"duration_seconds": duration,
"effective_fps": effective_fps,
},
"costmap": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": cell_size,
"radius_m": radius,
"cell_count": int(grid.shape[0]),
},
"point_accounting": {
"eligible": eligible_total,
"ground": ground_total,
"nonground": nonground_total,
"rejected": rejected_total,
"unaccounted": eligible_total - ground_total - nonground_total - rejected_total,
},
"performance": {
"candidate_tgs_ms": {
"p50": percentile(tgs_ms, 50),
"p95": percentile(tgs_ms, 95),
"p99": percentile(tgs_ms, 99),
"max": float(np.max(tgs_ms)),
},
"completion_age_ms": {
"p50": percentile(completion_ms, 50),
"p95": percentile(completion_ms, 95),
"p99": percentile(completion_ms, 99),
"max": float(np.max(completion_ms)),
},
"capacity_drop_count": capacity_drops,
},
"acceptance": acceptance,
"files": files,
"authority": {
"visual_quality_accepted": False,
"traversability_accepted": False,
"realtime_accepted": bool(all(acceptance.values())),
"integrated_graph_performance_accepted": False,
"navigation_or_actuation_allowed": False,
},
}
(output_root / "result.json").write_text(
json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
return result
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--run-root", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
arguments = parser.parse_args()
result = build(arguments.run_root, arguments.config, arguments.output_root)
print(json.dumps({"status": result["status"], **result["performance"]}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""Prepare every available RAVNOVES00 frame for the source-paced TGS shadow."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import numpy as np
from prepare_tgs_fail_closed_inputs import (
EXPECTED_ARRAYS,
SOURCE_PACK_SHA256,
TgsInputError,
_frame_points,
_validate_source,
gravity_local_xyzi,
sha256_file,
)
FULL_SCHEMA = "missioncore.m49-tgs-full-shadow-profile/v1"
INPUT_SCHEMA = "missioncore.m49-tgs-full-shadow-input/v1"
TIMELINE_FRAME_COUNT = 4_489
AVAILABLE_LIDAR_FRAME_COUNT = 3_928
def _bytes_sha256(content: bytes) -> str:
return hashlib.sha256(content).hexdigest()
def prepare(source_pack: Path, config_path: Path, output_root: Path) -> dict[str, object]:
if output_root.exists():
raise TgsInputError("TGS full-shadow input output root already exists")
if sha256_file(source_pack) != SOURCE_PACK_SHA256:
raise TgsInputError("RAVNOVES00 lidar-pack digest changed")
config = json.loads(config_path.read_text(encoding="utf-8"))
source = config.get("source", {})
profile = config.get("profile", {})
invariants = config.get("invariants", {})
if (
config.get("schema_version") != FULL_SCHEMA
or source.get("source_pack_sha256") != SOURCE_PACK_SHA256
or source.get("expected_timeline_frames") != TIMELINE_FRAME_COUNT
or source.get("expected_available_lidar_frames") != AVAILABLE_LIDAR_FRAME_COUNT
or source.get("input_coordinate_frame")
!= "map-gravity-local-translation-only"
or profile.get("id") != "causal_rolling_1s"
or profile.get("missing_lidar_policy") != "all-cells-unobserved"
or invariants.get("lidar_orientation_applied_to_tgs_input") is not False
or invariants.get("future_frames_used") is not False
or invariants.get("missing_lidar_means_unobserved") is not True
):
raise TgsInputError("TGS full-shadow profile changed")
required = EXPECTED_ARRAYS | {"source_frame_indices", "pose_quaternions_map_from_lidar"}
with np.load(source_pack, allow_pickle=False) as archive:
if not required.issubset(archive.files):
raise TgsInputError("RAVNOVES00 lidar-pack members changed")
arrays = {name: archive[name] for name in required}
_validate_source(arrays)
if (
arrays["source_frame_indices"].shape != (TIMELINE_FRAME_COUNT,)
or arrays["source_frame_indices"].dtype != np.int64
or arrays["pose_quaternions_map_from_lidar"].shape != (TIMELINE_FRAME_COUNT, 4)
or arrays["pose_quaternions_map_from_lidar"].dtype != np.float64
or int(np.count_nonzero(arrays["sample_available"])) != AVAILABLE_LIDAR_FRAME_COUNT
):
raise TgsInputError("RAVNOVES00 full timeline contract changed")
sequence_root = output_root / "profiles" / "causal_rolling_1s" / "velodyne"
sequence_root.mkdir(parents=True)
seconds = arrays["session_seconds"]
availability = arrays["sample_available"]
positions = arrays["pose_positions_map"]
history_seconds = float(profile["history_seconds"])
local_radius_m = float(profile["local_radius_m"])
records: list[dict[str, object]] = []
schedule_rows = [
"timeline_frame_index\tsource_frame_index\tsession_seconds\tavailable_slot\tpoint_count"
]
available_slot = 0
for frame_index in range(TIMELINE_FRAME_COUNT):
base = {
"timeline_frame_index": frame_index,
"frame_index": int(arrays["frame_indices"][frame_index]),
"source_frame_index": int(arrays["source_frame_indices"][frame_index]),
"session_seconds": float(seconds[frame_index]),
"position_map_m": [float(value) for value in positions[frame_index]],
"sample_available": bool(availability[frame_index]),
}
if not bool(availability[frame_index]):
records.append(
{
**base,
"available_slot": None,
"point_count": 0,
"contributing_frame_indices": [],
"relative_path": None,
"bytes": 0,
"sha256": None,
}
)
schedule_rows.append(
f"{frame_index}\t{base['source_frame_index']}\t{seconds[frame_index]:.9f}\t-1\t0"
)
continue
start = int(np.searchsorted(seconds, seconds[frame_index] - history_seconds, side="left"))
contributors = tuple(
index for index in range(start, frame_index + 1) if bool(availability[index])
)
if not contributors or contributors[-1] != frame_index:
raise TgsInputError("causal full-shadow profile does not contain its current frame")
points_map = np.concatenate(
[_frame_points(arrays, index) for index in contributors], axis=0
)
relative_xy = points_map[:, :2].astype(np.float64) - positions[frame_index, :2]
points_map = points_map[np.linalg.norm(relative_xy, axis=1) <= local_radius_m]
native = gravity_local_xyzi(points_map, positions[frame_index])
if native.shape[0] == 0:
raise TgsInputError("available full-shadow frame produced an empty cloud")
content = np.ascontiguousarray(native).tobytes()
target = sequence_root / f"{available_slot:06d}.bin"
target.write_bytes(content)
records.append(
{
**base,
"available_slot": available_slot,
"point_count": int(native.shape[0]),
"contributing_frame_indices": list(contributors),
"relative_path": target.relative_to(output_root).as_posix(),
"bytes": len(content),
"sha256": _bytes_sha256(content),
}
)
schedule_rows.append(
f"{frame_index}\t{base['source_frame_index']}\t{seconds[frame_index]:.9f}"
f"\t{available_slot}\t{native.shape[0]}"
)
available_slot += 1
if available_slot != AVAILABLE_LIDAR_FRAME_COUNT or len(records) != TIMELINE_FRAME_COUNT:
raise TgsInputError("TGS full-shadow frame accounting changed")
schedule_path = output_root / "schedule.tsv"
schedule_path.write_text("\n".join(schedule_rows) + "\n", encoding="utf-8")
manifest = {
"schema_version": INPUT_SCHEMA,
"source_pack_sha256": SOURCE_PACK_SHA256,
"config_sha256": sha256_file(config_path),
"coordinate_frame": "map-gravity-local",
"transform": "translation-only-preserve-map-gravity-axis",
"intensity_policy": "zero-filled-algorithm-compatibility-only",
"future_frames_used": False,
"timeline_frame_count": TIMELINE_FRAME_COUNT,
"available_lidar_frame_count": AVAILABLE_LIDAR_FRAME_COUNT,
"missing_lidar_frame_count": TIMELINE_FRAME_COUNT - AVAILABLE_LIDAR_FRAME_COUNT,
"schedule": {
"path": "schedule.tsv",
"bytes": schedule_path.stat().st_size,
"sha256": sha256_file(schedule_path),
},
"records": records,
}
manifest_path = output_root / "input-manifest.json"
manifest_path.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
return manifest
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-pack", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
arguments = parser.parse_args()
manifest = prepare(arguments.source_pack, arguments.config, arguments.output_root)
print(
json.dumps(
{
"ok": True,
"timeline_frames": manifest["timeline_frame_count"],
"available_lidar_frames": manifest["available_lidar_frame_count"],
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,181 @@
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#include "travel/kitti_loader.hpp"
#include "travel/point_types.hpp"
#include "travel/tgs.hpp"
namespace {
using Clock = std::chrono::steady_clock;
struct ScheduleRow {
std::size_t timeline_frame_index;
long long source_frame_index;
double session_seconds;
long long available_slot;
std::size_t point_count;
};
std::vector<ScheduleRow> readSchedule(const std::string& path) {
std::ifstream input(path);
if (!input) {
throw std::runtime_error("cannot open full-shadow schedule");
}
std::string line;
std::getline(input, line);
if (line != "timeline_frame_index\tsource_frame_index\tsession_seconds\tavailable_slot\tpoint_count") {
throw std::runtime_error("full-shadow schedule header changed");
}
std::vector<ScheduleRow> rows;
while (std::getline(input, line)) {
if (line.empty()) {
continue;
}
std::istringstream stream(line);
ScheduleRow row{};
if (!(stream >> row.timeline_frame_index >> row.source_frame_index >> row.session_seconds
>> row.available_slot >> row.point_count)) {
throw std::runtime_error("invalid full-shadow schedule row");
}
if (row.timeline_frame_index != rows.size()) {
throw std::runtime_error("full-shadow schedule is not contiguous");
}
rows.push_back(row);
}
if (rows.size() != 4489) {
throw std::runtime_error("full-shadow timeline frame count changed");
}
return rows;
}
void writeXYZI(const std::string& path, const travel::PointCloud<PointXYZILID>& cloud) {
std::ofstream output(path, std::ios::binary);
if (!output) {
throw std::runtime_error("cannot open full-shadow TGS output");
}
for (const auto& point : cloud.points) {
const float row[4] = {point.x, point.y, point.z, point.intensity};
output.write(reinterpret_cast<const char*>(row), sizeof(row));
}
if (!output) {
throw std::runtime_error("cannot write full-shadow TGS output");
}
}
double milliseconds(Clock::duration duration) {
return std::chrono::duration<double, std::milli>(duration).count();
}
} // namespace
int main(int argc, char** argv) {
if (argc != 5) {
std::cerr << "Usage: run_tgs_full_shadow <sequence_dir> <schedule.tsv> <output_dir> <timing.tsv>\n";
return 1;
}
try {
const std::string sequence_dir = argv[1];
const std::string schedule_path = argv[2];
const std::string output_dir = argv[3];
const std::string timing_path = argv[4];
const auto schedule = readSchedule(schedule_path);
KittiLoader loader(sequence_dir);
if (loader.size() != 3928) {
throw std::runtime_error("full-shadow available LiDAR frame count changed");
}
std::filesystem::create_directories(output_dir);
std::ofstream timing(timing_path);
if (!timing) {
throw std::runtime_error("cannot open full-shadow timing output");
}
timing << "timeline_frame_index\tsource_frame_index\tsession_seconds\tsample_available"
<< "\tavailable_slot\tinput_points\tground_points\tnonground_points"
<< "\ttgs_ms\tstage_wall_ms\tqueue_delay_ms\tcompletion_age_ms\tcapacity_drop\n";
timing << std::fixed << std::setprecision(6);
const double first_source_seconds = schedule.front().session_seconds;
const auto run_started = Clock::now();
std::size_t expected_slot = 0;
for (const auto& row : schedule) {
const auto target = run_started + std::chrono::duration_cast<Clock::duration>(
std::chrono::duration<double>(row.session_seconds - first_source_seconds));
const auto before_wait = Clock::now();
if (before_wait < target) {
std::this_thread::sleep_until(target);
}
const auto stage_started = Clock::now();
const double queue_delay_ms = std::max(0.0, milliseconds(stage_started - target));
std::size_t input_points = 0;
std::size_t ground_points = 0;
std::size_t nonground_points = 0;
double tgs_seconds = 0.0;
if (row.available_slot >= 0) {
if (static_cast<std::size_t>(row.available_slot) != expected_slot) {
throw std::runtime_error("full-shadow available slot order changed");
}
auto input_xyzi = loader.cloud(expected_slot);
if (!input_xyzi || input_xyzi->size() != row.point_count) {
throw std::runtime_error("full-shadow input point count changed");
}
auto input = std::make_shared<travel::PointCloud<PointXYZILID>>();
input->reserve(input_xyzi->size());
for (const auto& point : input_xyzi->points) {
PointXYZILID value{};
value.x = point.x;
value.y = point.y;
value.z = point.z;
value.intensity = point.intensity;
value.label = 0;
value.id = 0;
input->emplace_back(value);
}
travel::TravelGroundSeg<PointXYZILID> tgs;
tgs.setParams(
80.0, 1.0, 8.0, 3, 5, 10, 0.5, 0.125, 0.3, 0.940,
200.0, 0.03, 0.1, 1.0, true, false);
travel::PointCloud<PointXYZILID> ground;
travel::PointCloud<PointXYZILID> nonground;
tgs.estimateGround(*input, ground, nonground, tgs_seconds);
input_points = input->size();
ground_points = ground.size();
nonground_points = nonground.size();
const std::string base = output_dir + "/" + std::to_string(row.timeline_frame_index);
writeXYZI(base + "_ground.bin", ground);
writeXYZI(base + "_nonground.bin", nonground);
++expected_slot;
}
const auto completed = Clock::now();
timing << row.timeline_frame_index << '\t' << row.source_frame_index << '\t'
<< row.session_seconds << '\t' << (row.available_slot >= 0 ? 1 : 0) << '\t'
<< row.available_slot << '\t' << input_points << '\t' << ground_points << '\t'
<< nonground_points << '\t' << (tgs_seconds * 1000.0) << '\t'
<< milliseconds(completed - stage_started) << '\t' << queue_delay_ms << '\t'
<< std::max(0.0, milliseconds(completed - target)) << "\t0\n";
if ((row.timeline_frame_index + 1) % 100 == 0) {
timing.flush();
std::cout << "[TGS-FULL] frame=" << (row.timeline_frame_index + 1)
<< "/4489 available=" << expected_slot << "/3928\n";
}
}
timing.flush();
if (expected_slot != 3928) {
throw std::runtime_error("full-shadow available frame accounting changed");
}
std::cout << "[TGS-FULL] complete timeline=4489 available=3928\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "[TGS-FULL] " << error.what() << '\n';
return 2;
}
}
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
readonly INPUT_ROOT=/tgs/inputs
readonly OUTPUT_ROOT=/tgs/outputs/causal_rolling_1s
readonly TIMING_PATH=/tgs/tgs-full-timing.tsv
readonly BINARY=/tmp/run_tgs_full_shadow
test -f "${INPUT_ROOT}/input-manifest.json"
test -f "${INPUT_ROOT}/schedule.tsv"
test ! -e /tgs/outputs
test ! -e "${TIMING_PATH}"
g++ -std=c++17 -O3 -DNDEBUG -pthread \
-I/opt/travel/src/TRAVEL/cpp/travel/core \
-I/usr/include/eigen3 \
/release/run_tgs_full_shadow.cpp \
-o "${BINARY}"
mkdir -p "${OUTPUT_ROOT}"
exec /usr/bin/time -v "${BINARY}" \
"${INPUT_ROOT}/profiles/causal_rolling_1s" \
"${INPUT_ROOT}/schedule.tsv" \
"${OUTPUT_ROOT}" \
"${TIMING_PATH}"