import numpy as np from collections import deque, Counter # --- Grid Transformation Functions --- def remove_vertical_lines(ctx): rows, cols = len(ctx.grid), len(ctx.grid[0]) for obj in ctx.objects: columns = {} for r, c in obj[2]: if c not in columns: columns[c] = [] columns[c].append(r) for c, rows_in_col in columns.items(): if len(rows_in_col) > 1: unique_vals = {ctx.grid[r][c] for r in rows_in_col} if len(unique_vals) == 1: for r in rows_in_col: ctx.grid[r][c] = 0 return ctx.grid def fill_object_interior(ctx): """ Fills the interior of each object in the GPContext grid. Assumes ctx.objects has been extracted already. """ rows, cols = len(ctx.grid), len(ctx.grid[0]) for obj in ctx.objects: min_r = min(r for r, c in obj) max_r = max(r for r, c in obj) min_c = min(c for r, c in obj) max_c = max(c for r, c in obj) obj_color = ctx.grid[min_r][min_c] fill_color = (obj_color + 1) % 9 or 1 # consistent color, avoid zero for r in range(min_r, max_r + 1): for c in range(min_c, max_c + 1): if (r, c) not in obj and ctx.grid[r][c] == 0: ctx.grid[r][c] = fill_color return ctx.grid def move_right_most_object(ctx): if not ctx.objects: return ctx.grid # Get rightmost object: object with largest column index rightmost_object = max(ctx.objects, key=lambda obj: max(y for x, y in obj[2])) _, value, block = rightmost_object for x, y in block: ctx.grid[x][y] = 0 shift = 0 cols = len(ctx.grid[0]) while True: can_move = True for x, y in block: new_y = y + shift + 1 if new_y >= cols or ctx.grid[x][new_y] != 0: can_move = False break if not can_move: break shift += 1 for x, y in block: ctx.grid[x][y + shift] = value return ctx.grid def move_left_most_object(ctx): if not ctx.objects: return ctx.grid # Get leftmost object: object with smallest column index leftmost_object = min(ctx.objects, key=lambda obj: min(y for x, y in obj[2])) _, value, block = leftmost_object for x, y in block: ctx.grid[x][y] = 0 shift = 0 while True: can_move = True for x, y in block: new_y = y - (shift + 1) if new_y < 0 or ctx.grid[x][new_y] != 0: can_move = False break if not can_move: break shift += 1 for x, y in block: ctx.grid[x][y - shift] = value return ctx.grid def move_bottom_most_object(ctx): if not ctx.objects: return ctx.grid # Get bottommost object (last one in the list after sorting by top_row) bottom_object = ctx.objects[-1] _, value, block = bottom_object # Remove it from the grid for x, y in block: ctx.grid[x][y] = 0 # Compute shift (same as top, move down) shift = 0 rows = len(ctx.grid) while True: can_move = True for x, y in block: new_x = x + shift + 1 if new_x >= rows or ctx.grid[new_x][y] != 0: can_move = False break if not can_move: break shift += 1 # Place object for x, y in block: ctx.grid[x + shift][y] = value return ctx.grid def detect_objects(grid): """ Detects objects in an ARC grid. Objects are contiguous regions of the same color (4-connected). Returns a list of objects, where each object is a set of (row, col) coordinates. """ rows, cols = len(grid), len(grid[0]) visited = set() objects = [] def bfs(start_r, start_c, color): """ Perform BFS to find all connected pixels of the same color """ queue = deque([(start_r, start_c)]) obj_pixels = set() while queue: r, c = queue.popleft() if (r, c) in visited: continue visited.add((r, c)) obj_pixels.add((r, c)) # Check 4-connected neighbors (up, down, left, right) for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]: nr, nc = r + dr, c + dc if 0 <= nr < rows and 0 <= nc < cols and (nr, nc) not in visited: if grid[nr][nc] == color: queue.append((nr, nc)) return obj_pixels # Iterate over the grid to find objects for r in range(rows): for c in range(cols): if (r, c) not in visited and grid[r][c] != 0: # Ignore background (0) obj = bfs(r, c, grid[r][c]) objects.append(obj) return objects def highlight_detected_objects(grid): objects = detect_objects(grid) new_grid = [row[:] for row in grid] for idx, obj in enumerate(objects, start=1): for r, c in obj: new_grid[r][c] = (idx % 9) or 1 return new_grid def fill_object_interior(grid): """ Modifies the grid by filling the interiors of detected objects with a different color.""" objects = detect_objects(grid) rows, cols = len(grid), len(grid[0]) new_grid = [row[:] for row in grid] # Create a copy of the grid for obj in objects: min_r = min(r for r, c in obj) max_r = max(r for r, c in obj) min_c = min(c for r, c in obj) max_c = max(c for r, c in obj) # Find a new fill color (incrementing the current color modulo 9 for variation) obj_color = grid[min_r][min_c] fill_color = (obj_color + 1) % 9 if obj_color + 1 != 0 else 1 # Identify and fill the interior pixels of the object for r in range(min_r, max_r + 1): for c in range(min_c, max_c + 1): if (r, c) not in obj and grid[r][c] == 0: # Empty space inside the object new_grid[r][c] = fill_color return new_grid def diamirror(input_grid): return np.transpose(input_grid) import numpy as np def get_object_bounds(grid): grid = np.array(grid) top, bottom = None, None for i in range(grid.shape[0]): if np.any(grid[i] != 0): if top is None: top = i bottom = i return top, bottom def reverse_object_top_bottom(grid): grid = np.array(grid) top, bottom = get_object_bounds(grid) if top is None or bottom is None: return grid grid_copy = np.copy(grid) grid_copy[top:bottom+1] = np.flipud(grid[top:bottom+1]) return grid_copy def hmirror(input_grid: np.ndarray) -> np.ndarray: return np.fliplr(input_grid) def vmirrors(input_grid: np.ndarray) -> np.ndarray: return np.flipud(input_grid) def flip_horizontal(input_grid: np.ndarray) -> np.ndarray: return np.fliplr(input_grid) def flip_vertical(input_grid: np.ndarray) -> np.ndarray: return np.flipud(input_grid) def rotate_90(input_grid: np.ndarray) -> np.ndarray: return np.rot90(input_grid, k=-1) def rotate_180(input_grid: np.ndarray) -> np.ndarray: return np.rot90(input_grid, k=2) def rotate_270(input_grid: np.ndarray) -> np.ndarray: return np.rot90(input_grid, k=1) def identity(input_grid: np.ndarray) -> np.ndarray: return input_grid def find_center_pixel(grid): """Finds the center pixel of the input grid and returns it as a 1x1 output grid.""" center_index = len(grid[0]) // 2 # Get the middle index return [[grid[0][center_index]]] # Return as a 1x1 grid with the center pixel # --- Object Detection and Manipulation --- def detect_objects(grid): """Detects objects in the grid and returns a list of bounding boxes and pixel coordinates.""" height, width = len(grid), len(grid[0]) visited = set() objects = [] def bfs(r, c, color): """Finds all pixels belonging to an object using BFS.""" queue = [(r, c)] pixels = [] # Use a list instead of a set min_r, max_r, min_c, max_c = r, r, c, c while queue: x, y = queue.pop(0) if (x, y) in visited: continue visited.add((x, y)) pixels.append((x, y)) # Append to list min_r, max_r = min(min_r, x), max(max_r, x) min_c, max_c = min(min_c, y), max(max_c, y) for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: # Only vertical & horizontal connections nx, ny = x + dx, y + dy if (0 <= nx < height and 0 <= ny < width and (nx, ny) not in visited and grid[nx][ny] == color): queue.append((nx, ny)) return (min_r, max_r, min_c, max_c, color, pixels) # Return tuple, pixels as a list for r in range(height): for c in range(width): if grid[r][c] != 0 and (r, c) not in visited: visited.add((r, c)) objects.append(bfs(r, c, grid[r][c])) # Append tuple to list return objects # Ensure `objects` is a list, not a set def extract_bottom_object(grid): """Extracts the bottom-most object from the grid, crops it, and returns it as a new grid.""" objects = detect_objects(grid) if not objects: return grid bottom_object = max(objects, key=lambda obj: obj[1]) # obj[1] is max_r min_r, max_r, min_c, max_c, obj_color, pixels = bottom_object cropped_height = max_r - min_r + 1 cropped_width = max_c - min_c + 1 cropped_grid = np.zeros((cropped_height, cropped_width), dtype=int) for r, c in pixels: cropped_grid[r - min_r, c - min_c] = obj_color return cropped_grid.tolist() def keep_bottom_object(grid): """Keeps only the bottom-most object and removes all others.""" height, width = len(grid), len(grid[0]) objects = detect_objects(grid) output_grid = np.zeros((height, width), dtype=int) if not objects: return output_grid.tolist() bottom_object = max(objects, key=lambda obj: obj[1]) # obj[1] is max_r for r, c in bottom_object[5]: # obj[5] contains pixels output_grid[r][c] = bottom_object[4] # obj[4] is color return output_grid.tolist() def recolor_to_bottom_object(grid): """Recolors all objects to match the color of the bottom-most object.""" height, width = len(grid), len(grid[0]) objects = detect_objects(grid) output_grid = np.array(grid) if not objects: return output_grid.tolist() bottom_object = max(objects, key=lambda obj: obj[1]) # obj[1] is max_r bottom_color = bottom_object[4] # obj[4] is color for min_r, max_r, min_c, max_c, obj_color, pixels in objects: for r, c in pixels: output_grid[r][c] = bottom_color # Change to bottom-most object's color return output_grid.tolist() def remove_top_bottom_objects(grid): """Removes objects that touch either the top or bottom of the grid.""" height, width = len(grid), len(grid[0]) objects = detect_objects(grid) output_grid = np.zeros((height, width), dtype=int) if not objects: return output_grid.tolist() min_top = min(obj[0] for obj in objects) max_bottom = max(obj[1] for obj in objects) for (min_r, max_r, min_c, max_c, obj_color, pixels) in objects: if min_r == min_top or max_r == max_bottom: continue for r, c in pixels: output_grid[r][c] = obj_color return output_grid.tolist() def extract_topmost_object(grid): """Extracts the top-most object from the grid, crops it, and returns it as a new grid.""" objects = detect_objects(grid) if not objects: return grid topmost_object = min(objects, key=lambda obj: obj[0]) # obj[0] is min_r min_r, max_r, min_c, max_c, obj_color, pixels = topmost_object cropped_height = max_r - min_r + 1 cropped_width = max_c - min_c + 1 cropped_grid = np.zeros((cropped_height, cropped_width), dtype=int) for r, c in pixels: cropped_grid[r - min_r, c - min_c] = obj_color return cropped_grid.tolist() def swap_objects(grid): """Swaps detected objects in the grid.""" objects = detect_objects(grid) objects = sorted(objects, key=lambda obj: obj[1]) # Sort by vertical position object_positions = [obj[5] for obj in objects] # obj[5] contains pixels object_colors = [obj[4] for obj in objects] # obj[4] is color swapped_positions = object_positions[::-1] new_grid = np.zeros_like(grid) for color, new_positions in zip(object_colors, swapped_positions): for r, c in new_positions: new_grid[r][c] = color return new_grid.tolist() # --- Pixel & Color Manipulation --- def transform_blue_to_red(input_grid): """Transforms all blue (1) pixels to red (2).""" grid = np.array(input_grid) return np.where(grid == 1, 2, grid).tolist() def fill_downward(grid): """Fills non-zero pixels downward, propagating their colors downwards in each column.""" height, width = len(grid), len(grid[0]) output_grid = np.array(grid) for col in range(width): fill_color = 0 for row in range(height): if grid[row][col] != 0: fill_color = grid[row][col] if fill_color != 0: output_grid[row][col] = fill_color return output_grid.tolist() def remove_below_horizontal_line(grid): """Detects the first fully connected horizontal line and removes everything below it.""" height, width = len(grid), len(grid[0]) output_grid = np.array(grid) for row in range(height): if np.all(output_grid[row] != 0): output_grid[row + 1:] = 0 break return output_grid.tolist() def find_center_pixel(grid): """Finds the center of the grid and returns it as a 1x1 pixel grid.""" center_index = len(grid[0]) // 2 return [[grid[0][center_index]]] def extract_largest_row(grid): """Finds the row with the most non-zero elements and extracts it.""" grid = np.array(grid) max_length = 0 longest_row = [] for row in grid: row_values = row[row > 0] if len(row_values) > max_length: max_length = len(row_values) longest_row = row_values.tolist() return [longest_row] def extract_dominant_colors(grid): """Finds the two most dominant non-zero colors in the grid.""" flattened = [cell for row in grid for cell in row if cell != 0] color_counts = Counter(flattened) if not color_counts: return [[]] most_common_colors = [color for color, _ in color_counts.most_common(2)] return [[color for color in most_common_colors]] def remove_dominant_color(grid): """Removes the most dominant color from the grid.""" color_counts = Counter(cell for row in grid for cell in row if cell != 0) if color_counts: dominant_color = max(color_counts, key=color_counts.get) else: return grid return [[0 if cell == dominant_color else cell for cell in row] for row in grid] def find_least_dominant_pixel(grid): """Finds the least occurring non-zero pixel in the grid.""" pixel_counts = {} for row in grid: for value in row: if value != 0: pixel_counts[value] = pixel_counts.get(value, 0) + 1 if not pixel_counts: return None return min(pixel_counts, key=pixel_counts.get) def remove_least_dominant_pixel(grid): """Removes the least dominant pixel from the grid.""" rows, cols = len(grid), len(grid[0]) least_dominant_pixel = find_least_dominant_pixel(grid) if least_dominant_pixel is None: return grid new_grid = np.array(grid) for x in range(rows): for y in range(cols): if grid[x][y] == least_dominant_pixel: new_grid[x, y] = 0 return new_grid.tolist() def upscale(input_grid, upscale_factor=3): """Upscales the grid by expanding each pixel into a 3x3 block.""" def expand_pixel_with_grid(pixel, input_grid): if pixel == 0: return np.zeros((upscale_factor, upscale_factor), dtype=int) else: return input_grid input_rows, input_cols = len(input_grid), len(input_grid[0]) output_grid = np.zeros((input_rows * upscale_factor, input_cols * upscale_factor), dtype=int) for r in range(input_rows): for c in range(input_cols): expanded_block = expand_pixel_with_grid(input_grid[r][c], input_grid) output_grid[r * upscale_factor: (r + 1) * upscale_factor, c * upscale_factor: (c + 1) * upscale_factor] = expanded_block return output_grid def remove_center_object(grid): """Removes anything located at the center of the grid.""" height, width = len(grid), len(grid[0]) center_r, center_c = height // 2, width // 2 grid = np.array(grid) center_value = grid[center_r, center_c] if center_value != 0: grid[grid == center_value] = 0 return grid.tolist() import numpy as np def draw_horizontal_vertical(grid): """Adds a horizontal or vertical line of 8s based on object orientation.""" if grid is None or len(grid) == 0 or len(grid[0]) == 0: print("ERROR: Grid is empty. Cannot apply draw_horizontal_vertical.") return grid rows, cols = len(grid), len(grid[0]) print(f"Grid Shape Before Modification: {rows}x{cols}") # Debugging Info new_grid = np.array(grid) for r in range(rows): new_grid[r][-1] = 8 # Rightmost column for c in range(cols): new_grid[0][c] = 8 # Topmost row return new_grid.tolist()