From 468f9084ec25b10d80f99f5259e449ae78cc1c5b Mon Sep 17 00:00:00 2001 From: Poulpe Date: Tue, 21 Apr 2026 13:09:48 +0000 Subject: [PATCH] =?UTF-8?q?viewer=20on-demand=20=E2=80=94=20relancer=20vis?= =?UTF-8?q?er=20=C3=A0=20la=20demande=20depuis=20le=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 par {% endif %} {{ j._duration }} @@ -56,7 +56,7 @@ {% else %}merge final{% endif %} {% if s._duration %} — {{ s._duration }}{% endif %} {% if s.status == 'done' and s.output_ply %} - PLY + {% endif %} {% if s.status in ('queued','running') %} diff --git a/app/templates/index.html b/app/templates/index.html index aab473e..4b92445 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -24,5 +24,24 @@

Chargement…

+ + diff --git a/scripts/viser_ply.py b/scripts/viser_ply.py new file mode 100644 index 0000000..05c810d --- /dev/null +++ b/scripts/viser_ply.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Minimal standalone viser viewer for a single PLY point cloud. + +Usage: + python3 viser_ply.py path/to/cloud.ply --port 8200 +""" +from __future__ import annotations + +import argparse +import sys +import time + +import numpy as np + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("ply", help="PLY file to visualize") + ap.add_argument("--port", type=int, default=8200) + ap.add_argument("--point-size", type=float, default=0.01) + ap.add_argument("--downsample", type=float, default=0.0, + help="Voxel downsample size (0 = no downsample)") + ap.add_argument("--max-points", type=int, default=2_000_000, + help="Random subsample to this many points if cloud is larger") + args = ap.parse_args() + + try: + import open3d as o3d + import viser + except ImportError as e: + sys.exit(f"missing dep: {e}") + + pcd = o3d.io.read_point_cloud(args.ply) + if args.downsample > 0: + pcd = pcd.voxel_down_sample(args.downsample) + pts = np.asarray(pcd.points, dtype=np.float32) + cols = np.asarray(pcd.colors, dtype=np.float32) if pcd.has_colors() else np.ones_like(pts) * 0.7 + if len(pts) > args.max_points: + idx = np.random.choice(len(pts), args.max_points, replace=False) + pts, cols = pts[idx], cols[idx] + print(f"loaded {len(pts):,} pts from {args.ply}", flush=True) + + server = viser.ViserServer(host="0.0.0.0", port=args.port) + server.scene.add_point_cloud( + "/cloud", points=pts, colors=(cols * 255).astype(np.uint8), point_size=args.point_size + ) + print(f"viser listening on *:{args.port}") + while True: + time.sleep(60) + + +if __name__ == "__main__": + main()