# Volume 2.5D & robustesse a la decimation Application locale (Docker) qui, a partir d'un ou deux nuages de points (LAS/LAZ/COPC.laz/BIN) : 1. calcule un volume en 2.5D via CloudCompare, selon 2 modes au choix : - **Plan de reference Z constant** : un seul nuage, compare a un plan horizontal (altitude minimale par defaut). Fiable uniquement si le nuage ne contient QUE l'objet mesure (pas de terrain/contexte environnant) ; - **Comparaison a un second nuage** : deux nuages, la **surface superieure** (sommet de l'amas) et la **limite inferieure** (base/socle) — les densites sont d'abord appariees (le plus dense decime pour matcher l'autre), puis les deux sont decimes ensemble a chaque etape ; 2. dans les deux modes, decime progressivement en methode **spatiale**, facteur **x2** sur **5 etapes** (le pas spatial double a chaque etape, en partant d'un pas initial estime automatiquement = distance mediane au plus proche voisin, calculee par KD-tree sur un echantillon) ; 3. recalcule le volume a chaque niveau avec une **grille de calcul adaptee dynamiquement a la densite de chaque niveau** (grid step = pas spatial du niveau x2), et **comble les trous de scan** avant chaque calcul (interpolation Delaunay bornee par une distance max, pour ne pas combler les parties concaves du contour de l'objet) ; 4. assemble tous les nuages exploitables (complet + 5 decimations, x1 ou x2 selon le mode) dans un seul fichier `.bin` CloudCompare, nomme et consultable dans CloudCompare Desktop ; 5. produit un dossier de resultats : rapport CSV/JSON, cartes de hauteur par niveau (et par nuage en mode comparaison), graphiques de synthese (volume, nombre de points, robustesse) ; 6. expose tout ca dans une UI web simple avec historique des runs. ## Demarrage ```bash docker compose up -d --build ``` Puis ouvrir http://localhost:8080 Les donnees (uploads, resultats, base sqlite) sont persistees dans un volume Docker nomme `app-data` (pas un bind mount vers `./data`, voir "Deploiement Coolify" ci-dessous pour la raison). ## Authentification L'UI et l'API sont protegees par HTTP Basic Auth (identifiants dans `AUTH_USERNAME` / `AUTH_PASSWORD_HASH`, hash bcrypt — jamais le mot de passe en clair). Seul `/health` reste public (healthcheck Docker/Coolify). **Si ces deux variables ne sont pas definies, l'auth est desactivee** (pratique pour du dev local, un warning est logge au demarrage). En production/Coolify, toujours les definir. Generer un hash pour un nouveau mot de passe : ```bash node -e "console.log(require('bcryptjs').hashSync('VOTRE_MOT_DE_PASSE', 10))" ``` Puis definir dans Coolify (onglet "Environment Variables" de l'application, **pas** dans `docker-compose.yml` du repo, pour ne pas committer le hash) : ``` AUTH_USERNAME=nico AUTH_PASSWORD_HASH=$2a$10$... # sortie de la commande ci-dessus ``` En local avec `docker compose`, un fichier `.env` (gitignore) a la racine du repo fonctionne aussi : ``` AUTH_USERNAME=nico AUTH_PASSWORD_HASH=$2a$10$... ``` ## Deploiement Coolify Le repo est pret pour un deploiement Coolify via webhook (push sur `main` -> build + deploy auto) : - Le `Dockerfile` fait tourner **lint + typecheck + tests unitaires** avant de compiler — un commit qui casse l'un de ces trois echoue le build, donc n'est jamais deploye. - `HEALTHCHECK` integre au Dockerfile (`curl` sur `/health`, route publique non authentifiee) : Coolify l'utilise pour verifier que le deploiement est reellement sain. - Persistance : le volume `/data` doit etre monte comme un **volume nomme/gere par Coolify**, pas un bind mount vers le checkout git (Coolify re-clone le repo a chaque deploiement — un bind mount relatif au checkout perdrait toutes les donnees a chaque redeploy). Deux options : - Resource **"Docker Compose"** dans Coolify pointant sur `docker-compose.yml` du repo : le volume nomme `app-data` declare dedans est deja correct. - Resource **"Application"** (Dockerfile) : configurer un volume persistant sur `/data` depuis l'onglet "Storage" de Coolify, et le port expose sur `3000` (variable `PORT`, lue par l'appli). - Variables d'environnement a definir dans Coolify : `AUTH_USERNAME` / `AUTH_PASSWORD_HASH` (obligatoire, voir "Authentification" plus haut), et optionnellement `MAX_UPLOAD_MB`, `STATS_SAMPLE_CAP`, `CC_TIMEOUT_MS` si besoin de changer les defauts (voir tableau plus bas). - Le webhook de deploiement automatique est deja configure cote Coolify sur ce repo. ## Utilisation 1. "Nouveau run" -> choisir le type de comparaison : - **Plan de reference Z constant** : un fichier `.las`, `.laz`, `.copc.laz` ou `.bin`. - **Comparaison a un second nuage** : deux fichiers — "Surface superieure" (sommet de l'amas) et "Limite inferieure" (base/socle de l'amas). 2. Optionnel (options avancees) : override du plan de reference Z (mode Z constant uniquement, par defaut altitude minimale d'un echantillon), du pas spatial initial (par defaut : distance mediane au plus proche voisin) et/ou de la distance max de remplissage des trous (par defaut : grille du niveau x3 — augmenter si des trous de scan legitimes restent vides, diminuer si des creux du contour de l'objet sont a tort combles). 3. Le run tourne en arriere-plan (une file d'attente sequentielle traite les runs un par un). La page de detail se rafraichit automatiquement (polling 2s). 4. Une fois termine : tableau des niveaux (points par nuage, volume, surface, % de cellules de grille "matching"), cartes de hauteur (par nuage en mode comparaison), graphiques de synthese, et telechargements (`.bin` fusionne, `.csv`, dossier complet en `.zip`). ### Lecture des resultats - **% de cellules "matching"** (rapport de volume 2.5D de CloudCompare) : indique la part de la grille de calcul ou les deux surfaces sont effectivement comparables. Un niveau est marque "a verifier" des que ce taux passe sous 90 % (configurable via `matchingCellsWarnThreshold`, `config.ts`). - La decimation s'arrete automatiquement si un niveau (ou l'un des deux nuages en mode comparaison) tombe sous 25 points exploitables ; les niveaux suivants sont marques "skipped" avec la raison. - **Choix du mode** : le mode "Z constant" n'est fiable que si le nuage uploade ne contient QUE l'objet a mesurer. S'il contient aussi du terrain/contexte environnant (scan de site complet par exemple), le plan Z constant (altitude minimale de tout le nuage) n'a souvent aucun rapport avec la base reelle de l'objet et donne un volume trompeur — utiliser "Comparaison a un second nuage" avec une base dediee dans ce cas. ## Parametres (variables d'environnement, voir `docker-compose.yml`) | Variable | Defaut | Description | |---|---|---| | `PORT` | 3000 | port interne du serveur | | `DATA_DIR` | `/data` | dossier de persistance (uploads, resultats, sqlite) | | `AUTH_USERNAME` | _(non defini = auth off)_ | nom d'utilisateur HTTP Basic | | `AUTH_PASSWORD_HASH` | _(non defini = auth off)_ | hash bcrypt du mot de passe | | `MAX_UPLOAD_MB` | 2048 | taille max d'un nuage uploade | | `STATS_SAMPLE_CAP` | 50000 | nb de points echantillonnes pour estimer le pas spatial initial | | `CC_TIMEOUT_MS` | 600000 | timeout par appel CloudCompare CLI | ## Formats supportes `.las`, `.laz`, `.copc.laz` (lu comme un LAZ standard — l'indexation octree COPC n'est pas utilisee, seuls les points le sont), `.bin` (format natif CloudCompare). **E57 non supporte pour l'instant** (exclu volontairement du perimetre initial). ## Architecture - **Backend** : NestJS (TypeScript), file d'attente de jobs en memoire (un run a la fois), SQLite (`better-sqlite3`) pour l'historique des runs. - **Orchestration du pipeline CloudCompare** : chaque etape (estimation stats, conversion, decimation, volume, export) est un appel independant au CLI `CloudCompare -SILENT ...` (le CLI fonctionne comme une machine a etats sur UNE session de process, donc chaque etape = un process). - **Scripts Python** (`python/`) : `stats_helper.py` (distance au plus proche voisin via scipy KD-tree), `render_images.py` (cartes de hauteur + graphiques de synthese via matplotlib/numpy), `las_to_xyz.py` (conversion LAS/LAZ/COPC -> ASCII, voir "Limitation connue" ci-dessous). - **Frontend** : HTML/JS vanilla + Tailwind (CDN), servi en statique par NestJS. Pas de framework, pas de build front separe. - **Image Docker** : `debian:trixie-slim` + paquet apt `cloudcompare` (2.13.2, evite une compilation depuis les sources) + `xvfb` (CloudCompare reste une appli Qt, meme en `-SILENT` elle a besoin d'un display) + Node 20 + Python 3. ## Limitations connues - **Le paquet apt `cloudcompare` (Debian) ne fournit pas le plugin LAS/LAZ** (uniquement le plugin "Core I/O"). Consequence : les fichiers LAS/LAZ/COPC sont convertis en ASCII XYZ via `laspy` (Python) *avant* d'etre transmis a CloudCompare, qui lit l'ASCII nativement sans plugin. Ca fonctionne bien mais perd les attributs LAS annexes (intensite, classification, RGB...) — non utilises par ce calcul de volume de toute facon. - **Le meme paquet plante (assertion GDAL) sur l'export GeoTIFF natif** (`-RASTERIZE -OUTPUT_RASTER_Z`). Les cartes de hauteur sont donc reconstruites nous-memes (export ASCII du nuage + binning numpy/scipy + rendu matplotlib) plutot que de dependre du rasterizer CloudCompare. - Le plan de reference Z et le pas de grille de volume sont **approximatifs** quand ils sont auto-detectes (calcules sur un echantillon aleatoire de points, pas la totalite du nuage) — largement suffisant pour comparer les 6 niveaux entre eux, mais a corriger via l'override si une valeur exacte est necessaire. - Un seul run traite a la fois (file d'attente sequentielle) : adapte a un usage local mono-utilisateur, pas concu pour un usage concurrent multi-utilisateurs. ## Developpement local (sans Docker) Necessite CloudCompare installe localement + Python 3 avec `pip install -r python/requirements.txt`. ```bash npm install npm run lint # eslint npm run typecheck # tsc --noEmit npm run test # jest (tests unitaires) npm run build CLOUDCOMPARE_BIN="/chemin/vers/CloudCompare" PYTHON_BIN=python3 DATA_DIR=./data node dist/main.js ``` Voir [CLAUDE.md](CLAUDE.md) pour le detail des comportements CloudCompare CLI verifies empiriquement (pieges non documentes, limitations du paquet apt Debian, etc.) — a lire avant de toucher au pipeline.