Iter-1 patch (thresholds.yaml bottom_visible_pct_min 50→30) had zero effect:
04_frame_extract.py and 04b_trim_water.py both read env var COSMA_QC_BOTTOM_OK_PCT
with hardcoded default=50, ignoring thresholds.yaml entirely.
Add _load_bottom_ok_pct() loader in both stages: reads thresholds.yaml first,
falls back to COSMA_QC_BOTTOM_OK_PCT env var, then hardcoded 50.
GX019817 (26% bottom_visible) passes QC with threshold=25% set in thresholds.yaml.
Stage 04 frame extract:
- New lib_frame_qc.py: per-frame Laplacian/contrast/blue-dominance scoring
- Classes: bottom_visible / water_no_bottom / turbid_water / out_of_water
- Sample 1/5 frames after extraction, write qc.json per segment
- Record metrics (frames_total, frames_bottom_visible, bottom_visible_pct)
- Mark job degraded when bottom_visible_pct < 50%
Per-AUV viser view:
- scripts/viser_auv.py loads all PLYs of an AUV, color per file
- POST /pipeline/missions/{id}/auvs/{auv}/view rsyncs ply -> worker
- launches viser on hashed port 9300+, returns URL
- _pipeline.html exposes AUV list, JS handler opens viser tab
- db(): timeout=30 + PRAGMA busy_timeout=5000 pour éviter locked sous charge parallèle
- _ssh_via(): helper routant SSH z620 via worker (.82→worker→z620)
- _extract_on_remote_host(): toutes les commandes z620 passent via worker
- resume probe: ffprobe z620 également via worker
- reset jobs 11/17/18/20 en queued pour relancer
- dispatcher: col step ajoutee (migration). set a chaque phase:
scp N/M, ffmpeg N/M, trimming hors-eau, reconstruct demo.py
- _refresh_thumbnail() scp la DERNIERE frame extraite toutes les ~15s
pendant ffmpeg pour que le preview colle a la progression live
- template: cache-bust thumbnail via ?t=mtime, step affiche sous
progress bar, thumb revenue a 48x27
- CSS: .badge.busy -> animation spin (rotation infinie) au lieu de
juste une couleur, .step-text italique mute
1. _jobs_table.html: remplace la liste <li>flex par un vrai <table> avec
colonnes explicites: status · preview · #id · AUV+GP · label · duree
video · frames · hors-eau · progression · temps · actions. Stitches
restent en <ul> compacte.
2. main.py _build_acquisitions: probe TCP le viser_url avec cache 8s
avant de le passer au template. Si port mort -> d[viser_url]=None ->
pas de bouton affiche. Fini les liens qui mennent a rien.
3. style.css: purge des regles flex conflictuelles, rewrite propre pour
table.jobs-table, badges, prog-bar, btn-viser direct link.
dispatcher scp frame_*.jpg (premiere apres trim head) vers
/var/lib/cosma-qc/thumbnails/job_N.jpg a la fin de do_extract.
Endpoint GET /jobs/{id}/thumbnail serve via FileResponse. Template:
<img class=thumb src=jobs/N/thumbnail> si has_thumbnail. 48x27 px,
object-fit cover.
Backfill manuel des jobs deja done (9, 12, 13, 16, 19) via scp direct.
pick_worker trie les candidats par:
1. nombre de jobs deja assignes sur le worker (moins d abord)
2. VRAM free (plus d abord)
3. hostname (tiebreaker sans comparer les dicts)
Avant: le worker avec le plus de VRAM gagnait toujours (ex: .84 24GB vs
.87 12GB) donc tous les jobs empilaient sur .84 pendant que .87 idle.
fps=3 -> fps=2 via COSMA_QC_FPS dans dispatcher.env (cf user: 1 kt +
reconstruction SfM -> 2 fps pas stride 6).
Logs pick_worker ajoutes pour debug quand no candidate.
window_size selon len effectif (frame_count / stride):
<= 320 -> 16/2 (petit overhead)
<= 3000 -> 32/8
> 3000 -> 64/16 (moins de windows sur longues sequences)
Skip auto si video_duration_s < COSMA_QC_MIN_VIDEO_S (default 480s = 8min):
segments trop courts ne contiennent pas de plongee exploitable.
Reco user: videos < 8 min sont inutiles pour la reconstruction.
dashboard:
- job_id, AUV GP1/GP2 (serial en tooltip), segment_label, duree reelle,
nb frames, nb hors-eau trimes
- lien viser plain <a href> (plus de POST ni popup). Affiche uniquement
si job.done ET viser_url persistee (demo.py kept alive)
- CSS minimal: flex row, separateurs, skipped en italic mute
dispatcher:
- trim head ET tail (AUV hors-eau en debut + fin de session)
- migration DB: trimmed_head, trimmed_tail, video_duration_s
- do_extract persiste total_duration_s + trimmed counts via set_status
- run_one: RuntimeError(skipped_short) preserve le status=skipped
- min_frames underwater pour skip les segments trop courts
- ram_budget 0.45 -> 0.35 (OOM rc=137 avec 8237 frames sur 62GB RAM)
Les sessions record commencent systematiquement avec l AUV sur le pont
ou en surface. Les frames hors-eau polluent le model et bloquent l alignment
ICP du stitch.
trim_above_water_prefix detecte underwater par absorption du canal rouge
(mean_R < mean_G-5 ET mean_R < mean_B-5) et exige un streak consecutif
de 10 frames underwater pour lock le start. Tout ce qui precede est
supprime avant demo.py.
Opencv charge les frames en REDUCED_COLOR_4 pour acceleration.
Execute dans le venv lingbot-map cote worker (cv2 dispo).
Les segments auto-ingested peuvent contenir des vidéos non-exploitables
(GoPro sur le pont, hors-eau, bruyantes). Marquer le job skipped empeche
linclusion dans le stitch per_auv: total/done comptent hors-skipped, et
input_job_ids filtre status=done seulement.
Exemple: job 10 AUV209/10:58-11:05 = pull marron + gilet sauvetage sur
le pont pendant preparation, reconstruction 3D inutile pour le puzzle.
live button:
- POST /jobs/{id}/live
- Probe le port natif (worker viser_port_base + job_id) via nc
- 200 + viser_url si demo.py encore alive (necessite le patch keep-alive)
- 410 + fallback message si ferme
PLY button:
- POST /jobs/{id}/view (existant)
- Lance viser_ply.py standalone sur port VIEWER_PORT_BASE+id
Permet de choisir entre viser natif (PointCloudViewer de lingbot-map avec
camera frustums, filtre confiance interactif, animation) et viewer basic
XYZ+RGB uniquement.
1. Ne plus kill demo.py apres PLY saved: son viser/PointCloudViewer natif
(camera frustums, per-frame confidence filtering, animation) donne une
visu bcp plus propre que viser_ply.py standalone (XYZ+RGB seul).
Cout: ~6GB VRAM par job done garde alive jusquau prochain pick_worker
qui peut kill si besoin.
2. set_status clear auto le champ error quand status transitionne vers
extracting/running/done/queued: sinon les dashboards montrent les
erreurs historiques sur les jobs en cours de retry.
Bugs decouverts en live:
1. Les retries/restarts ne cleanaient pas frames_dir -> ffmpeg re-extrayait
par dessus les anciennes -> frame_count inflate (ex: 21991 au lieu de
11000) -> budget stride fausse -> OOM.
2. Budget 0.55*RAM laissait pas assez de headroom (OS + CUDA pinned buffers
+ autres processes) -> kill -9 a la limite. 0.45 plus conservateur.
Le wrapper viser cd /home/floppyrj45/ai-video/lingbot-map quel que soit le
worker. Sur .84 (user root) le path est /root/ai-video/lingbot-map, donc le
cd echouait + venv absent + open3d pas importe = viser ne demarrait jamais.
Utiliser worker[lingbot_path] qui est deja configure dans WORKERS.
Le cache src_*.MP4 sur les workers s empile: 12 fichiers pour 82 GB au pire.
Le thin pool LVM sur le host Proxmox est trop petit (810 GB pour 1144 GB
thick-provisionned) et se remplit a 100% en quelques heures de pipeline
-> I/O errors -> VMs auto-paused -> tout casse.
Fix: delete src_*.MP4 immediatement apres count_frames (les frames sont
deja extraites), puis fstrim en fin de job pour que le thin pool reclaim
les blocks immediatement via DISCARD/UNMAP.
Le setsid bash lance ffmpeg puis ecrit le code de retour:
echo $? > exit_file
Avec test -f on matchait le fichier pendant que le shell le creait vide
(write() du "> exit_file" cree le fichier avant fwrite). Resultat:
code_str="" -> isdigit()=False -> rc=1 -> ffmpeg failed false positive.
Fix: test -s (existe ET non-vide) pour attendre que echo ait termine.
- estimate_vram_mib: 3500+13*frames (absurde 110GB pour 8k frames) ? 6000 MiB fixe
(windowed+offload_to_cpu plafonne la VRAM indépendamment du total frames)
- do_reconstruct: stride adaptatif basé sur RAM worker (23GB .87, 62GB .84)
load_fn.py stack le tensor complet en CPU RAM (~3.15 MB/frame)
budget 55% RAM ? stride 2-3 pour jobs >4k frames
Debloque tous les jobs avant aucun ne pouvait passer pick_worker car
estimate dépassait la VRAM totale des GPUs.
- threading: main loop spawns run_one in a thread per queued job; up to len(WORKERS) concurrent.
- pick_worker: thread-safe VRAM reservation to avoid two threads picking the same GPU.
- pop_queued/pop_queued_stitch: atomic SELECT+UPDATE sous BEGIN IMMEDIATE (status claimed).
- Heartbeat daemon: thread qui ecrit dispatcher.heartbeat toutes les 5s (fini le faux dead pendant les jobs longs).
- run_one: libere la reservation VRAM sur finally (error/done/queued).