- 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).
- dispatcher.py : ffmpeg lancé en background (setsid), polling du
nombre de frames toutes les 5s → mise à jour du champ `progress`
en DB. ffprobe estime le total avant lancement pour calculer le %.
- _jobs_table.html : barre de progression visible sur les jobs
en status extracting/running
- style.css : styles .prog-wrap/.prog-fill/.prog-text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ingest.py : --remote-host <alias> pour scanner/exiftool via SSH, stocke
les chemins avec préfixe "alias:" pour que le worker sache puller direct
- dispatcher.py : scp_to_worker détecte "host:path" et fait pull remote
(worker → source host) au lieu du double hop via dispatcher
- _path_basename gère les paths préfixés pour ffmpeg
Permet d'ingester les vidéos depuis n'importe quelle machine accessible
en SSH sans passer 145GB par le conteneur FastAPI.
Le viser de demo.py était tué dès que le PLY était écrit (pour libérer la VRAM),
donc les liens dans le dashboard menaient vers ERR_CONNECTION_REFUSED.
Ajout d'un viewer standalone indépendant :
- scripts/viser_ply.py : charge un PLY via open3d + sert via viser (sans GPU),
subsample random à 2M pts max pour rester fluide
- app/main.py : routes POST /jobs/{id}/view et /stitches/{id}/view qui scp
le script sur le worker et lancent un viser détaché (nohup+setsid+disown via
wrapper shell déposé sur le worker)
- templates : remplace <a href> par <button class=viewer-btn> qui POST puis
window.open de l'URL retournée
- Dockerfile : copie scripts/ dans l'image (nécessaire pour scp-er viser_ply.py)
Quand un seul PLY est disponible (1 segment, 1 caméra), aucun alignement
n'est nécessaire. On copie directement le PLY en output et on marque la
stitch done plutôt qu'error. Cross-AUV est quand même tenté si conditions remplies.
- dispatcher: scp du MP4 source vers le worker avant ffmpeg (les chemins .82 ne sont pas accessibles côté .87)
- dispatcher: wrapper shell autour de demo.py pour killer viser dès que le PLY est écrit (setsid + pkill -f frames_dir)
- dispatcher: PLY_ok fallback — accepte rc!=0 si le PLY existe et a une taille > 0
- dispatcher: fallback frame_count abaissé à 150 pour l'estimation VRAM
- ingest: strip du suffixe timezone (+00:00) des timestamps exiftool QuickTimeUTC=1
Testé bout-en-bout sur GX010001.MP4 (70 frames, 10.6M pts PLY, VRAM peak 9.4 GB, kill viser OK).
- nvidia-smi : +temperature.gpu + power.draw
- UI : tags °C / W / espace disque libre
- Dispatcher heartbeat toutes les 4s → point vert/rouge en haut du monitor
- Fix Docker SSH : copie + chmod 600 au démarrage (Bad owner)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Table stitches (per_auv + cross_auv) avec cancel/retry API
- Dispatcher : PLY export auto (--save_ply), trigger stitch en cascade
quand tous les jobs d'un AUV sont done
- UI : section stitch live depuis DB avec statuts/durées/boutons
- Fix : <base href="/cosma-qc/"> + chemins relatifs pour Caddy subpath
- open3d 0.19.0 installé sur gpu (.87)
- SSH key .82→.87 configurée, alias gpu ajouté sur .82
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>