HorizonStream_Demo / infer.py
Chong CHENG
Update HorizonStream demo space
5a0b216
Raw
History Blame Contribute Delete
18.2 kB
import argparse
import copy
import os
import cv2
import yaml
from horizonstream.core.infer import run_inference_cfg
from horizonstream.utils.loop_assets import ensure_loop_assets, print_asset_warnings
def default_config_path() -> str:
return os.path.join(
os.path.dirname(__file__),
"configs",
"horizonstream_infer.yaml",
)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--config", default=default_config_path())
parser.add_argument("--img-path", default=None)
parser.add_argument("--video-path", default=None)
parser.add_argument("--fps", type=float, default=None, help="Target FPS for video frame sampling.")
parser.add_argument("--video-stride", type=int, default=None)
parser.add_argument("--video-scene-name", default=None)
parser.add_argument("--video-cache-root", default=None)
parser.add_argument("--video-overwrite", action="store_true")
parser.add_argument("--no-video-overwrite", action="store_true")
parser.add_argument("--seq-list", default=None)
parser.add_argument("--seq-match-mode", default=None, help="exact | contains")
parser.add_argument("--format", default=None, help="generalizable")
parser.add_argument("--data-roots-file", default=None)
parser.add_argument("--camera", default=None)
parser.add_argument("--output-root", default=None)
parser.add_argument("--abs-pose-source", choices=["online", "offline"], default=None)
parser.add_argument("--device", default=None)
parser.add_argument("--checkpoint", default=None)
parser.add_argument("--hf-repo", default=None)
parser.add_argument("--hf-file", default=None)
parser.add_argument("--window-size", type=int, default=None)
parser.add_argument("--sliding-size", type=int, default=None)
parser.add_argument("--offload-outputs-to-cpu", action="store_true")
parser.add_argument("--no-offload-outputs-to-cpu", action="store_true")
parser.add_argument("--offline-motion-averaging", action="store_true")
parser.add_argument("--no-offline-motion-averaging", action="store_true")
parser.add_argument("--rope-temporal-period", type=int, default=None)
parser.add_argument("--max-frames", type=int, default=None)
parser.add_argument("--max-full-pointcloud-points", type=int, default=None)
parser.add_argument("--max-frame-pointcloud-points", type=int, default=None)
parser.add_argument("--point-mask-sky", action="store_true")
parser.add_argument("--no-point-mask-sky", action="store_true")
parser.add_argument("--point-depth-min", type=float, default=None)
parser.add_argument("--point-depth-max", type=float, default=None)
parser.add_argument("--point-depth-percentile-min", type=float, default=None)
parser.add_argument("--point-depth-percentile-max", type=float, default=None)
parser.add_argument("--point-sky-color-filter", action="store_true")
parser.add_argument("--no-point-sky-color-filter", action="store_true")
parser.add_argument("--point-sky-white-threshold", type=int, default=None)
parser.add_argument("--point-sky-bright-any-threshold", type=int, default=None)
parser.add_argument("--point-sky-blue-b-min", type=int, default=None)
parser.add_argument("--point-sky-blue-r-max", type=int, default=None)
parser.add_argument("--point-sky-blue-g-min", type=int, default=None)
parser.add_argument("--point-sky-blue-rg-diff-max", type=int, default=None)
parser.add_argument("--point-sky-blue-bg-diff-min", type=int, default=None)
parser.add_argument("--point-sky-blue-br-diff-min", type=int, default=None)
parser.add_argument("--point-outlier-filter", action="store_true")
parser.add_argument("--no-point-outlier-filter", action="store_true")
parser.add_argument("--point-radius-outlier-radius", type=float, default=None)
parser.add_argument("--point-radius-outlier-min-points", type=int, default=None)
parser.add_argument("--point-voxel-size", type=float, default=None)
parser.add_argument("--point-random-sample-ratio", type=float, default=None)
parser.add_argument("--save-frame-points", action="store_true")
parser.add_argument("--no-save-frame-points", action="store_true")
parser.add_argument("--save-videos", action="store_true")
parser.add_argument("--no-save-videos", action="store_true")
parser.add_argument("--save-points", action="store_true")
parser.add_argument("--no-save-points", action="store_true")
parser.add_argument("--save-depth", action="store_true")
parser.add_argument("--no-save-depth", action="store_true")
parser.add_argument("--save-depth-conf", action="store_true")
parser.add_argument("--no-save-depth-conf", action="store_true")
parser.add_argument("--save-images", action="store_true")
parser.add_argument("--no-save-images", action="store_true")
parser.add_argument("--mask-sky", action="store_true")
parser.add_argument("--no-mask-sky", action="store_true")
parser.add_argument("--camera-preprocess", action="store_true")
parser.add_argument("--no-camera-preprocess", action="store_true")
parser.add_argument("--camera-preprocess-strict", action="store_true")
parser.add_argument("--no-camera-preprocess-strict", action="store_true")
parser.add_argument("--no-loop", action="store_true", help="Disable loop closure after inference.")
parser.add_argument("--no-loop-auto-download", action="store_true", help="Do not download missing loop assets automatically.")
parser.add_argument("--loop-preset", default=None, help="Preset name from online_loop_groups.")
parser.add_argument("--loop-methods", default="salad", help="Comma-separated loop retrieval methods.")
parser.add_argument("--salad-ckpt-path", default=None)
parser.add_argument("--salad-dino-weights-path", default=None)
parser.add_argument("--salad-score-thresh", type=float, default=None)
parser.add_argument("--retrieval-top-k", type=int, default=None)
parser.add_argument("--temporal-exclusion", type=int, default=None)
parser.add_argument("--min-frame-separation", type=int, default=None)
parser.add_argument("--loop-edge-score-threshold", type=float, default=None)
parser.add_argument("--pose-graph-loop-weight", type=float, default=None)
parser.add_argument("--pose-graph-trans-weight", type=float, default=None)
parser.add_argument("--pose-graph-rot-weight", type=float, default=None)
return parser
def load_config_with_overrides(args) -> dict:
with open(args.config, "r") as f:
cfg = yaml.safe_load(f) or {}
cfg.setdefault("model", {})
cfg.setdefault("data", {})
cfg.setdefault("inference", {})
cfg.setdefault("output", {})
if args.device is not None:
cfg["device"] = args.device
if args.img_path is not None:
cfg["data"]["img_path"] = args.img_path
if args.video_path is not None:
cfg["data"]["video_path"] = args.video_path
if args.video_stride is not None:
cfg["data"]["video_stride"] = int(args.video_stride)
if args.fps is not None:
cfg["data"]["video_sample_fps"] = float(args.fps)
if args.video_scene_name is not None:
cfg["data"]["video_scene_name"] = args.video_scene_name
if args.video_cache_root is not None:
cfg["data"]["video_cache_root"] = args.video_cache_root
if args.video_overwrite:
cfg["data"]["video_overwrite"] = True
if args.no_video_overwrite:
cfg["data"]["video_overwrite"] = False
if args.seq_list is not None:
cfg["data"]["seq_list"] = [s.strip() for s in args.seq_list.split(",") if s.strip()]
if args.seq_match_mode is not None:
cfg["data"]["seq_match_mode"] = args.seq_match_mode
if args.format is not None:
cfg["data"]["format"] = args.format
if args.data_roots_file is not None:
cfg["data"]["data_roots_file"] = args.data_roots_file
if args.camera is not None:
cfg["data"]["camera"] = args.camera
if args.max_frames is not None and args.max_frames > 0:
cfg["data"]["max_frames"] = args.max_frames
if args.camera_preprocess:
cfg["data"]["camera_preprocess"] = True
if args.no_camera_preprocess:
cfg["data"]["camera_preprocess"] = False
if args.camera_preprocess_strict:
cfg["data"]["camera_preprocess_strict"] = True
if args.no_camera_preprocess_strict:
cfg["data"]["camera_preprocess_strict"] = False
if args.output_root is not None:
cfg["output"]["root"] = args.output_root
if args.abs_pose_source is not None:
cfg["output"]["abs_pose_source"] = args.abs_pose_source
if args.checkpoint is not None:
cfg["model"]["checkpoint"] = args.checkpoint
if args.hf_repo is not None or args.hf_file is not None:
cfg["model"].setdefault("hf", {})
if args.hf_repo is not None:
cfg["model"]["hf"]["repo_id"] = args.hf_repo
if args.hf_file is not None:
cfg["model"]["hf"]["filename"] = args.hf_file
if args.checkpoint is None:
cfg["model"]["checkpoint"] = None
if args.window_size is not None:
cfg["inference"]["window_size"] = args.window_size
if args.sliding_size is not None:
cfg["inference"]["sliding_size"] = args.sliding_size
if args.offload_outputs_to_cpu:
cfg["inference"]["offload_outputs_to_cpu"] = True
if args.no_offload_outputs_to_cpu:
cfg["inference"]["offload_outputs_to_cpu"] = False
if args.offline_motion_averaging:
cfg["inference"]["enable_offline_motion_averaging"] = True
if args.no_offline_motion_averaging:
cfg["inference"]["enable_offline_motion_averaging"] = False
if args.rope_temporal_period is not None:
cfg.setdefault("model", {})
cfg["model"].setdefault("horizonstream_cfg", {})
cfg["model"]["horizonstream_cfg"].setdefault("agg_regator_cfg", {})
cfg["model"]["horizonstream_cfg"]["agg_regator_cfg"]["rope_temporal_period"] = int(args.rope_temporal_period)
if args.max_full_pointcloud_points is not None:
cfg["output"]["max_full_pointcloud_points"] = args.max_full_pointcloud_points
if args.max_frame_pointcloud_points is not None:
cfg["output"]["max_frame_pointcloud_points"] = args.max_frame_pointcloud_points
if args.point_mask_sky:
cfg["output"]["point_mask_sky"] = True
if args.no_point_mask_sky:
cfg["output"]["point_mask_sky"] = False
if args.point_depth_min is not None:
cfg["output"]["point_depth_min"] = args.point_depth_min
if args.point_depth_max is not None:
cfg["output"]["point_depth_max"] = args.point_depth_max
if args.point_depth_percentile_min is not None:
cfg["output"]["point_depth_percentile_min"] = args.point_depth_percentile_min
if args.point_depth_percentile_max is not None:
cfg["output"]["point_depth_percentile_max"] = args.point_depth_percentile_max
if args.point_sky_color_filter:
cfg["output"]["point_sky_color_filter"] = True
if args.no_point_sky_color_filter:
cfg["output"]["point_sky_color_filter"] = False
point_sky_color_args = {
"point_sky_white_threshold": args.point_sky_white_threshold,
"point_sky_bright_any_threshold": args.point_sky_bright_any_threshold,
"point_sky_blue_b_min": args.point_sky_blue_b_min,
"point_sky_blue_r_max": args.point_sky_blue_r_max,
"point_sky_blue_g_min": args.point_sky_blue_g_min,
"point_sky_blue_rg_diff_max": args.point_sky_blue_rg_diff_max,
"point_sky_blue_bg_diff_min": args.point_sky_blue_bg_diff_min,
"point_sky_blue_br_diff_min": args.point_sky_blue_br_diff_min,
}
for key, value in point_sky_color_args.items():
if value is not None:
cfg["output"][key] = value
if args.point_outlier_filter:
cfg["output"]["point_outlier_filter"] = True
if args.no_point_outlier_filter:
cfg["output"]["point_outlier_filter"] = False
if args.point_radius_outlier_radius is not None:
cfg["output"]["point_radius_outlier_radius"] = args.point_radius_outlier_radius
if args.point_radius_outlier_min_points is not None:
cfg["output"]["point_radius_outlier_min_points"] = args.point_radius_outlier_min_points
if args.point_voxel_size is not None:
cfg["output"]["point_voxel_size"] = args.point_voxel_size
if args.point_random_sample_ratio is not None:
cfg["output"]["point_random_sample_ratio"] = args.point_random_sample_ratio
if args.save_frame_points:
cfg["output"]["save_frame_points"] = True
if args.no_save_frame_points:
cfg["output"]["save_frame_points"] = False
if args.save_videos:
cfg["output"]["save_videos"] = True
if args.no_save_videos:
cfg["output"]["save_videos"] = False
if args.save_points:
cfg["output"]["save_points"] = True
if args.no_save_points:
cfg["output"]["save_points"] = False
if args.save_depth:
cfg["output"]["save_depth"] = True
if args.no_save_depth:
cfg["output"]["save_depth"] = False
if args.save_depth_conf:
cfg["output"]["save_depth_conf"] = True
if args.no_save_depth_conf:
cfg["output"]["save_depth_conf"] = False
if args.save_images:
cfg["output"]["save_images"] = True
if args.no_save_images:
cfg["output"]["save_images"] = False
if args.mask_sky:
cfg["output"]["mask_sky"] = True
if args.no_mask_sky:
cfg["output"]["mask_sky"] = False
cfg["data"]["format"] = "generalizable"
return cfg
def _comma_list(text):
if text is None:
return None
return [item.strip() for item in str(text).split(",") if item.strip()]
def _apply_loop_overrides(cfg: dict, args) -> None:
cfg.setdefault("online_loop", {})
loop = cfg["online_loop"]
if args.loop_preset is not None:
groups = cfg.get("online_loop_groups", {}) or {}
if args.loop_preset not in groups:
raise ValueError(
f"Unknown --loop-preset {args.loop_preset!r}; available presets: {sorted(groups)}"
)
loop.update(groups[args.loop_preset] or {})
if args.loop_methods is not None:
loop["methods"] = _comma_list(args.loop_methods)
if args.salad_ckpt_path is not None:
loop["salad_ckpt_path"] = args.salad_ckpt_path
if args.salad_dino_weights_path is not None:
loop["salad_dino_weights_path"] = args.salad_dino_weights_path
if args.salad_score_thresh is not None:
loop["salad_score_thresh"] = float(args.salad_score_thresh)
if args.retrieval_top_k is not None:
loop["retrieval_top_k"] = int(args.retrieval_top_k)
if args.temporal_exclusion is not None:
loop["temporal_exclusion"] = int(args.temporal_exclusion)
if args.min_frame_separation is not None:
loop["min_frame_separation"] = int(args.min_frame_separation)
if args.loop_edge_score_threshold is not None:
loop["loop_edge_score_threshold"] = float(args.loop_edge_score_threshold)
if args.pose_graph_loop_weight is not None:
loop["pose_graph_loop_weight"] = float(args.pose_graph_loop_weight)
if args.pose_graph_trans_weight is not None:
loop["pose_graph_trans_weight"] = float(args.pose_graph_trans_weight)
if args.pose_graph_rot_weight is not None:
loop["pose_graph_rot_weight"] = float(args.pose_graph_rot_weight)
def run_loop_closure_cfg(cfg: dict, preflight_only: bool = False, auto_download: bool = True) -> bool:
loop_cfg = copy.deepcopy(cfg)
output_root = os.path.abspath(os.path.expanduser(loop_cfg.get("output", {}).get("root", "outputs_horizonstream")))
loop_cfg.setdefault("online_loop", {})
loop_cfg["online_loop"]["reuse_online_poses"] = True
loop_cfg["online_loop"]["online_output_root"] = output_root
loop_cfg["online_loop"]["auto_output_suffix"] = False
loop_cfg.setdefault("output", {})["root"] = output_root
output_cfg = loop_cfg.get("output", {})
if not bool(output_cfg.get("save_points", True)) or not bool(output_cfg.get("save_depth", True)):
loop_cfg["online_loop"]["rebuild_pointcloud"] = False
ok, messages = ensure_loop_assets(loop_cfg["online_loop"], auto_download=auto_download)
if not ok:
print_asset_warnings(messages)
return False
if preflight_only:
return True
from horizonstream.loop.online_loop_reinfer import run_online_loop_reinfer
run_online_loop_reinfer(loop_cfg)
return True
def prepare_video_input_if_needed(cfg: dict) -> dict:
data_cfg = cfg.setdefault("data", {})
video_path = data_cfg.get("video_path", None)
if video_path in (None, "", "null"):
return cfg
output_cfg = cfg.setdefault("output", {})
stride = max(1, int(data_cfg.get("video_stride", 1)))
target_fps = data_cfg.get("video_sample_fps", None)
if target_fps not in (None, "", "null"):
target_fps = float(target_fps)
if output_cfg.get("video_fps", None) in (None, "", "null"):
output_cfg["video_fps"] = target_fps
elif output_cfg.get("video_fps", None) in (None, "", "null"):
cap = cv2.VideoCapture(os.path.abspath(os.path.expanduser(str(video_path))))
source_fps = float(cap.get(cv2.CAP_PROP_FPS) or 0.0) if cap.isOpened() else 0.0
cap.release()
if source_fps > 0.0:
output_cfg["video_fps"] = source_fps / float(stride)
data_cfg["format"] = "video"
data_cfg["camera_preprocess"] = False
return cfg
def main():
parser = build_parser()
args = parser.parse_args()
cfg = load_config_with_overrides(args)
_apply_loop_overrides(cfg, args)
cfg = prepare_video_input_if_needed(cfg)
run_loop = False
if not args.no_loop:
run_loop = run_loop_closure_cfg(
cfg,
preflight_only=True,
auto_download=not args.no_loop_auto_download,
)
run_inference_cfg(cfg)
if run_loop:
run_loop_closure_cfg(cfg, auto_download=False)
if __name__ == "__main__":
main()