#!/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())