| """ |
| Freckles Stress Test β Extreme Resolution & Out-of-Scope Noise |
| ================================================================ |
| Push Freckles to resolutions she's never seen, noise types she's |
| never trained on, and spatial structures that mix multiple types. |
| |
| Tests: |
| 1. Extreme Resolution: 256, 512, 1024, 2048, 4096, 8192 |
| Plus weird sizes: 36, 52, 76, 100, 140, 172, 204, 300, 444, 600 |
| 2. Out-of-Scope Noise: 16 novel types not in training set |
| 3. Spatial Matte: large images with regional noise zones |
| 4. Noise Triangulation: can omega tokens identify WHICH noise |
| is WHERE without any classification training? |
| 5. Multi-noise Composites: layered noise at varying intensities |
| |
| Usage: |
| python freckles_stress_test.py --checkpoint /path/to/best.pt |
| |
| Colab: |
| !python -m geolip_svae.noise_stress_test --model v41_freckles_256 |
| """ |
|
|
| import os |
| import math |
| import json |
| import time |
| import argparse |
| import numpy as np |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from collections import defaultdict |
| from tqdm import tqdm |
|
|
|
|
| |
| |
| |
|
|
| KNOWN_NAMES = { |
| 0: 'gaussian', 1: 'uniform', 2: 'uniform_sc', 3: 'poisson', |
| 4: 'pink', 5: 'brown', 6: 'salt_pepper', 7: 'sparse', |
| 8: 'block', 9: 'gradient', 10: 'checker', 11: 'mixed', |
| 12: 'structural', 13: 'cauchy', 14: 'exponential', 15: 'laplace', |
| } |
|
|
|
|
| def _pink(shape): |
| w = torch.randn(shape) |
| S = torch.fft.rfft2(w) |
| h, ww = shape[-2], shape[-1] |
| fy = torch.fft.fftfreq(h).unsqueeze(-1).expand(-1, ww // 2 + 1) |
| fx = torch.fft.rfftfreq(ww).unsqueeze(0).expand(h, -1) |
| return torch.fft.irfft2(S / torch.sqrt(fx**2 + fy**2).clamp(min=1e-8), s=(h, ww)) |
|
|
|
|
| def _brown(shape): |
| w = torch.randn(shape) |
| S = torch.fft.rfft2(w) |
| h, ww = shape[-2], shape[-1] |
| fy = torch.fft.fftfreq(h).unsqueeze(-1).expand(-1, ww // 2 + 1) |
| fx = torch.fft.rfftfreq(ww).unsqueeze(0).expand(h, -1) |
| return torch.fft.irfft2(S / (fx**2 + fy**2).clamp(min=1e-8), s=(h, ww)) |
|
|
|
|
| def gen_known_noise(t, s, rng=None): |
| if rng is None: rng = np.random.RandomState(42) |
| if t == 0: return torch.randn(3, s, s) |
| elif t == 1: return torch.rand(3, s, s) * 2 - 1 |
| elif t == 2: return (torch.rand(3, s, s) - 0.5) * 4 |
| elif t == 3: |
| lam = rng.uniform(0.5, 20.0) |
| return torch.poisson(torch.full((3, s, s), lam)) / lam - 1.0 |
| elif t == 4: |
| img = _pink((3, s, s)); return img / (img.std() + 1e-8) |
| elif t == 5: |
| img = _brown((3, s, s)); return img / (img.std() + 1e-8) |
| elif t == 6: |
| return torch.where(torch.rand(3, s, s) > 0.5, |
| torch.ones(3, s, s) * 2, -torch.ones(3, s, s) * 2) + torch.randn(3, s, s) * 0.1 |
| elif t == 7: |
| return torch.randn(3, s, s) * (torch.rand(3, s, s) > 0.9).float() * 3 |
| elif t == 8: |
| b = rng.randint(2, max(3, s // 4)) |
| sm = torch.randn(3, s // b + 1, s // b + 1) |
| return F.interpolate(sm.unsqueeze(0), size=s, mode='nearest').squeeze(0) |
| elif t == 9: |
| gy = torch.linspace(-2, 2, s).unsqueeze(1).expand(s, s) |
| gx = torch.linspace(-2, 2, s).unsqueeze(0).expand(s, s) |
| a = rng.uniform(0, 2 * math.pi) |
| return (math.cos(a) * gx + math.sin(a) * gy).unsqueeze(0).expand(3, -1, -1) + torch.randn(3, s, s) * 0.5 |
| elif t == 10: |
| cs = rng.randint(2, max(3, s // 4)) |
| cy = torch.arange(s) // cs; cx = torch.arange(s) // cs |
| return ((cy.unsqueeze(1) + cx.unsqueeze(0)) % 2).float().unsqueeze(0).expand(3, -1, -1) * 2 - 1 + torch.randn(3, s, s) * 0.3 |
| elif t == 11: |
| alpha = rng.uniform(0.2, 0.8) |
| return alpha * torch.randn(3, s, s) + (1 - alpha) * (torch.rand(3, s, s) * 2 - 1) |
| elif t == 12: |
| img = torch.zeros(3, s, s); h2 = s // 2; w2 = s // 2 |
| img[:, :h2, :w2] = torch.randn(3, h2, w2) |
| img[:, :h2, w2:s] = (torch.rand(3, h2, s - w2) * 2 - 1) |
| img[:, h2:s, :w2] = _pink((3, s - h2, w2)) / 2 |
| img[:, h2:s, w2:s] = torch.where(torch.rand(3, s - h2, s - w2) > 0.5, |
| torch.ones(3, s - h2, s - w2), -torch.ones(3, s - h2, s - w2)) |
| return img |
| elif t == 13: |
| return torch.tan(math.pi * (torch.rand(3, s, s) - 0.5)).clamp(-3, 3) |
| elif t == 14: |
| return torch.empty(3, s, s).exponential_(1.0) - 1.0 |
| elif t == 15: |
| u = torch.rand(3, s, s) - 0.5 |
| return -torch.sign(u) * torch.log1p(-2 * u.abs()) |
| return torch.randn(3, s, s) |
|
|
|
|
| |
| |
| |
|
|
| OOD_NAMES = { |
| 100: 'log_normal', |
| 101: 'beta', |
| 102: 'weibull', |
| 103: 'gumbel', |
| 104: 'rayleigh', |
| 105: 'perlin_approx', |
| 106: 'wavelet_noise', |
| 107: 'fractal_fbm', |
| 108: 'gabor', |
| 109: 'sine_composite', |
| 110: 'voronoi_approx', |
| 111: 'shot_noise', |
| 112: 'quantize_noise', |
| 113: 'jpeg_artifact', |
| 114: 'ring_noise', |
| 115: 'spiral_noise', |
| } |
|
|
|
|
| def gen_ood_noise(t, s, rng=None): |
| """Generate out-of-distribution noise types.""" |
| if rng is None: rng = np.random.RandomState(42) |
|
|
| if t == 100: |
| return torch.exp(torch.randn(3, s, s) * 0.5) - 1.5 |
|
|
| elif t == 101: |
| a = torch.distributions.Beta(0.5, 0.5) |
| return a.sample((3, s, s)) * 4 - 2 |
|
|
| elif t == 102: |
| u = torch.rand(3, s, s).clamp(min=1e-8) |
| k = 1.5 |
| return (-torch.log(u)).pow(1.0 / k) - 1.0 |
|
|
| elif t == 103: |
| u = torch.rand(3, s, s).clamp(1e-8, 1 - 1e-8) |
| return -(torch.log(-torch.log(u))) * 0.5 |
|
|
| elif t == 104: |
| return torch.sqrt(-2 * torch.log(torch.rand(3, s, s).clamp(min=1e-8))) - 1.0 |
|
|
| elif t == 105: |
| img = torch.zeros(3, s, s) |
| for octave in range(5): |
| freq = 2 ** octave |
| amp = 0.5 ** octave |
| small = torch.randn(3, max(2, s // (freq * 4) + 1), max(2, s // (freq * 4) + 1)) |
| up = F.interpolate(small.unsqueeze(0), size=s, mode='bilinear', align_corners=False).squeeze(0) |
| img = img + amp * up |
| return img / (img.std() + 1e-8) |
|
|
| elif t == 106: |
| img = torch.randn(3, s, s) |
| |
| S = torch.fft.rfft2(img) |
| h, w = s, s // 2 + 1 |
| mask = torch.ones(h, w) |
| cutoff = max(1, s // 16) |
| mask[:cutoff, :cutoff] = 0 |
| return torch.fft.irfft2(S * mask.unsqueeze(0), s=(s, s)) |
|
|
| elif t == 107: |
| img = torch.zeros(3, s, s) |
| H = 0.7 |
| for octave in range(8): |
| freq = 2 ** octave |
| amp = freq ** (-H) |
| small = torch.randn(3, max(2, s // freq + 1), max(2, s // freq + 1)) |
| up = F.interpolate(small.unsqueeze(0), size=s, mode='bilinear', align_corners=False).squeeze(0) |
| img = img + amp * up |
| return img / (img.std() + 1e-8) |
|
|
| elif t == 108: |
| y = torch.linspace(-3, 3, s).unsqueeze(1).expand(s, s) |
| x = torch.linspace(-3, 3, s).unsqueeze(0).expand(s, s) |
| angle = rng.uniform(0, math.pi) |
| freq = rng.uniform(2, 10) |
| xr = x * math.cos(angle) + y * math.sin(angle) |
| envelope = torch.exp(-(x**2 + y**2) / 2) |
| gabor = envelope * torch.cos(2 * math.pi * freq * xr) |
| return gabor.unsqueeze(0).expand(3, -1, -1) + torch.randn(3, s, s) * 0.2 |
|
|
| elif t == 109: |
| y = torch.linspace(0, 1, s).unsqueeze(1).expand(s, s) |
| x = torch.linspace(0, 1, s).unsqueeze(0).expand(s, s) |
| img = torch.zeros(3, s, s) |
| for c in range(3): |
| for _ in range(5): |
| fx = rng.uniform(1, 20) |
| fy = rng.uniform(1, 20) |
| phase = rng.uniform(0, 2 * math.pi) |
| img[c] += torch.sin(2 * math.pi * (fx * x + fy * y) + phase) |
| return img / (img.std() + 1e-8) |
|
|
| elif t == 110: |
| n_seeds = rng.randint(10, 50) |
| seeds = torch.rand(n_seeds, 2) * s |
| y = torch.arange(s).float().unsqueeze(1).expand(s, s) |
| x = torch.arange(s).float().unsqueeze(0).expand(s, s) |
| coords = torch.stack([y, x], dim=-1).reshape(-1, 2) |
| dists = torch.cdist(coords, seeds) |
| min_dist = dists.min(dim=1).values.reshape(s, s) |
| img = min_dist / (min_dist.max() + 1e-8) * 4 - 2 |
| return img.unsqueeze(0).expand(3, -1, -1) + torch.randn(3, s, s) * 0.1 |
|
|
| elif t == 111: |
| lam = rng.uniform(0.01, 0.2) |
| return torch.poisson(torch.full((3, s, s), lam)) * 3.0 - lam * 3 |
|
|
| elif t == 112: |
| levels = rng.randint(2, 8) |
| base = torch.randn(3, s, s) |
| quantized = torch.round(base * levels) / levels |
| return quantized + torch.randn(3, s, s) * 0.05 |
|
|
| elif t == 113: |
| bs = 8 |
| img = torch.randn(3, s, s) |
| small = F.avg_pool2d(img.unsqueeze(0), bs, bs) |
| img = F.interpolate(small, size=s, mode='nearest').squeeze(0) |
| return img + torch.randn(3, s, s) * 0.2 |
|
|
| elif t == 114: |
| y = torch.linspace(-1, 1, s).unsqueeze(1).expand(s, s) |
| x = torch.linspace(-1, 1, s).unsqueeze(0).expand(s, s) |
| r = torch.sqrt(x**2 + y**2) |
| freq = rng.uniform(3, 15) |
| rings = torch.sin(2 * math.pi * freq * r) |
| return rings.unsqueeze(0).expand(3, -1, -1) + torch.randn(3, s, s) * 0.3 |
|
|
| elif t == 115: |
| y = torch.linspace(-1, 1, s).unsqueeze(1).expand(s, s) |
| x = torch.linspace(-1, 1, s).unsqueeze(0).expand(s, s) |
| r = torch.sqrt(x**2 + y**2) |
| theta = torch.atan2(y, x) |
| freq = rng.uniform(2, 8) |
| spiral = torch.sin(2 * math.pi * freq * r + 3 * theta) |
| return spiral.unsqueeze(0).expand(3, -1, -1) + torch.randn(3, s, s) * 0.2 |
|
|
| return torch.randn(3, s, s) |
|
|
|
|
| |
| |
| |
|
|
| def gen_zone_matte(s, n_zones=4, rng=None): |
| """Generate image with distinct noise zones and a ground-truth zone map. |
| |
| Returns: |
| image: (3, s, s) β composite noise image |
| zone_map: (s, s) β integer map of which zone each pixel belongs to |
| zone_types: list of noise type IDs per zone |
| """ |
| if rng is None: rng = np.random.RandomState(42) |
|
|
| image = torch.zeros(3, s, s) |
| zone_map = torch.zeros(s, s, dtype=torch.long) |
|
|
| if n_zones == 4: |
| |
| h2 = s // 2 |
| types = rng.choice(16, size=4, replace=False).tolist() |
| image[:, :h2, :h2] = gen_known_noise(types[0], h2, rng).clamp(-4, 4) |
| image[:, :h2, h2:] = gen_known_noise(types[1], h2, rng).clamp(-4, 4) |
| image[:, h2:, :h2] = gen_known_noise(types[2], h2, rng).clamp(-4, 4) |
| image[:, h2:, h2:] = gen_known_noise(types[3], h2, rng).clamp(-4, 4) |
| zone_map[:h2, :h2] = 0 |
| zone_map[:h2, h2:] = 1 |
| zone_map[h2:, :h2] = 2 |
| zone_map[h2:, h2:] = 3 |
| return image, zone_map, types |
|
|
| elif n_zones == 9: |
| |
| h3 = s // 3 |
| types = rng.choice(16, size=9, replace=False).tolist() |
| for i in range(3): |
| for j in range(3): |
| idx = i * 3 + j |
| r0, r1 = i * h3, (i + 1) * h3 if i < 2 else s |
| c0, c1 = j * h3, (j + 1) * h3 if j < 2 else s |
| h = r1 - r0; w = c1 - c0 |
| noise = gen_known_noise(types[idx], max(h, w), rng).clamp(-4, 4) |
| image[:, r0:r1, c0:c1] = noise[:, :h, :w] |
| zone_map[r0:r1, c0:c1] = idx |
| return image, zone_map, types |
|
|
| elif n_zones == 16: |
| |
| h4 = s // 4 |
| types = list(range(16)) |
| rng.shuffle(types) |
| for i in range(4): |
| for j in range(4): |
| idx = i * 4 + j |
| r0, r1 = i * h4, (i + 1) * h4 if i < 3 else s |
| c0, c1 = j * h4, (j + 1) * h4 if j < 3 else s |
| h = r1 - r0; w = c1 - c0 |
| noise = gen_known_noise(types[idx], max(h, w), rng).clamp(-4, 4) |
| image[:, r0:r1, c0:c1] = noise[:, :h, :w] |
| zone_map[r0:r1, c0:c1] = idx |
| return image, zone_map, types |
|
|
| |
| types = rng.choice(16, size=n_zones, replace=True).tolist() |
| centers = torch.rand(n_zones, 2) * s |
| y = torch.arange(s).float().unsqueeze(1).expand(s, s) |
| x = torch.arange(s).float().unsqueeze(0).expand(s, s) |
| coords = torch.stack([y, x], dim=-1) |
| dists = torch.cdist(coords.reshape(-1, 2), centers).reshape(s, s, n_zones) |
| zone_map = dists.argmin(dim=-1) |
| for z in range(n_zones): |
| mask = (zone_map == z) |
| region_size = int(mask.sum().sqrt().item()) |
| if region_size < 4: |
| region_size = 4 |
| noise = gen_known_noise(types[z], region_size, rng).clamp(-4, 4) |
| noise_full = F.interpolate(noise.unsqueeze(0), size=s, mode='nearest').squeeze(0) |
| for c in range(3): |
| image[c][mask] = noise_full[c][mask] |
| return image, zone_map, types |
|
|
|
|
| |
| |
| |
|
|
| def extract_patches(images, ps=4): |
| B, C, H, W = images.shape |
| gh, gw = H // ps, W // ps |
| p = images.reshape(B, C, gh, ps, gw, ps) |
| return p.permute(0, 2, 4, 1, 3, 5).reshape(B, gh * gw, C * ps * ps), gh, gw |
|
|
|
|
| def stitch_patches(patches, gh, gw, ps=4): |
| B = patches.shape[0] |
| p = patches.reshape(B, gh, gw, 3, ps, ps) |
| return p.permute(0, 3, 1, 4, 2, 5).reshape(B, 3, gh * ps, gw * ps) |
|
|
|
|
| @torch.no_grad() |
| def tile_encode_full(model, image, tile_size=64, ps=4, device='cuda'): |
| """Tile-encode any resolution image. Returns omega grid + SVD cache.""" |
| C, H, W = image.shape |
| |
| pad_h = (tile_size - H % tile_size) % tile_size |
| pad_w = (tile_size - W % tile_size) % tile_size |
| if pad_h or pad_w: |
| image = F.pad(image, (0, pad_w, 0, pad_h), mode='reflect') |
| C, H, W = image.shape |
|
|
| tiles_h, tiles_w = H // tile_size, W // tile_size |
| gh_tile, gw_tile = tile_size // ps, tile_size // ps |
|
|
| all_S, all_S_orig = [], [] |
| for th in range(tiles_h): |
| for tw in range(tiles_w): |
| tile = image[:, th * tile_size:(th + 1) * tile_size, |
| tw * tile_size:(tw + 1) * tile_size] |
| tile = tile.unsqueeze(0).to(device) |
| out = model(tile) |
| all_S.append(out['svd']['S'].cpu()) |
| all_S_orig.append(out['svd']['S_orig'].cpu()) |
|
|
| |
| S_tiles = torch.cat(all_S, dim=1) |
| S_orig = torch.cat(all_S_orig, dim=1) |
| gh_full = tiles_h * gh_tile |
| gw_full = tiles_w * gw_tile |
| return S_tiles, S_orig, gh_full, gw_full |
|
|
|
|
| |
| |
| |
|
|
| @torch.no_grad() |
| def compute_reference_fingerprints(model, device, ps=4, n_samples=64): |
| """Compute mean omega token profile for each of the 16 known noise types.""" |
| rng = np.random.RandomState(42) |
| refs = {} |
| for t in range(16): |
| all_S = [] |
| for _ in range(n_samples): |
| img = gen_known_noise(t, 64, rng).clamp(-4, 4).unsqueeze(0).to(device) |
| out = model(img) |
| |
| all_S.append(out['svd']['S'].mean(dim=1)) |
| refs[t] = torch.cat(all_S, dim=0).mean(0) |
| return refs |
|
|
|
|
| def classify_omega(omega, refs): |
| """Classify an omega token against reference fingerprints. |
| |
| Args: |
| omega: (D,) single omega token |
| refs: dict {type_id: (D,) reference} |
| |
| Returns: |
| best_type, confidence (cosine similarity) |
| """ |
| best_sim, best_t = -1, 0 |
| for t, ref in refs.items(): |
| sim = F.cosine_similarity(omega.unsqueeze(0), ref.unsqueeze(0)).item() |
| if sim > best_sim: |
| best_sim = sim |
| best_t = t |
| return best_t, best_sim |
|
|
|
|
| |
| |
| |
|
|
| @torch.no_grad() |
| def test_extreme_resolution(model, device, ps=4): |
| """Tile-encode at extreme resolutions, measure reconstruction and timing.""" |
| print("\n" + "=" * 70) |
| print("TEST 1: Extreme Resolution Scaling") |
| print("=" * 70) |
|
|
| resolutions = [36, 52, 64, 76, 100, 128, 140, 172, 204, 256, 300, |
| 444, 512, 600, 1024, 2048, 4096] |
| rng = np.random.RandomState(42) |
| results = {} |
|
|
| for res in resolutions: |
| |
| if res % ps != 0: |
| print(f" {res}Γ{res} β skipped (not divisible by {ps})") |
| continue |
|
|
| n_patches = (res // ps) ** 2 |
| |
| tile = min(64, res) |
| if res % tile != 0: |
| tile = res |
|
|
| try: |
| t0 = time.time() |
| |
| img = gen_known_noise(0, res, rng).clamp(-4, 4) |
|
|
| if res <= 256: |
| |
| img_batch = img.unsqueeze(0).to(device) |
| out = model(img_batch) |
| mse = F.mse_loss(out['recon'], img_batch).item() |
| else: |
| |
| S, S_orig, gh, gw = tile_encode_full(model, img, tile_size=tile, ps=ps, device=device) |
| mse = -1 |
|
|
| elapsed = time.time() - t0 |
| mem_mb = torch.cuda.max_memory_allocated() / 1e6 if torch.cuda.is_available() else 0 |
|
|
| results[res] = { |
| 'patches': n_patches, |
| 'mse': mse, |
| 'time_s': elapsed, |
| 'mem_mb': mem_mb, |
| } |
| mse_str = f"{mse:.6f}" if mse >= 0 else "tile-only" |
| print(f" {res:>5d}Γ{res:<5d} {n_patches:>8d} patches | " |
| f"MSE={mse_str:>12s} | {elapsed:.2f}s | {mem_mb:.0f}MB") |
|
|
| if torch.cuda.is_available(): |
| torch.cuda.reset_peak_memory_stats() |
|
|
| except Exception as e: |
| print(f" {res:>5d}Γ{res:<5d} β FAILED: {e}") |
| results[res] = {'error': str(e)} |
|
|
| return results |
|
|
|
|
| |
| |
| |
|
|
| @torch.no_grad() |
| def test_ood_noise(model, device, ps=4, n_samples=8): |
| """Test reconstruction of noise types never seen during training.""" |
| print("\n" + "=" * 70) |
| print("TEST 2: Out-of-Distribution Noise Types") |
| print("=" * 70) |
|
|
| rng = np.random.RandomState(77) |
| results = {} |
|
|
| |
| known_mses = {} |
| for t in range(16): |
| imgs = torch.stack([gen_known_noise(t, 64, rng).clamp(-4, 4) |
| for _ in range(n_samples)]).to(device) |
| out = model(imgs) |
| known_mses[KNOWN_NAMES[t]] = F.mse_loss(out['recon'], imgs).item() |
|
|
| known_avg = np.mean(list(known_mses.values())) |
| print(f"\n Known noise avg MSE: {known_avg:.6f}") |
|
|
| |
| for t in sorted(OOD_NAMES.keys()): |
| imgs = torch.stack([gen_ood_noise(t, 64, rng).clamp(-4, 4) |
| for _ in range(n_samples)]).to(device) |
| out = model(imgs) |
| mse = F.mse_loss(out['recon'], imgs).item() |
|
|
| |
| erank = model.effective_rank(out['svd']['S'].reshape(-1, model.D)).mean().item() |
|
|
| ratio = mse / (known_avg + 1e-10) |
| results[OOD_NAMES[t]] = {'mse': mse, 'erank': erank, 'ratio_vs_known': ratio} |
| status = "β handles" if ratio < 10 else "β³ degrades" if ratio < 100 else "β fails" |
| print(f" {OOD_NAMES[t]:<18s} MSE={mse:.6f} er={erank:.2f} " |
| f"ratio={ratio:.1f}x {status}") |
|
|
| return results |
|
|
|
|
| |
| |
| |
|
|
| @torch.no_grad() |
| def test_noise_triangulation(model, device, ps=4): |
| """Can omega tokens identify which noise is where? |
| |
| Generate zoned images, encode, classify each patch against |
| reference fingerprints. Measure classification accuracy WITHOUT |
| any classification training β purely spectral similarity. |
| """ |
| print("\n" + "=" * 70) |
| print("TEST 3: Noise Triangulation (zero-shot spatial classification)") |
| print("=" * 70) |
|
|
| |
| refs = compute_reference_fingerprints(model, device, ps=ps) |
| print(f" Reference fingerprints computed for {len(refs)} types") |
|
|
| rng = np.random.RandomState(999) |
| results = {} |
|
|
| for n_zones, res in [(4, 128), (4, 256), (4, 512), |
| (9, 128), (9, 256), |
| (16, 256), (16, 512)]: |
| if res % ps != 0: |
| continue |
|
|
| accuracies = [] |
| for trial in range(5): |
| image, zone_map, zone_types = gen_zone_matte(res, n_zones, rng) |
| image = image.clamp(-4, 4) |
|
|
| |
| if res <= 256: |
| img_batch = image.unsqueeze(0).to(device) |
| out = model(img_batch) |
| S = out['svd']['S'] |
| else: |
| S, _, gh, gw = tile_encode_full(model, image, tile_size=64, ps=ps, device=device) |
|
|
| |
| gh_full = res // ps |
| gw_full = res // ps |
| correct, total = 0, 0 |
|
|
| for pi in range(S.shape[1]): |
| |
| row = (pi // gw_full) * ps + ps // 2 |
| col = (pi % gw_full) * ps + ps // 2 |
| row = min(row, res - 1) |
| col = min(col, res - 1) |
|
|
| |
| gt_zone = zone_map[row, col].item() |
| gt_type = zone_types[gt_zone] |
|
|
| |
| omega = S[0, pi].to(device) |
| pred_type, conf = classify_omega(omega, refs) |
|
|
| if pred_type == gt_type: |
| correct += 1 |
| total += 1 |
|
|
| acc = correct / total |
| accuracies.append(acc) |
|
|
| mean_acc = np.mean(accuracies) |
| std_acc = np.std(accuracies) |
| key = f"{n_zones}zones_{res}px" |
| results[key] = {'accuracy': mean_acc, 'std': std_acc, 'n_zones': n_zones, 'res': res} |
| print(f" {key:<20s} acc={mean_acc:.1%} Β± {std_acc:.1%} " |
| f"({res // ps}Γ{res // ps} grid, {n_zones} zones)") |
|
|
| return results |
|
|
|
|
| |
| |
| |
|
|
| @torch.no_grad() |
| def test_composites(model, device, ps=4, n_samples=8): |
| """Test reconstruction of layered noise composites.""" |
| print("\n" + "=" * 70) |
| print("TEST 4: Multi-Noise Composites") |
| print("=" * 70) |
|
|
| rng = np.random.RandomState(555) |
| results = {} |
|
|
| composites = [ |
| ('gauss+pink', [(0, 0.5), (4, 0.5)]), |
| ('gauss+salt', [(0, 0.7), (6, 0.3)]), |
| ('pink+brown', [(4, 0.5), (5, 0.5)]), |
| ('cauchy+laplace', [(13, 0.5), (15, 0.5)]), |
| ('checker+gradient', [(10, 0.6), (9, 0.4)]), |
| ('3-way: gauss+pink+block', [(0, 0.4), (4, 0.3), (8, 0.3)]), |
| ('4-way: gauss+unif+pink+expo', [(0, 0.25), (1, 0.25), (4, 0.25), (14, 0.25)]), |
| ('heavy: cauchy+salt+sparse', [(13, 0.4), (6, 0.3), (7, 0.3)]), |
| ('gentle: pink+brown+gradient', [(4, 0.4), (5, 0.3), (9, 0.3)]), |
| ('all_16_equal', [(t, 1/16) for t in range(16)]), |
| ] |
|
|
| for name, components in composites: |
| mses = [] |
| for _ in range(n_samples): |
| img = torch.zeros(3, 64, 64) |
| for noise_type, weight in components: |
| img = img + weight * gen_known_noise(noise_type, 64, rng).clamp(-4, 4) |
| img = img.clamp(-4, 4).unsqueeze(0).to(device) |
| out = model(img) |
| mses.append(F.mse_loss(out['recon'], img).item()) |
|
|
| avg_mse = np.mean(mses) |
| results[name] = {'mse': avg_mse, 'n_components': len(components)} |
| print(f" {name:<35s} MSE={avg_mse:.6f} ({len(components)} layers)") |
|
|
| return results |
|
|
|
|
| |
| |
| |
|
|
| def load_freckles(model_path=None, hf_version=None, device='cuda'): |
| if hf_version: |
| from huggingface_hub import hf_hub_download |
| ckpt_path = hf_hub_download( |
| repo_id='AbstractPhil/geolip-SVAE', |
| filename=f'{hf_version}/checkpoints/best.pt', |
| repo_type='model') |
| else: |
| ckpt_path = model_path |
|
|
| ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=False) |
| cfg = ckpt['config'] |
|
|
| |
| from geolip_core.linalg.eigh import FLEigh, _FL_MAX_N |
|
|
| def gram_eigh_svd(A): |
| B, M, N = A.shape |
| orig_dtype = A.dtype |
| if N <= _FL_MAX_N and A.is_cuda: |
| with torch.amp.autocast('cuda', enabled=False): |
| A_d = A.double() |
| G = torch.bmm(A_d.transpose(1, 2), A_d) |
| eigenvalues, V = FLEigh()(G.float()) |
| eigenvalues = eigenvalues.double().flip(-1) |
| V = V.double().flip(-1) |
| S = torch.sqrt(eigenvalues.clamp(min=1e-24)) |
| U = torch.bmm(A_d, V) / S.unsqueeze(1).clamp(min=1e-16) |
| Vh = V.transpose(-2, -1).contiguous() |
| return U.to(orig_dtype), S.to(orig_dtype), Vh.to(orig_dtype) |
| with torch.amp.autocast('cuda', enabled=False): |
| A_d = A.double() |
| G = torch.bmm(A_d.transpose(1, 2), A_d) |
| G.diagonal(dim1=-2, dim2=-1).add_(1e-12) |
| eigenvalues, V = torch.linalg.eigh(G) |
| eigenvalues = eigenvalues.flip(-1); V = V.flip(-1) |
| S = torch.sqrt(eigenvalues.clamp(min=1e-24)) |
| U = torch.bmm(A_d, V) / S.unsqueeze(1).clamp(min=1e-16) |
| Vh = V.transpose(-2, -1).contiguous() |
| return U.to(orig_dtype), S.to(orig_dtype), Vh.to(orig_dtype) |
|
|
| class SpectralCrossAttention(nn.Module): |
| def __init__(self, D, n_heads=2, max_alpha=0.2, alpha_init=-2.0): |
| super().__init__() |
| self.n_heads = n_heads; self.head_dim = D // n_heads |
| self.max_alpha = max_alpha; assert D % n_heads == 0 |
| self.qkv = nn.Linear(D, 3 * D); self.out_proj = nn.Linear(D, D) |
| self.norm = nn.LayerNorm(D); self.scale = self.head_dim ** -0.5 |
| self.alpha_logits = nn.Parameter(torch.full((D,), alpha_init)) |
| @property |
| def alpha(self): return self.max_alpha * torch.sigmoid(self.alpha_logits) |
| def forward(self, S): |
| B, N, D = S.shape; S_n = self.norm(S) |
| qkv = self.qkv(S_n).reshape(B, N, 3, self.n_heads, self.head_dim).permute(2, 0, 3, 1, 4) |
| q, k, v = qkv[0], qkv[1], qkv[2] |
| out = (((q @ k.transpose(-2, -1)) * self.scale).softmax(-1) @ v).transpose(1, 2).reshape(B, N, D) |
| return S * (1.0 + self.alpha.unsqueeze(0).unsqueeze(0) * torch.tanh(self.out_proj(out))) |
|
|
| class BoundarySmooth(nn.Module): |
| def __init__(self, channels=3, mid=8): |
| super().__init__() |
| self.net = nn.Sequential(nn.Conv2d(channels, mid, 3, padding=1), nn.GELU(), |
| nn.Conv2d(mid, channels, 3, padding=1)) |
| nn.init.zeros_(self.net[-1].weight); nn.init.zeros_(self.net[-1].bias) |
| def forward(self, x): return x + self.net(x) |
|
|
| class Freckles(nn.Module): |
| def __init__(self, V=48, D=4, ps=4, hidden=384, depth=4, n_cross=2): |
| super().__init__() |
| self.V, self.D, self.patch_size = V, D, ps |
| self.patch_dim = 3 * ps * ps; self.mat_dim = V * D |
| self.enc_in = nn.Linear(self.patch_dim, hidden) |
| self.enc_blocks = nn.ModuleList([nn.Sequential( |
| nn.LayerNorm(hidden), nn.Linear(hidden, hidden), |
| nn.GELU(), nn.Linear(hidden, hidden)) for _ in range(depth)]) |
| self.enc_out = nn.Linear(hidden, self.mat_dim) |
| nn.init.orthogonal_(self.enc_out.weight) |
| self.dec_in = nn.Linear(self.mat_dim, hidden) |
| self.dec_blocks = nn.ModuleList([nn.Sequential( |
| nn.LayerNorm(hidden), nn.Linear(hidden, hidden), |
| nn.GELU(), nn.Linear(hidden, hidden)) for _ in range(depth)]) |
| self.dec_out = nn.Linear(hidden, self.patch_dim) |
| self.cross_attn = nn.ModuleList([ |
| SpectralCrossAttention(D, n_heads=min(2, D)) for _ in range(n_cross)]) |
| self.boundary_smooth = BoundarySmooth(channels=3, mid=8) |
|
|
| def encode_patches(self, patches): |
| B, N, _ = patches.shape |
| h = F.gelu(self.enc_in(patches.reshape(B * N, -1))) |
| for block in self.enc_blocks: h = h + block(h) |
| M = F.normalize(self.enc_out(h).reshape(B * N, self.V, self.D), dim=-1) |
| U, S, Vt = gram_eigh_svd(M) |
| U = U.reshape(B, N, self.V, self.D); S = S.reshape(B, N, self.D) |
| Vt = Vt.reshape(B, N, self.D, self.D); M = M.reshape(B, N, self.V, self.D) |
| S_c = S |
| for layer in self.cross_attn: S_c = layer(S_c) |
| return {'U': U, 'S_orig': S, 'S': S_c, 'Vt': Vt, 'M': M} |
|
|
| def decode_patches(self, U, S, Vt): |
| B, N, V, D = U.shape |
| M_hat = torch.bmm(U.reshape(B*N, V, D) * S.reshape(B*N, D).unsqueeze(1), |
| Vt.reshape(B*N, D, D)) |
| h = F.gelu(self.dec_in(M_hat.reshape(B * N, -1))) |
| for block in self.dec_blocks: h = h + block(h) |
| return self.dec_out(h).reshape(B, N, -1) |
|
|
| def forward(self, images): |
| patches, gh, gw = extract_patches(images, self.patch_size) |
| svd = self.encode_patches(patches) |
| decoded = self.decode_patches(svd['U'], svd['S'], svd['Vt']) |
| recon = stitch_patches(decoded, gh, gw, self.patch_size) |
| return {'recon': self.boundary_smooth(recon), 'svd': svd} |
|
|
| @staticmethod |
| def effective_rank(S): |
| p = S / (S.sum(-1, keepdim=True) + 1e-8); p = p.clamp(min=1e-8) |
| return (-(p * p.log()).sum(-1)).exp() |
|
|
| model = Freckles(V=cfg['V'], D=cfg['D'], ps=cfg['patch_size'], |
| hidden=cfg['hidden'], depth=cfg['depth'], |
| n_cross=cfg['n_cross_layers']).to(device) |
| model.load_state_dict(ckpt['model_state_dict'], strict=True) |
| model.eval() |
| print(f" Loaded Freckles: {sum(p.numel() for p in model.parameters()):,} params") |
| print(f" Config: V={cfg['V']}, D={cfg['D']}, ps={cfg['patch_size']}") |
| return model, cfg |
|
|
|
|
| |
| |
| |
|
|
| def run_stress_test(model, device, ps=4, save_path=None): |
| print("\n" + "=" * 70) |
| print("FRECKLES STRESS TEST β Extreme Resolution & OOD Noise") |
| print("=" * 70) |
|
|
| t0 = time.time() |
| all_results = {} |
|
|
| all_results['extreme_resolution'] = test_extreme_resolution(model, device, ps=ps) |
| all_results['ood_noise'] = test_ood_noise(model, device, ps=ps) |
| all_results['noise_triangulation'] = test_noise_triangulation(model, device, ps=ps) |
| all_results['composites'] = test_composites(model, device, ps=ps) |
|
|
| elapsed = time.time() - t0 |
| print(f"\n{'=' * 70}") |
| print(f"STRESS TEST COMPLETE β {elapsed:.1f}s") |
| print(f"{'=' * 70}") |
|
|
| if save_path: |
| def to_json(obj): |
| if isinstance(obj, (torch.Tensor, np.ndarray)): |
| return float(obj) |
| if isinstance(obj, dict): |
| return {str(k): to_json(v) for k, v in obj.items()} |
| if isinstance(obj, (list, tuple)): |
| return [to_json(v) for v in obj] |
| return obj |
| with open(save_path, 'w') as f: |
| json.dump(to_json(all_results), f, indent=2) |
| print(f" Results saved: {save_path}") |
|
|
| return all_results |
|
|
|
|
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser(description='Freckles Stress Test') |
| parser.add_argument('--model', type=str, default='v40_freckles_noise') |
| parser.add_argument('--checkpoint', type=str, default=None) |
| parser.add_argument('--output', type=str, default='freckles_stress_test.json') |
| args = parser.parse_args() |
|
|
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
| model, cfg = load_freckles(model_path=args.checkpoint, hf_version=args.model, device=device) |
| run_stress_test(model, device, ps=cfg['patch_size'], save_path=args.output) |