{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":129649,"databundleVersionId":15582313},{"sourceType":"datasetVersion","sourceId":14731464,"datasetId":9413645,"databundleVersionId":15579445}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from pathlib import Path\nPRED_ROOT = Path(\"/kaggle/input/1231313213\") # Pred submmsion\nSIZE_MB = 400  # Since this submission only uploads images, you need to manually submit the average file size per frame. You need to calculate it locally using the command `du -s --block-size=1MB <path>`.\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# The following blocks cannot be modified","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ========= metrics: PSNR / SSIM / tOF + size -> submission.csv =========\nimport os, re, math, glob\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\nimport torch\nimport torch.nn.functional as F\n\n# ---------------------------\n# 0) Paths: change these two\n# ---------------------------\n# GT root: should contain scene/png/frameXXXXXX/camYY.png\nGT_ROOT   = Path(\"/kaggle/input/dy-gs-bench-dynamic-3-dgs-compression-icme-2026-grand-challenge\")  # GT\n# Pred root: your uploaded decoded PNG results, same structure as GT\n\n\n\nassert torch.cuda.is_available(), \"GPU not available. Please enable GPU in Notebook settings.\"\ndevice = torch.device(\"cuda\")\ntorch.backends.cudnn.benchmark = True\n\n\nTOF_DOWNSCALE = 0.25   \nTOF_STRIDE    = 1      \nHS_ALPHA      = 10.0\nHS_ITERS      = 40     \n\n# SSIM settings\nSSIM_KSIZE = 11\nSSIM_SIGMA = 1.5\n\n\n\n\n\ndef load_rgb_u8(path: Path) -> np.ndarray:\n    if not path.exists():\n        raise FileNotFoundError(f\"Missing file: {path}\")\n    return np.asarray(Image.open(path).convert(\"RGB\"), dtype=np.uint8)\n\ndef to_torch_rgb01(u8: np.ndarray) -> torch.Tensor:\n    # [H,W,3] uint8 -> [1,3,H,W] float32 in [0,1]\n    t = torch.from_numpy(u8).to(device=device, dtype=torch.float32) / 255.0\n    return t.permute(2,0,1).unsqueeze(0)\n\ndef to_gray01(rgb01: torch.Tensor) -> torch.Tensor:\n    # rgb01: [1,3,H,W] -> gray [1,1,H,W]\n    r, g, b = rgb01[:,0:1], rgb01[:,1:2], rgb01[:,2:3]\n    return 0.2989*r + 0.5870*g + 0.1140*b\n\n# ---------------------------\n#  PSNR \n# ---------------------------\n@torch.no_grad()\ndef psnr_rgb(pred01: torch.Tensor, gt01: torch.Tensor) -> float:\n    if pred01.shape != gt01.shape:\n        raise ValueError(f\"Shape mismatch: pred {pred01.shape} vs gt {gt01.shape}\")\n    mse = torch.mean((pred01 - gt01) ** 2).clamp_min(1e-12)\n    psnr = 10.0 * torch.log10(1.0 / mse)\n    return float(psnr.item())\n\n# ---------------------------\n# SSIM\n# ---------------------------\ndef gaussian_kernel_2d(ksize: int, sigma: float, device) -> torch.Tensor:\n    ax = torch.arange(ksize, device=device, dtype=torch.float32) - (ksize - 1) / 2.0\n    xx, yy = torch.meshgrid(ax, ax, indexing=\"ij\")\n    kernel = torch.exp(-(xx**2 + yy**2) / (2.0 * sigma * sigma))\n    kernel = kernel / kernel.sum()\n    return kernel\n\n@torch.no_grad()\ndef ssim_rgb(pred01: torch.Tensor, gt01: torch.Tensor, ksize=11, sigma=1.5) -> float:\n    if pred01.shape != gt01.shape:\n        raise ValueError(f\"Shape mismatch: pred {pred01.shape} vs gt {gt01.shape}\")\n\n\n    C1 = (0.01)**2\n    C2 = (0.03)**2\n\n    B, C, H, W = pred01.shape\n    kernel = gaussian_kernel_2d(ksize, sigma, device=pred01.device).view(1,1,ksize,ksize)\n\n    kernel = kernel.repeat(C, 1, 1, 1)  # [C,1,ks,ks]\n\n    def conv(x):\n        return F.conv2d(x, kernel, padding=ksize//2, groups=C)\n\n    mu_x = conv(pred01)\n    mu_y = conv(gt01)\n    mu_x2 = mu_x * mu_x\n    mu_y2 = mu_y * mu_y\n    mu_xy = mu_x * mu_y\n\n    sig_x2 = conv(pred01 * pred01) - mu_x2\n    sig_y2 = conv(gt01 * gt01) - mu_y2\n    sig_xy = conv(pred01 * gt01) - mu_xy\n\n    num = (2.0 * mu_xy + C1) * (2.0 * sig_xy + C2)\n    den = (mu_x2 + mu_y2 + C1) * (sig_x2 + sig_y2 + C2)\n    ssim_map = num / (den + 1e-12)\n\n\n    return float(ssim_map.mean().item())\n\n# ---------------------------\n#  Optical Flow on GPU: Horn-Schunck (no pretrained weights)\n#    Returns flow [1,2,H,W] in \"pixel\" units of the (downscaled) resolution.\n# ---------------------------\ndef sobel_kernels(device):\n    kx = torch.tensor([[-1,0,1],[-2,0,2],[-1,0,1]], device=device, dtype=torch.float32) / 8.0\n    ky = torch.tensor([[-1,-2,-1],[0,0,0],[1,2,1]], device=device, dtype=torch.float32) / 8.0\n    return kx.view(1,1,3,3), ky.view(1,1,3,3)\n\n@torch.no_grad()\ndef horn_schunck_flow(gray1: torch.Tensor, gray2: torch.Tensor, alpha=10.0, iters=40) -> torch.Tensor:\n\n    kx, ky = sobel_kernels(gray1.device)\n    Ix = F.conv2d(gray1, kx, padding=1) + F.conv2d(gray2, kx, padding=1)\n    Iy = F.conv2d(gray1, ky, padding=1) + F.conv2d(gray2, ky, padding=1)\n    It = gray2 - gray1\n\n    u = torch.zeros_like(gray1)\n    v = torch.zeros_like(gray1)\n\n    avg = torch.tensor([[1,2,1],[2,0,2],[1,2,1]], device=gray1.device, dtype=torch.float32) / 12.0\n    avg = avg.view(1,1,3,3)\n\n    for _ in range(iters):\n        u_avg = F.conv2d(u, avg, padding=1)\n        v_avg = F.conv2d(v, avg, padding=1)\n        der = (Ix*u_avg + Iy*v_avg + It) / (alpha*alpha + Ix*Ix + Iy*Iy + 1e-12)\n        u = u_avg - Ix * der\n        v = v_avg - Iy * der\n\n    flow = torch.cat([u, v], dim=1)  # [1,2,H,W]\n    return flow\n\n@torch.no_grad()\ndef tof_pair(gt_prev01, gt_cur01, pr_prev01, pr_cur01) -> float:\n    # require same resolution between GT and pred\n    if gt_prev01.shape != pr_prev01.shape or gt_cur01.shape != pr_cur01.shape or gt_prev01.shape != gt_cur01.shape:\n        raise ValueError(\"Resolution mismatch in tOF pair.\")\n\n    g1 = to_gray01(gt_prev01)\n    g2 = to_gray01(gt_cur01)\n    p1 = to_gray01(pr_prev01)\n    p2 = to_gray01(pr_cur01)\n\n    if TOF_DOWNSCALE != 1.0:\n        g1 = F.interpolate(g1, scale_factor=TOF_DOWNSCALE, mode=\"bilinear\", align_corners=False)\n        g2 = F.interpolate(g2, scale_factor=TOF_DOWNSCALE, mode=\"bilinear\", align_corners=False)\n        p1 = F.interpolate(p1, scale_factor=TOF_DOWNSCALE, mode=\"bilinear\", align_corners=False)\n        p2 = F.interpolate(p2, scale_factor=TOF_DOWNSCALE, mode=\"bilinear\", align_corners=False)\n\n    flow_gt = horn_schunck_flow(g1, g2, alpha=HS_ALPHA, iters=HS_ITERS)\n    flow_pr = horn_schunck_flow(p1, p2, alpha=HS_ALPHA, iters=HS_ITERS)\n\n    # L1 norm difference between flows (mean over all pixels and both channels)\n    return float(torch.mean(torch.abs(flow_gt - flow_pr)).item())\n\n# ---------------------------\n#  Collect all GT pngs & map to pred pngs by relative path\n#    Expect structure: <scene>/png/frameXXXXXX/camYY.png\n# ---------------------------\ngt_files = sorted(GT_ROOT.glob(\"**/png/frame*/cam*.png\"))\nif len(gt_files) == 0:\n    raise RuntimeError(f\"No GT png found under {GT_ROOT}. Expected **/png/frame*/cam*.png\")\n\n# verify pred exists\nmissing = 0\npairs = []\nfor gtp in gt_files:\n    rel = gtp.relative_to(GT_ROOT)\n    prp = PRED_ROOT / rel\n    if not prp.exists():\n        missing += 1\n        continue\n    pairs.append((gtp, prp, rel.as_posix()))\nif missing > 0:\n    raise RuntimeError(f\"Missing {missing} pred pngs under {PRED_ROOT} (must mirror GT structure).\")\n\nprint(f\"Found {len(pairs)} pairs.\")\n\n# ---------------------------\n#  PSNR / SSIM over all images \n# ---------------------------\npsnr_list = []\nssim_list = []\n\nfor gtp, prp, rel in tqdm(pairs):\n    gt_u8 = load_rgb_u8(gtp)\n    pr_u8 = load_rgb_u8(prp)\n\n    gt01 = to_torch_rgb01(gt_u8)\n    pr01 = to_torch_rgb01(pr_u8)\n\n    psnr_list.append(psnr_rgb(pr01, gt01))\n    ssim_list.append(ssim_rgb(pr01, gt01, ksize=SSIM_KSIZE, sigma=SSIM_SIGMA))\n\nmean_psnr = float(np.mean(psnr_list))\nmean_ssim = float(np.mean(ssim_list))\n\n# ---------------------------\n#  tOF\n# ---------------------------\n# Parse: <scene>/png/frame000123/cam01.png\nframe_re = re.compile(r\"^(.*)/png/frame(\\d+)/(cam\\d+\\.png)$\")\n\nseq = {}  # (scene, cam) -> list[(frame_idx, gt_path, pr_path)]\nfor gtp, prp, rel in tqdm(pairs):\n    m = frame_re.match(rel)\n    if not m:\n        continue\n    scene, fi, cam = m.group(1), int(m.group(2)), m.group(3)\n    seq.setdefault((scene, cam), []).append((fi, gtp, prp))\n\ntof_vals = []\nwith torch.no_grad():\n    for (scene, cam), items in seq.items():\n        items.sort(key=lambda x: x[0])\n        if len(items) < 2:\n            continue\n        for i in tqdm(range(1, len(items), TOF_STRIDE)):\n            _, gt_prev_p, pr_prev_p = items[i-1]\n            _, gt_cur_p,  pr_cur_p  = items[i]\n\n            gt_prev01 = to_torch_rgb01(load_rgb_u8(gt_prev_p))\n            gt_cur01  = to_torch_rgb01(load_rgb_u8(gt_cur_p))\n            pr_prev01 = to_torch_rgb01(load_rgb_u8(pr_prev_p))\n            pr_cur01  = to_torch_rgb01(load_rgb_u8(pr_cur_p))\n\n            tof_vals.append(tof_pair(gt_prev01, gt_cur01, pr_prev01, pr_cur01))\n\nmean_tof = float(np.mean(tof_vals)) if len(tof_vals) > 0 else 0.0\n\n# ---------------------------\n#  size :Manually fill in your true file size\n# ---------------------------\n\nassert SIZE_MB\n\nSIZE_MB = float(SIZE_MB)\n\n# ---------------------------\n#  Write one-row submission.csv\n# ---------------------------\nsub = pd.DataFrame([{\n    \"id\": \"sub(no id)\",\n    \"psnr\": mean_psnr,\n    \"size\": SIZE_MB,\n    \"ssim\": mean_ssim,\n    \"tof\":  mean_tof,\n}])\nsub.to_csv(\"submission.csv\", index=False)\n\nprint(\"Done.\")\nprint(sub)\nprint(\"Saved: submission.csv\")\n# ===================================================================================\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}