Image-Text-to-Text
Transformers
Safetensors
mage_vl
multimodal
vision-language-model
mage-vl
video-understanding
streaming
conversational
custom_code
Instructions to use microsoft/Mage-VL with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use microsoft/Mage-VL with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="microsoft/Mage-VL", trust_remote_code=True) messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoModelForImageTextToText model = AutoModelForImageTextToText.from_pretrained("microsoft/Mage-VL", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use microsoft/Mage-VL with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "microsoft/Mage-VL" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "microsoft/Mage-VL", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/microsoft/Mage-VL
- SGLang
How to use microsoft/Mage-VL with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "microsoft/Mage-VL" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "microsoft/Mage-VL", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "microsoft/Mage-VL" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "microsoft/Mage-VL", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use microsoft/Mage-VL with Docker Model Runner:
docker model run hf.co/microsoft/Mage-VL
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Build readiness-based patch collages using bitcost only. | |
| Pipeline: | |
| 1. sample frames at a fixed FPS or exact count | |
| 2. compute bitcost score maps | |
| 3. build fixed/readiness groups from bitcost scores | |
| 4. select 2x2 patch blocks by bitcost score | |
| 5. pack selected patches into canvases per group | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import Any, Dict, List | |
| import numpy as np | |
| try: | |
| import codec_dcvc_config as _dc # DCVC selection params from preprocessor_config.json | |
| except ImportError: | |
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..")) | |
| import codec_dcvc_config as _dc | |
| _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| # codec_tools/ is one level up from pipeline/ | |
| _CODEC_TOOLS_DIR = os.path.join(_SCRIPT_DIR, "..") | |
| sys.path.insert(0, _CODEC_TOOLS_DIR) | |
| from codec_patch_gop.video_processor import bitcost_item_to_score_map, cv_reader_fetch_bitcost # type: ignore | |
| from codec_patch_gop.frame_utils import decode_frames_bgr as decode_frames_bgr_gop # type: ignore | |
| from codec_patch_gop.video_probe import ffprobe_video_codec_name, ffprobe_keyframe_frame_ids # type: ignore | |
| from codec_patch_gop.utils import smart_resize # type: ignore | |
| from pipeline.generate_codec_patch_smart_resize import ensure_dir, get_total_frames_fps, sha1_8 # type: ignore | |
| from pipeline.process_video_bitcost_mv_mask_collage import ( # type: ignore | |
| build_adaptive_groups, | |
| compute_group_block_budget, | |
| encode_video_with_ffmpeg, | |
| estimate_readiness_threshold, | |
| prepare_frames, | |
| process_group, | |
| resize_and_pad_frames_to_shape, | |
| resolve_group_frame_limits, | |
| resize_within_max_dim, | |
| sample_frame_ids_at_fps, | |
| sample_frame_ids_uniform_count, | |
| save_canvases, | |
| shift_frame_ids_off_keyframes, | |
| dedup_and_refill_frame_ids, | |
| ) | |
| def _suppress_bottom_edge(score_maps: List[np.ndarray]) -> List[np.ndarray]: | |
| """Attenuate the bottom-edge band of each per-frame bit-cost map. | |
| DCVC neural entropy systematically over-weights the bottom-center overlay | |
| band (subtitles / timestamps / watermarks) that HEVC h264 bits do not, which | |
| steals patch budget from real content. codec.dcvc.bottom_atten<1 multiplies | |
| the bottom codec.dcvc.bottom_band fraction of each frame's height by that | |
| factor (default 1.0 = off). Applied to the bit-cost BEFORE grouping/selection.""" | |
| atten = float(_dc.get("bottom_atten")) | |
| band = float(_dc.get("bottom_band")) | |
| if atten >= 1.0 or band <= 0.0 or not score_maps: | |
| return score_maps | |
| out: List[np.ndarray] = [] | |
| for sm in score_maps: | |
| sm = np.asarray(sm, dtype=np.float32).copy() | |
| h = sm.shape[0] | |
| k = max(1, int(round(h * band))) | |
| sm[h - k:, ...] *= atten | |
| out.append(sm) | |
| return out | |
| def bitcost_items_to_score_maps( | |
| bitcost_items: List[Dict[str, Any]], | |
| out_h: int, | |
| out_w: int, | |
| bitcost_grid: str, | |
| bitcost_pct: float, | |
| bitcost_log_scale: bool, | |
| codec_name: str, | |
| ) -> List[np.ndarray]: | |
| score_maps: List[np.ndarray] = [] | |
| for item in bitcost_items: | |
| try: | |
| score = bitcost_item_to_score_map( | |
| item, | |
| out_h=int(out_h), | |
| out_w=int(out_w), | |
| grid=str(bitcost_grid), | |
| pct=float(bitcost_pct), | |
| log_scale=bool(bitcost_log_scale), | |
| codec_name=codec_name, | |
| ) | |
| except TypeError: | |
| score = bitcost_item_to_score_map( | |
| item, | |
| out_h=int(out_h), | |
| out_w=int(out_w), | |
| grid=str(bitcost_grid), | |
| pct=float(bitcost_pct), | |
| log_scale=bool(bitcost_log_scale), | |
| ) | |
| score_maps.append(np.asarray(score, dtype=np.float32)) | |
| # Ablation: replace bit-cost with uniform random -> random patch selection | |
| # (control baseline: fill canvases with random patches, NOT DCVC-selected ones). | |
| if _dc.get("random_select", bool): | |
| _rng = np.random.default_rng(int(_dc.get("random_seed"))) | |
| score_maps = [_rng.random(sm.shape, dtype=np.float32) for sm in score_maps] | |
| return score_maps | |
| def ffprobe_video_stream_stats(video_path: str) -> Dict[str, Any]: | |
| cmd = [ | |
| "ffprobe", | |
| "-v", "error", | |
| "-select_streams", "v:0", | |
| "-show_entries", "stream=width,height,bit_rate,avg_frame_rate:format=bit_rate,duration", | |
| "-of", "json", | |
| str(video_path), | |
| ] | |
| try: | |
| r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True) | |
| data = json.loads(r.stdout) | |
| except Exception: | |
| return { | |
| "stream_width": 0, | |
| "stream_height": 0, | |
| "stream_bit_rate": 0.0, | |
| "format_bit_rate": 0.0, | |
| "duration": 0.0, | |
| } | |
| stream = {} | |
| streams = data.get("streams") | |
| if isinstance(streams, list) and streams: | |
| if isinstance(streams[0], dict): | |
| stream = streams[0] | |
| fmt = data.get("format") if isinstance(data.get("format"), dict) else {} | |
| def _to_float(x: Any) -> float: | |
| try: | |
| return float(x) | |
| except Exception: | |
| return 0.0 | |
| return { | |
| "stream_width": int(stream.get("width", 0) or 0), | |
| "stream_height": int(stream.get("height", 0) or 0), | |
| "stream_bit_rate": _to_float(stream.get("bit_rate", 0.0)), | |
| "format_bit_rate": _to_float(fmt.get("bit_rate", 0.0)), | |
| "duration": _to_float(fmt.get("duration", 0.0)), | |
| } | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--video", required=True, type=str) | |
| ap.add_argument("--out_dir", required=True, type=str) | |
| ap.add_argument("--sample_fps", default=1.0, type=float) | |
| ap.add_argument("--num_sampled_frames", default=_dc.get("num_sampled_frames"), type=int, help="If >0, uniformly sample exactly this many frames across the full video and ignore --sample_fps.") | |
| ap.add_argument("--group_size", default=_dc.get("group_size"), type=int) | |
| ap.add_argument("--grouping_mode", default=_dc.get("grouping_mode"), choices=["fixed", "readiness"]) | |
| ap.add_argument("--images_per_group", default=_dc.get("images_per_group"), type=int) | |
| ap.add_argument("--patch", default=_dc.get("patch"), type=int) | |
| ap.add_argument("--max_dim", default=616, type=int) | |
| ap.add_argument("--max_pixels", default=_dc.get("max_pixels"), type=int, help="Max pixels for smart_resize (0 to use max_dim instead)") | |
| ap.add_argument("--no_resize", default=False, action=argparse.BooleanOptionalAction, help="Disable resize and only pad to patch-aligned size.") | |
| ap.add_argument("--decode_backsearch_max", default=_dc.get("decode_backsearch_max"), type=int) | |
| ap.add_argument("--bitcost_grid", default=_dc.get("bitcost_grid"), choices=["sub", "mb", "ctu", "adaptive"]) | |
| ap.add_argument("--bitcost_pct", default=_dc.get("bitcost_pct"), type=float) | |
| ap.add_argument("--bitcost_log_scale", default=True, action=argparse.BooleanOptionalAction) | |
| ap.add_argument("--avoid_keyframes", default=False, action=argparse.BooleanOptionalAction, help="Shift sampled frame ids away from exact keyframes/I-frames when possible.") | |
| ap.add_argument("--avoid_keyframe_offset", default=1, type=int, help="Frame offset step used when shifting sampled ids away from keyframes.") | |
| ap.add_argument("--canvas_format", default=_dc.get("canvas_format"), choices=["jpg", "png"], help="Format used to save output canvases.") | |
| ap.add_argument("--save_mask_video", default=False, action=argparse.BooleanOptionalAction) | |
| ap.add_argument("--video_fps", default=0.0, type=float, help="Output FPS for side-by-side mask video. 0 means use sample_fps.") | |
| ap.add_argument("--readiness_sum_threshold", default=0.0, type=float, help="Top-k candidate block-score sum threshold for adaptive grouping. <=0 means auto-estimate from fixed groups on this video.") | |
| ap.add_argument( | |
| "--readiness_sum_threshold_mode",default=_dc.get("readiness_sum_threshold_mode"), | |
| choices=["legacy", "fixed", "auto", "clamped_sqrt_bpppf"], | |
| help="How to determine the adaptive readiness-sum threshold. legacy preserves old behavior: use --readiness_sum_threshold if >0, else auto-estimate.", | |
| ) | |
| ap.add_argument( | |
| "--readiness_norm_sum_threshold", | |
| default=1050000.0, | |
| type=float, | |
| help="Normalized sum threshold used when --readiness_sum_threshold_mode=clamped_sqrt_bpppf.", | |
| ) | |
| ap.add_argument( | |
| "--bpppf_clamp_min", | |
| default=0.015, | |
| type=float, | |
| help="Lower clamp for bits_per_pixel_per_frame when using clamped_sqrt_bpppf threshold mode.", | |
| ) | |
| ap.add_argument( | |
| "--bpppf_clamp_max", | |
| default=0.09, | |
| type=float, | |
| help="Upper clamp for bits_per_pixel_per_frame when using clamped_sqrt_bpppf threshold mode.", | |
| ) | |
| ap.add_argument("--min_group_frames", default=_dc.get("min_group_frames"), type=int, help="Minimum sampled frames per adaptive group.") | |
| ap.add_argument("--max_group_frames", default=_dc.get("max_group_frames"), type=int, help="Maximum sampled frames per adaptive group.") | |
| ap.add_argument("--min_group_sec", default=0.0, type=float, help="Minimum real-time seconds per adaptive group. >0 overrides --min_group_frames.") | |
| ap.add_argument("--max_group_sec", default=0.0, type=float, help="Maximum real-time seconds per adaptive group. >0 overrides --max_group_frames.") | |
| ap.add_argument("--readiness_coverage_bins", default=_dc.get("readiness_coverage_bins"), type=int, help="Require selected high-score blocks to cover at least this many temporal bins.") | |
| ap.add_argument("--readiness_delta_ratio", default=_dc.get("readiness_delta_ratio"), type=float, help="If threshold is met and marginal gain ratio drops below this, cut the current group.") | |
| ap.add_argument("--stream_groups", default=False, action=argparse.BooleanOptionalAction, help="When using fixed grouping, process each group incrementally instead of decoding/scoring all sampled frames at once.") | |
| args = ap.parse_args() | |
| ensure_dir(args.out_dir) | |
| out_dir = Path(args.out_dir) | |
| total_frames, fps, h0, w0 = get_total_frames_fps(args.video) | |
| if total_frames <= 0: | |
| raise RuntimeError("cannot read video metadata") | |
| group_size = max(1, int(args.group_size)) | |
| if int(args.num_sampled_frames) > 0: | |
| frame_ids = sample_frame_ids_uniform_count(total_frames, int(args.num_sampled_frames)) | |
| else: | |
| frame_ids = sample_frame_ids_at_fps(total_frames, fps, args.sample_fps) | |
| keyframe_ids: List[int] = [] | |
| if bool(args.avoid_keyframes): | |
| keyframe_ids = ffprobe_keyframe_frame_ids(str(args.video), fps=float(fps), total_frames=int(total_frames)) | |
| frame_ids = shift_frame_ids_off_keyframes( | |
| frame_ids=frame_ids, | |
| keyframe_ids=keyframe_ids, | |
| total_frames=int(total_frames), | |
| offset=int(args.avoid_keyframe_offset), | |
| ) | |
| frame_ids = dedup_and_refill_frame_ids( | |
| frame_ids=frame_ids, | |
| total_frames=int(total_frames), | |
| keyframe_ids=keyframe_ids, | |
| ) | |
| codec_name = ffprobe_video_codec_name(args.video) | |
| ffprobe_stats = ffprobe_video_stream_stats(args.video) | |
| stream_groups = bool(args.stream_groups) | |
| grouping_mode = str(args.grouping_mode).lower().strip() | |
| min_group_frames, max_group_frames, group_limit_dbg = resolve_group_frame_limits( | |
| sample_fps=float(args.sample_fps), | |
| min_group_frames=int(args.min_group_frames), | |
| max_group_frames=int(args.max_group_frames), | |
| min_group_sec=float(args.min_group_sec), | |
| max_group_sec=float(args.max_group_sec), | |
| ) | |
| readiness_coverage_bins = int(max(1, int(args.readiness_coverage_bins))) | |
| readiness_sum_threshold = float(args.readiness_sum_threshold) | |
| readiness_sum_threshold_mode = str(args.readiness_sum_threshold_mode).lower().strip() | |
| stream_w = int(ffprobe_stats.get("stream_width", 0)) or int(w0) | |
| stream_h = int(ffprobe_stats.get("stream_height", 0)) or int(h0) | |
| video_bit_rate = float(ffprobe_stats.get("stream_bit_rate", 0.0)) | |
| if video_bit_rate <= 0.0: | |
| video_bit_rate = float(ffprobe_stats.get("format_bit_rate", 0.0)) | |
| fps_safe = float(fps) if float(fps) > 0 else 1.0 | |
| pixel_count = float(max(1, int(stream_w) * int(stream_h))) | |
| bits_per_frame = float(video_bit_rate) / float(max(fps_safe, 1e-6)) if video_bit_rate > 0 else 0.0 | |
| bits_per_pixel_per_frame = bits_per_frame / pixel_count if bits_per_frame > 0 else 0.0 | |
| clamp_min = float(max(0.0, float(args.bpppf_clamp_min))) | |
| clamp_max = float(max(0.0, float(args.bpppf_clamp_max))) | |
| clamped_bpppf = float(bits_per_pixel_per_frame) | |
| if clamp_min > 0.0: | |
| clamped_bpppf = max(clamped_bpppf, clamp_min) | |
| if clamp_max > 0.0: | |
| clamped_bpppf = min(clamped_bpppf, clamp_max) | |
| readiness_norm_sum_threshold = float(args.readiness_norm_sum_threshold) | |
| readiness_threshold_norm_factor = float(np.sqrt(clamped_bpppf)) if clamped_bpppf > 1e-12 else 1.0 | |
| frames_bgr: List[np.ndarray] = [] | |
| score_maps: List[np.ndarray] = [] | |
| h1 = w1 = hb = wb = s_full = resize_h = resize_w = pad_bottom = pad_right = 0 | |
| if grouping_mode == "readiness" or not stream_groups: | |
| frames_bgr = decode_frames_bgr_gop(args.video, frame_ids, backsearch_max=int(args.decode_backsearch_max), backend="auto") | |
| if len(frames_bgr) != len(frame_ids): | |
| raise RuntimeError(f"decoded {len(frames_bgr)} frames, expected {len(frame_ids)}") | |
| frames_bgr, resize_h, resize_w, pad_bottom, pad_right = prepare_frames( | |
| frames_bgr, | |
| patch=int(args.patch), | |
| max_dim=int(args.max_dim), | |
| max_pixels=int(args.max_pixels), | |
| no_resize=bool(args.no_resize), | |
| ) | |
| h1, w1 = frames_bgr[0].shape[:2] | |
| hb, wb, s_full, _summary_block_budget = compute_group_block_budget( | |
| h1=int(h1), | |
| w1=int(w1), | |
| patch=int(args.patch), | |
| images_per_group=int(args.images_per_group), | |
| ) | |
| bitcost_items = cv_reader_fetch_bitcost(args.video, frame_ids) | |
| if len(bitcost_items) != len(frame_ids): | |
| raise RuntimeError(f"bitcost fetch mismatch: {len(bitcost_items)} vs {len(frame_ids)}") | |
| score_maps = bitcost_items_to_score_maps( | |
| bitcost_items=bitcost_items, | |
| out_h=int(h1), | |
| out_w=int(w1), | |
| bitcost_grid=str(args.bitcost_grid), | |
| bitcost_pct=float(args.bitcost_pct), | |
| bitcost_log_scale=bool(args.bitcost_log_scale), | |
| codec_name=str(codec_name), | |
| ) | |
| score_maps = _suppress_bottom_edge(score_maps) | |
| if grouping_mode == "readiness": | |
| if readiness_sum_threshold_mode == "legacy": | |
| if readiness_sum_threshold <= 0.0: | |
| readiness_sum_threshold = estimate_readiness_threshold( | |
| masked_scores=score_maps, | |
| group_size=int(group_size), | |
| patch=int(args.patch), | |
| images_per_group=int(args.images_per_group), | |
| coverage_bins_target=int(readiness_coverage_bins), | |
| ) | |
| elif readiness_sum_threshold_mode == "auto": | |
| readiness_sum_threshold = estimate_readiness_threshold( | |
| masked_scores=score_maps, | |
| group_size=int(group_size), | |
| patch=int(args.patch), | |
| images_per_group=int(args.images_per_group), | |
| coverage_bins_target=int(readiness_coverage_bins), | |
| ) | |
| elif readiness_sum_threshold_mode == "clamped_sqrt_bpppf": | |
| readiness_sum_threshold = float(readiness_norm_sum_threshold) * float(readiness_threshold_norm_factor) | |
| elif readiness_sum_threshold_mode == "fixed": | |
| readiness_sum_threshold = float(readiness_sum_threshold) | |
| else: | |
| raise ValueError(f"unsupported readiness_sum_threshold_mode: {readiness_sum_threshold_mode}") | |
| # Optional post-scale of the (auto-)estimated threshold. Scale<1 lowers the | |
| # threshold so adaptive groups close sooner -> more, smaller groups -> more | |
| # canvases (spends more of the token budget, matching the HEVC baseline's | |
| # canvas count). codec.dcvc.threshold_scale (default 1.0 = unchanged). | |
| _thr_scale = float(_dc.get("threshold_scale")) | |
| if _thr_scale > 0 and _thr_scale != 1.0: | |
| readiness_sum_threshold = float(readiness_sum_threshold) * _thr_scale | |
| group_specs = build_adaptive_groups( | |
| frame_ids=[int(x) for x in frame_ids], | |
| masked_scores=score_maps, | |
| patch=int(args.patch), | |
| images_per_group=int(args.images_per_group), | |
| readiness_sum_threshold=float(readiness_sum_threshold), | |
| min_group_frames=int(min_group_frames), | |
| max_group_frames=int(max_group_frames), | |
| coverage_bins_target=int(readiness_coverage_bins), | |
| readiness_delta_ratio=float(args.readiness_delta_ratio), | |
| ) | |
| else: | |
| group_specs = [] | |
| for group_idx, start in enumerate(range(0, len(frame_ids), group_size)): | |
| end = min(len(frame_ids), start + group_size) | |
| group_specs.append({ | |
| "group_idx": int(group_idx), | |
| "start": int(start), | |
| "end": int(end), | |
| "frame_count": int(end - start), | |
| "stop_reason": "fixed_group_size", | |
| "readiness": {}, | |
| }) | |
| if stream_groups and grouping_mode != "fixed": | |
| print("[stream_groups] readiness mode still uses full-video buffering; falling back to non-stream behavior") | |
| if stream_groups and bool(args.no_resize): | |
| resize_h, resize_w = int(h0), int(w0) | |
| elif stream_groups and grouping_mode == "fixed": | |
| if int(args.max_pixels) > 0: | |
| resize_h, resize_w = smart_resize(h0, w0, factor=2 * int(args.patch), max_pixels=int(args.max_pixels)) | |
| else: | |
| resize_h, resize_w = resize_within_max_dim(h0, w0, max_dim=int(args.max_dim), factor=2 * int(args.patch)) | |
| h1 = int((int(resize_h) + (2 * int(args.patch) - 1)) // (2 * int(args.patch)) * (2 * int(args.patch))) | |
| w1 = int((int(resize_w) + (2 * int(args.patch) - 1)) // (2 * int(args.patch)) * (2 * int(args.patch))) | |
| pad_bottom = int(h1 - int(resize_h)) | |
| pad_right = int(w1 - int(resize_w)) | |
| hb, wb, s_full, _summary_block_budget = compute_group_block_budget( | |
| h1=int(h1), | |
| w1=int(w1), | |
| patch=int(args.patch), | |
| images_per_group=int(args.images_per_group), | |
| ) | |
| all_group_meta: List[Dict[str, Any]] = [] | |
| all_keep_patch_masks: List[np.ndarray] = [] | |
| all_canvases: List[np.ndarray] = [] | |
| all_patch_pos: List[np.ndarray] = [] | |
| all_src_pos: List[np.ndarray] = [] | |
| jpg_files: List[str] = [] | |
| global_img_offset = 0 | |
| for group_idx, spec in enumerate(group_specs): | |
| start = int(spec["start"]) | |
| end = int(spec["end"]) | |
| group_frames_count = end - start | |
| group_frame_ids = [int(x) for x in frame_ids[start:end]] | |
| if stream_groups and grouping_mode == "fixed": | |
| group_frames_bgr = decode_frames_bgr_gop( | |
| args.video, | |
| group_frame_ids, | |
| backsearch_max=int(args.decode_backsearch_max), | |
| backend="auto", | |
| ) | |
| if len(group_frames_bgr) != len(group_frame_ids): | |
| raise RuntimeError(f"group {group_idx}: decoded {len(group_frames_bgr)} frames, expected {len(group_frame_ids)}") | |
| group_frames_bgr, group_pad_bottom, group_pad_right = resize_and_pad_frames_to_shape( | |
| group_frames_bgr, | |
| resize_h=int(resize_h), | |
| resize_w=int(resize_w), | |
| factor=2 * int(args.patch), | |
| ) | |
| if group_idx == 0: | |
| pad_bottom, pad_right = int(group_pad_bottom), int(group_pad_right) | |
| h1, w1 = group_frames_bgr[0].shape[:2] | |
| group_bitcost_items = cv_reader_fetch_bitcost(args.video, group_frame_ids) | |
| if len(group_bitcost_items) != len(group_frame_ids): | |
| raise RuntimeError(f"group {group_idx}: bitcost fetch mismatch: {len(group_bitcost_items)} vs {len(group_frame_ids)}") | |
| group_scores = bitcost_items_to_score_maps( | |
| bitcost_items=group_bitcost_items, | |
| out_h=int(h1), | |
| out_w=int(w1), | |
| bitcost_grid=str(args.bitcost_grid), | |
| bitcost_pct=float(args.bitcost_pct), | |
| bitcost_log_scale=bool(args.bitcost_log_scale), | |
| codec_name=str(codec_name), | |
| ) | |
| else: | |
| group_frames_bgr = [fr.copy() for fr in frames_bgr[start:end]] | |
| group_scores = [sc.copy() for sc in score_maps[start:end]] | |
| if group_frames_count < 4: | |
| print(f"[Skip] Group {group_idx}: insufficient frames ({group_frames_count}/4), skipping entire group") | |
| continue | |
| elif group_frames_count < group_size: | |
| print(f"[Partial] Group {group_idx}: partial group with {group_frames_count}/{group_size} frames, processing available frames") | |
| meta, keep_patch_mask, images_rgb, patch_pos, src_pos, _img_ptr = process_group( | |
| group_idx=group_idx, | |
| group_frame_ids=group_frame_ids, | |
| group_frames_bgr=group_frames_bgr, | |
| group_scores=group_scores, | |
| images_per_group=int(args.images_per_group), | |
| patch=int(args.patch), | |
| ) | |
| if stream_groups and grouping_mode == "fixed": | |
| jpg_files.extend(save_canvases(images_rgb, str(out_dir), start_idx=int(global_img_offset), fmt=str(args.canvas_format), quality=95)) | |
| else: | |
| for canvas_rgb in images_rgb: | |
| all_canvases.append(canvas_rgb) | |
| patch_pos_global = patch_pos.copy() | |
| patch_pos_global[:, 0] += global_img_offset | |
| all_patch_pos.append(patch_pos_global) | |
| all_src_pos.append(src_pos) | |
| meta["global_img_offset"] = int(global_img_offset) | |
| meta["num_canvases"] = int(images_rgb.shape[0]) | |
| meta["group_start_idx"] = int(start) | |
| meta["group_end_idx"] = int(end) | |
| meta["group_frame_count"] = int(group_frames_count) | |
| meta["group_stop_reason"] = str(spec.get("stop_reason", "unknown")) | |
| meta["group_readiness"] = spec.get("readiness", {}) | |
| global_img_offset += int(images_rgb.shape[0]) | |
| all_group_meta.append(meta) | |
| if not (stream_groups and grouping_mode == "fixed"): | |
| all_keep_patch_masks.append(keep_patch_mask) | |
| if (not stream_groups or grouping_mode != "fixed") and not all_canvases: | |
| raise RuntimeError("no canvases produced") | |
| if not (stream_groups and grouping_mode == "fixed"): | |
| images_rgb_all = np.stack(all_canvases, axis=0).astype(np.uint8) | |
| jpg_files = save_canvases(images_rgb_all, str(out_dir), fmt=str(args.canvas_format), quality=95) | |
| total_images_out = int(images_rgb_all.shape[0]) | |
| else: | |
| total_images_out = int(global_img_offset) | |
| patch_position = np.concatenate(all_patch_pos, axis=0).astype(np.int32) | |
| src_patch_position_raw = np.concatenate(all_src_pos, axis=0).astype(np.int32) | |
| hb = int(h1 // int(args.patch)) | |
| wb = int(w1 // int(args.patch)) | |
| s_full = hb * wb | |
| raster_idx = ( | |
| patch_position[:, 0].astype(np.int64) * s_full | |
| + patch_position[:, 1].astype(np.int64) * wb | |
| + patch_position[:, 2].astype(np.int64) | |
| ) | |
| src_patch_position = np.zeros_like(src_patch_position_raw) | |
| src_patch_position[raster_idx] = src_patch_position_raw | |
| np.save(str(out_dir / "src_patch_position.npy"), src_patch_position, allow_pickle=False) | |
| np.save(str(out_dir / "frame_ids.npy"), np.asarray(frame_ids, dtype=np.int32), allow_pickle=False) | |
| if bool(args.save_mask_video) and all_keep_patch_masks: | |
| keep_patch_mask_full = np.concatenate(all_keep_patch_masks, axis=0).astype(np.uint8) | |
| out_fps = float(args.video_fps) if float(args.video_fps) > 0 else float(args.sample_fps) | |
| sbs_frames: List[np.ndarray] = [] | |
| masked_frames: List[np.ndarray] = [] | |
| p = int(args.patch) | |
| for fr, keep_grid in zip(frames_bgr, keep_patch_mask_full): | |
| masked = np.zeros_like(fr) | |
| ys, xs = np.where(keep_grid > 0) | |
| for ph, pw in zip(ys.tolist(), xs.tolist()): | |
| y0 = int(ph) * p | |
| x0 = int(pw) * p | |
| masked[y0:y0 + p, x0:x0 + p, :] = fr[y0:y0 + p, x0:x0 + p, :] | |
| sbs_frames.append(np.concatenate([fr, masked], axis=1)) | |
| masked_frames.append(masked) | |
| sbs_video_path = encode_video_with_ffmpeg(out_dir / "sampled_masked_sbs.mp4", out_fps, sbs_frames) | |
| masked_video_path = encode_video_with_ffmpeg(out_dir / "sampled_masked_only.mp4", out_fps, masked_frames) | |
| else: | |
| sbs_video_path = None | |
| masked_video_path = None | |
| summary = { | |
| "video": str(args.video), | |
| "video_hash": sha1_8(str(args.video)), | |
| "total_frames": int(total_frames), | |
| "orig_hw": [int(h0), int(w0)], | |
| "processed_hw": [int(h1), int(w1)], | |
| "resize_hw": [int(resize_h), int(resize_w)], | |
| "padded_hw": [int(h1), int(w1)], | |
| "pad_bottom_right": [int(pad_bottom), int(pad_right)], | |
| "fps": float(fps), | |
| "stream_hw": [int(stream_h), int(stream_w)], | |
| "video_bit_rate": float(video_bit_rate), | |
| "bits_per_frame": float(bits_per_frame), | |
| "bits_per_pixel_per_frame": float(bits_per_pixel_per_frame), | |
| "bits_per_pixel_per_frame_clamped": float(clamped_bpppf), | |
| "sample_fps": float(args.sample_fps), | |
| "num_sampled_frames": int(args.num_sampled_frames), | |
| "no_resize": bool(args.no_resize), | |
| "avoid_keyframes": bool(args.avoid_keyframes), | |
| "avoid_keyframe_offset": int(args.avoid_keyframe_offset), | |
| "keyframes_found": int(len(keyframe_ids)), | |
| "sampled_frames": int(len(frame_ids)), | |
| "group_size": int(args.group_size), | |
| "grouping_mode": str(grouping_mode), | |
| "images_per_group": int(args.images_per_group), | |
| "readiness_sum_threshold_mode": str(readiness_sum_threshold_mode), | |
| "readiness_sum_threshold": float(readiness_sum_threshold), | |
| "readiness_norm_sum_threshold": float(readiness_norm_sum_threshold), | |
| "readiness_threshold_norm_factor": float(readiness_threshold_norm_factor), | |
| "bpppf_clamp_min": float(args.bpppf_clamp_min), | |
| "bpppf_clamp_max": float(args.bpppf_clamp_max), | |
| "min_group_frames": int(min_group_frames), | |
| "max_group_frames": int(max_group_frames), | |
| "min_group_sec": float(args.min_group_sec), | |
| "max_group_sec": float(args.max_group_sec), | |
| "group_limit_debug": group_limit_dbg, | |
| "readiness_coverage_bins": int(readiness_coverage_bins), | |
| "readiness_delta_ratio": float(args.readiness_delta_ratio), | |
| "save_mask_video": bool(args.save_mask_video), | |
| "canvas_format": str(args.canvas_format), | |
| "video_fps": float(args.video_fps) if float(args.video_fps) > 0 else float(args.sample_fps), | |
| "mask_video_sbs": str(Path(sbs_video_path).name) if sbs_video_path is not None else None, | |
| "mask_video_only": str(Path(masked_video_path).name) if masked_video_path is not None else None, | |
| "patch": int(args.patch), | |
| "bitcost_grid": str(args.bitcost_grid), | |
| "codec_name": str(codec_name), | |
| "bitcost_pct": float(args.bitcost_pct), | |
| "bitcost_log_scale": bool(args.bitcost_log_scale), | |
| "score_source": "bitcost", | |
| "hb_wb": [int(hb), int(wb)], | |
| "S_full": int(s_full), | |
| "seq_len": int(len(frame_ids)), | |
| "num_groups": int(len(all_group_meta)), | |
| "tail_frames": int(len(frame_ids) % group_size) if grouping_mode == "fixed" else 0, | |
| "group_specs": [ | |
| { | |
| "group_idx": int(i), | |
| "start": int(spec.get("start", 0)), | |
| "end": int(spec.get("end", 0)), | |
| "frame_count": int(spec.get("frame_count", 0)), | |
| "stop_reason": str(spec.get("stop_reason", "unknown")), | |
| "readiness": spec.get("readiness", {}), | |
| } | |
| for i, spec in enumerate(group_specs) | |
| ], | |
| "total_images": int(total_images_out), | |
| "total_patches": int(patch_position.shape[0]), | |
| "canvas_files": jpg_files, | |
| "jpg_files": jpg_files, | |
| "out_dir": str(out_dir), | |
| "src_patch_position_sorted_by_t": True, | |
| "canvas_pixels_sorted_by_t": True, | |
| "groups": all_group_meta, | |
| } | |
| with open(out_dir / "meta.json", "w", encoding="utf-8") as f: | |
| json.dump(summary, f, ensure_ascii=False, indent=2) | |
| print(f"[done] sampled={len(frame_ids)} groups={len(all_group_meta)} out_dir={str(out_dir)}") | |
| if __name__ == "__main__": | |
| main() | |