Remove RRIM and Multi-Hillshade RGB, fix DTM resolution reuse bug, add --init to docker run

- Remove generate_rrim, generate_multi_hillshade, _compute_openness_both
- Remove corresponding VIZ_STEPS entries, COLORMAPS, RGB_LEGENDS, and tests
- Fix DTM resolution mismatch: existing DTM at different resolution is now
  regenerated instead of silently reused
- Propagate actual DTM resolution to visualizations and rendering
- Add --init to docker run commands for proper signal handling on Ctrl+C
- Add .playwright-mcp/ to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-05-14 02:19:42 +02:00
parent bf17ca4662
commit e2bd6b2536
6 changed files with 60 additions and 292 deletions

View File

@ -61,7 +61,7 @@ from .visualizations import (
generate_lrm, generate_svf, generate_openness,
generate_mslrm, generate_tpi, generate_sailore,
generate_roughness, generate_anomalies, generate_wavelet,
generate_flow, generate_rrim, generate_multi_hillshade, generate_local_dominance,
generate_flow, generate_local_dominance,
)
from .gpu import gpu_cleanup
from .ign import generate_ign_overlay
@ -87,8 +87,6 @@ VIZ_STEPS = [
('anomalies', generate_anomalies),
('wavelet', generate_wavelet),
('flow', generate_flow),
('rrim', lambda d, b, v, r, shared=None: generate_rrim(d, b, v, r, shared=shared)),
('multi_hillshade', lambda d, b, v, r, shared=None: generate_multi_hillshade(d, b, v, r, shared=shared)),
('local_dominance', generate_local_dominance),
('ortho', lambda d, b, v, r: generate_ign_overlay(
d, b, v, r,
@ -164,11 +162,14 @@ class LidarArchaeoPipeline:
return False
return True
def generate_all_visualizations(self, dtm_file, basename):
def generate_all_visualizations(self, dtm_file, basename, resolution=None):
"""Generate all archaeological visualizations for one DTM file.
Returns a dict of {name: tif_path} for successful generations.
Args:
resolution: Actual resolution from DTM geotransform. If None, uses self.resolution.
"""
if resolution is None:
resolution = self.resolution
logger.info(" Génération visualisations:")
# Create per-file subdirectory
@ -178,7 +179,7 @@ class LidarArchaeoPipeline:
# Pre-compute shared DEM data (gradient, NaN mask, LRM) once for all visualizations
logger.info(" Pré-calcul données partagées (gradient, LRM)...")
t_shared = time.time()
shared = SharedDEM(dtm_file, self.resolution)
shared = SharedDEM(dtm_file, resolution)
logger.info(f" ✓ Données partagées prêtes ({time.time()-t_shared:.1f}s)")
vis_results = {}
@ -225,9 +226,9 @@ class LidarArchaeoPipeline:
try:
# IGN overlays don't use SharedDEM (they download external data)
if name in ('ortho', 'topo'):
result = func(dtm_file, basename, file_vis_dir, self.resolution)
result = func(dtm_file, basename, file_vis_dir, resolution)
else:
result = func(dtm_file, basename, file_vis_dir, self.resolution, shared=shared)
result = func(dtm_file, basename, file_vis_dir, resolution, shared=shared)
vis_results[name] = result
elapsed = time.time() - t0
if result:
@ -250,7 +251,7 @@ class LidarArchaeoPipeline:
}
for name, tif_file in vis_results.items():
if tif_file and isinstance(tif_file, Path) and tif_file.suffix == '.tif' and tif_file.exists():
webp_file = tif_to_png(tif_file, file_vis_dir, self.resolution, keep_tif=self.keep_tif, source_info=source_info)
webp_file = tif_to_png(tif_file, file_vis_dir, resolution, keep_tif=self.keep_tif, source_info=source_info)
if webp_file:
logger.info(f"{webp_file.name}")
@ -271,17 +272,30 @@ class LidarArchaeoPipeline:
logger.info(f"FICHIER : {basename}")
logger.info("=" * 60)
# Skip ground classification + DTM if DTM already exists
# Skip ground classification + DTM if DTM already exists with matching resolution
# --force only affects visualizations/PDF, not classification/DTM
# Use --force-classification to force reclassification
dtm_path = self.dtm_dir / f"{basename}_dtm.tif"
if dtm_path.exists():
logger.info("[1/5] Classification du sol — sautée (DTM existant)")
logger.info("[2/5] Génération DTM — sautée (DTM existant)")
dtm_file = dtm_path
t_classif = 0
t_dtm = 0
else:
# Check that existing DTM resolution matches requested resolution
import rasterio
try:
with rasterio.open(dtm_path) as src:
existing_res = abs(src.transform.a)
if abs(existing_res - self.resolution) > 0.01:
logger.info(f"[1/5] DTM existant à {existing_res}m/px — résolution demandée {self.resolution}m/px → régénération")
dtm_path.unlink()
else:
logger.info(f"[1/5] Classification du sol — sautée (DTM existant à {existing_res}m/px)")
logger.info("[2/5] Génération DTM — sautée (DTM existant)")
dtm_file = dtm_path
t_classif = 0
t_dtm = 0
except Exception:
logger.warning(f"Impossible de lire le DTM existant — régénération")
dtm_path.unlink()
if not dtm_path.exists():
# Step 1: Ground classification
logger.info("[1/5] Classification du sol...")
t1 = time.time()
@ -302,9 +316,13 @@ class LidarArchaeoPipeline:
return False
logger.info(f" ✓ DTM terminé ({t_dtm:.1f}s)")
# Step 3: Visualizations
logger.info("[3/5] Visualisations archéologiques...")
self.generate_all_visualizations(dtm_file, basename)
# Step 3: Visualizations — use actual resolution from DTM
import rasterio
with rasterio.open(dtm_file) as src:
actual_res = abs(src.transform.a)
if abs(actual_res - self.resolution) > 0.01:
logger.info(f" Résolution DTM: {actual_res}m/px (demandée: {self.resolution}m/px)")
self.generate_all_visualizations(dtm_file, basename, actual_res)
# Step 4: PDF report
t_pdf = 0
@ -315,7 +333,7 @@ class LidarArchaeoPipeline:
else:
logger.info("[4/5] Rapport PDF A3...")
t4 = time.time()
generate_pdf_report(basename, file_vis_dir, self.pdf_dir, self.resolution)
generate_pdf_report(basename, file_vis_dir, self.pdf_dir, actual_res)
t_pdf = time.time() - t4
logger.info(f" ✓ Rapport PDF terminé ({t_pdf:.1f}s)")