#!/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])), } # Percentiles bas de Z : contrairement a zmin (sensible a un unique point aberrant, ex. bruit # multi-echo), un percentile est un estimateur robuste du plan de reference et n'est PAS biaise # par le sous-echantillonnage (la distribution marginale de Z ne depend pas de la densite de # points, au contraire de la distance au plus proche voisin). Voir CONCEPT.md ยง7.7. z_p01 = float(np.percentile(pts[:, 2], 0.1)) z_p1 = float(np.percentile(pts[:, 2], 1.0)) 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, "z_p01": z_p01, "z_p1": z_p1, "approximate": True, } print(json.dumps(result)) return 0 if __name__ == "__main__": sys.exit(main())