File size: 5,854 Bytes
12acbba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env python3
"""DCVC-RT variant of the cluster's bitcost-readiness codec generator.

This reuses ``codec_tools/pipeline/process_video_bitcost_readiness.py`` in full
and swaps ONLY the per-frame score-map source: instead of h264 per-block bits
(``cv_reader_fetch_bitcost``), it uses the DCVC-RT bit-cost bitmap. Everything
else — frame sampling, readiness grouping, 2x2-block selection, canvas packing,
``src_patch_position`` / ``meta.json`` writing — is identical, so the only
variable vs the baseline assets is "DCVC-RT bits vs h264 bitcost".

Usage mirrors the cluster generator (same CLI args), e.g.::

    python dcvc_readiness_gen.py --video V.mp4 --out_dir OUT \
        --num_sampled_frames 256 --grouping_mode readiness \
        --readiness_sum_threshold_mode auto --group_size 32 \
        --images_per_group 4 --patch 16 --max_pixels 150000 \
        --min_group_frames 8 --max_group_frames 128 --bitcost_grid sub

Env (infra only):
    DCVC_INTRA_TAR / DCVC_INTER_TAR  DCVC-RT checkpoints (default: bundled tars in
                                     the model dir; set to relocate)
    DCVC_DEVICE                      cuda:N (default cuda:0)
Selection/scoring knobs (qp, reset_interval, intra_period, max_side, and the
readiness grouping params) come from ``preprocessor_config.json``'s ``codec.dcvc``
via ``codec_dcvc_config`` — NOT from env. The DCVC-RT source is bundled at
``neural_codec/DCVC/`` and loaded by ``dcvc_rt_engine`` (no env var).
Run in the ``magevl`` conda env (torch + DCVC-RT ext + the codec/video deps).
"""

from __future__ import annotations

import os
import sys

import cv2
import numpy as np

# --- make repo + engine importable -----------------------------------------
# Bundled in the release ``neural_codec/`` package: codec_tools/ and dcvc_rt_engine.py
# live next to this file, so default both search roots to this directory (env
# vars still override, e.g. in the cluster/dev checkout).
_HERE = os.path.dirname(os.path.abspath(__file__))
_REPO = os.environ.get("DCVC_REPO_DIR", _HERE)          # dir containing codec_tools/
_ENGINE_DIR = os.environ.get("DCVC_ENGINE_DIR", _HERE)  # dir containing dcvc_rt_engine.py
for p in (_REPO, _ENGINE_DIR):
    if p not in sys.path:
        sys.path.insert(0, p)

import importlib

P = importlib.import_module("codec_tools.pipeline.process_video_bitcost_readiness")
from dcvc_rt_engine import DCVCRTEngine  # noqa: E402
import codec_dcvc_config as _dc  # noqa: E402  (DCVC params from preprocessor_config.json)


_ENGINE = None


def _get_engine() -> DCVCRTEngine:
    global _ENGINE
    if _ENGINE is None:
        _ENGINE = DCVCRTEngine(
            intra_ckpt=os.environ.get("DCVC_INTRA_TAR", os.path.join(_HERE, "dcvc_rt_intra.tar")),
            inter_ckpt=os.environ.get("DCVC_INTER_TAR", os.path.join(_HERE, "dcvc_rt_inter.tar")),
            device=os.environ.get("DCVC_DEVICE", "cuda:0"),
            intra_period=int(_dc.get("intra_period")),
            reset_interval=int(_dc.get("reset_interval")),
        )
    return _ENGINE


def _dcvc_bitmaps(video_path: str, frame_ids):
    """Sequentially DCVC-encode ``video_path`` and return {fid: bitmap (h/16,w/16)}."""
    eng = _get_engine()
    max_side = int(_dc.get("max_side"))
    needed = set(int(f) for f in frame_ids)
    max_fid = max(needed) if needed else -1

    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        raise RuntimeError(f"cannot open {video_path}")
    out = {}
    started = False
    last = None
    idx = 0
    while idx <= max_fid:
        ret, bgr = cap.read()
        if not ret or bgr is None:
            if last is None:
                break
            bgr = last
        else:
            last = bgr
        if max_side > 0:
            h, w = bgr.shape[:2]
            if max(h, w) > max_side:
                s = max_side / float(max(h, w))
                bgr = cv2.resize(bgr, (max(1, int(w * s)), max(1, int(h * s))), interpolation=cv2.INTER_AREA)
        rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
        if not started:
            _qp = int(_dc.get("qp"))
            eng.reset_sequence(rgb.shape[0], rgb.shape[1], qp_i=_qp, qp_p=_qp)
            started = True
        bm = eng.step(idx, rgb)
        if idx in needed:
            out[idx] = bm.float().cpu().numpy()
        idx += 1
    cap.release()
    return out


# ------------------------------------------------------------------ patches

_STATE: dict = {}


def _patched_fetch_bitcost(video_path, frame_ids):
    """Stand-in for cv_reader_fetch_bitcost: just carry (video, fids) forward."""
    _STATE["video"] = video_path
    return [{"fid": int(f)} for f in frame_ids]


def _patched_items_to_score_maps(bitcost_items, out_h, out_w, **kwargs):
    """Produce DCVC-RT score maps at (out_h, out_w) for the same frame ids."""
    # Random-patch baseline: skip the DCVC encode and return uniform random score
    # maps (control). Controlled by codec.dcvc.random_select in preprocessor_config.json.
    if _dc.get("random_select", bool):
        rng = np.random.default_rng(int(_dc.get("random_seed")))
        return [rng.random((int(out_h), int(out_w)), dtype=np.float32) for _ in bitcost_items]
    fids = [int(it["fid"]) for it in bitcost_items]
    bmaps = _dcvc_bitmaps(_STATE["video"], fids)
    maps = []
    for f in fids:
        bm = bmaps.get(f)
        if bm is None or bm.size == 0:
            bm = np.zeros((max(1, out_h // 16), max(1, out_w // 16)), dtype=np.float32)
        maps.append(cv2.resize(bm.astype(np.float32), (int(out_w), int(out_h)), interpolation=cv2.INTER_LINEAR))
    return maps


def main():
    # Swap only the score-map source; reuse the entire generator.
    P.cv_reader_fetch_bitcost = _patched_fetch_bitcost
    P.bitcost_items_to_score_maps = _patched_items_to_score_maps
    P.main()


if __name__ == "__main__":
    main()