rogermt commited on
Commit
9a57ca2
·
verified ·
1 Parent(s): 14faeea

Fix transforms.py upload — 33 transforms including object extraction, fill, connect, compress, proximity"

Browse files
Files changed (1) hide show
  1. itt_solver/transforms.py +450 -0
itt_solver/transforms.py CHANGED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from collections import deque
3
+ from .solver_core import tile_transform, fill_enclosed, Transform
4
+
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # Existing transforms (cleaned up to import Transform from solver_core)
8
+ # ---------------------------------------------------------------------------
9
+
10
+ def tile_to_target_shifted(shift=(1, 1), tile_factor=3):
11
+ """Tile the input tile_factor x tile_factor times, then roll by shift."""
12
+ def fn(phi):
13
+ h_in, w_in = phi.shape
14
+ out_shape = (h_in * tile_factor, w_in * tile_factor)
15
+ tiled = tile_transform(phi, out_shape)
16
+ tiled = np.roll(tiled, shift=shift, axis=(0, 1))
17
+ return tiled
18
+ return Transform(fn, f"ShiftedTile_s{shift}_f{tile_factor}")
19
+
20
+
21
+ def FillEnclosedHarmonic(boundary_mask=None):
22
+ def fn(phi):
23
+ bm = (phi != 0) if boundary_mask is None else boundary_mask
24
+ return fill_enclosed(phi, bm)
25
+ return Transform(fn, "FillEnclosedHarmonic")
26
+
27
+
28
+ def Rotate(k=1):
29
+ def fn(phi):
30
+ return np.rot90(phi, k)
31
+ return Transform(fn, f"Rotate_{90 * k}")
32
+
33
+
34
+ def Reflect(axis='h'):
35
+ def fn(phi):
36
+ if axis == 'h':
37
+ return np.flipud(phi)
38
+ return np.fliplr(phi)
39
+ return Transform(fn, f"Reflect_{axis}")
40
+
41
+
42
+ def ColorMap(mapping):
43
+ def fn(phi):
44
+ out = phi.copy()
45
+ for k, v in mapping.items():
46
+ out[phi == k] = v
47
+ return out
48
+ return Transform(fn, f"ColorMap_{mapping}")
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Kronecker / self-similar family
53
+ # ---------------------------------------------------------------------------
54
+
55
+ def KroneckerSelfSimilar():
56
+ """output = kron((input != 0).astype(int), input)"""
57
+ def fn(phi):
58
+ mask = (phi != 0).astype(phi.dtype)
59
+ return np.kron(mask, phi)
60
+ return Transform(fn, "KroneckerSelfSimilar")
61
+
62
+
63
+ def KroneckerSelfSimilarInv():
64
+ """output = kron(input, (input != 0).astype(int))"""
65
+ def fn(phi):
66
+ mask = (phi != 0).astype(phi.dtype)
67
+ return np.kron(phi, mask)
68
+ return Transform(fn, "KroneckerSelfSimilarInv")
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Mirror / kaleidoscope tiling
73
+ # ---------------------------------------------------------------------------
74
+
75
+ def MirrorTileH():
76
+ def fn(phi):
77
+ return np.hstack([phi, np.fliplr(phi)])
78
+ return Transform(fn, "MirrorTileH")
79
+
80
+ def MirrorTileV():
81
+ def fn(phi):
82
+ return np.vstack([phi, np.flipud(phi)])
83
+ return Transform(fn, "MirrorTileV")
84
+
85
+ def MirrorTile4Way():
86
+ def fn(phi):
87
+ top = np.hstack([phi, np.fliplr(phi)])
88
+ return np.vstack([top, np.flipud(top)])
89
+ return Transform(fn, "MirrorTile4Way")
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Upscale / zoom
94
+ # ---------------------------------------------------------------------------
95
+
96
+ def Upscale(k=2):
97
+ def fn(phi):
98
+ return np.kron(phi, np.ones((k, k), dtype=phi.dtype))
99
+ return Transform(fn, f"Upscale_{k}x")
100
+
101
+ def Downscale(k=2):
102
+ def fn(phi):
103
+ return phi[::k, ::k].copy()
104
+ return Transform(fn, f"Downscale_{k}x")
105
+
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # Stacking
109
+ # ---------------------------------------------------------------------------
110
+
111
+ def StackH(n=2):
112
+ def fn(phi):
113
+ return np.tile(phi, (1, n))
114
+ return Transform(fn, f"StackH_{n}")
115
+
116
+ def StackV(n=2):
117
+ def fn(phi):
118
+ return np.tile(phi, (n, 1))
119
+ return Transform(fn, f"StackV_{n}")
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Color manipulation
124
+ # ---------------------------------------------------------------------------
125
+
126
+ def RetainColor(color):
127
+ def fn(phi):
128
+ out = np.zeros_like(phi)
129
+ out[phi == color] = color
130
+ return out
131
+ return Transform(fn, f"RetainColor_{color}")
132
+
133
+ def RemoveColor(color):
134
+ def fn(phi):
135
+ out = phi.copy()
136
+ out[phi == color] = 0
137
+ return out
138
+ return Transform(fn, f"RemoveColor_{color}")
139
+
140
+ def InvertColors():
141
+ def fn(phi):
142
+ nonzero = phi[phi != 0]
143
+ if nonzero.size == 0:
144
+ return phi.copy()
145
+ from collections import Counter
146
+ top_color = Counter(nonzero.flatten().astype(int).tolist()).most_common(1)[0][0]
147
+ out = phi.copy()
148
+ mask_zero = (phi == 0)
149
+ mask_top = (phi == top_color)
150
+ out[mask_zero] = top_color
151
+ out[mask_top] = 0
152
+ return out
153
+ return Transform(fn, "InvertColors")
154
+
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Gravity
158
+ # ---------------------------------------------------------------------------
159
+
160
+ def GravityDown():
161
+ def fn(phi):
162
+ out = np.zeros_like(phi)
163
+ h, w = phi.shape
164
+ for c in range(w):
165
+ col = phi[:, c]
166
+ nonzero = col[col != 0]
167
+ if nonzero.size > 0:
168
+ out[h - nonzero.size:, c] = nonzero
169
+ return out
170
+ return Transform(fn, "GravityDown")
171
+
172
+ def GravityUp():
173
+ def fn(phi):
174
+ out = np.zeros_like(phi)
175
+ h, w = phi.shape
176
+ for c in range(w):
177
+ col = phi[:, c]
178
+ nonzero = col[col != 0]
179
+ if nonzero.size > 0:
180
+ out[:nonzero.size, c] = nonzero
181
+ return out
182
+ return Transform(fn, "GravityUp")
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # Overlay / composition
187
+ # ---------------------------------------------------------------------------
188
+
189
+ def OverlayTransparent(background):
190
+ bg = np.array(background, dtype=float)
191
+ def fn(phi):
192
+ out = bg.copy()
193
+ mask = (phi != 0)
194
+ if phi.shape != out.shape:
195
+ p = tile_transform(phi, out.shape)
196
+ m = (p != 0)
197
+ out[m] = p[m]
198
+ else:
199
+ out[mask] = phi[mask]
200
+ return out
201
+ return Transform(fn, "OverlayTransparent")
202
+
203
+
204
+ # ---------------------------------------------------------------------------
205
+ # Border / crop helpers
206
+ # ---------------------------------------------------------------------------
207
+
208
+ def CropToContent():
209
+ def fn(phi):
210
+ rows = np.any(phi != 0, axis=1)
211
+ cols = np.any(phi != 0, axis=0)
212
+ if not rows.any():
213
+ return phi.copy()
214
+ rmin, rmax = np.where(rows)[0][[0, -1]]
215
+ cmin, cmax = np.where(cols)[0][[0, -1]]
216
+ return phi[rmin:rmax + 1, cmin:cmax + 1].copy()
217
+ return Transform(fn, "CropToContent")
218
+
219
+ def Transpose():
220
+ def fn(phi):
221
+ return phi.T.copy()
222
+ return Transform(fn, "Transpose")
223
+
224
+
225
+ # ---------------------------------------------------------------------------
226
+ # Object-based transforms (using object_layer)
227
+ # ---------------------------------------------------------------------------
228
+
229
+ def ExtractLargestObject():
230
+ def fn(phi):
231
+ from .object_layer import extract_objects, object_to_cropped_grid
232
+ objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
233
+ if not objs:
234
+ return phi.copy()
235
+ return object_to_cropped_grid(objs[0]).astype(float)
236
+ return Transform(fn, "ExtractLargestObject")
237
+
238
+ def ExtractSmallestObject():
239
+ def fn(phi):
240
+ from .object_layer import extract_objects, object_to_cropped_grid
241
+ objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
242
+ if not objs:
243
+ return phi.copy()
244
+ return object_to_cropped_grid(objs[-1]).astype(float)
245
+ return Transform(fn, "ExtractSmallestObject")
246
+
247
+ def ExtractUniqueObject():
248
+ def fn(phi):
249
+ from .object_layer import extract_objects, unique_object, object_to_cropped_grid
250
+ objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
251
+ u = unique_object(objs)
252
+ if u is None:
253
+ return phi.copy()
254
+ return object_to_cropped_grid(u).astype(float)
255
+ return Transform(fn, "ExtractUniqueObject")
256
+
257
+ def ExtractMostCommonObject():
258
+ def fn(phi):
259
+ from .object_layer import extract_objects, most_common_object, object_to_cropped_grid
260
+ objs = extract_objects(phi, univalued=True, connectivity=4, without_bg=True)
261
+ mc = most_common_object(objs)
262
+ if mc is None:
263
+ return phi.copy()
264
+ return object_to_cropped_grid(mc).astype(float)
265
+ return Transform(fn, "ExtractMostCommonObject")
266
+
267
+ def KeepLargestObject():
268
+ def fn(phi):
269
+ from .object_layer import extract_objects, object_to_grid, most_common_color
270
+ grid = np.rint(phi).astype(int)
271
+ bg = most_common_color(grid)
272
+ objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
273
+ if not objs:
274
+ return phi.copy()
275
+ return object_to_grid(objs[0], grid.shape, bg=bg).astype(float)
276
+ return Transform(fn, "KeepLargestObject")
277
+
278
+ def KeepSmallestObject():
279
+ def fn(phi):
280
+ from .object_layer import extract_objects, object_to_grid, most_common_color
281
+ grid = np.rint(phi).astype(int)
282
+ bg = most_common_color(grid)
283
+ objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
284
+ if not objs:
285
+ return phi.copy()
286
+ return object_to_grid(objs[-1], grid.shape, bg=bg).astype(float)
287
+ return Transform(fn, "KeepSmallestObject")
288
+
289
+ def SortObjectsBySize():
290
+ def fn(phi):
291
+ from .object_layer import extract_objects, paint, most_common_color, canvas
292
+ grid = np.rint(phi).astype(int)
293
+ bg = most_common_color(grid)
294
+ objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
295
+ result = canvas(bg, grid.shape)
296
+ for obj in sorted(objs, key=len, reverse=True):
297
+ result = paint(result, obj)
298
+ return result.astype(float)
299
+ return Transform(fn, "SortObjectsBySize")
300
+
301
+
302
+ # ---------------------------------------------------------------------------
303
+ # Fill / connect / compress
304
+ # ---------------------------------------------------------------------------
305
+
306
+ def FillInterior():
307
+ def fn(phi):
308
+ from .object_layer import extract_objects, object_bbox, object_color
309
+ grid = np.rint(phi).astype(int).copy()
310
+ objs = extract_objects(grid, univalued=True, connectivity=4, without_bg=True)
311
+ for obj in objs:
312
+ rmin, cmin, rmax, cmax = object_bbox(obj)
313
+ color = object_color(obj)
314
+ h, w = rmax - rmin + 1, cmax - cmin + 1
315
+ local_visited = np.zeros((h, w), dtype=bool)
316
+ for _, (r, c) in obj:
317
+ local_visited[r - rmin, c - cmin] = True
318
+ queue = deque()
319
+ exterior = np.zeros((h, w), dtype=bool)
320
+ for i in range(h):
321
+ for j in range(w):
322
+ if (i == 0 or i == h-1 or j == 0 or j == w-1) and not local_visited[i, j]:
323
+ exterior[i, j] = True
324
+ queue.append((i, j))
325
+ while queue:
326
+ r, c = queue.popleft()
327
+ for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
328
+ nr, nc = r+dr, c+dc
329
+ if 0 <= nr < h and 0 <= nc < w and not exterior[nr, nc] and not local_visited[nr, nc]:
330
+ exterior[nr, nc] = True
331
+ queue.append((nr, nc))
332
+ for i in range(h):
333
+ for j in range(w):
334
+ if not local_visited[i, j] and not exterior[i, j]:
335
+ grid[i + rmin, j + cmin] = color
336
+ return grid.astype(float)
337
+ return Transform(fn, "FillInterior")
338
+
339
+ def ConnectSameColorH():
340
+ def fn(phi):
341
+ grid = np.rint(phi).astype(int).copy()
342
+ h, w = grid.shape
343
+ from .object_layer import most_common_color
344
+ bg = most_common_color(grid)
345
+ for r in range(h):
346
+ colored = [(c, int(grid[r, c])) for c in range(w) if grid[r, c] != bg]
347
+ by_color = {}
348
+ for c, val in colored:
349
+ by_color.setdefault(val, []).append(c)
350
+ for val, cols in by_color.items():
351
+ if len(cols) >= 2:
352
+ cols.sort()
353
+ for i in range(len(cols) - 1):
354
+ for c in range(cols[i], cols[i+1] + 1):
355
+ grid[r, c] = val
356
+ return grid.astype(float)
357
+ return Transform(fn, "ConnectSameColorH")
358
+
359
+ def ConnectSameColorV():
360
+ def fn(phi):
361
+ grid = np.rint(phi).astype(int).copy()
362
+ h, w = grid.shape
363
+ from .object_layer import most_common_color
364
+ bg = most_common_color(grid)
365
+ for c in range(w):
366
+ colored = [(r, int(grid[r, c])) for r in range(h) if grid[r, c] != bg]
367
+ by_color = {}
368
+ for r, val in colored:
369
+ by_color.setdefault(val, []).append(r)
370
+ for val, rows in by_color.items():
371
+ if len(rows) >= 2:
372
+ rows.sort()
373
+ for i in range(len(rows) - 1):
374
+ for r in range(rows[i], rows[i+1] + 1):
375
+ grid[r, c] = val
376
+ return grid.astype(float)
377
+ return Transform(fn, "ConnectSameColorV")
378
+
379
+ def CompressGrid():
380
+ def fn(phi):
381
+ grid = np.rint(phi).astype(int)
382
+ rows = [grid[0]]
383
+ for i in range(1, grid.shape[0]):
384
+ if not np.array_equal(grid[i], grid[i-1]):
385
+ rows.append(grid[i])
386
+ result = np.array(rows, dtype=int)
387
+ cols = [result[:, 0]]
388
+ for j in range(1, result.shape[1]):
389
+ if not np.array_equal(result[:, j], result[:, j-1]):
390
+ cols.append(result[:, j])
391
+ result = np.column_stack(cols) if cols else result
392
+ return result.astype(float)
393
+ return Transform(fn, "CompressGrid")
394
+
395
+ def RemoveBlackLines():
396
+ def fn(phi):
397
+ grid = np.rint(phi).astype(int)
398
+ row_mask = np.any(grid != 0, axis=1)
399
+ if row_mask.any():
400
+ grid = grid[row_mask]
401
+ col_mask = np.any(grid != 0, axis=0)
402
+ if col_mask.any():
403
+ grid = grid[:, col_mask]
404
+ return grid.astype(float)
405
+ return Transform(fn, "RemoveBlackLines")
406
+
407
+ def ColorByProximity():
408
+ def fn(phi):
409
+ grid = np.rint(phi).astype(int).copy()
410
+ from .object_layer import most_common_color
411
+ bg = most_common_color(grid)
412
+ h, w = grid.shape
413
+ dist = np.full((h, w), float('inf'))
414
+ queue = deque()
415
+ for r in range(h):
416
+ for c in range(w):
417
+ if grid[r, c] != bg:
418
+ dist[r, c] = 0
419
+ queue.append((r, c))
420
+ while queue:
421
+ r, c = queue.popleft()
422
+ for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
423
+ nr, nc = r+dr, c+dc
424
+ if 0 <= nr < h and 0 <= nc < w and dist[nr, nc] > dist[r, c] + 1:
425
+ dist[nr, nc] = dist[r, c] + 1
426
+ grid[nr, nc] = grid[r, c]
427
+ queue.append((nr, nc))
428
+ return grid.astype(float)
429
+ return Transform(fn, "ColorByProximity")
430
+
431
+ def DrawBorder():
432
+ def fn(phi):
433
+ grid = np.rint(phi).astype(int)
434
+ from .object_layer import most_common_color
435
+ bg = most_common_color(grid)
436
+ h, w = grid.shape
437
+ result = np.full_like(grid, bg)
438
+ for r in range(h):
439
+ for c in range(w):
440
+ if grid[r, c] != bg:
441
+ is_border = False
442
+ for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
443
+ nr, nc = r+dr, c+dc
444
+ if nr < 0 or nr >= h or nc < 0 or nc >= w or grid[nr, nc] == bg:
445
+ is_border = True
446
+ break
447
+ if is_border:
448
+ result[r, c] = grid[r, c]
449
+ return result.astype(float)
450
+ return Transform(fn, "DrawBorder")