script-volume-auto/python/stats_helper.py
Nicolas Fryder 79daa30a8a Implementation initiale : calcul de volume 2.5D CloudCompare et test de robustesse par decimation spatiale progressive
- Backend NestJS orchestrant CloudCompare CLI (stats, decimation x2 sur 5 etapes, volume, assemblage .bin)
- Scripts Python (laspy, scipy, matplotlib) pour la conversion LAS/LAZ et la generation d'images
- Frontend web simple (Tailwind CDN) avec historique des runs
- Image Docker (debian:trixie-slim + cloudcompare apt + xvfb) prete pour deploiement Coolify
- Lint/typecheck/tests unitaires integres comme portes de qualite au build Docker
- CLAUDE.md documentant les comportements CloudCompare CLI verifies empiriquement
2026-07-10 13:55:25 +02:00

72 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""
Calcule des statistiques (distance moyenne/mediane au plus proche voisin, bbox)
a partir d'un echantillon de points XYZ (fichier ASCII exporte par CloudCompare).
Usage:
python3 stats_helper.py --input sample.xyz
Sortie: un objet JSON unique sur stdout.
"""
import argparse
import json
import sys
import numpy as np
from scipy.spatial import cKDTree
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True, help="Fichier ASCII XYZ (espace separe)")
args = parser.parse_args()
try:
pts = np.loadtxt(args.input, dtype=np.float64, usecols=(0, 1, 2))
except Exception as exc: # fichier vide, format inattendu, etc.
print(json.dumps({"error": f"lecture impossible: {exc}"}))
return 1
if pts.ndim == 1:
# une seule ligne -> un seul point
pts = pts.reshape(1, -1)
count = int(pts.shape[0])
if count < 2:
print(json.dumps({"error": f"pas assez de points dans l'echantillon ({count})"}))
return 1
bbox = {
"xmin": float(np.min(pts[:, 0])),
"xmax": float(np.max(pts[:, 0])),
"ymin": float(np.min(pts[:, 1])),
"ymax": float(np.max(pts[:, 1])),
"zmin": float(np.min(pts[:, 2])),
"zmax": float(np.max(pts[:, 2])),
}
tree = cKDTree(pts)
# k=2 : le plus proche voisin de chaque point est le point lui-meme (distance 0),
# le 2e plus proche est le vrai plus proche voisin.
dists, _ = tree.query(pts, k=2, workers=-1)
nn_dist = dists[:, 1]
nn_dist = nn_dist[np.isfinite(nn_dist) & (nn_dist > 0)]
if nn_dist.size == 0:
print(json.dumps({"error": "distances au plus proche voisin toutes nulles/invalides (points dupliques ?)"}))
return 1
result = {
"count": count,
"mean_nn": float(np.mean(nn_dist)),
"median_nn": float(np.median(nn_dist)),
"std_nn": float(np.std(nn_dist)),
"bbox": bbox,
"approximate": True,
}
print(json.dumps(result))
return 0
if __name__ == "__main__":
sys.exit(main())