Buckets:
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| import sys | |
| import zipfile | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Any, Iterable | |
| import imageio.v2 as imageio | |
| import numpy as np | |
| import torch | |
| from decord import VideoReader, cpu | |
| from PIL import Image | |
| from tqdm import tqdm | |
| REPO_ROOT = Path(__file__).resolve().parents[2] | |
| RAE_SRC_ROOT = REPO_ROOT / "RAE" / "src" | |
| RAE_DECODER_CONFIG = REPO_ROOT / "RAE" / "configs" / "decoder" / "ViTXL" | |
| if str(REPO_ROOT) not in sys.path: | |
| sys.path.insert(0, str(REPO_ROOT)) | |
| if str(RAE_SRC_ROOT) not in sys.path: | |
| sys.path.append(str(RAE_SRC_ROOT)) | |
| from omegaconf import DictConfig, OmegaConf # noqa: E402 | |
| from stage1 import RAE # noqa: E402 | |
| from utils.model_utils import instantiate_from_config # noqa: E402 | |
| from utils.train_utils import parse_configs # noqa: E402 | |
| DEFAULT_RAE_ROOT = Path("/mnt/posttrain/zhaoshitian/models/RAE-collections") | |
| DEFAULT_FLUX2_AE_PATH = Path("/mnt/posttrain/zhaoshitian/models/FLUX.2-klein-4B/vae/ae_bfl_format.safetensors") | |
| DEFAULT_FPS = 8.0 | |
| class BackboneSpec: | |
| encoder_cls: str | |
| encoder_config_path: Path | |
| encoder_param_key: str | |
| encoder_input_size: int | |
| decoder_relpath: tuple[str, ...] | |
| stats_relpath: tuple[str, ...] | |
| output_suffix: str | |
| encoder_extra_params: dict[str, Any] = field(default_factory=dict) | |
| is_flux2_ae: bool = False | |
| class ResolvedRAEConfig: | |
| config: DictConfig | |
| source: str | |
| backbone: str | |
| encoder_reference: str | |
| encoder_input_size: int | |
| decoder_path: str | None | |
| stats_path: str | None | |
| output_suffix: str | |
| BACKBONE_SPECS: dict[str, BackboneSpec] = { | |
| "dinov2": BackboneSpec( | |
| encoder_cls="Dinov2withNorm", | |
| encoder_config_path=Path("/mnt/posttrain/zhaoshitian/models/dinov2-with-registers-base"), | |
| encoder_param_key="dinov2_path", | |
| encoder_input_size=224, | |
| decoder_relpath=("decoders", "dinov2", "wReg_base", "ViTXL_n08", "model.pt"), | |
| stats_relpath=("stats", "dinov2", "wReg_base", "imagenet1k", "stat.pt"), | |
| output_suffix="dinov2_wreg_base", | |
| encoder_extra_params={"normalize": True}, | |
| ), | |
| "mae": BackboneSpec( | |
| encoder_cls="MAEwNorm", | |
| encoder_config_path=Path("/mnt/posttrain/zhaoshitian/models/vit-mae-base"), | |
| encoder_param_key="model_name", | |
| encoder_input_size=256, | |
| decoder_relpath=("decoders", "mae", "base_p16", "ViTXL_n08", "model.pt"), | |
| stats_relpath=("stats", "mae", "base_p16", "ImageNet1k", "stat.pt"), | |
| output_suffix="mae_base_p16", | |
| ), | |
| "siglip2": BackboneSpec( | |
| encoder_cls="SigLIP2wNorm", | |
| encoder_config_path=Path("/mnt/posttrain/zhaoshitian/models/siglip2-base-patch16-256"), | |
| encoder_param_key="model_name", | |
| encoder_input_size=256, | |
| decoder_relpath=("decoders", "siglip2", "base_p16_i256", "ViTXL_n08", "model.pt"), | |
| stats_relpath=("stats", "siglip2", "base_p16_i256", "ImageNet1k", "stat.pt"), | |
| output_suffix="siglip2_base_p16_i256", | |
| ), | |
| "flux2_ae": BackboneSpec( | |
| encoder_cls="Flux2AutoEncoder", | |
| encoder_config_path=DEFAULT_FLUX2_AE_PATH, | |
| encoder_param_key="weight_path", | |
| encoder_input_size=256, | |
| decoder_relpath=(), | |
| stats_relpath=(), | |
| output_suffix="flux2_ae", | |
| is_flux2_ae=True, | |
| ), | |
| } | |
| ENCODER_CLS_TO_BACKBONE = { | |
| spec.encoder_cls: backbone | |
| for backbone, spec in BACKBONE_SPECS.items() | |
| } | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser( | |
| description="Extract UCF101 video latents with local RAE backbones or FLUX.2 AE and optionally decode reconstructions." | |
| ) | |
| parser.add_argument( | |
| "--config", | |
| type=Path, | |
| default=None, | |
| help="Optional stage-1 or stage-2 YAML. If omitted, the script builds a stage-1 RAE config from --rae-backbone.", | |
| ) | |
| parser.add_argument( | |
| "--rae-backbone", | |
| type=str, | |
| choices=sorted(BACKBONE_SPECS), | |
| default="dinov2", | |
| help="Backbone/autoencoder used when --config is not supplied.", | |
| ) | |
| parser.add_argument( | |
| "--rae-root", | |
| type=Path, | |
| default=DEFAULT_RAE_ROOT, | |
| help="Root directory containing RAE decoder and latent normalization weights.", | |
| ) | |
| parser.add_argument( | |
| "--encoder-path", | |
| type=Path, | |
| default=None, | |
| help="Optional local encoder path override, or FLUX.2 AE checkpoint path for --rae-backbone flux2_ae.", | |
| ) | |
| parser.add_argument( | |
| "--video-data-dir-path", | |
| type=Path, | |
| help="Root directory containing video data and data.json. Videos are resolved relative to this directory.", | |
| ) | |
| parser.add_argument( | |
| "--dataset-name", | |
| type=str, | |
| default="ucf101", | |
| help="Dataset name used in default output directory paths (e.g. 'bridgedatav2').", | |
| ) | |
| parser.add_argument( | |
| "--data-split", | |
| type=str, | |
| default=None, | |
| help="If set, only load entries whose 'split' field matches (e.g. 'raw', 'scripted_raw').", | |
| ) | |
| parser.add_argument( | |
| "--data-category", | |
| type=str, | |
| default=None, | |
| help="If set, only load entries whose 'category' field matches (e.g. 'toykitchen1').", | |
| ) | |
| parser.add_argument( | |
| "--output-dir", | |
| type=Path, | |
| default=None, | |
| help="Directory to store latent feature `.npz` files. Defaults to a backbone-specific UCF101 directory.", | |
| ) | |
| parser.add_argument( | |
| "--reconstruction-dir", | |
| type=Path, | |
| default=None, | |
| help="Directory to store decoded reconstruction videos. Defaults to a backbone-specific UCF101 directory.", | |
| ) | |
| parser.add_argument( | |
| "--image-size", | |
| type=int, | |
| default=None, | |
| help="Center-crop size before encoding. Defaults to the selected encoder's input size.", | |
| ) | |
| parser.add_argument("--encode-batch-size", type=int, default=16, help="Batch size used for latent extraction.") | |
| parser.add_argument("--decode-batch-size", type=int, default=16, help="Batch size used for latent decoding.") | |
| parser.add_argument("--device", type=str, default=None, help="Torch device. Defaults to cuda when available.") | |
| parser.add_argument("--max-videos", type=int, default=None, help="Optional cap for debugging a subset of videos.") | |
| parser.add_argument("--overwrite", action="store_true", help="Recompute outputs even when they already exist.") | |
| parser.add_argument("--skip-reconstruction", action="store_true", help="Only save latent features, skip mp4 reconstructions.") | |
| return parser.parse_args() | |
| def get_device(explicit: str | None) -> torch.device: | |
| if explicit: | |
| return torch.device(explicit) | |
| return torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| def center_crop_arr(pil_image: Image.Image, image_size: int) -> Image.Image: | |
| while min(*pil_image.size) >= 2 * image_size: | |
| pil_image = pil_image.resize(tuple(x // 2 for x in pil_image.size), resample=Image.BOX) | |
| scale = image_size / min(*pil_image.size) | |
| pil_image = pil_image.resize(tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC) | |
| arr = np.array(pil_image) | |
| crop_y = (arr.shape[0] - image_size) // 2 | |
| crop_x = (arr.shape[1] - image_size) // 2 | |
| return Image.fromarray(arr[crop_y : crop_y + image_size, crop_x : crop_x + image_size]) | |
| def load_video_data_entries( | |
| data_dir: Path, | |
| *, | |
| split: str | None = None, | |
| category: str | None = None, | |
| ) -> list[str]: | |
| """Load video entries from a ``data.json`` file in *data_dir*. | |
| The JSON must be an array of objects with a ``"video_path"`` key:: | |
| [ | |
| { | |
| "id": 0, | |
| "video_path": "raw/00_2022-07-09_14-27-32_traj10_images0.mp4", | |
| "split": "raw", | |
| "category": "toykitchen2" | |
| }, | |
| ... | |
| ] | |
| *video_path* values are treated as relative to *data_dir*. | |
| Parameters | |
| ---------- | |
| data_dir: | |
| Directory containing ``data.json``. | |
| split: | |
| If given, only return entries whose ``"split"`` field matches. | |
| category: | |
| If given, only return entries whose ``"category"`` field matches. | |
| Returns | |
| ------- | |
| list[str] | |
| A flat list of ``video_path`` strings. | |
| """ | |
| data_json = data_dir / "data.json" | |
| if not data_json.is_file(): | |
| raise FileNotFoundError(f"data.json not found in {data_dir}") | |
| with open(data_json, encoding="utf-8") as fh: | |
| all_entries: list[dict[str, Any]] = json.load(fh) | |
| entries: list[str] = [] | |
| for entry in all_entries: | |
| video_path = entry.get("video_path") | |
| if not video_path: | |
| continue | |
| if split is not None and entry.get("split") != split: | |
| continue | |
| if category is not None and entry.get("category") != category: | |
| continue | |
| entries.append(video_path) | |
| return entries | |
| def load_split_entries(split_zip: Path, split_name: str) -> list[str]: | |
| if not split_zip.exists(): | |
| raise FileNotFoundError(f"Split zip not found: {split_zip}") | |
| split_member = f"ucfTrainTestlist/{split_name}" | |
| with zipfile.ZipFile(split_zip) as zf: | |
| try: | |
| lines = zf.read(split_member).decode("utf-8").splitlines() | |
| except KeyError as exc: | |
| raise KeyError(f"Split file {split_member} not found in {split_zip}") from exc | |
| entries: list[str] = [] | |
| for line in lines: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| entries.append(line.split()[0]) | |
| return entries | |
| def validate_video_root(video_root: Path) -> None: | |
| if video_root.exists(): | |
| return | |
| raise FileNotFoundError( | |
| f"Video root not found: {video_root}. Please extract UCF101.rar into class folders before running preprocessing." | |
| ) | |
| def load_video_frames(video_path: Path, image_size: int) -> tuple[np.ndarray, float]: | |
| vr = VideoReader(str(video_path), ctx=cpu(0)) | |
| fps = DEFAULT_FPS | |
| try: | |
| avg_fps = float(vr.get_avg_fps()) | |
| if avg_fps > 0: | |
| fps = avg_fps | |
| except Exception: | |
| pass | |
| frames = [] | |
| for idx in range(len(vr)): | |
| frame = vr[idx].asnumpy() | |
| frame = np.array(center_crop_arr(Image.fromarray(frame), image_size), copy=False) | |
| frames.append(frame) | |
| if not frames: | |
| raise ValueError(f"No frames decoded from {video_path}") | |
| return np.stack(frames, axis=0), fps | |
| def frames_to_tensor(frames: np.ndarray) -> torch.Tensor: | |
| tensor = torch.from_numpy(frames).permute(0, 3, 1, 2).float() | |
| return tensor / 255.0 | |
| def batched_indices(length: int, batch_size: int) -> Iterable[tuple[int, int]]: | |
| for start in range(0, length, batch_size): | |
| yield start, min(start + batch_size, length) | |
| def encode_frames(rae: nn.Module, frames: torch.Tensor, batch_size: int, device: torch.device) -> torch.Tensor: | |
| chunks = [] | |
| for start, end in batched_indices(frames.shape[0], batch_size): | |
| chunk = frames[start:end].to(device, non_blocking=True) | |
| chunks.append(rae.encode(chunk).cpu()) | |
| return torch.cat(chunks, dim=0) | |
| def decode_latents(rae: nn.Module, latents: torch.Tensor, batch_size: int, device: torch.device) -> torch.Tensor: | |
| chunks = [] | |
| for start, end in batched_indices(latents.shape[0], batch_size): | |
| chunk = latents[start:end].to(device, non_blocking=True) | |
| chunks.append(rae.decode(chunk).cpu().clamp(0.0, 1.0)) | |
| return torch.cat(chunks, dim=0) | |
| def save_video(frames: torch.Tensor, save_path: Path, fps: float) -> None: | |
| save_path.parent.mkdir(parents=True, exist_ok=True) | |
| if frames.dim() != 4: | |
| raise ValueError(f"Expected video tensor with 4 dims, got {tuple(frames.shape)}") | |
| if frames.shape[1] in (1, 3): | |
| frames = frames.permute(0, 2, 3, 1) | |
| frames_uint8 = frames.mul(255.0).round().clamp(0, 255).byte().numpy() | |
| writer = imageio.get_writer(str(save_path), fps=fps) | |
| try: | |
| for frame in frames_uint8: | |
| writer.append_data(frame) | |
| finally: | |
| writer.close() | |
| def build_output_paths(feature_root: Path, recon_root: Path, relative_video_path: Path) -> tuple[Path, Path]: | |
| class_name = relative_video_path.parent.name | |
| video_stem = relative_video_path.stem | |
| feature_path = feature_root / class_name / f"{video_stem}_patch_tokens.npz" | |
| recon_path = recon_root / class_name / f"{video_stem}.mp4" | |
| return feature_path, recon_path | |
| def validate_existing_path(path: Path, label: str) -> None: | |
| if not path.exists(): | |
| raise FileNotFoundError(f"{label} not found: {path}") | |
| def sanitize_name(value: str) -> str: | |
| cleaned = re.sub(r"[^a-z0-9]+", "_", value.lower()).strip("_") | |
| return cleaned or "custom" | |
| def split_name_to_tag(split_name: str) -> str: | |
| stem = Path(split_name).stem.lower() | |
| match = re.fullmatch(r"(train|test)list0*([0-9]+)", stem) | |
| if match: | |
| return f"{match.group(1)}_split{int(match.group(2))}" | |
| return sanitize_name(stem) | |
| def default_feature_root(dataset_name: str, split_name: str, output_suffix: str) -> Path: | |
| return REPO_ROOT / "video_features" / f"{dataset_name}_{split_name_to_tag(split_name)}_{output_suffix}" | |
| def default_reconstruction_root(dataset_name: str, split_name: str, output_suffix: str) -> Path: | |
| return REPO_ROOT / "video_reconstructions" / f"{dataset_name}_{split_name_to_tag(split_name)}_{output_suffix}" | |
| def extract_rae_config(full_cfg: DictConfig, source_name: str) -> DictConfig: | |
| rae_config, *_ = parse_configs(full_cfg) | |
| if rae_config is not None: | |
| return rae_config | |
| if "target" in full_cfg: | |
| return full_cfg | |
| raise ValueError(f"Config {source_name} does not contain a stage_1 section.") | |
| def resolve_config_reference(raw_value: str, config_path: Path | None, rae_root: Path) -> str: | |
| if raw_value.startswith("models/"): | |
| return str(rae_root / Path(raw_value).relative_to("models")) | |
| if raw_value.startswith("configs/"): | |
| return str((REPO_ROOT / "RAE" / raw_value).resolve()) | |
| if raw_value.startswith("./") or raw_value.startswith("../"): | |
| if config_path is not None: | |
| return str((config_path.parent / raw_value).resolve()) | |
| return raw_value | |
| def normalize_rae_config_paths(rae_config: DictConfig, *, config_path: Path | None, rae_root: Path) -> DictConfig: | |
| normalized = OmegaConf.create(OmegaConf.to_container(rae_config, resolve=True)) | |
| params = normalized.get("params") | |
| if params is None: | |
| normalized["params"] = {} | |
| params = normalized["params"] | |
| for key in ("encoder_config_path", "decoder_config_path", "pretrained_decoder_path", "normalization_stat_path"): | |
| raw_value = params.get(key) | |
| if isinstance(raw_value, str): | |
| params[key] = resolve_config_reference(raw_value, config_path, rae_root) | |
| encoder_params = params.get("encoder_params") | |
| if isinstance(encoder_params, (dict, DictConfig)): | |
| for key in ("dinov2_path", "model_name"): | |
| raw_value = encoder_params.get(key) | |
| if isinstance(raw_value, str): | |
| encoder_params[key] = resolve_config_reference(raw_value, config_path, rae_root) | |
| return normalized | |
| def apply_encoder_override(rae_config: DictConfig, encoder_path: Path | None) -> DictConfig: | |
| if encoder_path is None: | |
| return rae_config | |
| selected_encoder_path = encoder_path.expanduser() | |
| validate_existing_path(selected_encoder_path, "Encoder override") | |
| params = rae_config.get("params") | |
| if params is None: | |
| rae_config["params"] = {} | |
| params = rae_config["params"] | |
| params["encoder_config_path"] = str(selected_encoder_path) | |
| encoder_cls = params.get("encoder_cls", "") | |
| encoder_params = params.get("encoder_params") | |
| if encoder_params is None: | |
| params["encoder_params"] = {} | |
| encoder_params = params["encoder_params"] | |
| if encoder_cls == "Dinov2withNorm": | |
| encoder_params["dinov2_path"] = str(selected_encoder_path) | |
| else: | |
| encoder_params["model_name"] = str(selected_encoder_path) | |
| return rae_config | |
| def is_local_path_reference(raw_value: str) -> bool: | |
| return raw_value.startswith("/") or raw_value.startswith("./") or raw_value.startswith("../") | |
| def validate_local_paths_in_rae_config(rae_config: DictConfig) -> None: | |
| params = rae_config.get("params", {}) | |
| for key, label in ( | |
| ("encoder_config_path", "encoder config path"), | |
| ("decoder_config_path", "decoder config path"), | |
| ("pretrained_decoder_path", "decoder checkpoint"), | |
| ("normalization_stat_path", "latent normalization stats"), | |
| ): | |
| raw_value = params.get(key) | |
| if isinstance(raw_value, str) and is_local_path_reference(raw_value): | |
| validate_existing_path(Path(raw_value).expanduser(), label) | |
| encoder_params = params.get("encoder_params") | |
| if isinstance(encoder_params, (dict, DictConfig)): | |
| for key, label in (("dinov2_path", "encoder path"), ("model_name", "encoder path")): | |
| raw_value = encoder_params.get(key) | |
| if isinstance(raw_value, str) and is_local_path_reference(raw_value): | |
| validate_existing_path(Path(raw_value).expanduser(), label) | |
| def build_auto_rae_config(backbone: str, *, rae_root: Path, encoder_path: Path | None) -> ResolvedRAEConfig: | |
| spec = BACKBONE_SPECS[backbone] | |
| selected_encoder_path = (encoder_path or spec.encoder_config_path).expanduser() | |
| if spec.is_flux2_ae: | |
| validate_existing_path(selected_encoder_path, "FLUX.2 AE checkpoint") | |
| config = OmegaConf.create( | |
| { | |
| "target": "data_processing.flux2_ae.wrapper.Flux2AutoEncoderWrapper", | |
| "params": {"weight_path": str(selected_encoder_path)}, | |
| } | |
| ) | |
| return ResolvedRAEConfig( | |
| config=config, | |
| source="auto", | |
| backbone=backbone, | |
| encoder_reference=str(selected_encoder_path), | |
| encoder_input_size=spec.encoder_input_size, | |
| decoder_path=str(selected_encoder_path), | |
| stats_path=None, | |
| output_suffix=spec.output_suffix, | |
| ) | |
| decoder_path = (rae_root / Path(*spec.decoder_relpath)).expanduser() | |
| stats_path = (rae_root / Path(*spec.stats_relpath)).expanduser() | |
| validate_existing_path(rae_root, "RAE root") | |
| validate_existing_path(selected_encoder_path, f"{backbone} encoder") | |
| validate_existing_path(RAE_DECODER_CONFIG, "RAE decoder config") | |
| validate_existing_path(decoder_path, f"{backbone} decoder checkpoint") | |
| validate_existing_path(stats_path, f"{backbone} latent normalization stats") | |
| encoder_params = dict(spec.encoder_extra_params) | |
| encoder_params[spec.encoder_param_key] = str(selected_encoder_path) | |
| config = OmegaConf.create( | |
| { | |
| "target": "stage1.RAE", | |
| "params": { | |
| "encoder_cls": spec.encoder_cls, | |
| "encoder_config_path": str(selected_encoder_path), | |
| "encoder_input_size": spec.encoder_input_size, | |
| "encoder_params": encoder_params, | |
| "decoder_config_path": str(RAE_DECODER_CONFIG), | |
| "pretrained_decoder_path": str(decoder_path), | |
| "noise_tau": 0.0, | |
| "reshape_to_2d": True, | |
| "normalization_stat_path": str(stats_path), | |
| }, | |
| } | |
| ) | |
| return ResolvedRAEConfig( | |
| config=config, | |
| source="auto", | |
| backbone=backbone, | |
| encoder_reference=str(selected_encoder_path), | |
| encoder_input_size=spec.encoder_input_size, | |
| decoder_path=str(decoder_path), | |
| stats_path=str(stats_path), | |
| output_suffix=spec.output_suffix, | |
| ) | |
| def inspect_rae_config(rae_config: DictConfig, *, source: str) -> ResolvedRAEConfig: | |
| params = rae_config.get("params", {}) | |
| encoder_cls = params.get("encoder_cls", "") | |
| backbone = ENCODER_CLS_TO_BACKBONE.get(encoder_cls, "custom") | |
| if backbone in BACKBONE_SPECS: | |
| output_suffix = BACKBONE_SPECS[backbone].output_suffix | |
| else: | |
| output_suffix = sanitize_name(Path(source).stem if source != "auto" else encoder_cls or "custom") | |
| encoder_params = params.get("encoder_params", {}) | |
| encoder_reference = params.get("encoder_config_path") | |
| if isinstance(encoder_params, (dict, DictConfig)): | |
| encoder_reference = encoder_params.get("dinov2_path", encoder_params.get("model_name", encoder_reference)) | |
| return ResolvedRAEConfig( | |
| config=rae_config, | |
| source=source, | |
| backbone=backbone, | |
| encoder_reference=str(encoder_reference) if encoder_reference is not None else "<unknown>", | |
| encoder_input_size=int(params.get("encoder_input_size", 256)), | |
| decoder_path=params.get("pretrained_decoder_path"), | |
| stats_path=params.get("normalization_stat_path"), | |
| output_suffix=output_suffix, | |
| ) | |
| def resolve_rae_config(args: argparse.Namespace) -> ResolvedRAEConfig: | |
| rae_root = args.rae_root.expanduser() | |
| if args.config is None: | |
| return build_auto_rae_config( | |
| args.rae_backbone, | |
| rae_root=rae_root, | |
| encoder_path=args.encoder_path.expanduser() if args.encoder_path is not None else None, | |
| ) | |
| config_path = args.config.expanduser() | |
| validate_existing_path(config_path, "Config") | |
| full_cfg = OmegaConf.load(config_path) | |
| rae_config = extract_rae_config(full_cfg, str(config_path)) | |
| normalized_rae_config = normalize_rae_config_paths(rae_config, config_path=config_path, rae_root=rae_root) | |
| normalized_rae_config = apply_encoder_override( | |
| normalized_rae_config, | |
| args.encoder_path.expanduser() if args.encoder_path is not None else None, | |
| ) | |
| validate_local_paths_in_rae_config(normalized_rae_config) | |
| return inspect_rae_config(normalized_rae_config, source=str(config_path)) | |
| def resolve_runtime_args(args: argparse.Namespace, resolved: ResolvedRAEConfig) -> None: | |
| args.video_root = args.video_data_dir_path.expanduser() | |
| args.rae_root = args.rae_root.expanduser() | |
| if args.image_size is None: | |
| args.image_size = resolved.encoder_input_size | |
| if args.output_dir is None: | |
| args.output_dir = default_feature_root(args.dataset_name, args.data_split or "data", resolved.output_suffix) | |
| else: | |
| args.output_dir = args.output_dir.expanduser() | |
| if args.reconstruction_dir is None: | |
| args.reconstruction_dir = default_reconstruction_root(args.dataset_name, args.data_split or "data", resolved.output_suffix) | |
| else: | |
| args.reconstruction_dir = args.reconstruction_dir.expanduser() | |
| def print_run_configuration(args: argparse.Namespace, resolved: ResolvedRAEConfig, device: torch.device) -> None: | |
| print(f"RAE source: {resolved.source}") | |
| print(f"RAE backbone: {resolved.backbone}") | |
| print(f"Encoder path/id: {resolved.encoder_reference}") | |
| if resolved.decoder_path is not None: | |
| print(f"Decoder checkpoint: {resolved.decoder_path}") | |
| if resolved.stats_path is not None: | |
| print(f"Normalization stats: {resolved.stats_path}") | |
| print(f"Encoder input size: {resolved.encoder_input_size}") | |
| print(f"Frame crop size: {args.image_size}") | |
| print(f"Output features: {args.output_dir}") | |
| if not args.skip_reconstruction: | |
| print(f"Output reconstructions: {args.reconstruction_dir}") | |
| print(f"Device: {device}") | |
| def load_rae(rae_config: DictConfig, device: torch.device) -> nn.Module: | |
| rae: nn.Module = instantiate_from_config(rae_config).to(device) | |
| rae.eval() | |
| return rae | |
| def process_video( | |
| rae: nn.Module, | |
| video_path: Path, | |
| relative_video_path: Path, | |
| feature_path: Path, | |
| recon_path: Path, | |
| args: argparse.Namespace, | |
| device: torch.device, | |
| ) -> None: | |
| need_features = args.overwrite or not feature_path.exists() | |
| need_reconstruction = not args.skip_reconstruction and (args.overwrite or not recon_path.exists()) | |
| if not need_features and not need_reconstruction: | |
| return | |
| frames_np, fps = load_video_frames(video_path, args.image_size) | |
| frame_tensor = frames_to_tensor(frames_np) | |
| latents = encode_frames(rae, frame_tensor, args.encode_batch_size, device) | |
| timesteps = np.linspace(0.0, 1.0, latents.shape[0], dtype=np.float32) | |
| if need_features: | |
| feature_path.parent.mkdir(parents=True, exist_ok=True) | |
| np.savez(feature_path, features=latents.numpy().astype(np.float32), timesteps=timesteps) | |
| if need_reconstruction: | |
| recon_frames = decode_latents(rae, latents, args.decode_batch_size, device) | |
| save_video(recon_frames, recon_path, fps=fps) | |
| def main() -> None: | |
| args = parse_args() | |
| device = get_device(args.device) | |
| resolved_rae = resolve_rae_config(args) | |
| resolve_runtime_args(args, resolved_rae) | |
| validate_video_root(args.video_root) | |
| args.output_dir.mkdir(parents=True, exist_ok=True) | |
| if not args.skip_reconstruction: | |
| args.reconstruction_dir.mkdir(parents=True, exist_ok=True) | |
| print_run_configuration(args, resolved_rae, device) | |
| entries = load_video_data_entries( | |
| args.video_data_dir_path, | |
| split=args.data_split, | |
| category=args.data_category, | |
| ) | |
| if args.max_videos is not None: | |
| entries = entries[: args.max_videos] | |
| missing = [entry for entry in entries if not (args.video_root / entry).exists()] | |
| if missing: | |
| preview = ", ".join(missing[:5]) | |
| raise FileNotFoundError(f"Missing {len(missing)} videos under {args.video_root}. First missing entries: {preview}") | |
| rae = load_rae(resolved_rae.config, device) | |
| progress = tqdm(entries, desc="Extracting video latents") | |
| for entry in progress: | |
| relative_video_path = Path(entry) | |
| video_path = args.video_root / relative_video_path | |
| feature_path, recon_path = build_output_paths(args.output_dir, args.reconstruction_dir, relative_video_path) | |
| process_video(rae, video_path, relative_video_path, feature_path, recon_path, args, device) | |
| progress.set_postfix_str(relative_video_path.name) | |
| print(f"Saved latent features to {args.output_dir}") | |
| if not args.skip_reconstruction: | |
| print(f"Saved reconstruction videos to {args.reconstruction_dir}") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 26.5 kB
- Xet hash:
- 08220fd4890ff1f91d7392b067d5c1ddebf0b3a1765906eaa4b762016c3b68ff
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.