Improve visualizations: adaptive scales, revert z-score to std normalization
- MSRM/TPI/roughness/anomalies: revert z-score (x-mean)/std to std normalization x/std to preserve contrast and visibility of linear features (paths, ditches, trenches) - MSRM: adaptive scales based on resolution, archaeological weight combination - TPI: extend from 2 to 4 scales (3m/15m/50m/200m) with weighted combination - Hillshade: 8 directions instead of 4, altitude 35° instead of 30° - LRM: adaptive sigma based on resolution - Openness: doubled radius (100m instead of 50m) - Roughness: multi-scale (3m fine + 15m broad) instead of single 5x5 window - Anomalies: uses MSRM multi-scale relief instead of single LRM 15m - Wavelet: 8 adaptive scales, std normalization, archaeological weights - Remove svf (Sky-View Factor) and local_dominance visualizations - Add AVIF format support (default), quality 98 - Add multi-resolution support (-r 0.5,0.2) - Improve Ctrl+C handling for immediate process termination - Update rendering.py descriptions for all modified visualizations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -1,8 +1,8 @@
|
||||
"""Rendering module: colormap registry, GeoTIFF-to-WebP conversion, and PDF report generation.
|
||||
"""Rendering module: colormap registry, GeoTIFF-to-image conversion, and PDF report generation.
|
||||
|
||||
Contains:
|
||||
- COLORMAPS: registry mapping filename keywords to (cmap, title, legend, description)
|
||||
- tif_to_png(): convert a GeoTIFF to a WebP visualization with legend, scale bar, north arrow
|
||||
- tif_to_png(): convert a GeoTIFF to a WebP/AVIF visualization with legend, scale bar, north arrow
|
||||
- generate_pdf_report(): generate an A3 PDF report with all visualizations
|
||||
"""
|
||||
|
||||
@ -74,18 +74,10 @@ COLORMAPS = {
|
||||
'description': 'Détecte les ruptures de pente — utile pour bords de terrasses et levées',
|
||||
'vmin_mode': 'symmetric', 'sym_pct': (5, 95),
|
||||
},
|
||||
'svf': {
|
||||
'cmap': 'viridis',
|
||||
'title': 'Sky-View Factor (Ray-tracing 16 directions)',
|
||||
'legend': 'Fraction de ciel visible depuis chaque point\nSombre = Encaissé (fossés, vallées, rues)\nClair = Dégagé (sommets, plateformes, plateaux)',
|
||||
'description': 'Ray-tracing sur 16 azimuts — élimine l\'éclairage, détecte structures linéaires et enclos',
|
||||
'vmin_mode': 'percentile', 'vmin_pct': 5,
|
||||
'vmax_mode': 'percentile', 'vmax_pct': 95,
|
||||
},
|
||||
'mslrm': {
|
||||
'cmap': 'RdBu_r',
|
||||
'title': 'MSRM - Multi-Scale Relief Model (5 échelles)',
|
||||
'legend': 'Relief combiné à 5 échelles (5m à 100m)\nRouge = Surélévation (mur, tumulus, levée)\nBleu = Dépression (fossé, douve, fossé)\n\nDifférence avec LRM:\nLRM = 1 échelle (15m)\nMSRM = 5 échelles combinées\nMSRM détecte du micro au macro',
|
||||
'title': 'MSRM - Multi-Scale Relief Model (échelles adaptatives)',
|
||||
'legend': 'Relief combiné multi-échelles (adapté à la résolution)\nRouge = Surélévation (mur, tumulus, levée)\nBleu = Dépression (fossé, douve)\n\nDifférence avec LRM:\nLRM = 1 échelle (15m)\nMSRM = échelles combinées pondérées\nMSRM détecte du micro au macro',
|
||||
'description': 'Combine LRM à 5 échelles — détecte structures de 5m à 100m simultanément',
|
||||
'vmin_mode': 'symmetric', 'sym_pct': (2, 98),
|
||||
},
|
||||
@ -114,8 +106,8 @@ COLORMAPS = {
|
||||
},
|
||||
'tpi': {
|
||||
'cmap': 'BrBG',
|
||||
'title': 'TPI - Topographic Position Index (2 échelles)',
|
||||
'legend': 'Position dans le paysage\nBrun/Sombre = Plus bas que le voisinage (fossé, vallée)\nVert/Clair = Plus haut que le voisinage (crête, plateau)\nCombine échelle fine 5m + large 100m',
|
||||
'title': 'TPI - Topographic Position Index (4 échelles)',
|
||||
'legend': 'Position dans le paysage\nBrun/Sombre = Plus bas que le voisinage (fossé, vallée)\nVert/Clair = Plus haut que le voisinage (crête, plateau)\nCombine 4 échelles : 3m, 15m, 50m, 200m',
|
||||
'description': 'Identifie la position topographique — utile pour repérer crêtes vs vallées à grande échelle',
|
||||
'vmin_mode': 'symmetric', 'sym_pct': (2, 98),
|
||||
},
|
||||
@ -128,23 +120,23 @@ COLORMAPS = {
|
||||
},
|
||||
'roughness': {
|
||||
'cmap': 'magma',
|
||||
'title': 'Rugosité de Surface (Écart-type local 5m)',
|
||||
'legend': 'Irrégularité du terrain dans un voisinage de 5m\nSombre = Surface lisse (route, mur, sol plat)\nClair = Surface rugueuse (végétation, ruines, pierres)\nMax: {vmax:.2f}m',
|
||||
'title': 'Rugosité Multi-Échelle (3m + 15m)',
|
||||
'legend': 'Irrégularité du terrain combinée fine + large\nSombre = Surface lisse (route, mur, sol plat)\nClair = Surface rugueuse (végétation, ruines, pierres)\nCombine rugosité fine 3m (70%) + large 15m (30%)',
|
||||
'description': 'Mesure la variabilité locale — surfaces anthropiques lisses vs naturelles rugueuses',
|
||||
'vmin_mode': 'fixed', 'vmin_val': 0,
|
||||
'vmax_mode': 'percentile', 'vmax_pct': 97,
|
||||
},
|
||||
'anomalies': {
|
||||
'cmap': 'coolwarm',
|
||||
'title': 'Anomalies Statistiques (Z-score x Moran\'s I)',
|
||||
'legend': 'Anomalies topographiques significatives\nRouge vif = Surélévation anormale (mur, tumulus)\nBleu vif = Dépression anormale (fossé, doline)\nBlanc/gris = Normal\n\nCombine z-score (intensité) et\nMoran\'s I (regroupement spatial)',
|
||||
'title': 'Anomalies Statistiques (MSRM multi-échelle + Moran\'s I)',
|
||||
'legend': 'Anomalies topographiques significatives\nRouge vif = Surélévation anormale (mur, tumulus)\nBleu vif = Dépression anormale (fossé, doline)\nBlanc/gris = Normal\n\nCombine MSRM normalisé (intensité) et\nMoran\'s I (regroupement spatial)',
|
||||
'description': 'Détecte uniquement les anomalies statistiquement significatives — filtre le bruit de fond',
|
||||
'vmin_mode': 'symmetric', 'sym_pct': (5, 95),
|
||||
},
|
||||
'wavelet': {
|
||||
'cmap': 'cividis',
|
||||
'title': 'Ondelette Mexican Hat (CWT multi-échelle)',
|
||||
'legend': 'Réponse de la transformée en ondelette à 5 échelles\nÉchelles: 2m, 5m, 10m, 20m, 50m\n\nClair = Structure détectée à cette échelle\nSombre = Pas de structure\n\nOptimisé pour formes circulaires:\ntumulus, enclos, fossés annulaires',
|
||||
'legend': 'Réponse de la transformée en ondelette\nÉchelles adaptées à la résolution\n\nClair = Structure détectée à cette échelle\nSombre = Pas de structure\n\nOptimisé pour formes circulaires:\ntumulus, enclos, fossés annulaires',
|
||||
'description': 'Transformée en ondelette 2D — excellente pour détecter structures circulaires',
|
||||
'vmin_mode': 'symmetric', 'sym_pct': (2, 98),
|
||||
},
|
||||
@ -156,14 +148,6 @@ COLORMAPS = {
|
||||
'vmin_mode': 'fixed', 'vmin_val': 0,
|
||||
'vmax_mode': 'percentile', 'vmax_pct': 98,
|
||||
},
|
||||
'local_dominance': {
|
||||
'cmap': 'RdYlBu_r',
|
||||
'title': 'Dominance Locale (position relative dans le voisinage)',
|
||||
'legend': 'Proportion du voisinage sous le point central\nRouge = Point dominant (sommet, crête)\nBleu = Point encaissé (fossé, vallée)\nRayon: 15m',
|
||||
'description': 'Mesure la saillie locale — complémentaire de l\'openness',
|
||||
'vmin_mode': 'percentile', 'vmin_pct': 2,
|
||||
'vmax_mode': 'percentile', 'vmax_pct': 98,
|
||||
},
|
||||
}
|
||||
|
||||
# RGB entries (ortho/topo) are handled specially
|
||||
@ -271,24 +255,26 @@ def _nice_scale(extent_m):
|
||||
return chosen, f"{chosen} m"
|
||||
|
||||
|
||||
def tif_to_png(tif_file, vis_dir, resolution, keep_tif=False, source_info=None, quality=85):
|
||||
"""Convert GeoTIFF to visualization WebP with GPS coordinates, legend, and scale bar.
|
||||
def tif_to_png(tif_file, vis_dir, resolution, keep_tif=False, source_info=None, quality=98, output_format='avif'):
|
||||
"""Convert GeoTIFF to visualization image (WebP or AVIF) with GPS coordinates, legend, and scale bar.
|
||||
|
||||
Args:
|
||||
tif_file: Path to input GeoTIFF.
|
||||
vis_dir: Output directory for the WebP file.
|
||||
vis_dir: Output directory for the image file.
|
||||
resolution: Grid resolution in m/px.
|
||||
keep_tif: If True, keep the source TIFF after conversion.
|
||||
source_info: Dict with method/date/basename for metadata.
|
||||
quality: WebP quality (1-100). Use 100 for lossless. Default 85.
|
||||
quality: Image quality (1-100). Use 100 for lossless. Default 95.
|
||||
output_format: Output format ('webp' or 'avif'). Default 'webp'.
|
||||
|
||||
Returns:
|
||||
Path to output WebP file, or None on failure.
|
||||
Path to output image file, or None on failure.
|
||||
"""
|
||||
if not tif_file or not tif_file.exists():
|
||||
return None
|
||||
|
||||
webp_file = vis_dir / f"{tif_file.stem}.webp"
|
||||
ext = 'avif' if output_format == 'avif' else 'webp'
|
||||
output_file = vis_dir / f"{tif_file.stem}.{ext}"
|
||||
|
||||
try:
|
||||
with rasterio.open(tif_file) as src:
|
||||
@ -582,7 +568,7 @@ def tif_to_png(tif_file, vis_dir, resolution, keep_tif=False, source_info=None,
|
||||
|
||||
fig.patch.set_facecolor('white')
|
||||
|
||||
# Save as PNG then convert to WebP — fixed layout, no bbox_inches='tight'
|
||||
# Save as PNG then convert to final format — fixed layout, no bbox_inches='tight'
|
||||
save_dpi = 200 if width > 3000 else 150
|
||||
png_temp = vis_dir / f"{tif_file.stem}_temp.png"
|
||||
try:
|
||||
@ -591,20 +577,21 @@ def tif_to_png(tif_file, vis_dir, resolution, keep_tif=False, source_info=None,
|
||||
plt.close()
|
||||
|
||||
img = PILImage.open(str(png_temp))
|
||||
pil_format = 'AVIF' if output_format == 'avif' else 'WEBP'
|
||||
if quality >= 100:
|
||||
img.save(str(webp_file), format='WEBP', lossless=True)
|
||||
img.save(str(output_file), format=pil_format, lossless=True)
|
||||
else:
|
||||
img.save(str(webp_file), format='WEBP', quality=quality)
|
||||
img.save(str(output_file), format=pil_format, quality=quality)
|
||||
png_temp.unlink(missing_ok=True)
|
||||
|
||||
# Delete source TIFF (unless --keep-tif)
|
||||
if not keep_tif:
|
||||
tif_file.unlink(missing_ok=True)
|
||||
|
||||
return webp_file
|
||||
return output_file
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" Erreur conversion WebP: {e}", exc_info=True)
|
||||
logger.error(f" Erreur conversion {ext.upper()}: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user