Add visualization picker to zone generation and unify tile colors
- Web map: multi-select picker in the generation bar (aspect, slope, positive openness, anisotropic openness, wavelet) passed to the API; the layer panel is restricted to the same shortlist (PANEL_VIZ) and a refresh button rebuilds the index when new layers appear on disk; jobs started outside the UI are now adopted into the visible queue - Uniform colors across tiles: openness/anisotropic/sailore now store local z-scores, and all renderers use fixed ranges (0-3 sigma, SVF 0-1 physical, slope 0-30 deg) instead of per-tile percentile stretches - Ray-tracing falls back to CPU when VRAM is exhausted so openness and SVF no longer fail silently on shared GPUs - build_index merges visualizations available at only one resolution into the displayed tile so in-progress layers stay visible - 11 new tests (142 passing)
This commit is contained in:
@ -79,3 +79,97 @@ def test_build_command_ground_classification():
|
||||
assert cmd[i + 1] == method
|
||||
assert ("--force" in cmd) == regenerate
|
||||
assert ("--force-classification" in cmd) == regenerate
|
||||
|
||||
|
||||
def test_build_command_default_viz_aspect():
|
||||
"""Sans choix de visualisation, la commande génère uniquement aspect."""
|
||||
from lidar_pipeline.webapp import _build_command
|
||||
cmd = _build_command([(1054, 6882)])
|
||||
i = cmd.index("--only")
|
||||
assert cmd[i + 1] == "aspect"
|
||||
|
||||
|
||||
def test_build_command_custom_viz():
|
||||
"""Les visualisations demandées sont passées à --only dans l'ordre."""
|
||||
from lidar_pipeline.webapp import _build_command
|
||||
cmd = _build_command([(1054, 6882)], viz=["aspect", "wavelet", "aniso_open"])
|
||||
i = cmd.index("--only")
|
||||
assert cmd[i + 1:i + 4] == ["aspect", "wavelet", "aniso_open"]
|
||||
|
||||
|
||||
def test_viz_step_names_match_pipeline():
|
||||
"""Les noms acceptés par l'API sont exactement les étapes VIZ_STEPS du pipeline."""
|
||||
from lidar_pipeline.webapp import _viz_step_names
|
||||
from lidar_pipeline.pipeline import VIZ_STEPS
|
||||
assert _viz_step_names() == [n for n, _ in VIZ_STEPS]
|
||||
|
||||
|
||||
def test_generate_rejects_unknown_viz():
|
||||
"""L'API refuse un nom de visualisation inconnu (HTTPException 400)."""
|
||||
from fastapi import HTTPException
|
||||
from lidar_pipeline.webapp import generate, GenerateRequest
|
||||
req = GenerateRequest(tiles=[[1054, 6882]], viz=["wavelet", "inconnu"])
|
||||
try:
|
||||
generate(req)
|
||||
assert False, "une HTTPException était attendue"
|
||||
except HTTPException as e:
|
||||
assert e.status_code == 400
|
||||
assert "inconnu" in e.detail
|
||||
|
||||
|
||||
def test_generate_accepts_wavelet_and_aniso():
|
||||
"""L'API accepte wavelet + aniso_open et les transmet à --only."""
|
||||
import subprocess
|
||||
import lidar_pipeline.webapp as webapp
|
||||
captured = {}
|
||||
|
||||
def fake_popen(cmd, **kwargs):
|
||||
captured["cmd"] = cmd
|
||||
|
||||
class _P:
|
||||
def wait(self):
|
||||
return 0
|
||||
|
||||
def poll(self):
|
||||
return 0
|
||||
return _P()
|
||||
|
||||
webapp.subprocess.Popen = fake_popen
|
||||
try:
|
||||
req = webapp.GenerateRequest(tiles=[[1054, 6882]],
|
||||
viz=["wavelet", "aniso_open"])
|
||||
result = webapp.generate(req)
|
||||
assert result["demarré"] is True
|
||||
i = captured["cmd"].index("--only")
|
||||
assert captured["cmd"][i + 1:i + 3] == ["wavelet", "aniso_open"]
|
||||
finally:
|
||||
webapp.subprocess.Popen = subprocess.Popen
|
||||
|
||||
|
||||
def test_available_layers_scans_disk(tmp_path, monkeypatch):
|
||||
"""/api/layers retourne les couches présentes (clé → label)."""
|
||||
import lidar_pipeline.webapp as webapp
|
||||
monkeypatch.setattr(webapp, "OUTPUT_DIR", tmp_path)
|
||||
vis = tmp_path / "visualisations" / "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
|
||||
vis.mkdir(parents=True)
|
||||
(vis / "LHD_FXX_1054_6882_PTS_LAMB93_IGN69_wavelet.avif").write_bytes(b"x")
|
||||
layers = webapp.available_layers()
|
||||
assert layers.get("wavelet")
|
||||
assert "aspect" not in layers
|
||||
|
||||
|
||||
def test_rebuild_index_background(tmp_path, monkeypatch):
|
||||
"""POST /api/rebuild lance build_index en arrière-plan puis termine."""
|
||||
import time as _time
|
||||
import lidar_pipeline.webapp as webapp
|
||||
import lidar_pipeline.index as index_mod
|
||||
calls = []
|
||||
monkeypatch.setattr(index_mod, "build_index", lambda out: calls.append(out))
|
||||
assert webapp.rebuild_index()["demarré"] is True
|
||||
for _ in range(100):
|
||||
if not webapp.rebuild_status()["running"]:
|
||||
break
|
||||
_time.sleep(0.05)
|
||||
assert calls # build_index a bien été appelé
|
||||
assert webapp.rebuild_status()["running"] is False
|
||||
assert webapp.rebuild_status()["done"] is not None
|
||||
|
||||
Reference in New Issue
Block a user