Files
lidar_rendu/lidar_pipeline/gpu.py
Jacquin Antoine ad762e682d Suppression éclairage solaire, GPU accéléré, --file multi, tests unitaires
- Suppression de generate_solar (éclairage solaire) des visualisations
- Accélération GPU de hillshade, slope, aspect, curvature, depressions,
  anomalies, roughness, texture GLCM, flow (sink filling)
- Nettoyage mémoire GPU entre visualisations (gpu_cleanup)
- Correction OOM texture GLCM: calcul entropie bin par bin au lieu d'un
  tableau 3D massif sur GPU
- Correction bug: xp_minimum_filter manquant dans imports visualizations
- Option --file accepte plusieurs noms complets sans extension
- run.sh affiche l'aide si appelé sans arguments
- Option --test pour exécuter les tests unitaires dans Docker
- Filtre ReturnNumber>=1 intégré dans le pipeline PDAL (plus d'erreur SMRF)
- 60 tests unitaires: GPU, visualisations, rendering, DTM, pipeline, CLI
- Ajout pytest au Dockerfile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-10 00:57:39 +02:00

79 lines
2.4 KiB
Python

"""GPU acceleration helpers for LiDAR pipeline.
Provides CuPy/numpy abstraction layer. If CuPy is available and a CUDA GPU
is detected, array operations are accelerated on the GPU. Otherwise, all
operations fall back to numpy/scipy on CPU.
"""
import logging
import numpy as np
from scipy import ndimage
logger = logging.getLogger("lidar")
# GPU detection - must happen at import time
HAS_GPU = False
_gpu_name = None
_gpu_mem_gb = 0
_xp = np # Default: CPU
try:
import cupy as cp
import cupyx.scipy.ndimage as cp_ndimage
_gpu_info = cp.cuda.runtime.getDeviceProperties(0)
_gpu_name = _gpu_info['name'].decode() if isinstance(_gpu_info['name'], bytes) else str(_gpu_info['name'])
_gpu_mem_gb = _gpu_info['totalGlobalMem'] // (1024 ** 3)
HAS_GPU = True
_xp = cp
except (ImportError, Exception):
pass
def log_gpu_status():
"""Log GPU detection result. Called after logging is configured."""
if HAS_GPU:
logger.info(f"GPU détectée: {_gpu_name} ({_gpu_mem_gb} Go VRAM)")
else:
logger.info("Pas de GPU — mode CPU uniquement")
def to_gpu(arr):
"""Send array to GPU if available, otherwise return as float64 numpy."""
if HAS_GPU:
return cp.asarray(arr.astype(np.float64))
return arr.astype(np.float64)
def to_cpu(arr):
"""Bring array back to CPU (numpy). No-op if already on CPU."""
if HAS_GPU and isinstance(arr, cp.ndarray):
return cp.asnumpy(arr)
return arr
def xp_gaussian_filter(arr, sigma):
"""Gaussian filter — uses GPU if array is on GPU, CPU otherwise."""
if HAS_GPU and isinstance(arr, cp.ndarray):
return cp_ndimage.gaussian_filter(arr, sigma)
return ndimage.gaussian_filter(arr, sigma)
def xp_uniform_filter(arr, size):
"""Uniform filter — uses GPU if array is on GPU, CPU otherwise."""
if HAS_GPU and isinstance(arr, cp.ndarray):
return cp_ndimage.uniform_filter(arr, size)
return ndimage.uniform_filter(arr, size)
def xp_minimum_filter(arr, footprint=None, size=None):
"""Minimum filter — uses GPU if array is on GPU, CPU otherwise."""
if HAS_GPU and isinstance(arr, cp.ndarray):
return cp_ndimage.minimum_filter(arr, footprint=footprint, size=size)
return ndimage.minimum_filter(arr, footprint=footprint, size=size)
def gpu_cleanup():
"""Free GPU memory. Call between visualizations to prevent OOM."""
if HAS_GPU:
cp.get_default_memory_pool().free_all_blocks()