Skip SharedDEM computation when all visualizations already exist
Two optimizations to avoid ~2min wasted per file on re-runs: 1. pipeline.py: Check which visualizations need regeneration before computing SharedDEM. If all WebP outputs exist, skip SharedDEM entirely. If only IGN overlays need updating, also skip SharedDEM. 2. visualizations.py: Make SharedDEM attributes lazy (filled, gradient, lrm_15) so only the data actually needed is computed. For example, if only hillshade is regenerated, LRM at 15m is never calculated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -33,10 +33,13 @@ else:
|
||||
class SharedDEM:
|
||||
"""Pre-computed DEM data shared across all visualizations.
|
||||
|
||||
Reads the DEM once and pre-computes:
|
||||
Reads the DEM once and lazily computes on first access:
|
||||
- NaN mask and filled DEM (avoids 20+ calls to _fill_nans)
|
||||
- Gradient components (shared by hillshade, slope, aspect, curvature)
|
||||
- LRM at 15m kernel (shared by lrm + anomalies)
|
||||
|
||||
Attributes are computed lazily on first access to avoid computing
|
||||
data that is never used (e.g. LRM when only hillshade needs generation).
|
||||
"""
|
||||
|
||||
def __init__(self, dem_file, resolution):
|
||||
@ -48,25 +51,69 @@ class SharedDEM:
|
||||
self.nan_mask = np.isnan(dem_np)
|
||||
self.dem_np = dem_np.astype(np.float32)
|
||||
|
||||
# Pre-fill NaNs once (saves ~20 calls to NearestNDInterpolator)
|
||||
self.filled, _ = _fill_nans(self.dem_np)
|
||||
# Lazy caches — computed on first access
|
||||
self._filled = None
|
||||
self._gradient = None # (dy, dx, slope_rad, slope_deg, aspect)
|
||||
self._lrm_15 = None
|
||||
|
||||
# Initialize GPU lazy caches before any filter calls
|
||||
# GPU lazy caches
|
||||
self._filled_gpu = None
|
||||
self._dem_gpu = None
|
||||
|
||||
# Pre-compute gradient (shared by hillshade, slope, aspect, curvature)
|
||||
self.dy = np.gradient(self.filled, resolution, axis=0)
|
||||
self.dx = np.gradient(self.filled, resolution, axis=1)
|
||||
self.slope_rad = np.arctan(np.sqrt(self.dx**2 + self.dy**2))
|
||||
self.slope_deg = np.degrees(self.slope_rad)
|
||||
self.aspect = np.mod(np.degrees(np.arctan2(self.dy, self.dx)), 360)
|
||||
@property
|
||||
def filled(self):
|
||||
"""Filled DEM (NaN interpolated) — computed lazily."""
|
||||
if self._filled is None:
|
||||
logger.debug(" → Calcul filled DEM (interpolation NaN)...")
|
||||
self._filled, _ = _fill_nans(self.dem_np)
|
||||
return self._filled
|
||||
|
||||
# Pre-compute LRM at 15m (shared by lrm + anomalies)
|
||||
sigma_15 = 15.0 / resolution
|
||||
local_mean_15 = _filter_nanaware_from_filled(self, xp_gaussian_filter, sigma=sigma_15)
|
||||
self.lrm_15 = self.dem_np - local_mean_15
|
||||
self.lrm_15[self.nan_mask] = np.nan
|
||||
@property
|
||||
def dy(self):
|
||||
self._ensure_gradient()
|
||||
return self._gradient[0]
|
||||
|
||||
@property
|
||||
def dx(self):
|
||||
self._ensure_gradient()
|
||||
return self._gradient[1]
|
||||
|
||||
@property
|
||||
def slope_rad(self):
|
||||
self._ensure_gradient()
|
||||
return self._gradient[2]
|
||||
|
||||
@property
|
||||
def slope_deg(self):
|
||||
self._ensure_gradient()
|
||||
return self._gradient[3]
|
||||
|
||||
@property
|
||||
def aspect(self):
|
||||
self._ensure_gradient()
|
||||
return self._gradient[4]
|
||||
|
||||
@property
|
||||
def lrm_15(self):
|
||||
"""LRM at 15m kernel — computed lazily."""
|
||||
if self._lrm_15 is None:
|
||||
logger.debug(" → Calcul LRM 15m...")
|
||||
sigma_15 = 15.0 / self.resolution
|
||||
local_mean_15 = _filter_nanaware_from_filled(self, xp_gaussian_filter, sigma=sigma_15)
|
||||
self._lrm_15 = self.dem_np - local_mean_15
|
||||
self._lrm_15[self.nan_mask] = np.nan
|
||||
return self._lrm_15
|
||||
|
||||
def _ensure_gradient(self):
|
||||
"""Compute gradient components lazily on first access."""
|
||||
if self._gradient is None:
|
||||
logger.debug(" → Calcul gradient...")
|
||||
dy = np.gradient(self.filled, self.resolution, axis=0)
|
||||
dx = np.gradient(self.filled, self.resolution, axis=1)
|
||||
slope_rad = np.arctan(np.sqrt(dx**2 + dy**2))
|
||||
slope_deg = np.degrees(slope_rad)
|
||||
aspect = np.mod(np.degrees(np.arctan2(dy, dx)), 360)
|
||||
self._gradient = (dy, dx, slope_rad, slope_deg, aspect)
|
||||
|
||||
@property
|
||||
def filled_gpu(self):
|
||||
|
||||
Reference in New Issue
Block a user