Accélérer la rugosité 2.5x via écarts-types par sommes intégrales

This commit is contained in:
Antoine Jacquin
2026-09-16 20:19:15 +02:00
parent 0887d240f7
commit 06a8dd5604

View File

@ -843,11 +843,55 @@ def generate_sailore(dem_file, basename, vis_dir, resolution, shared=None):
# Roughness # Roughness
# ============================================================ # ============================================================
def _integral_sums(x):
"""Sommes intégrales 2D : S[i,j] = somme de x[0:i, 0:j] (float64).
Ligne/colonne 0 remplies de zéros — permet la somme d'une fenêtre
quelconque par 4 coins, y compris contre le bord (indices 0).
"""
rows, cols = x.shape
S = xp.zeros((rows + 1, cols + 1), dtype=np.float64)
S[1:, 1:] = xp.cumsum(xp.cumsum(x.astype(np.float64), axis=0), axis=1)
return S
def _box_std_from_integral(Sx, Sx2, size):
"""Écart-type local sur fenêtre size×size via sommes intégrales.
Coût indépendant de la taille de fenêtre (4 accès par pixel) — contre un
uniform_filter dont le coût croît avec la fenêtre (75 px à 0,2 m pour
l'échelle large). Aux bords, la fenêtre est tronquée et normalisée par le
nombre réel d'éléments (les dalles se recouvrent, le bord est sans effet
visuel).
"""
rows = Sx.shape[0] - 1
cols = Sx.shape[1] - 1
r = size // 2
iy0 = xp.maximum(xp.arange(rows) - r, 0)
iy1 = xp.minimum(xp.arange(rows) + r + 1, rows)
ix0 = xp.maximum(xp.arange(cols) - r, 0)
ix1 = xp.minimum(xp.arange(cols) + r + 1, cols)
# Nombre d'éléments réels de la fenêtre (tronquée aux bords)
counts = ((iy1 - iy0)[:, None] * (ix1 - ix0)[None, :]).astype(np.float64)
def box_sum(S):
return (S[iy1][:, ix1] - S[iy0][:, ix1]
- S[iy1][:, ix0] + S[iy0][:, ix0])
mean = box_sum(Sx) / counts
mean_sq = box_sum(Sx2) / counts
return xp.sqrt(xp.maximum(mean_sq - mean * mean, 0))
def generate_roughness(dem_file, basename, vis_dir, resolution, shared=None): def generate_roughness(dem_file, basename, vis_dir, resolution, shared=None):
"""Surface roughness - multi-scale standard deviation (GPU-accelerated). """Surface roughness - multi-scale standard deviation (GPU-accelerated).
Combines fine (3m) and broad (15m) roughness for better detection Combines fine (3m) and broad (15m) roughness for better detection
of archaeological features at multiple scales. of archaeological features at multiple scales. Les écarts-types locaux
sont calculés par sommes intégrales : deux cumsum partagés entre les
deux échelles, extraction par 4 coins — coût constant quelle que soit
la fenêtre (un uniform_filter coûte proportionnellement à sa taille,
75 px à 0,2 m pour l'échelle large).
""" """
gpu_tag = " [GPU]" if _gpu_mod.HAS_GPU else "" gpu_tag = " [GPU]" if _gpu_mod.HAS_GPU else ""
logger.info(f" → Rugosité de surface{gpu_tag}...") logger.info(f" → Rugosité de surface{gpu_tag}...")
@ -860,38 +904,33 @@ def generate_roughness(dem_file, basename, vis_dir, resolution, shared=None):
crs = shared.crs crs = shared.crs
dem_np = shared.dem_np dem_np = shared.dem_np
nan_mask = shared.nan_mask nan_mask = shared.nan_mask
if _gpu_mod.HAS_GPU:
filled = shared.filled_gpu
else:
filled = shared.filled
else: else:
dem_np, transform, crs = _read_dem(dem_file) dem_np, transform, crs = _read_dem(dem_file)
nan_mask = np.isnan(dem_np) nan_mask = np.isnan(dem_np)
filled, _ = _fill_nans(dem_np)
if _gpu_mod.HAS_GPU:
filled = to_gpu(filled)
# Sommes intégrales partagées par les deux échelles (X et X²)
Sx = _integral_sums(filled)
Sx2 = _integral_sums(filled.astype(np.float64) ** 2)
# Fine roughness (3m window)
fine_size = max(3, int(3 / resolution)) fine_size = max(3, int(3 / resolution))
if fine_size % 2 == 0: if fine_size % 2 == 0:
fine_size += 1 fine_size += 1
if shared:
fine_mean = _filter_nanaware_from_filled(shared, xp_uniform_filter, size=fine_size)
fine_mean_sq = _filter_nanaware(shared.filled.astype(np.float64)**2, xp_uniform_filter, size=fine_size)
fine_mean_sq[shared.nan_mask] = np.nan
else:
fine_mean = _filter_nanaware(dem_np.astype(np.float64), xp_uniform_filter, size=fine_size)
fine_mean_sq = _filter_nanaware(dem_np.astype(np.float64)**2, xp_uniform_filter, size=fine_size)
roughness_fine = np.sqrt(np.maximum(fine_mean_sq - fine_mean * fine_mean, 0))
roughness_fine[nan_mask] = np.nan
# Broad roughness (15m window)
broad_size = max(3, int(15 / resolution)) broad_size = max(3, int(15 / resolution))
if broad_size % 2 == 0: if broad_size % 2 == 0:
broad_size += 1 broad_size += 1
if shared: roughness_fine = to_cpu(_box_std_from_integral(Sx, Sx2, fine_size))
broad_mean = _filter_nanaware_from_filled(shared, xp_uniform_filter, size=broad_size) roughness_broad = to_cpu(_box_std_from_integral(Sx, Sx2, broad_size))
broad_mean_sq = _filter_nanaware(shared.filled.astype(np.float64)**2, xp_uniform_filter, size=broad_size) del Sx, Sx2
broad_mean_sq[shared.nan_mask] = np.nan gpu_cleanup()
else: roughness_fine[nan_mask] = np.nan
broad_mean = _filter_nanaware(dem_np.astype(np.float64), xp_uniform_filter, size=broad_size)
broad_mean_sq = _filter_nanaware(dem_np.astype(np.float64)**2, xp_uniform_filter, size=broad_size)
roughness_broad = np.sqrt(np.maximum(broad_mean_sq - broad_mean * broad_mean, 0))
roughness_broad[nan_mask] = np.nan roughness_broad[nan_mask] = np.nan
# Std normalization per scale then weighted combination # Std normalization per scale then weighted combination