Rendu: zones NaN transparentes au lieu d'interpolation DTM

L'interpolation fillnodata du DTM générait des artefacts visuels
(zones lissées artificielles). Revenu au DTM avec NaN conservés.

Nouvelle approche pour le rendu:
- NaN remplacés par la médiane des valeurs valides (valeur neutre)
- Après application de la colormap, un masque alpha rend les zones
  NaN transparentes au lieu de les afficher
- interpolation='bilinear' sur imshow pour un rendu lisse
- Figure adaptative: 20-40 pouces selon la taille des données
- DPI 200 pour les images > 3000px

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-05-10 11:56:11 +02:00
parent 8e4b4bb10b
commit fd965e512c
2 changed files with 29 additions and 19 deletions

View File

@ -285,25 +285,12 @@ def create_dtm_fast(las_file, basename, dtm_dir, resolution):
dtm = stat.statistic.T dtm = stat.statistic.T
dtm = dtm[::-1, :] # Flip Y so north is at top dtm = dtm[::-1, :] # Flip Y so north is at top
# Interpolate NaN gaps using distance-weighted nearest-neighbor fill # Keep NaN for zones without LiDAR data — no interpolation
nan_count = np.count_nonzero(np.isnan(dtm)) nan_count = np.count_nonzero(np.isnan(dtm))
if nan_count > 0: if nan_count > 0:
total = dtm.size total = dtm.size
nan_pct = 100.0 * nan_count / total nan_pct = 100.0 * nan_count / total
logger.info(f" {nan_count:,} pixels sans données ({nan_pct:.1f}%) — interpolation...") logger.info(f" {nan_count:,} pixels sans données ({nan_pct:.1f}%)")
from rasterio.fill import fillnodata
# rasterio.fill.fillnodata uses GDAL's interpolation:
# fills gaps from surrounding valid pixels with distance weighting
dtm_filled = fillnodata(dtm.astype(np.float32), mask=~np.isnan(dtm),
max_search_distance=max(width, height) // 4)
dtm = dtm_filled.astype(np.float64)
remaining = np.count_nonzero(np.isnan(dtm))
if remaining > 0:
logger.warning(f" {remaining:,} pixels encore sans données après interpolation")
else:
logger.info(f" ✓ Interpolation terminée — tous les trous comblés")
# Save as GeoTIFF # Save as GeoTIFF
output_tif = dtm_dir / f"{basename}_dtm.tif" output_tif = dtm_dir / f"{basename}_dtm.tif"

View File

@ -323,13 +323,36 @@ def tif_to_png(tif_file, vis_dir, resolution):
valid_data = np.asarray(data.compressed() if hasattr(data, 'compressed') else data.flatten()) valid_data = np.asarray(data.compressed() if hasattr(data, 'compressed') else data.flatten())
valid_data = valid_data[~np.isnan(valid_data)] valid_data = valid_data[~np.isnan(valid_data)]
# Convert MaskedArray to plain ndarray (NaN-filled) to avoid numpy warnings # Track NaN mask before converting to plain ndarray
nan_mask = None
if not is_rgb:
if isinstance(data, np.ma.MaskedArray): if isinstance(data, np.ma.MaskedArray):
nan_mask = data.mask.copy()
data = np.ma.filled(data, np.nan) data = np.ma.filled(data, np.nan)
elif np.any(np.isnan(data)):
nan_mask = np.isnan(data)
# For rendering: replace NaN with neutral value to avoid interpolation halos
if nan_mask is not None and np.any(nan_mask) and len(valid_data) > 0:
fill_value = float(np.median(valid_data))
data[nan_mask] = fill_value
nan_mask = nan_mask # keep for later
# Apply colormap # Apply colormap
data, cmap, title, legend_label, description, is_rgb_result = _apply_colormap(data, tif_file) data, cmap, title, legend_label, description, is_rgb_result = _apply_colormap(data, tif_file)
# Apply NaN mask: make zones without data transparent
has_nan_mask = nan_mask is not None and not is_rgb_result
if has_nan_mask:
# data is normalized 0-1 from _apply_colormap; apply cmap to get RGBA
cmap_obj = plt.get_cmap(cmap) if isinstance(cmap, str) else cmap
rgba = cmap_obj(data) # (H, W, 4) float RGBA
rgba[nan_mask, 3] = 0.0 # transparent where no data
data = rgba
is_rgba = True
else:
is_rgba = False
# Create figure — adapt width to data resolution for sharp rendering # Create figure — adapt width to data resolution for sharp rendering
# At high res (5000+px wide), we need a larger figure to avoid downsampling artifacts # At high res (5000+px wide), we need a larger figure to avoid downsampling artifacts
fig_width = max(20, width / 150) fig_width = max(20, width / 150)
@ -342,7 +365,7 @@ def tif_to_png(tif_file, vis_dir, resolution):
left=0.06, right=0.88, top=0.93, bottom=0.08) left=0.06, right=0.88, top=0.93, bottom=0.08)
ax = fig.add_subplot(gs[0]) ax = fig.add_subplot(gs[0])
if is_rgb: if is_rgba or is_rgb:
im = ax.imshow(data, aspect='equal', origin='upper', im = ax.imshow(data, aspect='equal', origin='upper',
interpolation='bilinear') interpolation='bilinear')
else: else: