Suppression de la visualisation Texture GLCM

- Suppression de generate_texture() de visualizations.py
- Suppression de l'entrée 'texture' de VIZ_STEPS et COLORMAPS
- Suppression du test TestTexture
- Mise à jour README (19 → 18 visualisations)
- Mise à jour CLAUDE.md (17 → 16 fonctions generate_*)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-05-10 03:30:07 +02:00
parent bc92689acc
commit f03b3873bd
6 changed files with 8 additions and 107 deletions

View File

@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
LiDAR archaeological processing pipeline that generates 19 terrain visualizations from LAZ/LAS point clouds. Runs in Docker with optional NVIDIA GPU acceleration (CuPy). Designed for French LiDAR HD data in Lambert 93 (EPSG:2154).
LiDAR archaeological processing pipeline that generates 18 terrain visualizations from LAZ/LAS point clouds. Runs in Docker with optional NVIDIA GPU acceleration (CuPy). Designed for French LiDAR HD data in Lambert 93 (EPSG:2154).
## Commands
@ -33,7 +33,7 @@ docker run --rm --gpus all -v $(pwd)/input:/data/input:ro -v $(pwd)/output:/data
- **`cli.py`** — argparse + logging setup. Entry point via `python -m lidar_pipeline`.
- **`pipeline.py`** — `LidarArchaeoPipeline` orchestrator. `VIZ_STEPS` registry maps names to generate functions. `FilePrefixFilter` for parallel logging.
- **`dtm.py`** — PDAL ground classification (SMRF/PMF/CSF + auto-detection) and DTM generation via scipy `binned_statistic_2d`.
- **`visualizations.py`** — 17 `generate_*` functions + 2 IGN overlay lambdas. All take `(dem_file, basename, vis_dir, resolution)` and return a TIF path or None.
- **`visualizations.py`** — 16 `generate_*` functions + 2 IGN overlay lambdas. All take `(dem_file, basename, vis_dir, resolution)` and return a TIF path or None.
- **`gpu.py`** — CuPy/numpy abstraction: `HAS_GPU`, `to_gpu()`, `to_cpu()`, `xp_gaussian_filter()`, `xp_uniform_filter()`, `xp_minimum_filter()`, `gpu_cleanup()`. Falls back to CPU gracefully.
- **`ign.py`** — IGN WMTS tile download + overlay generation for orthophoto and topographic maps.
- **`rendering.py`** — `COLORMAPS` dict maps filename keywords to (cmap, title, legend, description). `tif_to_png()` converts TIF→WebP with legend/scale/north arrow. `generate_pdf_report()` creates A3 PDF.

View File

@ -2,7 +2,7 @@
Workflow automatisé pour générer des visualisations exploitables à partir de données LiDAR HD (IGN) pour la détection de structures archéologiques. Tourne en Docker avec accélération GPU optionnelle (NVIDIA/CuPy).
## Visualisations (19 par fichier)
## Visualisations (18 par fichier)
### Visualisations principales
| # | Visualisation | Utilité archéologique |
@ -26,14 +26,13 @@ Workflow automatisé pour générer des visualisations exploitables à partir de
| 13 | **Rugosité** | Écart-type de l'élévation | Surfaces anthropiques vs naturelles |
| 14 | **Anomalies statistiques** | Z-score + Local Moran's I | Anomalies topographiques significatives |
| 15 | **Ondelette Mexican Hat** | CWT 2D multi-échelle | Tumulus, fossés circulaires |
| 16 | **Texture GLCM** | Contraste, entropie, homogénéité | Labour, surfaces archéologiques |
| 17 | **Accumulation de flux** | Algorithme D8 hydrologique | Fossés d'enceinte, routes antiques |
| 16 | **Accumulation de flux** | Algorithme D8 hydrologique | Fossés d'enceinte, routes antiques |
### Cartes de référence IGN
| # | Visualisation | Source |
|---|--------------|--------|
| 18 | **Photographie aérienne IGN** | Orthophotographie WMTS |
| 19 | **Carte topographique IGN** | Plan IGN V2 WMTS |
| 17 | **Photographie aérienne IGN** | Orthophotographie WMTS |
| 18 | **Carte topographique IGN** | Plan IGN V2 WMTS |
## Classification du sol

View File

@ -44,7 +44,7 @@ from .visualizations import (
generate_hillshade, generate_slope, generate_aspect, generate_curvature,
generate_lrm, generate_svf, generate_openness,
generate_mslrm, generate_tpi, generate_depressions, generate_sailore,
generate_roughness, generate_anomalies, generate_wavelet, generate_texture,
generate_roughness, generate_anomalies, generate_wavelet,
generate_flow,
)
from .gpu import gpu_cleanup
@ -71,7 +71,6 @@ VIZ_STEPS = [
('roughness', generate_roughness),
('anomalies', generate_anomalies),
('wavelet', generate_wavelet),
('texture', generate_texture),
('flow', generate_flow),
('ortho', lambda d, b, v, r: generate_ign_overlay(
d, b, v, r,

View File

@ -156,13 +156,6 @@ COLORMAPS = {
'description': 'Transformée en ondelette 2D — excellente pour détecter structures circulaires',
'vmin_mode': 'symmetric', 'sym_pct': (2, 98),
},
'texture': {
'cmap': 'inferno',
'title': 'Texture GLCM (Contraste + Entropie - Homogénéité)',
'legend': 'Analyse de la texture du relief (fenêtre 5m)\nClair = Texture hétérogène (labour, ruines, sol perturbé)\nSombre = Texture homogène (sol nu, route, zone plate)\n\nCombine contraste, entropie et homogénéité',
'description': 'Distingue surfaces anthropiques (labour, chemins) des naturelles',
'vmin_mode': 'symmetric', 'sym_pct': (2, 98),
},
'flow': {
'cmap': 'Blues',
'title': 'Accumulation de Flux Hydrologique (D8)',
@ -525,7 +518,7 @@ def generate_pdf_report(basename, vis_dir, pdf_dir, resolution):
order = ['mslrm', 'svf', 'negative_openness',
'positive_openness', 'sailore', 'depressions', 'hillshade_multi',
'lrm', 'tpi', 'slope', 'curvature', 'aspect',
'roughness', 'anomalies', 'wavelet', 'texture', 'flow']
'roughness', 'anomalies', 'wavelet', 'flow']
def sort_key(f):
name = f.stem.lower()

View File

@ -190,14 +190,6 @@ class TestWavelet:
assert result.exists()
class TestTexture:
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
from lidar_pipeline.visualizations import generate_texture
result = generate_texture(synthetic_dem, "test", tmp_output_dir, 5.0)
assert result is not None
assert result.exists()
class TestFlow:
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
from lidar_pipeline.visualizations import generate_flow

View File

@ -676,88 +676,6 @@ def generate_wavelet(dem_file, basename, vis_dir, resolution):
return None
# ============================================================
# Texture GLCM
# ============================================================
def generate_texture(dem_file, basename, vis_dir, resolution):
"""GLCM-inspired texture analysis — contrast, entropy, homogeneity (GPU-accelerated)."""
gpu_tag = " [GPU]" if HAS_GPU else ""
logger.info(f" → Texture GLCM{gpu_tag}...")
t0 = time.time()
output = vis_dir / f"{basename}_texture.tif"
try:
dem_np, transform, crs = _read_dem(dem_file)
# Hillshade — compute on CPU to avoid holding DEM on GPU during texture
gy, gx = np.gradient(dem_np, resolution)
slope = np.arctan(np.sqrt(gx**2 + gy**2))
alt_rad = np.radians(45)
az_rad = np.radians(315)
aspect = np.arctan2(gy, gx)
shading = (np.sin(alt_rad) * np.cos(slope) +
np.cos(alt_rad) * np.sin(slope) *
np.cos(az_rad - aspect))
hillshade = np.clip(shading, 0, 1)
valid = np.asarray(hillshade[~np.isnan(hillshade)])
if len(valid) == 0:
raise ValueError("No valid data for texture analysis")
lo, hi = np.percentile(valid, (1, 99))
img = np.clip((hillshade - lo) / max(hi - lo, 0.001), 0, 1)
del hillshade, shading, slope, aspect, gy, gx # free memory
window = int(5 / resolution)
if window % 2 == 0:
window += 1
# Contrast (variance) — GPU-accelerated
img_gpu = to_gpu(img.astype(np.float32))
local_mean = xp_uniform_filter(img_gpu, size=window)
local_mean_sq = xp_uniform_filter(img_gpu * img_gpu, size=window)
contrast = to_cpu(local_mean_sq - local_mean * local_mean).astype(np.float64)
del img_gpu, local_mean, local_mean_sq # free GPU memory
# Entropy — compute bin-by-bin to avoid large 3D allocation
n_bins = 16
img_clean = np.nan_to_num(img, nan=0.0)
img_uint8 = np.clip(img_clean * 255, 0, 255).astype(np.uint8)
quantized = (img_uint8 // (256 // n_bins)).astype(np.int32)
entropy = np.zeros_like(img, dtype=np.float64)
win_area = max(window * window, 1)
for b in range(n_bins):
plane = (quantized == b).astype(np.float32)
plane_gpu = to_gpu(plane)
prob_plane = to_cpu(xp_uniform_filter(plane_gpu, size=window))
prob_val = prob_plane / win_area
prob_val = np.clip(prob_val, 1e-10, None)
entropy -= prob_val * np.log2(prob_val)
del plane_gpu # free GPU memory per bin
del quantized, img_uint8 # free CPU memory
# Homogeneity — 1 / (1 + variance)
homogeneity = 1.0 / (1.0 + contrast)
def norm(arr):
valid_arr = arr[~np.isnan(arr)]
if len(valid_arr) == 0:
return arr
std_val = max(np.std(valid_arr), 0.01)
return (arr - np.mean(valid_arr)) / std_val
texture_combined = 0.4 * norm(contrast) + 0.4 * norm(entropy) - 0.2 * norm(homogeneity)
_save_tif(output, texture_combined, transform, crs)
logger.info(f" ✓ Texture terminée ({time.time()-t0:.1f}s){gpu_tag}")
return output
except Exception as e:
logger.error(f" ✗ Erreur texture GLCM: {e}", exc_info=True)
return None
# ============================================================
# Flow accumulation
# ============================================================