Buckets:
| #!/usr/bin/env python | |
| """MoGe-2 depth over every clip of the ``twanghcmut/droid-sample`` bucket. | |
| Unlike ``moge_run_clips.py`` (which serves the 10 processed episodes of the | |
| datagen bucket), this targets the 56-clip diversity sample: 10 labs, 9 object | |
| classes, 23.7k frames, clips from 117 to 2449 frames. That size forces three | |
| choices, each of which was measured rather than assumed: | |
| 1. **No per-frame ``.npz``.** At ~5.6 MB/frame compressed the cache would be | |
| ~133 GB, on a volume sitting at 99%. The panel video is the deliverable here; | |
| re-running inference is 2.7 GPU-hours, which is cheaper than keeping 133 GB | |
| around. ``moge_run_clips.py`` still caches, because 10 clips is 5 GB. | |
| 2. **Depth kept in RAM, not spilled to disk.** fp16 depth (1.8 MB/frame) plus the | |
| already-colourised normal panel (2.8 MB/frame) is 4.6 MB/frame -- 11 GB for the | |
| longest clip, against ~580 GB free. RGB is NOT buffered; the video is simply | |
| decoded a second time for the render pass, which costs nothing next to the | |
| forward pass. Spilling instead would have written ~109 GB through an array that | |
| a concurrent training job is already I/O-bound on. | |
| 3. **Global depth range, computed after the full pass -- NOT estimated from a | |
| prefix.** Estimating the colour range from the first 30 frames was tried and | |
| rejected on measured evidence: across the 10 datagen clips it moved the p2 | |
| bound by up to +154 mm and the near-field lower bound by up to +106 mm, because | |
| early frames predate the arm coming close to the camera, so the near extreme is | |
| simply absent from the prefix. A range that wrong makes the near-field panel | |
| misleading for most of the clip, which defeats its whole purpose. | |
| Env: ``moge``. Usage: | |
| CUDA_VISIBLE_DEVICES=2 /home/quang/miniconda3/envs/moge/bin/python -u \\ | |
| scripts/moge_run_sample_bucket.py --index <index.csv> [--shard 0 2] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| REPO_ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(REPO_ROOT / "scripts")) | |
| from moge_render_panels import ( # noqa: E402 | |
| TRAJECTORY_FPS, colorize_depth, colorize_normal, label, write_h264, | |
| ) | |
| _PERCENTILES = (2.0, 98.0) | |
| def label2(img: np.ndarray, line1: str, line2: str) -> np.ndarray: | |
| out = label(img, line1) | |
| for colour, thick in ((0, 0, 0), 4), ((235, 235, 235), 1): | |
| cv2.putText(out, line2, (14, 68), cv2.FONT_HERSHEY_SIMPLEX, 0.62, colour, | |
| thick, cv2.LINE_AA) | |
| return out | |
| def resolve_video(uuid: str, videos_root: Path) -> Path: | |
| hits = sorted(videos_root.glob(f"{uuid}/recordings/MP4/*.mp4")) | |
| if len(hits) != 1: | |
| raise FileNotFoundError(f"{uuid}: expected exactly 1 mp4, found {len(hits)}") | |
| return hits[0] | |
| def run_clip(model, row: dict, videos_root: Path, out_root: Path, | |
| device: torch.device, near_pcts: tuple[float, float], | |
| resolution_level: int, fps: float, skip_existing: bool, | |
| save_npz: bool) -> dict: | |
| uuid = row["uuid"] | |
| out_dir = out_root / uuid | |
| npz_dir = out_dir / "per_frame" | |
| stats_p = out_dir / "stats.json" | |
| if skip_existing and stats_p.exists() and (out_dir / "panels_2x2.mp4").exists(): | |
| print(f"=== {uuid} :: already done, skipping") | |
| return json.loads(stats_p.read_text()) | |
| video = resolve_video(uuid, videos_root) | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| if save_npz: | |
| npz_dir.mkdir(parents=True, exist_ok=True) | |
| cap = cv2.VideoCapture(str(video)) | |
| n_reported = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| n_index = int(row.get("frames") or 0) | |
| print(f"=== {uuid} :: {w}x{h}, {n_reported} frames " | |
| f"({'matches' if n_reported == n_index else f'INDEX SAYS {n_index}'}) " | |
| f"| {row.get('lab')} / {row.get('object_class')}") | |
| depths: list[np.ndarray] = [] | |
| normals: list[np.ndarray] = [] | |
| t = 0 | |
| t0 = time.time() | |
| with torch.inference_mode(): | |
| while True: | |
| ok, frame_bgr = cap.read() | |
| if not ok: | |
| break | |
| rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) | |
| image = torch.tensor(rgb / 255.0, dtype=torch.float32, | |
| device=device).permute(2, 0, 1) | |
| out = model.infer(image, resolution_level=resolution_level) # no fov_x | |
| depth = out["depth"].float().cpu().numpy() | |
| mask = out["mask"].cpu().numpy().astype(bool) | |
| normal = out["normal"].float().cpu().numpy() if "normal" in out else None | |
| if save_npz: | |
| payload = {"depth": depth.astype(np.float32), "mask": mask, | |
| "intrinsics": out["intrinsics"].float().cpu().numpy()} | |
| if normal is not None: | |
| payload["normal"] = normal.astype(np.float16) | |
| np.savez_compressed(npz_dir / f"frame_{t:05d}.npz", **payload) | |
| depths.append(depth.astype(np.float16)) | |
| normals.append(colorize_normal(normal, mask) if normal is not None | |
| else np.zeros((h, w, 3), np.uint8)) | |
| t += 1 | |
| cap.release() | |
| n = len(depths) | |
| dt = time.time() - t0 | |
| if n == 0: | |
| raise RuntimeError(f"{uuid}: decoded 0 frames from {video}") | |
| pool = np.concatenate([d[np.isfinite(d)].astype(np.float32).ravel()[::17] | |
| for d in depths]) | |
| lo, hi = np.percentile(pool, _PERCENTILES) | |
| nlo, nhi = np.percentile(pool, near_pcts) | |
| mmpl, near_mmpl = (hi - lo) * 1000 / 255, (nhi - nlo) * 1000 / 255 | |
| print(f" {n} frames in {dt:.1f}s ({n / dt:.2f} fps) | global {lo:.3f}-{hi:.3f} m " | |
| f"({mmpl:.1f} mm/lvl) | near {nlo:.3f}-{nhi:.3f} m ({near_mmpl:.1f} mm/lvl)") | |
| instr = (row.get("instruction_1") or row.get("current_task") or "")[:78] | |
| def frames(): | |
| cap2 = cv2.VideoCapture(str(video)) | |
| try: | |
| for i in range(n): | |
| ok, rgb_bgr = cap2.read() | |
| if not ok: | |
| break | |
| d32 = depths[i].astype(np.float32) | |
| valid = np.isfinite(d32) | |
| tl = label2(rgb_bgr, f"RGB {row.get('lab')} / {row.get('object_class')}", | |
| instr) | |
| tr = label(colorize_depth(d32, lo, hi), | |
| f"MoGe-2 depth {lo:.2f}-{hi:.2f} m ({mmpl:.1f} mm/level)") | |
| bl = label(colorize_depth(d32, nlo, nhi), | |
| f"same depth, near-field range {nlo:.2f}-{nhi:.2f} m " | |
| f"({near_mmpl:.1f} mm/level)") | |
| br = label(colorize_normal(np.zeros((h, w, 3), np.float32), valid) | |
| if normals[i] is None else normals[i], "MoGe-2 normal") | |
| yield np.vstack([np.hstack([tl, tr]), np.hstack([bl, br])]) | |
| finally: | |
| cap2.release() | |
| out_video = out_dir / "panels_2x2.mp4" | |
| write_h264(frames(), out_video, fps) | |
| depths.clear() | |
| normals.clear() | |
| stats = { | |
| "uuid": uuid, "lab": row.get("lab"), "object_class": row.get("object_class"), | |
| "task_class": row.get("task_class"), "instruction": row.get("instruction_1"), | |
| "video": str(video.relative_to(REPO_ROOT)), | |
| "width": w, "height": h, "n_frames_processed": n, | |
| "n_frames_index_csv": n_index, "output_fps": fps, | |
| "seconds": round(dt, 2), "fps_inference": round(n / dt, 3), | |
| "depth_range_m": [float(lo), float(hi)], | |
| "depth_mm_per_colour_level": round(mmpl, 2), | |
| "near_field_percentiles": list(near_pcts), | |
| "near_field_range_m": [float(nlo), float(nhi)], | |
| "near_field_mm_per_colour_level": round(near_mmpl, 2), | |
| "resolution_level": resolution_level, | |
| "panels_video": str(out_video.relative_to(REPO_ROOT)), | |
| "preview_bytes": out_video.stat().st_size, | |
| } | |
| stats_p.write_text(json.dumps(stats, indent=2)) | |
| return stats | |
| def main() -> None: | |
| ap = argparse.ArgumentParser(description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument("--index", type=Path, required=True, help="the bucket's index.csv") | |
| ap.add_argument("--videos-root", type=Path, default=REPO_ROOT / "data/droid_coverage56") | |
| ap.add_argument("--out-root", type=Path, default=REPO_ROOT / "outputs/moge/sample_bucket") | |
| ap.add_argument("--model", default="Ruicheng/moge-2-vitl-normal") | |
| ap.add_argument("--resolution-level", type=int, default=9) | |
| ap.add_argument("--near-percentiles", type=float, nargs=2, default=(25.0, 60.0)) | |
| ap.add_argument("--fps", type=float, default=TRAJECTORY_FPS) | |
| ap.add_argument("--shard", type=int, nargs=2, default=(0, 1), metavar=("I", "N"), | |
| help="process clips i, i+N, i+2N... Round-robin, so long clips " | |
| "spread evenly across shards instead of piling into one.") | |
| ap.add_argument("--skip-existing", action=argparse.BooleanOptionalAction, default=True) | |
| ap.add_argument("--save-npz", action=argparse.BooleanOptionalAction, default=True, | |
| help="keep the full per-frame arrays (default: on)") | |
| ap.add_argument("--uuid", nargs="*", default=None, | |
| help="run only these uuid(s); default: every row of the index") | |
| args = ap.parse_args() | |
| rows = list(csv.DictReader(args.index.open())) | |
| if args.uuid: | |
| wanted = set(args.uuid) | |
| found = {r["uuid"] for r in rows} & wanted | |
| if wanted - found: | |
| raise SystemExit(f"uuid(s) not in index: {sorted(wanted - found)}") | |
| rows = [r for r in rows if r["uuid"] in wanted] | |
| i, nsh = args.shard | |
| mine = rows[i::nsh] | |
| print(f"shard {i}/{nsh}: {len(mine)} of {len(rows)} clips, " | |
| f"{sum(int(r.get('frames') or 0) for r in mine)} frames") | |
| from moge.model.v2 import MoGeModel | |
| device = torch.device("cuda") | |
| print(f"loading {args.model} ...") | |
| model = MoGeModel.from_pretrained(args.model).to(device).eval() | |
| all_stats = [] | |
| for row in mine: | |
| try: | |
| all_stats.append(run_clip(model, row, args.videos_root, args.out_root, | |
| device, tuple(args.near_percentiles), | |
| args.resolution_level, args.fps, | |
| args.skip_existing, args.save_npz)) | |
| except Exception as exc: | |
| print(f" FAILED {row['uuid']}: {type(exc).__name__}: {exc}") | |
| all_stats.append({"uuid": row["uuid"], | |
| "error": f"{type(exc).__name__}: {exc}"}) | |
| args.out_root.mkdir(parents=True, exist_ok=True) | |
| out = args.out_root / f"summary_shard{i}of{nsh}.json" | |
| out.write_text(json.dumps({"model": args.model, | |
| "resolution_level": args.resolution_level, | |
| "clips": all_stats}, indent=2)) | |
| print(f"wrote {out}") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 11.1 kB
- Xet hash:
- 3a8370f5c9cb743152e148341b2950b415e8d1f4b879aad67312eb99bc2eca3d
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.