Remove PMF, fix NaN in gradient visualizations, fix pos_open/neg_open shared param
- Remove PMF from ground classification options (PDAL recommends SMRF over PMF) - Auto-detection now uses CSF for urban/complex terrain instead of PMF - Add z_std > 30m heuristic to auto-select CSF for complex terrain - Fix pos_open/neg_open lambda missing 'shared' parameter (NameError in workers) - Fix NaN mask not restored in hillshade, slope, aspect, curvature (gradient-based products computed on filled DEM lost NaN transparency) - Add nan_mask parameter to _save_tif for centralized NaN restoration - DTM TIF kept by default (no longer deleted after WebP conversion) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -100,8 +100,18 @@ def _filter_nanaware_from_filled(shared, filter_func, *args, **kwargs):
|
||||
return result
|
||||
|
||||
|
||||
def _save_tif(output_path, data, transform, crs, dtype='float32', count=1, nodata=None):
|
||||
"""Helper to save a 2D or 3D array as GeoTIFF."""
|
||||
def _save_tif(output_path, data, transform, crs, dtype='float32', count=1, nodata=None, nan_mask=None):
|
||||
"""Helper to save a 2D or 3D array as GeoTIFF.
|
||||
|
||||
Args:
|
||||
nan_mask: Optional boolean mask (True=NaN) to apply before saving.
|
||||
Restores NaN zones in gradient-derived products that were
|
||||
computed on the filled DEM.
|
||||
"""
|
||||
if nan_mask is not None:
|
||||
data = np.array(data, dtype=dtype, copy=True)
|
||||
data[nan_mask] = np.nan
|
||||
|
||||
# Auto-detect nodata for float types with NaN
|
||||
if nodata is None and dtype.startswith('float') and np.any(np.isnan(data)):
|
||||
nodata = float('nan')
|
||||
@ -238,7 +248,9 @@ def generate_hillshade(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
combined_hillshade = xp.mean(xp.array(hillshades), axis=0)
|
||||
slope_shaded = cos_slope
|
||||
combined = 0.7 * combined_hillshade + 0.3 * slope_shaded
|
||||
_save_tif(output, to_cpu(combined), transform, crs)
|
||||
|
||||
nan_mask = shared.nan_mask if shared else np.isnan(to_cpu(dem_np))
|
||||
_save_tif(output, to_cpu(combined), transform, crs, nan_mask=nan_mask)
|
||||
logger.info(f" ✓ Hillshade terminé ({time.time()-t0:.1f}s){gpu_tag}")
|
||||
return output
|
||||
except Exception as e:
|
||||
@ -258,6 +270,7 @@ def generate_slope(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
transform = shared.transform
|
||||
crs = shared.crs
|
||||
slope = shared.slope_deg
|
||||
nan_mask = shared.nan_mask
|
||||
if HAS_GPU:
|
||||
slope = to_gpu(slope)
|
||||
else:
|
||||
@ -265,7 +278,8 @@ def generate_slope(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
dem = to_gpu(dem_np)
|
||||
dy, dx = xp.gradient(dem)
|
||||
slope = xp.arctan(xp.sqrt(dx**2 + dy**2)) * 180 / xp.pi
|
||||
_save_tif(output, to_cpu(slope) if HAS_GPU else slope, transform, crs)
|
||||
nan_mask = np.isnan(dem_np)
|
||||
_save_tif(output, to_cpu(slope) if HAS_GPU else slope, transform, crs, nan_mask=nan_mask)
|
||||
logger.info(f" ✓ Pente terminée ({time.time()-t0:.1f}s){gpu_tag}")
|
||||
return output
|
||||
except Exception as e:
|
||||
@ -285,6 +299,7 @@ def generate_aspect(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
transform = shared.transform
|
||||
crs = shared.crs
|
||||
aspect = shared.aspect
|
||||
nan_mask = shared.nan_mask
|
||||
if HAS_GPU:
|
||||
aspect = to_gpu(aspect)
|
||||
else:
|
||||
@ -293,7 +308,8 @@ def generate_aspect(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
dy, dx = xp.gradient(dem)
|
||||
aspect = xp.arctan2(dy, dx) * 180 / xp.pi
|
||||
aspect = xp.mod(aspect, 360)
|
||||
_save_tif(output, to_cpu(aspect) if HAS_GPU else aspect, transform, crs)
|
||||
nan_mask = np.isnan(dem_np)
|
||||
_save_tif(output, to_cpu(aspect) if HAS_GPU else aspect, transform, crs, nan_mask=nan_mask)
|
||||
logger.info(f" ✓ Aspect terminé ({time.time()-t0:.1f}s){gpu_tag}")
|
||||
return output
|
||||
except Exception as e:
|
||||
@ -314,6 +330,7 @@ def generate_curvature(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
crs = shared.crs
|
||||
dx = shared.dx
|
||||
dy = shared.dy
|
||||
nan_mask = shared.nan_mask
|
||||
if HAS_GPU:
|
||||
dx = to_gpu(dx)
|
||||
dy = to_gpu(dy)
|
||||
@ -321,10 +338,11 @@ def generate_curvature(dem_file, basename, vis_dir, resolution, shared=None):
|
||||
dem_np, transform, crs = _read_dem(dem_file)
|
||||
dem = to_gpu(dem_np)
|
||||
dy, dx = xp.gradient(dem)
|
||||
nan_mask = np.isnan(dem_np)
|
||||
d2z_dx2 = xp.gradient(dx, axis=1)
|
||||
d2z_dy2 = xp.gradient(dy, axis=0)
|
||||
curvature = (d2z_dx2 + d2z_dy2) / 2
|
||||
_save_tif(output, to_cpu(curvature), transform, crs)
|
||||
_save_tif(output, to_cpu(curvature), transform, crs, nan_mask=nan_mask)
|
||||
logger.info(f" ✓ Courbure terminée ({time.time()-t0:.1f}s){gpu_tag}")
|
||||
return output
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user