Diffuser la qualité des dalles dans l'inventaire et la conserver sur la carte légère

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 15:29:06 +02:00
parent 473097257e
commit 3b4d106cc2
4 changed files with 58 additions and 0 deletions

View File

@ -1220,11 +1220,15 @@ def build_index(output_dir, output_format='avif'):
for k in _VIZ_FALLBACK_ORDER if k in all_viz_keys} for k in _VIZ_FALLBACK_ORDER if k in all_viz_keys}
for k in sorted(all_viz_keys): for k in sorted(all_viz_keys):
viz_meta.setdefault(k, {'label': VIZ_LABELS.get(k, k)}) viz_meta.setdefault(k, {'label': VIZ_LABELS.get(k, k)})
from .quality import load_quality_table
inventory_path = output_dir / 'index_tiles.json' inventory_path = output_dir / 'index_tiles.json'
inventory_path.write_text(json.dumps({ inventory_path.write_text(json.dumps({
'tiles': display_tiles, 'tiles': display_tiles,
'viz_meta': viz_meta, 'viz_meta': viz_meta,
'stats': {'n_tiles': len(display_tiles)}, 'stats': {'n_tiles': len(display_tiles)},
# Qualité par dalle (encart de l'export PDF) : recopiée sur les
# machines légères pour rester disponible amont éteint.
'quality': load_quality_table(output_dir),
}, ensure_ascii=False), encoding='utf-8') }, ensure_ascii=False), encoding='utf-8')
logger.info(f"Inventaire généré : {inventory_path} " logger.info(f"Inventaire généré : {inventory_path} "

View File

@ -653,3 +653,15 @@ def test_urls_versioned_by_served_file_mtime(tmp_path):
url_after = tiles05b[0]['viz']['hillshade_multi']['thumb'] url_after = tiles05b[0]['viz']['hillshade_multi']['thumb']
assert url_after.split('?')[0] == url_before.split('?')[0] assert url_after.split('?')[0] == url_before.split('?')[0]
assert url_after != url_before assert url_after != url_before
def test_inventory_contains_quality_table(tmp_path):
import json
from lidar_pipeline.index import build_index
from lidar_pipeline.quality import write_quality
from lidar_pipeline.tests.test_tiles import _make_dalle
base = _make_dalle(tmp_path, 1054, 6882, ["relief_oriente"])
write_quality(tmp_path, base, {"version": 1, "ground_density": 7.5})
build_index(tmp_path, "webp")
inv = json.loads((tmp_path / "index_tiles.json").read_text(encoding="utf-8"))
assert inv["quality"] == {base: {"version": 1, "ground_density": 7.5}}

View File

@ -712,3 +712,20 @@ def test_webp_subtiles_indexed_and_rendered_nearest(tmp_path):
assert img is not None assert img is not None
grays = {p[0] for p in img.getdata() if p[3] == 255} grays = {p[0] for p in img.getdata() if p[3] == 255}
assert grays <= {0, 255} # aucun gris intermédiaire inventé assert grays <= {0, 255} # aucun gris intermédiaire inventé
def test_remote_quality_persisted_locally(tmp_path, monkeypatch):
"""La table qualité amont est recopiée en sidecars locaux (Pi autonome)."""
from lidar_pipeline import tiles
from lidar_pipeline.quality import read_quality
payload = _remote_payload()
base = "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
payload["quality"] = {base: {"version": 1, "ground_density": 3.0},
"../evasion": {"version": 1}}
monkeypatch.setattr(tiles, "REMOTE_SOURCE_URL", "http://amont")
monkeypatch.setattr(tiles, "_remote_payload", lambda force=False: payload)
monkeypatch.setattr(tiles, "_remote_cache", dict(tiles._remote_cache, index=None, built=None))
tiles._remote_index(tmp_path, force=True)
assert read_quality(tmp_path, base) == {"version": 1, "ground_density": 3.0}
assert not (tmp_path / "evasion.json").exists()
assert list((tmp_path / "quality").iterdir()) == [tmp_path / "quality" / f"{base}.json"]

View File

@ -305,6 +305,30 @@ def _remote_payload(force=False):
return payload return payload
_SAFE_BASENAME = re.compile(r"^[A-Za-z0-9_.-]+$")
def _persist_remote_quality(output_dir, payload):
"""Recopie la table qualité amont en sidecars locaux.
L'encart qualité de l'export PDF lit toujours le disque local : la
carte autonome (amont éteint) garde ainsi les dernières valeurs connues.
Returns: nombre de sidecars (ré)écrits.
"""
from .quality import QUALITY_VERSION, write_quality
written = 0
for base, data in (payload.get("quality") or {}).items():
if (not isinstance(base, str) or not _SAFE_BASENAME.match(base)
or base.startswith(".") or not isinstance(data, dict)
or data.get("version") != QUALITY_VERSION):
continue
try:
written += bool(write_quality(output_dir, base, data))
except OSError as e:
logger.debug(f"Sidecar qualité non recopié ({base}) : {e}")
return written
def _remote_index(output_dir, force=False): def _remote_index(output_dir, force=False):
"""Inventaire construit depuis l'amont : sources non encore rapatriées. """Inventaire construit depuis l'amont : sources non encore rapatriées.
@ -320,6 +344,7 @@ def _remote_index(output_dir, force=False):
and _remote_cache.get("built") is payload): and _remote_cache.get("built") is payload):
return _remote_cache["index"] return _remote_cache["index"]
output_dir = Path(output_dir) output_dir = Path(output_dir)
_persist_remote_quality(output_dir, payload)
layers = {} layers = {}
for entry in payload["tiles"]: for entry in payload["tiles"]:
col, row = entry.get("col"), entry.get("row") col, row = entry.get("col"), entry.get("row")