Calculer le sidecar qualité après le DTM et ajouter --quality-backfill

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 15:17:32 +02:00
parent 1db98c733d
commit 7d5b5ecd42
4 changed files with 200 additions and 0 deletions

View File

@ -148,3 +148,94 @@ def load_quality_table(output_dir):
if data is not None:
table[f.stem] = data
return table
_LAZ_EXTS = (".copc.laz", ".copc.las", ".laz", ".las")
def _laz_basename(path):
"""Nom de dalle sans extension LiDAR (.copc.laz compris)."""
name = Path(path).name
for ext in _LAZ_EXTS:
if name.lower().endswith(ext):
return name[:-len(ext)]
return Path(path).stem
def _cell_bounds_of(basename):
"""Emprise nominale 1 km d'une dalle LHD, ou None hors motif LHD."""
from .index import parse_basename_coords
coords = parse_basename_coords(basename)
if coords is None:
return None
col, row = coords
return (col * 1000.0, (row - 1) * 1000.0, (col + 1) * 1000.0, row * 1000.0)
def quality_from_las(las_path, bounds, codes=None):
"""Qualité mesurée depuis un LAS/LAZ ; None en cas d'échec (jamais d'exception).
codes : classes à retenir (retours ≥ 1) ; None = fichier déjà filtré sol.
"""
try:
import laspy
import numpy as np
las = laspy.read(str(las_path))
keep = np.ones(len(las.points), dtype=bool)
if codes is not None:
keep = ((np.asarray(las.return_number) >= 1)
& (np.asarray(las.number_of_returns) >= 1)
& np.isin(np.asarray(las.classification), np.asarray(sorted(codes))))
try:
gps = np.asarray(las.gps_time, dtype=np.float64)[keep]
except AttributeError:
gps = None
adjusted = las.header.global_encoding.gps_time_type == laspy.header.GpsTimeType.STANDARD
created = las.header.creation_date
return compute_quality(np.asarray(las.x)[keep], np.asarray(las.y)[keep], gps,
bounds, gps_adjusted=adjusted,
header_date=created.isoformat() if created else None)
except Exception as e: # noqa: BLE001 — qualité best-effort
logger.warning(f" Mesure de qualité impossible ({Path(las_path).name}) : {e}")
return None
def ensure_quality(las_path, basename, output_dir, codes=None):
"""Garantit le sidecar qualité d'une dalle (calcul si absent ou périmé).
Returns:
True si un sidecar valide existe à la sortie.
"""
if read_quality(output_dir, basename) is not None:
return True
bounds = _cell_bounds_of(basename)
if bounds is None:
return False
data = quality_from_las(las_path, bounds, codes)
if data is None:
return False
try:
write_quality(output_dir, basename, data)
except OSError as e:
logger.warning(f" Écriture du sidecar qualité impossible : {e}")
return False
return True
def backfill_quality(input_dir, output_dir, codes=(2,)):
"""Rattrapage : sidecar qualité de chaque LAZ LHD d'input_dir qui n'en a pas.
Returns:
Nombre de sidecars écrits.
"""
files = sorted(p for p in Path(input_dir).iterdir()
if p.is_file() and p.name.lower().endswith(_LAZ_EXTS))
written = 0
for i, laz in enumerate(files, 1):
base = _laz_basename(laz)
if read_quality(output_dir, base) is not None or _cell_bounds_of(base) is None:
continue
logger.info(f"Qualité [{i}/{len(files)}] {base}")
if ensure_quality(laz, base, output_dir, codes):
written += 1
return written