twanghcmut/backup-foundation-physics / scripts /export_zeroshot_control.py
twanghcmut's picture
download
raw
3.92 kB
#!/usr/bin/env python
"""Write the trainer's own composited RGB control video for one window, standalone.
Exists because a zero-shot probe of the pretrained VACE checkpoint must be fed
*the same tensor the trainer will feed it*, not a proxy. The first probe run in
this project passed ``control_depth.mkv`` alone and produced a false-colour
depth passthrough -- but the trainer never sees that file on its own:
``fpgm.training.types.BundleAssemblyConfig`` records that VACE's branch has one
``vace_patch_embedding`` taking one ``vace_video``, so the three control
channels are composited into a single RGB video (R=depth, G=seg id normalised,
B=normal-Z) before they reach it. That probe therefore tested a strictly poorer
input than training uses, and its result cannot be carried over.
This reuses :func:`fpgm.training.bundle_io.load_window_arrays` -- the trainer's
actual decode+composite path -- rather than reimplementing the channel order or
the seg normalisation, so the probe cannot silently drift from the trainer.
Written as FFV1-in-Matroska (lossless). H.264 would be the obvious choice for
something a video model reads, but the G channel is *segmentation ids*, and
:class:`fpgm.viz.video.LosslessWriter`'s own docstring records why that is
unsafe: lossy ringing shifting an id by +-1 relabels robot pixels as object
pixels. decord decodes FFV1/mkv fine here (verified against this window's own
``control_depth.mkv``).
Usage:
PYTHONPATH=src python scripts/export_zeroshot_control.py \\
--window outputs/datagen/<uuid>/<cam>/vace/window_00000_00081 \\
--out outputs/zeroshot/control_composite.mkv
"""
from __future__ import annotations
import argparse
import subprocess
from pathlib import Path
import numpy as np
from fpgm.training.bundle_io import load_window_arrays
from fpgm.training.manifest import _parse_window_dir
from fpgm.training.types import BundleAssemblyConfig
from fpgm.viz.video import _resolve_ffmpeg
def write_ffv1_rgb(frames: np.ndarray, out: Path, fps: int = 16) -> None:
"""``(T, H, W, 3)`` uint8 -> lossless FFV1 in Matroska via an ffmpeg pipe.
``fpgm.viz.video.LosslessWriter`` is grayscale-only by construction, so this
uses the same codec/container through its own pipe rather than widening a
class whose docstring is explicitly about single-channel integer data.
"""
t, h, w, _ = frames.shape
out.parent.mkdir(parents=True, exist_ok=True)
cmd = [
_resolve_ffmpeg(), "-y", "-hide_banner", "-loglevel", "error",
"-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", str(fps),
"-i", "pipe:0", "-c:v", "ffv1", "-level", "3", "-pix_fmt", "gbrp",
str(out),
]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
assert p.stdin is not None
try:
for i in range(t):
p.stdin.write(frames[i].tobytes())
finally:
p.stdin.close()
if p.wait() != 0:
raise RuntimeError(f"ffmpeg failed writing {out}")
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--window", required=True, type=Path)
ap.add_argument("--out", required=True, type=Path)
ap.add_argument("--fps", type=int, default=16)
args = ap.parse_args()
sample = _parse_window_dir(args.window)
cfg = BundleAssemblyConfig()
arrays = load_window_arrays(sample, cfg)
control = arrays["control"]
write_ffv1_rgb(control, args.out, fps=args.fps)
per_channel = control.reshape(-1, 3).mean(axis=0).round(2)
print(f"window: {args.window}")
print(f"control: {control.shape} {control.dtype} (R=depth, G=seg/{cfg.max_seg_id}, B=normal-Z)")
print(f"channel mean R/G/B: {per_channel}")
print(f"nonzero seg pixels: {(control[..., 1] > 0).mean():.4%}")
print(f"caption: {arrays['caption']}")
print(f"wrote: {args.out}")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
3.92 kB
·
Xet hash:
e99e2a036eb33fe6231b03101ddede6bcd9d77ed1872e22c42270e7bd2ca34f6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.