twanghcmut's picture
download
raw
5.68 kB
#!/usr/bin/env python
"""Mask stage variant for a SECOND object per episode, with an explicit prompt.
`run_object_pipeline.py`'s own `mask` stage always derives its SAM 3.1 prompt
from the episode's `current_task` sentence (`TaskPromptDeriver`), which only
ever targets the sentence's primary/direct-object noun phrase (e.g. "brick"
for "Put brick in drawer shelf and close drawer") -- there is no CLI override
for a *second* object like the drawer/shelf mentioned later in the same
sentence. This script is exactly `run_mask()`'s body, with the prompt
candidate list passed in explicitly instead of derived, and writing the same
on-disk layout (`1_mask/{meta.json,mask_frame0.npz,frame0_bgr.png,crop_*}`) so
`run_object_pipeline.py --stages mesh,align` can be pointed at this script's
`--outputs` directory afterwards and just work, unmodified.
Usage:
PYTHONPATH=src python scripts/mask_object_manual.py \\
--episode <uuid> --camera ext1 --clip 0:11 \\
--prompts "drawer shelf,drawer,cabinet drawer,shelf" \\
--outputs outputs/<uuid>/objects_shelf/0_11
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
import cv2
import numpy as np
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "src"))
from fpgm.objects import crop # noqa: E402
from fpgm.pipeline.frames import ClipFrameSource # noqa: E402
from fpgm.types import DataError # noqa: E402
from fpgm.utils.logging import get_logger, setup_logging # noqa: E402
# Reuse run_object_pipeline.py's private clip-context/metadata loaders instead
# of duplicating them -- same trick as scripts/render_fixed_initial_view.py.
import importlib.util
_spec = importlib.util.spec_from_file_location(
"run_object_pipeline", REPO_ROOT / "scripts" / "run_object_pipeline.py"
)
rop = importlib.util.module_from_spec(_spec)
sys.modules[_spec.name] = rop
_spec.loader.exec_module(rop)
logger = get_logger("mask_object_manual")
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--episode", required=True)
p.add_argument("--camera", default="ext1", choices=["ext1", "ext2"])
p.add_argument("--clip", required=True)
p.add_argument("--prompts", required=True, help="comma-separated SAM 3.1 prompt candidates, tried in order")
p.add_argument("--outputs", type=Path, required=True)
p.add_argument("--device", default="cuda")
return p.parse_args()
def main() -> int:
args = parse_args()
setup_logging()
from fpgm.config import SegmentationConfig
from fpgm.segmentation.sam3 import Sam3VideoSegmenter
candidates = [c.strip() for c in args.prompts.split(",") if c.strip()]
out_dir = rop.ensure_dir(rop.stage_dir(args.outputs, "mask"))
ctx = rop._load_clip_context(args.episode, args.camera, args.clip)
logger.info("clip %s: manual prompt candidates %s", args.clip, candidates)
seg_cfg = SegmentationConfig()
segmenter = Sam3VideoSegmenter(seg_cfg, device=args.device)
video_start = int(ctx.timing.clip_frame_to_video_frame(0))
video_end = int(ctx.timing.clip_frame_to_video_frame(ctx.timing.n_frames))
with ClipFrameSource(
str(ctx.mp4_path), video_start, video_end, stride=ctx.timing.annotation_stride
) as frames:
width, height = frames.resolution
frame0_bgr = frames.read(0)
cv2.imwrite(str(out_dir / "frame0_bgr.png"), frame0_bgr)
masklets = None
obj_id: int | None = None
used_prompt = ""
for prompt in candidates:
segmenter.start_session(str(frames.frame_dir))
try:
segmenter.add_text_prompt(seg_cfg.prompt_frame_idx, prompt)
found = segmenter.collect_masklets()
if not found:
logger.info("prompt %r matched nothing; trying next candidate", prompt)
continue
obj_id = segmenter.select_object(found)
masklets, used_prompt = found, prompt
break
finally:
segmenter.close_session()
if masklets is None or obj_id is None:
raise DataError(f"mask stage: SAM 3.1 found nothing for any of {candidates}")
masklet = masklets[obj_id]
if 0 not in masklet.frames or not masklet.frames[0].any():
raise DataError(
f"mask stage: object mask is empty on frame 0 of clip {args.clip}; frame 0 "
"is the only frame with dense depth, so reconstruction cannot proceed -- "
"try a different --clip"
)
mask_frame0 = masklet.frames[0]
best_frame = crop.select_best_frame(masklet, prefer="largest")
best_frame_bgr = frames.read(best_frame)
obj_crop = crop.crop_from_mask(
best_frame_bgr, masklet.frames[best_frame], frame_idx=best_frame, prompt=used_prompt
)
crop.save_debug(obj_crop, out_dir, frame_bgr=best_frame_bgr)
np.savez_compressed(out_dir / "mask_frame0.npz", mask=mask_frame0)
meta = {
"episode": args.episode,
"camera": args.camera,
"camera_serial": ctx.serial,
"clip": args.clip,
"prompt": used_prompt,
"obj_id": int(obj_id),
"video_resolution": [width, height],
"best_frame": int(best_frame),
"n_masklets": len(masklets),
}
(out_dir / "meta.json").write_text(json.dumps(meta, indent=2))
print(f"used_prompt={used_prompt!r} -> {out_dir}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

Xet Storage Details

Size:
5.68 kB
·
Xet hash:
5a850e523f93aee7d19622ff1482c22e93234dd2d4259d8a5e36f645e306b0ab

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