Faire du relief orienté la seule couche produite et affichée
PANEL_VIZ ne contient plus que relief_oriente et pilote désormais tout : le pipeline sans --only ne produit que cette couche, la génération lancée depuis la carte aussi, et la carte ne liste ni ne sert en tuiles (panneau, XYZ, TileJSON, WMTS, JOSM) les autres visualisations présentes sur disque. Les autres visualisations restent calculables explicitement avec --only. Corrige au passage _panel_viz_steps, qui ne retenait que les couches dont le nom de fichier diffère du nom d'étape : la génération depuis la carte ne produisait que l'openness positive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -34,7 +34,8 @@
|
|||||||
- **Default output is AVIF**, not WebP. Use `--format webp` for WebP. Quality default is 60 (visually lossless on smooth color ramps, ~÷3 vs q98).
|
- **Default output is AVIF**, not WebP. Use `--format webp` for WebP. Quality default is 60 (visually lossless on smooth color ramps, ~÷3 vs q98).
|
||||||
- **Calage vertical des faisceaux de vol** : chaque tuile mélange plusieurs passes (1-2 `PointSourceId` par passe) parfois biaisées verticalement de quelques cm (±2,5 cm mesurés sur 1000_6882). `create_dtm_fast` mesure l'offset robuste de chaque faisceau (points sol, maille 1 m, surface médiane itérée 3×) et retranche les offsets ≥ 0,5 cm (`STRIP_ALIGN_THRESHOLD` dans `dtm.py`) avant rastérisation. Offsets calculés **par tuile** (ils dérivent le long d'une ligne de vol : jamais de table globale), mémoïsés par LAS sol, consignés dans `DTM/*_dtm*_stripalign.json` (sidecar de cache : absent, ou version/seuil/paramètres différents ⇒ régénération du DTM). Désactivable : `--no-strip-align`.
|
- **Calage vertical des faisceaux de vol** : chaque tuile mélange plusieurs passes (1-2 `PointSourceId` par passe) parfois biaisées verticalement de quelques cm (±2,5 cm mesurés sur 1000_6882). `create_dtm_fast` mesure l'offset robuste de chaque faisceau (points sol, maille 1 m, surface médiane itérée 3×) et retranche les offsets ≥ 0,5 cm (`STRIP_ALIGN_THRESHOLD` dans `dtm.py`) avant rastérisation. Offsets calculés **par tuile** (ils dérivent le long d'une ligne de vol : jamais de table globale), mémoïsés par LAS sol, consignés dans `DTM/*_dtm*_stripalign.json` (sidecar de cache : absent, ou version/seuil/paramètres différents ⇒ régénération du DTM). Désactivable : `--no-strip-align`.
|
||||||
- **Gigue intra-faisceau (2ᵉ passe du calage)** : les lignes de balayage successives d'une MÊME passe peuvent être décalées verticalement de façon aléatoire (vibration capteur / bruit haute fréquence de trajectoire) — un offset constant par faisceau n'y suffit pas. `_strip_jitter_offsets` découpe chaque faisceau en fenêtres de temps GPS (`STRIP_JITTER_BIN` = 0,1 s, origine de temps propre à chaque faisceau), mesure l'offset robuste de chaque fenêtre contre la surface médiane des AUTRES faisceaux (maille 1 m partagée, ≥ `STRIP_JITTER_MIN_CELLS` = 40 cellules), lisse la série (médiane glissante `STRIP_JITTER_SMOOTH` = 5 fenêtres), la borne à ± `STRIP_JITTER_MAX` (10 cm) puis l'interpole au temps GPS de chaque point (`_apply_strip_jitter`) ; en recouvrement à deux faisceaux, chacun reçoit une série (chacun absorbe sa part). Requiert la dimension `gps_time` (silencieusement ignorée sinon). Sidecar version 2 (séries dans `jitter`), couverte par `--no-strip-align`.
|
- **Gigue intra-faisceau (2ᵉ passe du calage)** : les lignes de balayage successives d'une MÊME passe peuvent être décalées verticalement de façon aléatoire (vibration capteur / bruit haute fréquence de trajectoire) — un offset constant par faisceau n'y suffit pas. `_strip_jitter_offsets` découpe chaque faisceau en fenêtres de temps GPS (`STRIP_JITTER_BIN` = 0,1 s, origine de temps propre à chaque faisceau), mesure l'offset robuste de chaque fenêtre contre la surface médiane des AUTRES faisceaux (maille 1 m partagée, ≥ `STRIP_JITTER_MIN_CELLS` = 40 cellules), lisse la série (médiane glissante `STRIP_JITTER_SMOOTH` = 5 fenêtres), la borne à ± `STRIP_JITTER_MAX` (10 cm) puis l'interpole au temps GPS de chaque point (`_apply_strip_jitter`) ; en recouvrement à deux faisceaux, chacun reçoit une série (chacun absorbe sa part). Requiert la dimension `gps_time` (silencieusement ignorée sinon). Sidecar version 2 (séries dans `jitter`), couverte par `--no-strip-align`.
|
||||||
- **Relief orienté (`relief_oriente`, couche par défaut de la carte)** : image RGB unique (GeoTIFF uint8 3 bandes, rendue telle quelle comme ortho/topo via `RGB_KEYWORDS` dans `rendering.py`) — clarté CIELAB = openness positive **locale** (MNT − gaussienne `RELIEF_DETREND_M` = 10 m, rayons `RELIEF_RADII_M` = 5/10/20 m, 16 directions) 65 % + ombrage 35 % ; teinte = aspect, chroma fixe (`RELIEF_CHROMA`). Échelle log fixe `RELIEF_OPEN_RANGE` (pas de statistique par dalle) et support total 40 m < bande de raccord 100 m : dalles jointives. Rapide : détendance + rayons sur grille décimée ~0,8 m (`RELIEF_GRID_M`), noyau dédié qui n'accumule que la moyenne des angles (`_mean_horizon_*` : CuPy `RawKernel` sur GPU, numba parallèle sur CPU, numpy en repli), colorisation fusionnée (numba) ou vectorisée sans trigonométrie (CuPy) via une table L* × teinte (`_relief_lut`). ~5 s de calcul hors préparation sur CPU 12 cœurs. Tout changement de constante change le rendu : régénérer les dalles (`--only relief_oriente --force`).
|
- **Couches produites et servies = `PANEL_VIZ` (`index.py`, aujourd'hui `('relief_oriente',)`)** : pipeline sans `--only`/`--skip` (`panel_steps()`), génération lancée depuis la carte (`_panel_viz_steps` dans `mapserve.py`) et couches servies (`tiles.available_layers` filtre : panneau, `/tiles/…`, TileJSON, WMTS, JOSM). Les autres visualisations restent calculables avec `--only`. Les tests de la carte lèvent la restriction via `_setup(..., panel=None)`.
|
||||||
|
- **Relief orienté (`relief_oriente`, seule couche de la carte)** : image RGB unique (GeoTIFF uint8 3 bandes, rendue telle quelle comme ortho/topo via `RGB_KEYWORDS` dans `rendering.py`) — clarté CIELAB = openness positive **locale** (MNT − gaussienne `RELIEF_DETREND_M` = 10 m, rayons `RELIEF_RADII_M` = 5/10/20 m, 16 directions) 65 % + ombrage 35 % ; teinte = aspect, chroma fixe (`RELIEF_CHROMA`). Échelle log fixe `RELIEF_OPEN_RANGE` (pas de statistique par dalle) et support total 40 m < bande de raccord 100 m : dalles jointives. Rapide : détendance + rayons sur grille décimée ~0,8 m (`RELIEF_GRID_M`), noyau dédié qui n'accumule que la moyenne des angles (`_mean_horizon_*` : CuPy `RawKernel` sur GPU, numba parallèle sur CPU, numpy en repli), colorisation fusionnée (numba) ou vectorisée sans trigonométrie (CuPy) via une table L* × teinte (`_relief_lut`). ~5 s de calcul hors préparation sur CPU 12 cœurs. Tout changement de constante change le rendu : régénérer les dalles (`--only relief_oriente --force`).
|
||||||
- **Openness sous-échantillonnée** : `generate_openness` calcule le lancé de rayons (l'étape la plus coûteuse : 532 s/tuile à 0,2 m sur CPU) sur une grille décimée par blocs (`OPENNESS_DOWNSAMPLE = 2` : max par bloc en positive, min en négative — préserve les reliefs qui bornent l'horizon) puis rééchantillonne en bilinéaire. Coût ÷ facteur³ : 532 s → 40 s (×13). Signal archéologique préservé (corr. 0,93 après lissage) ; la texture de bruit sub-métrique disparaît. `--openness-downsample 1` = pleine résolution. SVF et openness anisotrope ne sont PAS concernées.
|
- **Openness sous-échantillonnée** : `generate_openness` calcule le lancé de rayons (l'étape la plus coûteuse : 532 s/tuile à 0,2 m sur CPU) sur une grille décimée par blocs (`OPENNESS_DOWNSAMPLE = 2` : max par bloc en positive, min en négative — préserve les reliefs qui bornent l'horizon) puis rééchantillonne en bilinéaire. Coût ÷ facteur³ : 532 s → 40 s (×13). Signal archéologique préservé (corr. 0,93 après lissage) ; la texture de bruit sub-métrique disparaît. `--openness-downsample 1` = pleine résolution. SVF et openness anisotrope ne sont PAS concernées.
|
||||||
- **Raccord des bords entre tuiles** : les rendus à grand noyau (openness/SVF : rayons 100 m ; LRM : 15 m) tronquent leur fenêtre au bord de dalle — bandes d'artefacts à chaque changement de tuile. `--edge-buffer N` (défaut 0 = off ; case « Raccord des bords » de la carte, `EDGE_BUFFER_METERS` = 100 m dans `mapserve.py`) fait rastériser le DTM sur la **dalle nominale 1 km alignée sur la grille** plus une bande de N m remplie avec les points sol des 8 LAZ voisines (`_neighbor_ground_points` dans `dtm.py` : PDAL en flux, découpe + filtre de classes IGN ; voisine absente = téléchargement automatique depuis le catalogue IGN avant le run, **isolée dans `input/edge_neighbors/`** pour ne pas gonfler le corpus des passes globales (dédupliqué sur tout le lot, `_fetch_edge_neighbors` dans `pipeline.py`) ; introuvable ou échec = bande vide). Les visualisations calculent sur l'emprise étendue puis `rendering.py` (`_core_tile_window`, via `tif_to_crop`/`tif_to_png`) recadre les sorties sur la dalle 1 km exacte lue dans le nom LHD — les AVIF restent des carrés 1 km alignés dans la mosaïque. Tampon consigné dans le tag GeoTIFF `LIDAR_EDGE_BUFFER` du DTM : changer `--edge-buffer` invalide le cache DTM automatiquement (tag absent = 0). Bandes voisines non calées par faisceaux (contexte seul, recadrée hors image finale). Coût : ~7 s de lecture par voisine + ~44 % de pixels en plus à 100 m/0,2 m. Nom hors pattern LHD : option ignorée (bornes d'en-tête, pas de recadrage).
|
- **Raccord des bords entre tuiles** : les rendus à grand noyau (openness/SVF : rayons 100 m ; LRM : 15 m) tronquent leur fenêtre au bord de dalle — bandes d'artefacts à chaque changement de tuile. `--edge-buffer N` (défaut 0 = off ; case « Raccord des bords » de la carte, `EDGE_BUFFER_METERS` = 100 m dans `mapserve.py`) fait rastériser le DTM sur la **dalle nominale 1 km alignée sur la grille** plus une bande de N m remplie avec les points sol des 8 LAZ voisines (`_neighbor_ground_points` dans `dtm.py` : PDAL en flux, découpe + filtre de classes IGN ; voisine absente = téléchargement automatique depuis le catalogue IGN avant le run, **isolée dans `input/edge_neighbors/`** pour ne pas gonfler le corpus des passes globales (dédupliqué sur tout le lot, `_fetch_edge_neighbors` dans `pipeline.py`) ; introuvable ou échec = bande vide). Les visualisations calculent sur l'emprise étendue puis `rendering.py` (`_core_tile_window`, via `tif_to_crop`/`tif_to_png`) recadre les sorties sur la dalle 1 km exacte lue dans le nom LHD — les AVIF restent des carrés 1 km alignés dans la mosaïque. Tampon consigné dans le tag GeoTIFF `LIDAR_EDGE_BUFFER` du DTM : changer `--edge-buffer` invalide le cache DTM automatiquement (tag absent = 0). Bandes voisines non calées par faisceaux (contexte seul, recadrée hors image finale). Coût : ~7 s de lecture par voisine + ~44 % de pixels en plus à 100 m/0,2 m. Nom hors pattern LHD : option ignorée (bornes d'en-tête, pas de recadrage).
|
||||||
- **Tests use lazy imports inside each test function**, never at module top, to avoid importing CuPy/GDAL at import time.
|
- **Tests use lazy imports inside each test function**, never at module top, to avoid importing CuPy/GDAL at import time.
|
||||||
|
|||||||
@ -7,6 +7,8 @@ Workflow automatisé pour générer des visualisations exploitables à partir de
|
|||||||
### Relief orienté (couche par défaut)
|
### Relief orienté (couche par défaut)
|
||||||
Une seule image fusionne le micro-relief et l'orientation des pentes : la **clarté** porte le relief local (openness positive sur MNT détendancé, rayons 5–20 m, plus un léger ombrage), la **teinte** porte l'orientation (aspect, cercle CIELAB à clarté constante : aucune couleur ne crée de faux relief). Échelle fixe : les dalles voisines se raccordent sans couture.
|
Une seule image fusionne le micro-relief et l'orientation des pentes : la **clarté** porte le relief local (openness positive sur MNT détendancé, rayons 5–20 m, plus un léger ombrage), la **teinte** porte l'orientation (aspect, cercle CIELAB à clarté constante : aucune couleur ne crée de faux relief). Échelle fixe : les dalles voisines se raccordent sans couture.
|
||||||
|
|
||||||
|
C'est la **seule couche produite et affichée par défaut** (`PANEL_VIZ` dans `index.py`) : un traitement sans `--only`, la génération lancée depuis la carte et la carte elle-même (panneau, tuiles XYZ, TileJSON, WMTS) se limitent au relief orienté. Les visualisations ci-dessous restent calculables explicitement (`--only slope aspect ...`).
|
||||||
|
|
||||||
### Visualisations principales
|
### Visualisations principales
|
||||||
| # | Visualisation | Utilité archéologique |
|
| # | Visualisation | Utilité archéologique |
|
||||||
|---|--------------|----------------------|
|
|---|--------------|----------------------|
|
||||||
|
|||||||
@ -80,6 +80,11 @@ URL pour la couche choisie.
|
|||||||
|
|
||||||
## Pile de couches
|
## Pile de couches
|
||||||
|
|
||||||
|
La carte ne sert que les couches de `PANEL_VIZ` (`index.py`) : aujourd'hui le
|
||||||
|
seul **relief orienté**, qui fusionne openness locale et orientation des pentes
|
||||||
|
en une image. Les autres visualisations présentes sur disque ne sont ni listées
|
||||||
|
ni servies en tuiles.
|
||||||
|
|
||||||
Chaque couche du panneau porte trois réglages, tous persistés et transportés
|
Chaque couche du panneau porte trois réglages, tous persistés et transportés
|
||||||
par le lien de partage :
|
par le lien de partage :
|
||||||
|
|
||||||
|
|||||||
@ -220,15 +220,15 @@ DEFAULT_OPACITY = {}
|
|||||||
DEFAULT_BLEND = {'hillshade_multi': 'multiply', 'solar': 'multiply', 'svf': 'multiply'}
|
DEFAULT_BLEND = {'hillshade_multi': 'multiply', 'solar': 'multiply', 'svf': 'multiply'}
|
||||||
|
|
||||||
# Couche « principale » (mise en avant ; index_tiles.json + affichage en
|
# Couche « principale » (mise en avant ; index_tiles.json + affichage en
|
||||||
# direct de la webapp) : le relief archéologique, pas l'aspect.
|
# direct de la webapp) : le relief orienté, qui fusionne openness et aspect.
|
||||||
DEFAULT_VIZ = 'positive_openness'
|
DEFAULT_VIZ = 'relief_oriente'
|
||||||
|
|
||||||
# Restriction du panneau de couches : seule la sélection ci-dessous est
|
# Couches produites et affichées : seule cette sélection est générée par
|
||||||
# proposée (couches conservées ; les autres ne sont plus ni affichées ni
|
# défaut (pipeline sans --only, génération depuis la carte) et servie par la
|
||||||
# référencées). None = toutes les visualisations présentes sur disque.
|
# carte (panneau, tuiles XYZ, TileJSON, WMTS). Les autres visualisations
|
||||||
# Le sélecteur de génération/régénération est piloté par le même registre
|
# restent calculables avec --only mais ne sont plus proposées.
|
||||||
# La pente y reste proposée mais éteinte par défaut.
|
# None = toutes les visualisations présentes sur disque.
|
||||||
PANEL_VIZ = ('relief_oriente', 'slope', 'aspect', 'positive_openness')
|
PANEL_VIZ = ('relief_oriente',)
|
||||||
|
|
||||||
# Correspondance mot-clé de fichier de sortie → nom d'étape --only du pipeline
|
# Correspondance mot-clé de fichier de sortie → nom d'étape --only du pipeline
|
||||||
# (les trois visualisations dont le nom de sortie diffère du nom d'étape,
|
# (les trois visualisations dont le nom de sortie diffère du nom d'étape,
|
||||||
@ -243,6 +243,13 @@ KEYWORD_TO_STEP = {
|
|||||||
STEP_TO_KEYWORD = {step: kw for kw, step in KEYWORD_TO_STEP.items()}
|
STEP_TO_KEYWORD = {step: kw for kw, step in KEYWORD_TO_STEP.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def panel_steps():
|
||||||
|
"""Noms d'étapes --only des couches produites par défaut (PANEL_VIZ)."""
|
||||||
|
if PANEL_VIZ is None:
|
||||||
|
return None
|
||||||
|
return [KEYWORD_TO_STEP.get(k, k) for k in PANEL_VIZ]
|
||||||
|
|
||||||
|
|
||||||
def step_to_keyword(step):
|
def step_to_keyword(step):
|
||||||
"""Nom d'étape du pipeline (ex: 'pos_open') → mot-clé de fichier ('positive_openness')."""
|
"""Nom d'étape du pipeline (ex: 'pos_open') → mot-clé de fichier ('positive_openness')."""
|
||||||
return STEP_TO_KEYWORD.get(step, step)
|
return STEP_TO_KEYWORD.get(step, step)
|
||||||
|
|||||||
@ -1084,9 +1084,8 @@ def _viz_step_names():
|
|||||||
|
|
||||||
def _panel_viz_steps():
|
def _panel_viz_steps():
|
||||||
"""Couches réellement affichées par la carte, en noms d'étapes --only."""
|
"""Couches réellement affichées par la carte, en noms d'étapes --only."""
|
||||||
from .index import PANEL_VIZ, KEYWORD_TO_STEP
|
from .index import panel_steps
|
||||||
keys = [k for k in PANEL_VIZ if k in KEYWORD_TO_STEP]
|
return panel_steps() or _viz_step_names()
|
||||||
return [KEYWORD_TO_STEP[k] for k in keys] or _viz_step_names()
|
|
||||||
|
|
||||||
|
|
||||||
def _viz_step_labels():
|
def _viz_step_labels():
|
||||||
|
|||||||
@ -187,7 +187,11 @@ class LidarArchaeoPipeline:
|
|||||||
raise ValueError(f"Visualisations inconnues: {', '.join(invalid)}. Disponibles: {', '.join(all_viz_names)}")
|
raise ValueError(f"Visualisations inconnues: {', '.join(invalid)}. Disponibles: {', '.join(all_viz_names)}")
|
||||||
self.viz_steps = [(n, f) for n, f in VIZ_STEPS if n not in skip_viz]
|
self.viz_steps = [(n, f) for n, f in VIZ_STEPS if n not in skip_viz]
|
||||||
else:
|
else:
|
||||||
self.viz_steps = VIZ_STEPS
|
# Sans --only/--skip : seules les couches affichées par la carte
|
||||||
|
# (PANEL_VIZ, index.py) sont produites.
|
||||||
|
from .index import panel_steps
|
||||||
|
default = panel_steps()
|
||||||
|
self.viz_steps = [(n, f) for n, f in VIZ_STEPS if default is None or n in default]
|
||||||
|
|
||||||
logger.info("Pipeline initialisé")
|
logger.info("Pipeline initialisé")
|
||||||
logger.info(f" Entrée : {self.input_dir}")
|
logger.info(f" Entrée : {self.input_dir}")
|
||||||
|
|||||||
@ -18,9 +18,12 @@ class _FakeRequest:
|
|||||||
"port": 8975})()
|
"port": 8975})()
|
||||||
|
|
||||||
|
|
||||||
def _setup(tmp_path, monkeypatch, layers=("aspect", "slope")):
|
def _setup(tmp_path, monkeypatch, layers=("aspect", "slope"), panel=None):
|
||||||
|
"""Dalle factice ; panel=None lève la restriction PANEL_VIZ pour que les
|
||||||
|
couches de test (aspect, slope) soient servies."""
|
||||||
import lidar_pipeline.mapserve as mapserve
|
import lidar_pipeline.mapserve as mapserve
|
||||||
from lidar_pipeline import tiles
|
from lidar_pipeline import index, tiles
|
||||||
|
monkeypatch.setattr(index, "PANEL_VIZ", panel)
|
||||||
_make_dalle(tmp_path, 1054, 6882, list(layers))
|
_make_dalle(tmp_path, 1054, 6882, list(layers))
|
||||||
monkeypatch.setattr(mapserve, "OUTPUT_DIR", tmp_path)
|
monkeypatch.setattr(mapserve, "OUTPUT_DIR", tmp_path)
|
||||||
tiles.source_index(tmp_path, force=True)
|
tiles.source_index(tmp_path, force=True)
|
||||||
@ -209,6 +212,19 @@ def test_map_meta(tmp_path, monkeypatch):
|
|||||||
assert meta["default_layers"] == ["relief_oriente"]
|
assert meta["default_layers"] == ["relief_oriente"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_panel_layers_are_served(tmp_path, monkeypatch):
|
||||||
|
"""Seules les couches de PANEL_VIZ sont affichées et servies ; la
|
||||||
|
génération depuis la carte ne produit qu'elles."""
|
||||||
|
from fastapi import HTTPException
|
||||||
|
mapserve = _setup(tmp_path, monkeypatch, layers=("relief_oriente", "aspect", "slope"),
|
||||||
|
panel=("relief_oriente",))
|
||||||
|
meta = mapserve.map_meta()
|
||||||
|
assert [l["key"] for l in meta["layers"]] == ["relief_oriente"]
|
||||||
|
with pytest.raises(HTTPException):
|
||||||
|
asyncio.run(mapserve.tile("aspect", 15, 1, "1.png"))
|
||||||
|
assert mapserve._panel_viz_steps() == ["relief_oriente"]
|
||||||
|
|
||||||
|
|
||||||
def test_map_tile_info(tmp_path, monkeypatch):
|
def test_map_tile_info(tmp_path, monkeypatch):
|
||||||
"""/api/map/tile identifie la dalle sous un point et lit ses métadonnées."""
|
"""/api/map/tile identifie la dalle sous un point et lit ses métadonnées."""
|
||||||
mapserve = _setup(tmp_path, monkeypatch)
|
mapserve = _setup(tmp_path, monkeypatch)
|
||||||
@ -542,11 +558,8 @@ def _reset_bg(mapserve):
|
|||||||
|
|
||||||
def test_tile_cache_only_never_renders(tmp_path, monkeypatch):
|
def test_tile_cache_only_never_renders(tmp_path, monkeypatch):
|
||||||
"""Mode cache seule : tuile manquante = transparente non mémorisable."""
|
"""Mode cache seule : tuile manquante = transparente non mémorisable."""
|
||||||
import lidar_pipeline.mapserve as mapserve
|
mapserve = _setup(tmp_path, monkeypatch, layers=("slope",))
|
||||||
from lidar_pipeline import tiles
|
from lidar_pipeline import tiles
|
||||||
_make_dalle(tmp_path, 1054, 6882, ["slope"])
|
|
||||||
monkeypatch.setattr(mapserve, "OUTPUT_DIR", tmp_path)
|
|
||||||
tiles.source_index(tmp_path, force=True)
|
|
||||||
monkeypatch.setenv("LIDAR_TILE_CACHE_ONLY", "1")
|
monkeypatch.setenv("LIDAR_TILE_CACHE_ONLY", "1")
|
||||||
z = 15
|
z = 15
|
||||||
x, y = _tile_of_cell(1054, 6882, z)
|
x, y = _tile_of_cell(1054, 6882, z)
|
||||||
|
|||||||
@ -24,6 +24,15 @@ class TestVizSteps:
|
|||||||
from lidar_pipeline.pipeline import VIZ_STEPS
|
from lidar_pipeline.pipeline import VIZ_STEPS
|
||||||
assert len(VIZ_STEPS) == 16
|
assert len(VIZ_STEPS) == 16
|
||||||
|
|
||||||
|
def test_default_run_produces_only_relief(self, tmp_path):
|
||||||
|
"""Sans --only : seule la couche affichée (relief orienté) est produite ;
|
||||||
|
--only reste libre pour les autres visualisations."""
|
||||||
|
from lidar_pipeline.pipeline import LidarArchaeoPipeline
|
||||||
|
p = LidarArchaeoPipeline(tmp_path, tmp_path / "out")
|
||||||
|
assert [n for n, _ in p.viz_steps] == ["relief_oriente"]
|
||||||
|
p = LidarArchaeoPipeline(tmp_path, tmp_path / "out2", only_viz=["slope"])
|
||||||
|
assert [n for n, _ in p.viz_steps] == ["slope"]
|
||||||
|
|
||||||
def test_ortho_and_topo_present(self):
|
def test_ortho_and_topo_present(self):
|
||||||
from lidar_pipeline.pipeline import VIZ_STEPS
|
from lidar_pipeline.pipeline import VIZ_STEPS
|
||||||
names = [name for name, _ in VIZ_STEPS]
|
names = [name for name, _ in VIZ_STEPS]
|
||||||
|
|||||||
@ -119,9 +119,10 @@ def test_zoom_supported_bounds():
|
|||||||
# Index des sources
|
# Index des sources
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def test_source_index_and_layers(tmp_path):
|
def test_source_index_and_layers(tmp_path, monkeypatch):
|
||||||
"""L'index liste les couches présentes et leurs paliers (grossier → fin)."""
|
"""L'index liste les couches présentes et leurs paliers (grossier → fin)."""
|
||||||
from lidar_pipeline import tiles
|
from lidar_pipeline import index, tiles
|
||||||
|
monkeypatch.setattr(index, "PANEL_VIZ", None)
|
||||||
_make_dalle(tmp_path, 1054, 6882, ["aspect", "slope"])
|
_make_dalle(tmp_path, 1054, 6882, ["aspect", "slope"])
|
||||||
layers = tiles.source_index(tmp_path, force=True)
|
layers = tiles.source_index(tmp_path, force=True)
|
||||||
assert set(layers) == {"aspect", "slope"}
|
assert set(layers) == {"aspect", "slope"}
|
||||||
@ -132,6 +133,15 @@ def test_source_index_and_layers(tmp_path):
|
|||||||
set(tiles.available_layers(tmp_path)) == {"slope", "aspect"}
|
set(tiles.available_layers(tmp_path)) == {"slope", "aspect"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_available_layers_follow_panel(tmp_path, monkeypatch):
|
||||||
|
"""Couches sur disque hors PANEL_VIZ : ni affichées ni servies."""
|
||||||
|
from lidar_pipeline import index, tiles
|
||||||
|
_make_dalle(tmp_path, 1054, 6882, ["aspect", "relief_oriente"])
|
||||||
|
tiles.source_index(tmp_path, force=True)
|
||||||
|
monkeypatch.setattr(index, "PANEL_VIZ", ("relief_oriente",))
|
||||||
|
assert tiles.available_layers(tmp_path) == ["relief_oriente"]
|
||||||
|
|
||||||
|
|
||||||
def test_grid_bounds(tmp_path):
|
def test_grid_bounds(tmp_path):
|
||||||
"""Emprise L93 et WGS84 de la grille disponible."""
|
"""Emprise L93 et WGS84 de la grille disponible."""
|
||||||
from lidar_pipeline import tiles
|
from lidar_pipeline import tiles
|
||||||
|
|||||||
@ -501,9 +501,13 @@ def source_index(output_dir, force=False):
|
|||||||
|
|
||||||
|
|
||||||
def available_layers(output_dir):
|
def available_layers(output_dir):
|
||||||
"""Couches présentes sur disque, ordonnées comme le panneau de la carte."""
|
"""Couches présentes sur disque et affichées (PANEL_VIZ), ordonnées
|
||||||
|
comme le panneau de la carte."""
|
||||||
|
from . import index as index_mod
|
||||||
from .index import _VIZ_FALLBACK_ORDER
|
from .index import _VIZ_FALLBACK_ORDER
|
||||||
found = set(source_index(output_dir))
|
found = set(source_index(output_dir))
|
||||||
|
if index_mod.PANEL_VIZ is not None:
|
||||||
|
found &= set(index_mod.PANEL_VIZ)
|
||||||
ordered = [v for v in _VIZ_FALLBACK_ORDER if v in found]
|
ordered = [v for v in _VIZ_FALLBACK_ORDER if v in found]
|
||||||
ordered += sorted(found - set(ordered))
|
ordered += sorted(found - set(ordered))
|
||||||
return ordered
|
return ordered
|
||||||
|
|||||||
Reference in New Issue
Block a user