geolip-SVAE / v41_freckles_256 /freckles_noise_array_test.py
AbstractPhil's picture
Create freckles_noise_array_test.py
2bec3fb verified
Raw
History Blame Contribute Delete
36.5 kB
"""
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 NOISE (the 16 training types for reference fingerprints)
# ═══════════════════════════════════════════════════════════════
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)
# ═══════════════════════════════════════════════════════════════
# OUT-OF-SCOPE NOISE TYPES (never trained on)
# ═══════════════════════════════════════════════════════════════
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: # log-normal
return torch.exp(torch.randn(3, s, s) * 0.5) - 1.5
elif t == 101: # beta
a = torch.distributions.Beta(0.5, 0.5)
return a.sample((3, s, s)) * 4 - 2
elif t == 102: # weibull
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: # gumbel
u = torch.rand(3, s, s).clamp(1e-8, 1 - 1e-8)
return -(torch.log(-torch.log(u))) * 0.5
elif t == 104: # rayleigh
return torch.sqrt(-2 * torch.log(torch.rand(3, s, s).clamp(min=1e-8))) - 1.0
elif t == 105: # perlin approximation (multi-octave interpolated noise)
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: # wavelet-like (high frequency bands)
img = torch.randn(3, s, s)
# Kill low frequencies
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: # fractional Brownian motion approximation
img = torch.zeros(3, s, s)
H = 0.7 # Hurst exponent
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: # gabor-like (oriented sinusoidal + gaussian envelope)
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: # sine composite (multiple frequencies)
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: # voronoi approximation (nearest-seed distance)
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: # shot noise (poisson with very low lambda)
lam = rng.uniform(0.01, 0.2)
return torch.poisson(torch.full((3, s, s), lam)) * 3.0 - lam * 3
elif t == 112: # quantization noise
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: # jpeg-like block artifact (8Γ—8 blocks with discontinuities)
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: # ring/radial noise
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: # spiral noise
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)
# ═══════════════════════════════════════════════════════════════
# SPATIAL MATTE GENERATORS
# ═══════════════════════════════════════════════════════════════
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:
# Quadrant split
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:
# 3Γ—3 grid
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:
# 4Γ—4 grid β€” all 16 types
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
# Random blobs
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
# ═══════════════════════════════════════════════════════════════
# PATCH / TILE UTILITIES
# ═══════════════════════════════════════════════════════════════
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 to tile_size multiple
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())
# Reshape into full grid
S_tiles = torch.cat(all_S, dim=1) # (1, total_patches, D)
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
# ═══════════════════════════════════════════════════════════════
# REFERENCE FINGERPRINTS
# ═══════════════════════════════════════════════════════════════
@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)
# Mean omega token across all patches
all_S.append(out['svd']['S'].mean(dim=1))
refs[t] = torch.cat(all_S, dim=0).mean(0) # (D,)
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
# ═══════════════════════════════════════════════════════════════
# TEST 1: EXTREME RESOLUTIONS
# ═══════════════════════════════════════════════════════════════
@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:
# Skip if resolution not divisible by ps
if res % ps != 0:
print(f" {res}Γ—{res} β€” skipped (not divisible by {ps})")
continue
n_patches = (res // ps) ** 2
# Use smaller tile for very large images
tile = min(64, res)
if res % tile != 0:
tile = res # fall back to full if not tileable
try:
t0 = time.time()
# Test with gaussian
img = gen_known_noise(0, res, rng).clamp(-4, 4)
if res <= 256:
# Direct encoding
img_batch = img.unsqueeze(0).to(device)
out = model(img_batch)
mse = F.mse_loss(out['recon'], img_batch).item()
else:
# Tile encoding
S, S_orig, gh, gw = tile_encode_full(model, img, tile_size=tile, ps=ps, device=device)
mse = -1 # can't easily compute pixel MSE for tiled without full decode
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
# ═══════════════════════════════════════════════════════════════
# TEST 2: OUT-OF-SCOPE NOISE
# ═══════════════════════════════════════════════════════════════
@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 baseline
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}")
# OOD types
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()
# Effective rank of OOD
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
# ═══════════════════════════════════════════════════════════════
# TEST 3: SPATIAL MATTE β€” NOISE TRIANGULATION
# ═══════════════════════════════════════════════════════════════
@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)
# Build reference fingerprints
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)
# Encode
if res <= 256:
img_batch = image.unsqueeze(0).to(device)
out = model(img_batch)
S = out['svd']['S'] # (1, N, D)
else:
S, _, gh, gw = tile_encode_full(model, image, tile_size=64, ps=ps, device=device)
# Classify each patch
gh_full = res // ps
gw_full = res // ps
correct, total = 0, 0
for pi in range(S.shape[1]):
# Patch grid position
row = (pi // gw_full) * ps + ps // 2
col = (pi % gw_full) * ps + ps // 2
row = min(row, res - 1)
col = min(col, res - 1)
# Ground truth zone
gt_zone = zone_map[row, col].item()
gt_type = zone_types[gt_zone]
# Classify by omega fingerprint
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
# ═══════════════════════════════════════════════════════════════
# TEST 4: MULTI-NOISE COMPOSITES
# ═══════════════════════════════════════════════════════════════
@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
# ═══════════════════════════════════════════════════════════════
# LOAD MODEL (same as noise_diagnostic.py)
# ═══════════════════════════════════════════════════════════════
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']
# Inline Freckles
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
# ═══════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════
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)