Upgrade PDAL to 2.10 via conda-forge, add COPC v1.1 support
- Dockerfile: install PDAL 2.10.1 from conda-forge (was 2.3 from apt) Ubuntu 22.04's PDAL 2.3 cannot read COPC v1.1 files from IGN LiDAR HD - dtm.py: add _read_with_pdal() fallback for COPC files that laspy can't read - dtm.py: validate_laz() now tries PDAL when laspy fails - dtm.py: create_dtm_fast() and detect_ground_method() use PDAL fallback - ign.py: auto-retry at lower zoom on 404 errors - pipeline.py: check DTM resolution mismatch and regenerate if needed - pipeline.py: propagate actual DTM resolution to visualizations - pipeline.py: add --init to docker run for proper Ctrl+C signal handling - Remove RRIM and Multi-Hillshade RGB visualizations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
12
Dockerfile
12
Dockerfile
@ -3,17 +3,21 @@ FROM nvidia/cuda:12.4.0-devel-ubuntu22.04
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Europe/Paris
|
||||
|
||||
# Install PDAL and system packages
|
||||
# Install system packages + Miniforge for PDAL >= 2.5 (Ubuntu 22.04 ships PDAL 2.3 which can't read COPC v1.1)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
pdal \
|
||||
liblaszip8 \
|
||||
gdal-bin \
|
||||
python3-gdal \
|
||||
python3-pip \
|
||||
python3-dev \
|
||||
build-essential \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& wget -q https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -O /tmp/miniforge.sh \
|
||||
&& bash /tmp/miniforge.sh -b -p /opt/conda \
|
||||
&& rm /tmp/miniforge.sh \
|
||||
&& /opt/conda/bin/conda install -y -c conda-forge pdal \
|
||||
&& ln -sf /opt/conda/bin/pdal /usr/local/bin/pdal \
|
||||
&& /opt/conda/bin/conda clean -afy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@ -127,24 +127,84 @@ def create_csf_pipeline(input_laz, output_las):
|
||||
def validate_laz(laz_file):
|
||||
"""Quick integrity check for a LAZ/LAS file.
|
||||
|
||||
Reads the header with laspy to detect truncated or corrupted files
|
||||
before launching expensive PDAL processing.
|
||||
Tries laspy first (fast header read), then PDAL as fallback for COPC files
|
||||
that laspy cannot read.
|
||||
|
||||
Returns:
|
||||
True if file is readable, False otherwise.
|
||||
"""
|
||||
# Try laspy first (fast)
|
||||
import laspy
|
||||
try:
|
||||
with laspy.open(str(laz_file)) as f:
|
||||
# Just read the header — fast and catches truncated files
|
||||
header = f.header
|
||||
_ = header.point_count
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: try PDAL (handles COPC v1.1 that laspy can't read)
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["pdal", "info", str(laz_file), "--summary"],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
logger.error(f" ✗ Fichier illisible: {laz_file.name}")
|
||||
logger.error(f" PDAL: {result.stderr.strip()[:200]}")
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
logger.error(f" ✗ Impossible de vérifier le fichier: {laz_file.name}")
|
||||
logger.error(f" → Re-télécharger depuis https://ign.fr/lidar-hd")
|
||||
return False
|
||||
|
||||
|
||||
def _read_with_pdal(laz_file):
|
||||
"""Read a LAZ/LAS file via PDAL when laspy fails (e.g. COPC v1.1).
|
||||
|
||||
Returns a laspy.LasData object, or None on failure.
|
||||
"""
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
try:
|
||||
# Convert COPC to LAS via PDAL, then read with laspy
|
||||
with tempfile.NamedTemporaryFile(suffix='.las', delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
|
||||
pipeline = json.dumps({
|
||||
"pipeline": [
|
||||
str(laz_file),
|
||||
{"type": "writers.las", "filename": tmp_path}
|
||||
]
|
||||
})
|
||||
|
||||
result = subprocess.run(
|
||||
["pdal", "pipeline", "--stdin"],
|
||||
input=pipeline, capture_output=True, text=True, timeout=300
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.warning(f" PDAL conversion échouée: {result.stderr[:200]}")
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
import laspy
|
||||
las = laspy.read(tmp_path)
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except Exception:
|
||||
pass
|
||||
return las
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" ✗ Fichier corrompu ou incomplet: {laz_file.name}")
|
||||
logger.error(f" {e}")
|
||||
logger.error(f" → Re-télécharger depuis https://ign.fr/lidar-hd")
|
||||
return False
|
||||
logger.warning(f" PDAL fallback échoué: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def detect_ground_method(laz_file):
|
||||
@ -164,10 +224,16 @@ def detect_ground_method(laz_file):
|
||||
"""
|
||||
import laspy
|
||||
|
||||
# Try laspy first, then PDAL for COPC files
|
||||
las = None
|
||||
try:
|
||||
las = laspy.read(str(laz_file))
|
||||
except Exception as e:
|
||||
logger.warning(f" Impossible de lire le nuage pour auto-détection: {e}")
|
||||
logger.warning(f" laspy: {e}")
|
||||
logger.info(f" → Lecture via PDAL pour auto-détection...")
|
||||
las = _read_with_pdal(laz_file)
|
||||
|
||||
if las is None:
|
||||
logger.info(f" → Méthode: SMRF (défaut — lecture impossible)")
|
||||
return 'smrf'
|
||||
|
||||
@ -334,6 +400,16 @@ def create_dtm_fast(las_file, basename, dtm_dir, resolution, force=False):
|
||||
|
||||
try:
|
||||
las = laspy.read(str(las_file))
|
||||
except Exception as e:
|
||||
# laspy can't read COPC v1.1 — try PDAL conversion
|
||||
logger.warning(f" laspy: {e}")
|
||||
logger.info(f" → Conversion via PDAL pour lecture COPC...")
|
||||
las = _read_with_pdal(las_file)
|
||||
if las is None:
|
||||
logger.error(f" ✗ Impossible de lire {las_file.name}")
|
||||
return None
|
||||
|
||||
try:
|
||||
|
||||
min_x, max_x = float(las.header.min[0]), float(las.header.max[0])
|
||||
min_y, max_y = float(las.header.min[1]), float(las.header.max[1])
|
||||
|
||||
Reference in New Issue
Block a user