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
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Open viser viewer with all PLYs from one AUV.
|
|
|
|
Usage:
|
|
viser_auv.py --ply-dir /path/to/auv/ply --port 9210
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--ply-dir", required=True)
|
|
ap.add_argument("--port", type=int, default=9210)
|
|
ap.add_argument("--point-size", type=float, default=0.01)
|
|
ap.add_argument("--max-points-per-ply", type=int, default=1_500_000)
|
|
args = ap.parse_args()
|
|
|
|
try:
|
|
import open3d as o3d
|
|
import viser
|
|
except ImportError as e:
|
|
sys.exit(f"missing dep: {e}")
|
|
|
|
ply_dir = Path(args.ply_dir)
|
|
plys = sorted(ply_dir.glob("**/*.ply"))
|
|
print(f"Found {len(plys)} PLY files in {ply_dir}", flush=True)
|
|
if not plys:
|
|
sys.exit("no PLY found")
|
|
|
|
server = viser.ViserServer(host="0.0.0.0", port=args.port)
|
|
palette = [
|
|
(1.0, 0.30, 0.30), (0.30, 1.0, 0.30), (0.30, 0.55, 1.0),
|
|
(1.0, 0.85, 0.20), (1.0, 0.30, 1.0), (0.30, 1.0, 1.0),
|
|
(1.0, 0.55, 0.20), (0.55, 0.30, 1.0),
|
|
]
|
|
|
|
for i, p in enumerate(plys):
|
|
pcd = o3d.io.read_point_cloud(str(p))
|
|
pts = np.asarray(pcd.points, dtype=np.float32)
|
|
if len(pts) == 0:
|
|
print(f" ! {p.name}: empty", flush=True)
|
|
continue
|
|
if pcd.has_colors():
|
|
cols = np.asarray(pcd.colors, dtype=np.float32)
|
|
else:
|
|
cols = np.tile(palette[i % len(palette)], (len(pts), 1)).astype(np.float32)
|
|
if len(pts) > args.max_points_per_ply:
|
|
idx = np.random.choice(len(pts), args.max_points_per_ply, replace=False)
|
|
pts = pts[idx]
|
|
cols = cols[idx]
|
|
# viser wants uint8 colors
|
|
cols_u8 = (cols * 255).clip(0, 255).astype(np.uint8)
|
|
name = f"/{p.parent.name}_{p.stem}"
|
|
server.scene.add_point_cloud(
|
|
name=name, points=pts, colors=cols_u8, point_size=args.point_size
|
|
)
|
|
print(f" + {p.name}: {len(pts):,} pts", flush=True)
|
|
|
|
print(f"Viser ready on port {args.port}", flush=True)
|
|
while True:
|
|
time.sleep(60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|