Raccorder les bords de tuiles voisines et encadrer les rendus en cours
Le MNT de chaque dalle s'étend d'une bande de 100 m remplie avec les points sol des 8 tuiles voisines (option « Raccord des bords ») : les rendus à grand noyau (openness, SVF, LRM) deviennent continus d'une dalle à l'autre, les images restent recadrées sur le kilomètre exact. Pendant une génération, la carte encadre les dalles du run : orange pulsant en cours de rendu, rouge en échec — les coins WGS84 sont portés par /api/status pour toutes les dalles non terminées. La file de génération trie les dalles du nord au sud et passe à 10 workers GPU.
This commit is contained in:
@ -525,10 +525,12 @@ class GenerateRequest(BaseModel):
|
||||
"unclassified(1), eau(9), virtuel(66), "
|
||||
"pont(17), sursol(64). Mode pur, "
|
||||
"aucune retouche. (défaut: sol)")
|
||||
bare_earth: bool = Field(False,
|
||||
description="Sol nu : DTM au retour le plus bas de "
|
||||
"chaque cellule (requalifie le point le plus "
|
||||
"bas en terrain)")
|
||||
edge_buffer: bool = Field(False,
|
||||
description="Raccord des bords : étendre le MNT d'une "
|
||||
"bande de 100 m remplie avec les points sol "
|
||||
"des 8 tuiles voisines — rendus continus "
|
||||
"d'une tuile à l'autre (recadrés sur la "
|
||||
"dalle 1 km exacte)")
|
||||
viz: list = Field(None,
|
||||
description="Visualisations à générer, noms d'étapes du "
|
||||
"pipeline (ex: aspect, wavelet, slope) ; "
|
||||
@ -844,6 +846,29 @@ def missing_cells_with_corners(cells, output_dir, include_done=False, viz=None):
|
||||
return todo
|
||||
|
||||
|
||||
def _attach_frame_bounds(tiles):
|
||||
"""Ajoute aux tuiles non terminées leurs coins WGS84 (cadre sur la carte).
|
||||
|
||||
L'interface dessine un cadre coloré autour des dalles du run en cours
|
||||
(orange : rendu en cours, bleu : en attente, rouge : échec) ; les coins
|
||||
viennent de la grille L93 (col, row) → bornes exactes via PROJ. Les
|
||||
dalles terminées n'ont pas de cadre : coins omis pour alléger le sondage
|
||||
/api/status (toutes les ~2 s pendant un run).
|
||||
"""
|
||||
from .index import attach_gps_bounds, parse_basename_coords
|
||||
todo = []
|
||||
for tile in tiles:
|
||||
if tile.get("state") == "done":
|
||||
continue
|
||||
coords = parse_basename_coords(tile.get("name") or "")
|
||||
if not coords:
|
||||
continue
|
||||
tile["col"], tile["row"] = coords
|
||||
todo.append(tile)
|
||||
if todo:
|
||||
attach_gps_bounds(todo)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
index = OUTPUT_DIR / "index.html"
|
||||
@ -880,6 +905,9 @@ def status(request: Request = None):
|
||||
data["regen_allowed"] = regen_allowed
|
||||
return data
|
||||
from .progress import progress_snapshot
|
||||
tiles = progress_snapshot(OUTPUT_DIR, viz_labels=_viz_step_labels(),
|
||||
run_id=_job.get("run_id"))
|
||||
_attach_frame_bounds(tiles)
|
||||
with _job_lock:
|
||||
pending = bool(_queue)
|
||||
if pending:
|
||||
@ -900,9 +928,9 @@ def status(request: Request = None):
|
||||
# L'interface masque les boutons de génération hors réseau local
|
||||
"regen_allowed": regen_allowed,
|
||||
# Tuiles une par une (nom court, type, état) — agrégées depuis les
|
||||
# événements écrits par le pipeline (.generation.events.jsonl)
|
||||
"tiles": progress_snapshot(OUTPUT_DIR, viz_labels=_viz_step_labels(),
|
||||
run_id=_job.get("run_id")),
|
||||
# événements écrits par le pipeline (.generation.events.jsonl) ;
|
||||
# les non terminées portent leurs coins pour le cadre sur la carte
|
||||
"tiles": tiles,
|
||||
}
|
||||
|
||||
|
||||
@ -1213,7 +1241,10 @@ def cell_at_point(lat: float, lng: float):
|
||||
return {"col": col, "row": row, "corners": tile.get("corners") or []}
|
||||
|
||||
|
||||
def _build_command(tiles, regenerate=False, ground_class="ign", bare_earth=False, ign_classes="sol", viz=None, reclassify=False):
|
||||
EDGE_BUFFER_METERS = 100.0 # raccord des bords : largeur de bande (m)
|
||||
|
||||
|
||||
def _build_command(tiles, regenerate=False, ground_class="ign", ign_classes="sol", viz=None, reclassify=False, edge_buffer=False):
|
||||
"""Commande de génération : téléchargement IGN + traitement des fichiers.
|
||||
|
||||
La classification du sol est choisie via `ground_class` (défaut : "ign",
|
||||
@ -1226,8 +1257,10 @@ def _build_command(tiles, regenerate=False, ground_class="ign", bare_earth=False
|
||||
Avec reclassify=True, --force-classification relance la classification
|
||||
même à méthode inchangée (sinon, choisir une méthode différente via
|
||||
ground_class/ign_classes reclassifie déjà les tuiles concernées).
|
||||
Avec bare_earth=True, le DTM est ramené au retour le plus bas de chaque
|
||||
cellule (sol nu). Avec viz, on choisit les visualisations générées
|
||||
Avec edge_buffer=True, le MNT est étendu d'une bande de
|
||||
EDGE_BUFFER_METERS remplie avec les points sol des tuiles voisines
|
||||
(raccord des bords : rendus continus entre tuiles, images recadrées sur
|
||||
la dalle 1 km). Avec viz, on choisit les visualisations générées
|
||||
(noms d'étapes du pipeline, ex. ["aspect", "wavelet", "slope"] ;
|
||||
défaut : aspect).
|
||||
"""
|
||||
@ -1240,14 +1273,14 @@ def _build_command(tiles, regenerate=False, ground_class="ign", bare_earth=False
|
||||
"--only", *(viz or _panel_viz_steps()),
|
||||
"--ground-classification", ground_class,
|
||||
"--ign-classes", ign_classes]
|
||||
if bare_earth:
|
||||
cmd += ["--bare-earth"]
|
||||
if edge_buffer:
|
||||
cmd += ["--edge-buffer", f"{EDGE_BUFFER_METERS:g}"]
|
||||
if regenerate:
|
||||
cmd += ["--force"]
|
||||
if reclassify:
|
||||
cmd += ["--force-classification"]
|
||||
if os.environ.get("LIDAR_GPU", "") == "1":
|
||||
cmd += ["-g", "all", "-w", os.environ.get("LIDAR_WORKERS", "2")]
|
||||
cmd += ["-g", "all", "-w", os.environ.get("LIDAR_WORKERS", "10")]
|
||||
# Carte régénérée après chaque tuile terminée : la webapp l'affiche en
|
||||
# direct via /api/tiles pendant le run.
|
||||
cmd += ["--incremental-index"]
|
||||
@ -1301,6 +1334,10 @@ def _resolve_request(req):
|
||||
raise HTTPException(
|
||||
400, f"méthode de classification invalide : {req.ground_class!r} "
|
||||
f"(attendu : {', '.join(GROUND_CLASS_METHODS)})")
|
||||
# Génération du nord au sud (row décroissante, puis col croissante) : les
|
||||
# workers prennent les tuiles dans l'ordre de soumission et la carte se
|
||||
# remplit de haut en bas pendant le run.
|
||||
tiles.sort(key=lambda cr: (-cr[1], cr[0]))
|
||||
return tiles, viz
|
||||
|
||||
|
||||
@ -1311,8 +1348,9 @@ def _launch_job(tiles, viz, req, qid=None):
|
||||
la demande vient de la file (suivi côté interface).
|
||||
"""
|
||||
cmd = _build_command(tiles, regenerate=req.regenerate, ground_class=req.ground_class,
|
||||
bare_earth=req.bare_earth, ign_classes=req.ign_classes,
|
||||
viz=viz, reclassify=req.reclassify)
|
||||
ign_classes=req.ign_classes,
|
||||
viz=viz, reclassify=req.reclassify,
|
||||
edge_buffer=req.edge_buffer)
|
||||
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
log_fh = open(LOG_FILE, "w", encoding="utf-8")
|
||||
# Nouveau run : journal d'événements remis à zéro (les tuiles affichées
|
||||
|
||||
Reference in New Issue
Block a user