Add 14 new transforms: object extraction, fill interior, connect, compress, proximity, draw border — 33 total
Browse files- itt_solver/transforms.py +0 -253
itt_solver/transforms.py
CHANGED
|
@@ -1,253 +0,0 @@
|
|
| 1 |
-
import numpy as np
|
| 2 |
-
from .solver_core import tile_transform, fill_enclosed, Transform
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# ---------------------------------------------------------------------------
|
| 6 |
-
# Existing transforms (cleaned up to import Transform from solver_core)
|
| 7 |
-
# ---------------------------------------------------------------------------
|
| 8 |
-
|
| 9 |
-
def tile_to_target_shifted(shift=(1, 1), tile_factor=3):
|
| 10 |
-
"""Tile the input tile_factor×tile_factor times, then roll by shift."""
|
| 11 |
-
def fn(phi):
|
| 12 |
-
h_in, w_in = phi.shape
|
| 13 |
-
out_shape = (h_in * tile_factor, w_in * tile_factor)
|
| 14 |
-
tiled = tile_transform(phi, out_shape)
|
| 15 |
-
tiled = np.roll(tiled, shift=shift, axis=(0, 1))
|
| 16 |
-
return tiled
|
| 17 |
-
return Transform(fn, f"ShiftedTile_s{shift}_f{tile_factor}")
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
def FillEnclosedHarmonic(boundary_mask=None):
|
| 21 |
-
def fn(phi):
|
| 22 |
-
bm = (phi != 0) if boundary_mask is None else boundary_mask
|
| 23 |
-
return fill_enclosed(phi, bm)
|
| 24 |
-
return Transform(fn, "FillEnclosedHarmonic")
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def Rotate(k=1):
|
| 28 |
-
def fn(phi):
|
| 29 |
-
return np.rot90(phi, k)
|
| 30 |
-
return Transform(fn, f"Rotate_{90 * k}")
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
def Reflect(axis='h'):
|
| 34 |
-
def fn(phi):
|
| 35 |
-
if axis == 'h':
|
| 36 |
-
return np.flipud(phi)
|
| 37 |
-
return np.fliplr(phi)
|
| 38 |
-
return Transform(fn, f"Reflect_{axis}")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def ColorMap(mapping):
|
| 42 |
-
def fn(phi):
|
| 43 |
-
out = phi.copy()
|
| 44 |
-
for k, v in mapping.items():
|
| 45 |
-
out[phi == k] = v
|
| 46 |
-
return out
|
| 47 |
-
return Transform(fn, f"ColorMap_{mapping}")
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
# ---------------------------------------------------------------------------
|
| 51 |
-
# NEW transforms — Kronecker / self‑similar family
|
| 52 |
-
# ---------------------------------------------------------------------------
|
| 53 |
-
|
| 54 |
-
def KroneckerSelfSimilar():
|
| 55 |
-
"""output = kron((input != 0).astype(int), input)
|
| 56 |
-
|
| 57 |
-
This is the exact transform for ARC task 007bbfb7 and the general
|
| 58 |
-
family of "use the input's own nonzero pattern as a meta‑layout
|
| 59 |
-
for placing copies of itself". Works for any 2‑color grid.
|
| 60 |
-
"""
|
| 61 |
-
def fn(phi):
|
| 62 |
-
mask = (phi != 0).astype(phi.dtype)
|
| 63 |
-
return np.kron(mask, phi)
|
| 64 |
-
return Transform(fn, "KroneckerSelfSimilar")
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
def KroneckerSelfSimilarInv():
|
| 68 |
-
"""output = kron(input, (input != 0).astype(int))
|
| 69 |
-
|
| 70 |
-
Mirror variant — identical result for symmetric inputs, differs
|
| 71 |
-
for asymmetric ones.
|
| 72 |
-
"""
|
| 73 |
-
def fn(phi):
|
| 74 |
-
mask = (phi != 0).astype(phi.dtype)
|
| 75 |
-
return np.kron(phi, mask)
|
| 76 |
-
return Transform(fn, "KroneckerSelfSimilarInv")
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
# ---------------------------------------------------------------------------
|
| 80 |
-
# NEW transforms — mirror / kaleidoscope tiling
|
| 81 |
-
# ---------------------------------------------------------------------------
|
| 82 |
-
|
| 83 |
-
def MirrorTileH():
|
| 84 |
-
"""Horizontal mirror tile: [abc] → [abc|cba]"""
|
| 85 |
-
def fn(phi):
|
| 86 |
-
return np.hstack([phi, np.fliplr(phi)])
|
| 87 |
-
return Transform(fn, "MirrorTileH")
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
def MirrorTileV():
|
| 91 |
-
"""Vertical mirror tile: stack input then its vertical reflection."""
|
| 92 |
-
def fn(phi):
|
| 93 |
-
return np.vstack([phi, np.flipud(phi)])
|
| 94 |
-
return Transform(fn, "MirrorTileV")
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
def MirrorTile4Way():
|
| 98 |
-
"""Full kaleidoscope: 2×2 mirror tile (D4 reflections)."""
|
| 99 |
-
def fn(phi):
|
| 100 |
-
top = np.hstack([phi, np.fliplr(phi)])
|
| 101 |
-
return np.vstack([top, np.flipud(top)])
|
| 102 |
-
return Transform(fn, "MirrorTile4Way")
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
# ---------------------------------------------------------------------------
|
| 106 |
-
# NEW transforms — upscale / zoom
|
| 107 |
-
# ---------------------------------------------------------------------------
|
| 108 |
-
|
| 109 |
-
def Upscale(k=2):
|
| 110 |
-
"""Pixel‑repeat upscale: each pixel becomes a k×k block."""
|
| 111 |
-
def fn(phi):
|
| 112 |
-
return np.kron(phi, np.ones((k, k), dtype=phi.dtype))
|
| 113 |
-
return Transform(fn, f"Upscale_{k}x")
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
def Downscale(k=2):
|
| 117 |
-
"""Downsample by taking every k‑th pixel (inverse of Upscale)."""
|
| 118 |
-
def fn(phi):
|
| 119 |
-
return phi[::k, ::k].copy()
|
| 120 |
-
return Transform(fn, f"Downscale_{k}x")
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
# ---------------------------------------------------------------------------
|
| 124 |
-
# NEW transforms — stacking
|
| 125 |
-
# ---------------------------------------------------------------------------
|
| 126 |
-
|
| 127 |
-
def StackH(n=2):
|
| 128 |
-
"""Tile horizontally n times: [A] → [A|A|...|A]."""
|
| 129 |
-
def fn(phi):
|
| 130 |
-
return np.tile(phi, (1, n))
|
| 131 |
-
return Transform(fn, f"StackH_{n}")
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
def StackV(n=2):
|
| 135 |
-
"""Tile vertically n times."""
|
| 136 |
-
def fn(phi):
|
| 137 |
-
return np.tile(phi, (n, 1))
|
| 138 |
-
return Transform(fn, f"StackV_{n}")
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
# ---------------------------------------------------------------------------
|
| 142 |
-
# NEW transforms — color manipulation
|
| 143 |
-
# ---------------------------------------------------------------------------
|
| 144 |
-
|
| 145 |
-
def RetainColor(color):
|
| 146 |
-
"""Keep only pixels of the given color; zero the rest."""
|
| 147 |
-
def fn(phi):
|
| 148 |
-
out = np.zeros_like(phi)
|
| 149 |
-
out[phi == color] = color
|
| 150 |
-
return out
|
| 151 |
-
return Transform(fn, f"RetainColor_{color}")
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def RemoveColor(color):
|
| 155 |
-
"""Zero out all pixels of the given color."""
|
| 156 |
-
def fn(phi):
|
| 157 |
-
out = phi.copy()
|
| 158 |
-
out[phi == color] = 0
|
| 159 |
-
return out
|
| 160 |
-
return Transform(fn, f"RemoveColor_{color}")
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
def InvertColors():
|
| 164 |
-
"""Swap black (0) with the most common non‑zero color."""
|
| 165 |
-
def fn(phi):
|
| 166 |
-
nonzero = phi[phi != 0]
|
| 167 |
-
if nonzero.size == 0:
|
| 168 |
-
return phi.copy()
|
| 169 |
-
from collections import Counter
|
| 170 |
-
top_color = Counter(nonzero.flatten().astype(int).tolist()).most_common(1)[0][0]
|
| 171 |
-
out = phi.copy()
|
| 172 |
-
mask_zero = (phi == 0)
|
| 173 |
-
mask_top = (phi == top_color)
|
| 174 |
-
out[mask_zero] = top_color
|
| 175 |
-
out[mask_top] = 0
|
| 176 |
-
return out
|
| 177 |
-
return Transform(fn, "InvertColors")
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
# ---------------------------------------------------------------------------
|
| 181 |
-
# NEW transforms — gravity
|
| 182 |
-
# ---------------------------------------------------------------------------
|
| 183 |
-
|
| 184 |
-
def GravityDown():
|
| 185 |
-
"""Non‑zero pixels fall to the bottom within each column."""
|
| 186 |
-
def fn(phi):
|
| 187 |
-
out = np.zeros_like(phi)
|
| 188 |
-
h, w = phi.shape
|
| 189 |
-
for c in range(w):
|
| 190 |
-
col = phi[:, c]
|
| 191 |
-
nonzero = col[col != 0]
|
| 192 |
-
if nonzero.size > 0:
|
| 193 |
-
out[h - nonzero.size:, c] = nonzero
|
| 194 |
-
return out
|
| 195 |
-
return Transform(fn, "GravityDown")
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
def GravityUp():
|
| 199 |
-
"""Non‑zero pixels rise to the top within each column."""
|
| 200 |
-
def fn(phi):
|
| 201 |
-
out = np.zeros_like(phi)
|
| 202 |
-
h, w = phi.shape
|
| 203 |
-
for c in range(w):
|
| 204 |
-
col = phi[:, c]
|
| 205 |
-
nonzero = col[col != 0]
|
| 206 |
-
if nonzero.size > 0:
|
| 207 |
-
out[:nonzero.size, c] = nonzero
|
| 208 |
-
return out
|
| 209 |
-
return Transform(fn, "GravityUp")
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
# ---------------------------------------------------------------------------
|
| 213 |
-
# NEW transforms — overlay / composition
|
| 214 |
-
# ---------------------------------------------------------------------------
|
| 215 |
-
|
| 216 |
-
def OverlayTransparent(background):
|
| 217 |
-
"""Overlay: background pixels are replaced by foreground where foreground != 0."""
|
| 218 |
-
bg = np.array(background, dtype=float)
|
| 219 |
-
def fn(phi):
|
| 220 |
-
out = bg.copy()
|
| 221 |
-
mask = (phi != 0)
|
| 222 |
-
if phi.shape != out.shape:
|
| 223 |
-
p = tile_transform(phi, out.shape)
|
| 224 |
-
m = (p != 0)
|
| 225 |
-
out[m] = p[m]
|
| 226 |
-
else:
|
| 227 |
-
out[mask] = phi[mask]
|
| 228 |
-
return out
|
| 229 |
-
return Transform(fn, "OverlayTransparent")
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
# ---------------------------------------------------------------------------
|
| 233 |
-
# NEW transforms — border / crop helpers
|
| 234 |
-
# ---------------------------------------------------------------------------
|
| 235 |
-
|
| 236 |
-
def CropToContent():
|
| 237 |
-
"""Crop grid to bounding box of non‑zero content."""
|
| 238 |
-
def fn(phi):
|
| 239 |
-
rows = np.any(phi != 0, axis=1)
|
| 240 |
-
cols = np.any(phi != 0, axis=0)
|
| 241 |
-
if not rows.any():
|
| 242 |
-
return phi.copy()
|
| 243 |
-
rmin, rmax = np.where(rows)[0][[0, -1]]
|
| 244 |
-
cmin, cmax = np.where(cols)[0][[0, -1]]
|
| 245 |
-
return phi[rmin:rmax + 1, cmin:cmax + 1].copy()
|
| 246 |
-
return Transform(fn, "CropToContent")
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
def Transpose():
|
| 250 |
-
"""Matrix transpose."""
|
| 251 |
-
def fn(phi):
|
| 252 |
-
return phi.T.copy()
|
| 253 |
-
return Transform(fn, "Transpose")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|