script-volume-auto/README.md
Nicolas Fryder 7f2e4c323b Auth HTTP Basic + grille de volume adaptative par niveau
- Grille de calcul du volume adaptee dynamiquement au pas spatial de chaque niveau (au lieu d'une
  grille fixe derivee du niveau 0) : evite l'effondrement artificiel du "matching cells %" observe
  des le premier niveau de decimation (99% -> 58% -> 13% -> 3% -> 0.9% -> 0.2%), qui refletait un
  desalignement grille/densite plutot qu'une vraie perte d'information.
- Authentification HTTP Basic sur toute l'app (UI + API), identifiants en env (AUTH_USERNAME /
  AUTH_PASSWORD_HASH hashe bcrypt), desactivable en dev local si non definis. /health reste public
  pour le healthcheck Docker/Coolify.
2026-07-10 14:34:27 +02:00

166 lines
8.8 KiB
Markdown

# Volume 2.5D & robustesse a la decimation
Application locale (Docker) qui, a partir d'un nuage de points (LAS/LAZ/COPC.laz/BIN) :
1. calcule son volume en 2.5D via CloudCompare (plan de reference = altitude minimale du nuage) ;
2. le 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 le **meme plan de reference**, et une **grille de calcul
adaptee dynamiquement a la densite de chaque niveau** (grid step = pas spatial du niveau x2) pour
eviter des grilles artificiellement vides quand la decimation depasse la resolution de depart ;
4. assemble les 6 nuages (complet + 5 decimations) 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, 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 un fichier `.las`, `.laz`, `.copc.laz` ou `.bin`.
2. Optionnel : override du plan de reference Z (par defaut : altitude minimale d'un echantillon du
nuage) et/ou du pas spatial initial (par defaut : distance mediane au plus proche voisin).
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 6 niveaux (points, volume, surface, % de cellules de grille
"matching"), cartes de hauteur, 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 (nuage / plan de reference) sont effectivement comparables. Quand la
decimation depasse la resolution de la grille de calcul, ce pourcentage chute — c'est le signal
principal de perte de robustesse. Un niveau est marque "a verifier" des que ce taux passe sous 90 %
(configurable via `MATCHING_CELLS_WARN_THRESHOLD` cote code, `config.ts`).
- La decimation s'arrete automatiquement si un niveau tombe sous 25 points exploitables ; les niveaux
suivants sont marques "skipped" avec la raison.
## 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.