Download flexicm/utils/codec_test.py from SCU-VIP-Lab/FlexICM: direct link, hf CLI and curl.
- Browser
- Download file 7.89 kB
-
https://huggingface.co/SCU-VIP-Lab/FlexICM/resolve/19051ddde5ff4d3bb2d65fa930bb0af2ade29df1/flexicm/utils/codec_test.py
- Command line
-
hf download hf://SCU-VIP-Lab/FlexICM@19051ddde5ff4d3bb2d65fa930bb0af2ade29df1/flexicm/utils/codec_test.py
-
curl -L -o codec_test.py https://huggingface.co/SCU-VIP-Lab/FlexICM/resolve/19051ddde5ff4d3bb2d65fa930bb0af2ade29df1/flexicm/utils/codec_test.py
7.89 kB
| """Shared helpers for codec test / eval scripts.""" | |
| from __future__ import annotations | |
| import os | |
| from typing import Dict, Optional, Tuple | |
| import torch | |
| from flexicm.utils.alignment import Alignment | |
| from flexicm.utils.train_utils import AverageMeter | |
| def resolve_ckpt(path: str, repo_root: str, label: str = "checkpoint") -> str: | |
| if not path: | |
| raise FileNotFoundError(f"{label}: path is empty") | |
| if not os.path.isabs(path): | |
| path = os.path.join(repo_root, path) | |
| placeholder = os.path.join(os.path.dirname(path), "PLACEHOLDER") | |
| if os.path.isfile(placeholder) and not os.path.isfile(path): | |
| raise FileNotFoundError( | |
| f"{label}: not ready (PLACEHOLDER still present):\n {placeholder}\n" | |
| f"Expected real weights at:\n {path}\n" | |
| "See checkpoints/README.md" | |
| ) | |
| if not os.path.isfile(path): | |
| raise FileNotFoundError(f"{label}: missing file: {path}") | |
| if os.path.basename(path) == "PLACEHOLDER" or path.endswith(".txt"): | |
| raise FileNotFoundError(f"{label}: refusing placeholder file: {path}") | |
| return path | |
| def crop_feature_to_image(h: torch.Tensor, image_hw: Tuple[int, int]) -> torch.Tensor: | |
| """Crop decoded feature h (H/4, W/4 of padded input) to original image size / 4.""" | |
| H, W = image_hw | |
| return h[..., : H // 4, : W // 4] | |
| def pad_for_codec(images: torch.Tensor, divisor: int = 256, device=None): | |
| align = Alignment(divisor=divisor, mode="pad", padding_mode="constant") | |
| if device is not None: | |
| align = align.to(device) | |
| return align.align(images), align | |
| def likelihood_bpp(likelihoods: Dict[str, torch.Tensor], num_pixels: int) -> torch.Tensor: | |
| import math | |
| return sum( | |
| (torch.log(lik).sum() / (-math.log(2) * num_pixels)) | |
| for lik in likelihoods.values() | |
| ) | |
| def actual_bitstream_bpp(strings, num_pixels: int) -> float: | |
| """Estimate bpp from CompressAI byte strings: [[y_bytes...], [z_bytes...]].""" | |
| total_bits = 0 | |
| for group in strings: | |
| for s in group: | |
| if isinstance(s, (bytes, bytearray)): | |
| total_bits += len(s) * 8 | |
| elif torch.is_tensor(s): | |
| total_bits += int(s.numel() * s.element_size() * 8) | |
| else: | |
| total_bits += len(s) * 8 | |
| return total_bits / float(num_pixels) | |
| def test_taic_loader( | |
| model, | |
| teacher, | |
| loader, | |
| criterion, | |
| device, | |
| align_divisor: int = 256, | |
| run_actual_bpp: bool = False, | |
| max_batches: Optional[int] = None, | |
| log_every: int = 50, | |
| ): | |
| """Run codec test: likelihood bpp + feature distortion (+ optional real bpp).""" | |
| model.eval() | |
| teacher.eval() | |
| meters = {k: AverageMeter() for k in ("loss", "bpp", "distortion", "actual_bpp")} | |
| if run_actual_bpp: | |
| model.update(force=True) | |
| for i, images in enumerate(loader): | |
| if max_batches is not None and i >= max_batches: | |
| break | |
| images = images.to(device) | |
| N, _, H, W = images.shape | |
| num_pixels = N * H * W | |
| # Keep codec + teacher on the padded grid so Swin always sees a | |
| # patch/window-divisible size. bpp still uses the original pixel count. | |
| x, _ = pad_for_codec(images, divisor=align_divisor, device=device) | |
| out = model(x) | |
| gt = teacher.gt_features(x) | |
| pred = teacher.pred_features(out["h"]) | |
| stats = criterion(out, pred, gt, num_pixels=num_pixels) | |
| meters["loss"].update(stats["loss"].item(), n=N) | |
| meters["bpp"].update(stats["bpp"].item(), n=N) | |
| meters["distortion"].update(stats["distortion"].item(), n=N) | |
| if run_actual_bpp: | |
| try: | |
| enc = model.compress(x) | |
| dec = model.decompress( | |
| enc["strings"], enc["shape"], x_size=(x.shape[2], x.shape[3]) | |
| ) | |
| abpp = actual_bitstream_bpp(enc["strings"], num_pixels) | |
| meters["actual_bpp"].update(abpp, n=N) | |
| # sanity: decoded h spatial size | |
| _ = dec["h"] | |
| except Exception as e: | |
| if i == 0: | |
| print(f"[warn] actual bpp / compress-decompress failed: {e}") | |
| if i % log_every == 0: | |
| msg = ( | |
| f"[{i}/{len(loader)}] bpp={meters['bpp'].avg:.4f} " | |
| f"D={meters['distortion'].avg:.6f} loss={meters['loss'].avg:.4f}" | |
| ) | |
| if run_actual_bpp and meters["actual_bpp"].count > 0: | |
| msg += f" actual_bpp={meters['actual_bpp'].avg:.4f}" | |
| print(msg) | |
| result = { | |
| "bpp": meters["bpp"].avg, | |
| "distortion": meters["distortion"].avg, | |
| "loss": meters["loss"].avg, | |
| "num_batches": meters["bpp"].count, | |
| } | |
| if run_actual_bpp and meters["actual_bpp"].count > 0: | |
| result["actual_bpp"] = meters["actual_bpp"].avg | |
| return result | |
| def test_ctaic_loader( | |
| ext_model, | |
| base_model, | |
| teacher, | |
| loader, | |
| criterion, | |
| device, | |
| use_condition: bool = True, | |
| align_divisor: int = 256, | |
| run_actual_bpp: bool = False, | |
| max_batches: Optional[int] = None, | |
| log_every: int = 50, | |
| ): | |
| """Codec test for C-TAIC; bpp is extension-layer only (paper Sec.IV.E.2).""" | |
| ext_model.eval() | |
| base_model.eval() | |
| teacher.eval() | |
| meters = {k: AverageMeter() for k in ("loss", "bpp", "distortion", "actual_bpp")} | |
| if run_actual_bpp: | |
| ext_model.update(force=True) | |
| for i, images in enumerate(loader): | |
| if max_batches is not None and i >= max_batches: | |
| break | |
| images = images.to(device) | |
| N, _, H, W = images.shape | |
| num_pixels = N * H * W | |
| # Keep codec + teacher on the padded grid (same rationale as TAIC test). | |
| x, _ = pad_for_codec(images, divisor=align_divisor, device=device) | |
| y_b = None | |
| if use_condition: | |
| base_out = base_model(x) | |
| y_b = base_out["y_hat"] | |
| out = ext_model(x, y_b_hat=y_b, use_condition=use_condition and y_b is not None) | |
| gt = teacher.gt_features(x) | |
| pred = teacher.pred_features(out["h"]) | |
| stats = criterion(out, pred, gt, num_pixels=num_pixels) | |
| meters["loss"].update(stats["loss"].item(), n=N) | |
| meters["bpp"].update(stats["bpp"].item(), n=N) | |
| meters["distortion"].update(stats["distortion"].item(), n=N) | |
| if run_actual_bpp: | |
| try: | |
| enc = ext_model.compress( | |
| x, y_b_hat=y_b, use_condition=use_condition and y_b is not None | |
| ) | |
| dec = ext_model.decompress( | |
| enc["strings"], | |
| enc["shape"], | |
| x_size=(x.shape[2], x.shape[3]), | |
| y_b_hat=y_b, | |
| use_condition=use_condition and y_b is not None, | |
| ) | |
| abpp = actual_bitstream_bpp(enc["strings"], num_pixels) | |
| meters["actual_bpp"].update(abpp, n=N) | |
| _ = dec["h"] | |
| except Exception as e: | |
| if i == 0: | |
| print(f"[warn] actual bpp / compress-decompress failed: {e}") | |
| if i % log_every == 0: | |
| msg = ( | |
| f"[{i}/{len(loader)}] bpp={meters['bpp'].avg:.4f} " | |
| f"D={meters['distortion'].avg:.6f} loss={meters['loss'].avg:.4f}" | |
| ) | |
| if run_actual_bpp and meters["actual_bpp"].count > 0: | |
| msg += f" actual_bpp={meters['actual_bpp'].avg:.4f}" | |
| print(msg) | |
| result = { | |
| "bpp": meters["bpp"].avg, | |
| "distortion": meters["distortion"].avg, | |
| "loss": meters["loss"].avg, | |
| "num_batches": meters["bpp"].count, | |
| "use_condition": use_condition, | |
| } | |
| if run_actual_bpp and meters["actual_bpp"].count > 0: | |
| result["actual_bpp"] = meters["actual_bpp"].avg | |
| return result | |