feat: finalize corrected-route planning and Rerun recording review

This commit is contained in:
DCCONSTRUCTIONS
2026-09-22 10:10:03 +03:00
parent c804d89b18
commit 2e5d52521f
132 changed files with 14141 additions and 898 deletions
+211
View File
@@ -0,0 +1,211 @@
"""Content-addressed, paired display derivatives, never planning references.
Only an offline publisher may attach an explicitly reviewed version to a source
overview. HTTP callers select a digest, not filesystem paths or a mutable latest
map. Both representations preserve point/pose correspondence and share colors.
"""
from __future__ import annotations
import hashlib
import io
import json
import os
import re
from dataclasses import dataclass
from pathlib import Path
from tempfile import TemporaryDirectory
from uuid import uuid4
import numpy as np
from k1link.reconstruction.map_version import json_bytes
SCHEMA = "missioncore.overview-comparison/v1"
MAX_POINTS = 180_000
MAX_POSES = 20_000
MAX_BYTES = 8 * 1024 * 1024
def _digest(value):
if not isinstance(value, str) or not re.fullmatch(r"[a-f0-9]{64}", value):
raise ValueError("Invalid comparison identity.")
return value
def _read(path, maximum):
if path.is_symlink() or path.stat().st_size > maximum:
raise ValueError("Invalid comparison artifact.")
with path.open("rb") as stream:
value = stream.read(maximum + 1)
if len(value) > maximum:
raise ValueError("Comparison exceeds display budget.")
return value
@dataclass(frozen=True)
class OverviewComparison:
generation: str
document: dict
original: np.ndarray
corrected: np.ndarray
original_route: np.ndarray
corrected_route: np.ndarray
@property
def bounds(self):
# Only bounds are combined; there is no correspondence or fitting here.
return np.array(
[
self.original.min(axis=0),
self.original.max(axis=0),
self.corrected.min(axis=0),
self.corrected.max(axis=0),
]
)
def metadata(self):
return dict(
generation=self.generation,
map_generation=self.document["map_generation"],
sample_points=len(self.original),
source_points=self.document["source_points"],
height_min_m=min(-3.0, float(self.bounds[:, 2].min())),
height_max_m=max(80.0, float(self.bounds[:, 2].max())),
original_path_m=self.document["original_path_m"],
corrected_path_m=self.document["corrected_path_m"],
)
def _arrays(payload):
# np.savez (uncompressed) is deliberate: reject compressed archive bombs.
import zipfile
with zipfile.ZipFile(io.BytesIO(payload)) as archive:
if len(archive.infolist()) != 4 or any(
x.compress_type != zipfile.ZIP_STORED for x in archive.infolist()
):
raise ValueError("Invalid comparison array archive.")
with np.load(io.BytesIO(payload), allow_pickle=False) as data:
result = [
np.array(data[k])
for k in ("original", "corrected", "original_route", "corrected_route")
]
a, b, p, q = result
if (
a.shape != b.shape
or p.shape != q.shape
or not 1 <= len(a) <= MAX_POINTS
or not 2 <= len(p) <= MAX_POSES
or any(
x.ndim != 2
or x.shape[1:] != (3,)
or x.dtype != np.dtype("<f4")
or not np.isfinite(x).all()
for x in result
)
):
raise ValueError("Invalid paired comparison geometry.")
return result
def load_comparison(
root: Path,
overview_generation: str,
session_id: str,
source_digests: dict,
generation: str | None = None,
):
root = Path(root)
_digest(overview_generation)
pointer = root / "selected" / f"{overview_generation}.json"
if generation is None:
if not pointer.exists():
return None
generation = json.loads(_read(pointer, 4096))["generation"]
_digest(generation)
directory = root / generation
if directory.is_symlink():
raise ValueError("Comparison directory must not be a link.")
payload = _read(directory / "manifest.json", 32_768)
if hashlib.sha256(payload).hexdigest() != generation:
raise ValueError("Comparison manifest changed.")
doc = json.loads(payload)
if (
doc.get("schema_version") != SCHEMA
or doc.get("session_id") != session_id
or doc.get("overview_generation") != overview_generation
or doc.get("source_digests") != source_digests
or doc.get("view_only") is not True
):
raise ValueError("Comparison belongs to another recording generation.")
_digest(doc["map_generation"])
arrays = _read(directory / "geometry.npz", MAX_BYTES)
if hashlib.sha256(arrays).hexdigest() != doc["geometry_sha256"]:
raise ValueError("Comparison geometry changed.")
return OverviewComparison(generation, doc, *_arrays(arrays))
def publish_comparison(
root,
*,
session_id,
overview_generation,
source_digests,
map_generation,
source_points,
original_path_m,
corrected_path_m,
original,
corrected,
original_route,
corrected_route,
):
"""Publish a view-only pair; no catalog or mission mutation, no implicit promotion."""
root = Path(root)
root.mkdir(parents=True, exist_ok=True)
_digest(overview_generation)
_digest(map_generation)
with TemporaryDirectory(prefix=".comparison-", dir=root) as temporary:
stage = Path(temporary)
np.savez(
stage / "geometry.npz",
**{
name: np.asarray(value, dtype="<f4")
for name, value in (
("original", original),
("corrected", corrected),
("original_route", original_route),
("corrected_route", corrected_route),
)
},
)
payload = _read(stage / "geometry.npz", MAX_BYTES)
_arrays(payload)
doc = dict(
schema_version=SCHEMA,
session_id=session_id,
overview_generation=overview_generation,
source_digests=source_digests,
map_generation=map_generation,
source_points=source_points,
original_path_m=original_path_m,
corrected_path_m=corrected_path_m,
view_only=True,
geometry_sha256=hashlib.sha256(payload).hexdigest(),
)
manifest = json_bytes(doc)
generation = hashlib.sha256(manifest).hexdigest()
(stage / "manifest.json").write_bytes(manifest)
if not (root / generation).exists():
stage.rename(root / generation)
load_comparison(root, overview_generation, session_id, source_digests, generation)
pointers = root / "selected"
pointers.mkdir(exist_ok=True)
temporary = pointers / f".{uuid4().hex}.json"
try:
temporary.write_bytes(json_bytes(dict(generation=generation)))
os.replace(temporary, pointers / f"{overview_generation}.json")
finally:
temporary.unlink(missing_ok=True)
return generation