| import numpy as np |
| from collections import deque |
| from .solver_core import tile_transform, fill_enclosed, Transform |
|
|
|
|
| |
| |
| |
|
|
| 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}") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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}") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|