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>
This commit is contained in:
Jacquin Antoine
2026-05-10 00:57:39 +02:00
parent f07e915f6d
commit ad762e682d
17 changed files with 998 additions and 252 deletions

View File

@ -23,6 +23,9 @@ logger = logging.getLogger("lidar")
def create_smrf_pipeline(input_laz, output_las):
"""Create a PDAL pipeline JSON for SMRF ground classification.
Includes a filter for ReturnNumber/NumberOfReturns >= 1 to handle
LiDAR HD files that may contain points with invalid return numbers.
Args:
input_laz: Path to input LAZ/LAS file.
output_las: Path to output classified LAS file.
@ -33,6 +36,10 @@ def create_smrf_pipeline(input_laz, output_las):
pipeline = {
"pipeline": [
str(input_laz),
{
"type": "filters.range",
"limits": "ReturnNumber[1:],NumberOfReturns[1:]"
},
{
"type": "filters.smrf",
"ignore": "Classification[7:7]",
@ -87,36 +94,7 @@ def classify_ground(laz_file, temp_dir):
logger.info(" ✓ Classification sol terminée")
return output_las
except subprocess.CalledProcessError as e:
error_msg = e.stderr.decode()
logger.error(f" ✗ Erreur PDAL: {error_msg}")
# If error is about ReturnNumber=0, try filtering those points
if "ReturnNumber" in error_msg and "NumberOfReturns" in error_msg:
logger.info(" → Tentative de filtrage des points ReturnNumber=0...")
filtered_pipeline = [
{"type": "readers.las", "filename": str(laz_file)},
{"type": "filters.range", "limits": "ReturnNumber[1:],NumberOfReturns[1:]"},
{"type": "filters.smrf", "scalar": 1.25},
{"type": "filters.range", "limits": "Classification[2:2]"},
{"type": "writers.las", "filename": str(output_las), "extra_dims": "all"}
]
filtered_json = json.dumps(filtered_pipeline)
with open(pipeline_file, 'w') as f:
f.write(filtered_json)
try:
subprocess.run(
["pdal", "pipeline", str(pipeline_file)],
capture_output=True, check=True
)
logger.info(" ✓ Classification sol terminée (points filtrés)")
return output_las
except subprocess.CalledProcessError as e2:
logger.error(f" ✗ Erreur même avec filtrage: {e2.stderr.decode()}")
return None
logger.error(f" ✗ Erreur classification PDAL: {e.stderr.decode()}")
return None