ARC-AGI / itt_solver /transforms.py
rogermt's picture
Fix transforms.py upload — 33 transforms including object extraction, fill, connect, compress, proximity"
9a57ca2 verified
import numpy as np
from collections import deque
from .solver_core import tile_transform, fill_enclosed, Transform
# ---------------------------------------------------------------------------
# Existing transforms (cleaned up to import Transform from solver_core)
# ---------------------------------------------------------------------------
def tile_to_target_shifted(shift=(1, 1), tile_factor=3):
"""Tile the input tile_factor x tile_factor times, then roll by shift."""
def fn(phi):
h_in, w_in = phi.shape
out_shape = (h_in * tile_factor, w_in * tile_factor)
tiled = tile_transform(phi, out_shape)
tiled = np.roll(tiled, shift=shift, axis=(0, 1))
return tiled
return Transform(fn, f"ShiftedTile_s{shift}_f{tile_factor}")
def FillEnclosedHarmonic(boundary_mask=None):
def fn(phi):
bm = (phi != 0) if boundary_mask is None else boundary_mask
return fill_enclosed(phi, bm)
return Transform(fn, "FillEnclosedHarmonic")
def Rotate(k=1):
def fn(phi):
return np.rot90(phi, k)
return Transform(fn, f"Rotate_{90 * k}")
def Reflect(axis='h'):
def fn(phi):
if axis == 'h':
return np.flipud(phi)
return np.fliplr(phi)
return Transform(fn, f"Reflect_{axis}")
def ColorMap(mapping):
def fn(phi):
out = phi.copy()
for k, v in mapping.items():
out[phi == k] = v
return out
return Transform(fn, f"ColorMap_{mapping}")
# ---------------------------------------------------------------------------
# Kronecker / self-similar family
# ---------------------------------------------------------------------------
def KroneckerSelfSimilar():
"""output = kron((input != 0).astype(int), input)"""
def fn(phi):
mask = (phi != 0).astype(phi.dtype)
return np.kron(mask, phi)
return Transform(fn, "KroneckerSelfSimilar")
def KroneckerSelfSimilarInv():
"""output = kron(input, (input != 0).astype(int))"""
def fn(phi):
mask = (phi != 0).astype(phi.dtype)
return np.kron(phi, mask)
return Transform(fn, "KroneckerSelfSimilarInv")
# ---------------------------------------------------------------------------
# Mirror / kaleidoscope tiling
# ---------------------------------------------------------------------------
def MirrorTileH():
def fn(phi):
return np.hstack([phi, np.fliplr(phi)])
return Transform(fn, "MirrorTileH")
def MirrorTileV():
def fn(phi):
return np.vstack([phi, np.flipud(phi)])
return Transform(fn, "MirrorTileV")
def MirrorTile4Way():
def fn(phi):
top = np.hstack([phi, np.fliplr(phi)])
return np.vstack([top, np.flipud(top)])
return Transform(fn, "MirrorTile4Way")
# ---------------------------------------------------------------------------
# Upscale / zoom
# ---------------------------------------------------------------------------
def Upscale(k=2):
def fn(phi):
return np.kron(phi, np.ones((k, k), dtype=phi.dtype))
return Transform(fn, f"Upscale_{k}x")
def Downscale(k=2):
def fn(phi):
return phi[::k, ::k].copy()
return Transform(fn, f"Downscale_{k}x")
# ---------------------------------------------------------------------------
# Stacking
# ---------------------------------------------------------------------------
def StackH(n=2):
def fn(phi):
return np.tile(phi, (1, n))
return Transform(fn, f"StackH_{n}")
def StackV(n=2):
def fn(phi):
return np.tile(phi, (n, 1))
return Transform(fn, f"StackV_{n}")
# ---------------------------------------------------------------------------
# Color manipulation
# ---------------------------------------------------------------------------
def RetainColor(color):
def fn(phi):
out = np.zeros_like(phi)
out[phi == color] = color
return out
return Transform(fn, f"RetainColor_{color}")
def RemoveColor(color):
def fn(phi):
out = phi.copy()
out[phi == color] = 0
return out
return Transform(fn, f"RemoveColor_{color}")
def InvertColors():
def fn(phi):
nonzero = phi[phi != 0]
if nonzero.size == 0:
return phi.copy()
from collections import Counter
top_color = Counter(nonzero.flatten().astype(int).tolist()).most_common(1)[0][0]
out = phi.copy()
mask_zero = (phi == 0)
mask_top = (phi == top_color)
out[mask_zero] = top_color
out[mask_top] = 0
return out
return Transform(fn, "InvertColors")
# ---------------------------------------------------------------------------
# Gravity
# ---------------------------------------------------------------------------
def GravityDown():
def fn(phi):
out = np.zeros_like(phi)
h, w = phi.shape
for c in range(w):
col = phi[:, c]
nonzero = col[col != 0]
if nonzero.size > 0:
out[h - nonzero.size:, c] = nonzero
return out
return Transform(fn, "GravityDown")
def GravityUp():
def fn(phi):
out = np.zeros_like(phi)
h, w = phi.shape
for c in range(w):
col = phi[:, c]
nonzero = col[col != 0]
if nonzero.size > 0:
out[:nonzero.size, c] = nonzero
return out
return Transform(fn, "GravityUp")
# ---------------------------------------------------------------------------
# Overlay / composition
# ---------------------------------------------------------------------------
def OverlayTransparent(background):
bg = np.array(background, dtype=float)
def fn(phi):
out = bg.copy()
mask = (phi != 0)
if phi.shape != out.shape:
p = tile_transform(phi, out.shape)
m = (p != 0)
out[m] = p[m]
else:
out[mask] = phi[mask]
return out
return Transform(fn, "OverlayTransparent")
# ---------------------------------------------------------------------------
# Border / crop helpers
# ---------------------------------------------------------------------------
def CropToContent():
def fn(phi):
rows = np.any(phi != 0, axis=1)
cols = np.any(phi != 0, axis=0)
if not rows.any():
return phi.copy()
rmin, rmax = np.where(rows)[0][[0, -1]]
cmin, cmax = np.where(cols)[0][[0, -1]]
return phi[rmin:rmax + 1, cmin:cmax + 1].copy()
return Transform(fn, "CropToContent")
def Transpose():
def fn(phi):
return phi.T.copy()
return Transform(fn, "Transpose")
# ---------------------------------------------------------------------------
# Object-based transforms (using object_layer)
# ---------------------------------------------------------------------------
def ExtractLargestObject():
def fn(phi):
from .object_layer import extract_objects, object_to_cropped_grid
objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
if not objs:
return phi.copy()
return object_to_cropped_grid(objs[0]).astype(float)
return Transform(fn, "ExtractLargestObject")
def ExtractSmallestObject():
def fn(phi):
from .object_layer import extract_objects, object_to_cropped_grid
objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
if not objs:
return phi.copy()
return object_to_cropped_grid(objs[-1]).astype(float)
return Transform(fn, "ExtractSmallestObject")
def ExtractUniqueObject():
def fn(phi):
from .object_layer import extract_objects, unique_object, object_to_cropped_grid
objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
u = unique_object(objs)
if u is None:
return phi.copy()
return object_to_cropped_grid(u).astype(float)
return Transform(fn, "ExtractUniqueObject")
def ExtractMostCommonObject():
def fn(phi):
from .object_layer import extract_objects, most_common_object, object_to_cropped_grid
objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
mc = most_common_object(objs)
if mc is None:
return phi.copy()
return object_to_cropped_grid(mc).astype(float)
return Transform(fn, "ExtractMostCommonObject")
def KeepLargestObject():
def fn(phi):
from .object_layer import extract_objects, object_to_grid, most_common_color
grid = np.rint(phi).astype(int)
bg = most_common_color(grid)
objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
if not objs:
return phi.copy()
return object_to_grid(objs[0], grid.shape, bg=bg).astype(float)
return Transform(fn, "KeepLargestObject")
def KeepSmallestObject():
def fn(phi):
from .object_layer import extract_objects, object_to_grid, most_common_color
grid = np.rint(phi).astype(int)
bg = most_common_color(grid)
objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
if not objs:
return phi.copy()
return object_to_grid(objs[-1], grid.shape, bg=bg).astype(float)
return Transform(fn, "KeepSmallestObject")
def SortObjectsBySize():
def fn(phi):
from .object_layer import extract_objects, paint, most_common_color, canvas
grid = np.rint(phi).astype(int)
bg = most_common_color(grid)
objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
result = canvas(bg, grid.shape)
for obj in sorted(objs, key=len, reverse=True):
result = paint(result, obj)
return result.astype(float)
return Transform(fn, "SortObjectsBySize")
# ---------------------------------------------------------------------------
# Fill / connect / compress
# ---------------------------------------------------------------------------
def FillInterior():
def fn(phi):
from .object_layer import extract_objects, object_bbox, object_color
grid = np.rint(phi).astype(int).copy()
objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
for obj in objs:
rmin, cmin, rmax, cmax = object_bbox(obj)
color = object_color(obj)
h, w = rmax - rmin + 1, cmax - cmin + 1
local_visited = np.zeros((h, w), dtype=bool)
for _, (r, c) in obj:
local_visited[r - rmin, c - cmin] = True
queue = deque()
exterior = np.zeros((h, w), dtype=bool)
for i in range(h):
for j in range(w):
if (i == 0 or i == h-1 or j == 0 or j == w-1) and not local_visited[i, j]:
exterior[i, j] = True
queue.append((i, j))
while queue:
r, c = queue.popleft()
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
nr, nc = r+dr, c+dc
if 0 <= nr < h and 0 <= nc < w and not exterior[nr, nc] and not local_visited[nr, nc]:
exterior[nr, nc] = True
queue.append((nr, nc))
for i in range(h):
for j in range(w):
if not local_visited[i, j] and not exterior[i, j]:
grid[i + rmin, j + cmin] = color
return grid.astype(float)
return Transform(fn, "FillInterior")
def ConnectSameColorH():
def fn(phi):
grid = np.rint(phi).astype(int).copy()
h, w = grid.shape
from .object_layer import most_common_color
bg = most_common_color(grid)
for r in range(h):
colored = [(c, int(grid[r, c])) for c in range(w) if grid[r, c] != bg]
by_color = {}
for c, val in colored:
by_color.setdefault(val, []).append(c)
for val, cols in by_color.items():
if len(cols) >= 2:
cols.sort()
for i in range(len(cols) - 1):
for c in range(cols[i], cols[i+1] + 1):
grid[r, c] = val
return grid.astype(float)
return Transform(fn, "ConnectSameColorH")
def ConnectSameColorV():
def fn(phi):
grid = np.rint(phi).astype(int).copy()
h, w = grid.shape
from .object_layer import most_common_color
bg = most_common_color(grid)
for c in range(w):
colored = [(r, int(grid[r, c])) for r in range(h) if grid[r, c] != bg]
by_color = {}
for r, val in colored:
by_color.setdefault(val, []).append(r)
for val, rows in by_color.items():
if len(rows) >= 2:
rows.sort()
for i in range(len(rows) - 1):
for r in range(rows[i], rows[i+1] + 1):
grid[r, c] = val
return grid.astype(float)
return Transform(fn, "ConnectSameColorV")
def CompressGrid():
def fn(phi):
grid = np.rint(phi).astype(int)
rows = [grid[0]]
for i in range(1, grid.shape[0]):
if not np.array_equal(grid[i], grid[i-1]):
rows.append(grid[i])
result = np.array(rows, dtype=int)
cols = [result[:, 0]]
for j in range(1, result.shape[1]):
if not np.array_equal(result[:, j], result[:, j-1]):
cols.append(result[:, j])
result = np.column_stack(cols) if cols else result
return result.astype(float)
return Transform(fn, "CompressGrid")
def RemoveBlackLines():
def fn(phi):
grid = np.rint(phi).astype(int)
row_mask = np.any(grid != 0, axis=1)
if row_mask.any():
grid = grid[row_mask]
col_mask = np.any(grid != 0, axis=0)
if col_mask.any():
grid = grid[:, col_mask]
return grid.astype(float)
return Transform(fn, "RemoveBlackLines")
def ColorByProximity():
def fn(phi):
grid = np.rint(phi).astype(int).copy()
from .object_layer import most_common_color
bg = most_common_color(grid)
h, w = grid.shape
dist = np.full((h, w), float('inf'))
queue = deque()
for r in range(h):
for c in range(w):
if grid[r, c] != bg:
dist[r, c] = 0
queue.append((r, c))
while queue:
r, c = queue.popleft()
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
nr, nc = r+dr, c+dc
if 0 <= nr < h and 0 <= nc < w and dist[nr, nc] > dist[r, c] + 1:
dist[nr, nc] = dist[r, c] + 1
grid[nr, nc] = grid[r, c]
queue.append((nr, nc))
return grid.astype(float)
return Transform(fn, "ColorByProximity")
def DrawBorder():
def fn(phi):
grid = np.rint(phi).astype(int)
from .object_layer import most_common_color
bg = most_common_color(grid)
h, w = grid.shape
result = np.full_like(grid, bg)
for r in range(h):
for c in range(w):
if grid[r, c] != bg:
is_border = False
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
nr, nc = r+dr, c+dc
if nr < 0 or nr >= h or nc < 0 or nc >= w or grid[nr, nc] == bg:
is_border = True
break
if is_border:
result[r, c] = grid[r, c]
return result.astype(float)
return Transform(fn, "DrawBorder")