def test_adjacency_extraction(resources: Resources) -> None: # TODO: generalize this to more than the four cardinal directions direction_offsets = list(enumerate([(0, -1), (1, 0), (0, 1), (-1, 0)])) filename = resources.get_image("samples/Red Maze.png") img = imageio.imread(filename) tile_size = 1 pattern_width = 2 periodic = False _tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog( img, tile_size) pattern_catalog, _pattern_weights, _pattern_list, pattern_grid = wfc_patterns.make_pattern_catalog( tile_grid, pattern_width, periodic) adjacency_relations = wfc_adjacency.adjacency_extraction( pattern_grid, pattern_catalog, direction_offsets) assert ((0, -1), -6150964001204120324, -4042134092912931260) in adjacency_relations assert ((-1, 0), -4042134092912931260, 3069048847358774683) in adjacency_relations assert ((1, 0), -3950451988873469076, -3950451988873469076) in adjacency_relations assert ((-1, 0), -3950451988873469076, -3950451988873469076) in adjacency_relations assert ((0, 1), -3950451988873469076, 3336256675067683735) in adjacency_relations assert (not ((0, -1), -3950451988873469076, -3950451988873469076) in adjacency_relations) assert (not ((0, 1), -3950451988873469076, -3950451988873469076) in adjacency_relations)
def test_pattern_to_tile(resources): filename = resources.get_image("samples/Red Maze.png") img = imageio.imread(filename) tile_size = 1 pattern_width = 2 _tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog(img, tile_size) pattern_catalog, _pattern_weights, _pattern_list, pattern_grid = wfc_patterns.make_pattern_catalog( tile_grid, pattern_width ) new_tile_grid = wfc_patterns.pattern_grid_to_tiles(pattern_grid, pattern_catalog) assert np.array_equal(tile_grid, new_tile_grid)
def test_make_pattern_catalog(resources: Resources) -> None: filename = resources.get_image("samples/Red Maze.png") img = imageio.imread(filename) tile_size = 1 pattern_width = 2 _tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog( img, tile_size) pattern_catalog, pattern_weights, pattern_list, _pattern_grid = wfc_patterns.make_pattern_catalog( tile_grid, pattern_width) assert pattern_weights[-6150964001204120324] == 1 assert pattern_list[3] == 2800765426490226432 assert pattern_catalog[5177878755649963747][0][1] == -8754995591521426669
def _test_recurse_vs_loop(resources): # FIXME: run_recurse or run_loop do not exist anymore filename = resources.get_image("samples/Red Maze.png") img = imageio.imread(filename) tile_size = 1 pattern_width = 2 rotations = 0 output_size = [84, 84] direction_offsets = list(enumerate([(0, -1), (1, 0), (0, 1), (-1, 0)])) _tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog(img, tile_size) pattern_catalog, pattern_weights, pattern_list, pattern_grid = wfc_patterns.make_pattern_catalog( tile_grid, pattern_width, rotations ) adjacency_relations = wfc_adjacency.adjacency_extraction( pattern_grid, pattern_catalog, direction_offsets ) number_of_patterns = len(pattern_weights) decode_patterns = {x: i for i, x in enumerate(pattern_list)} adjacency_list = {} for i, d in direction_offsets: adjacency_list[d] = [set() for i in pattern_weights] for i in adjacency_relations: adjacency_list[i[0]][decode_patterns[i[1]]].add(decode_patterns[i[2]]) wave = wfc_solver.makeWave(number_of_patterns, output_size[0], output_size[1]) adjacency_matrix = wfc_solver.makeAdj(adjacency_list) solution_loop = wfc_solver.run( wave.copy(), adjacency_matrix, locationHeuristic=wfc_solver.lexicalLocationHeuristic, patternHeuristic=wfc_solver.lexicalPatternHeuristic, periodic=True, backtracking=False, onChoice=None, onBacktrack=None, ) solution_recurse = wfc_solver.run_recurse( wave.copy(), adjacency_matrix, locationHeuristic=wfc_solver.lexicalLocationHeuristic, patternHeuristic=wfc_solver.lexicalPatternHeuristic, periodic=True, backtracking=False, onChoice=None, onBacktrack=None, ) assert numpy.array_equiv(solution_loop, solution_recurse)
def bit_wfc(level_bits, fixed_tiles): """ Perform WFC to create a new tile grid based on default """ #TODO: move this stuff into Context.__init__? width = level_bits.shape[0] height = level_bits.shape[1] tile_size = 1 pattern_size = 2 # Get the tile grid # Tile catalog maps from the hash to a 1x1x40 vector, reversing the hashing process print("\tGetting tile grid") tile_catalog, level_data_tiles, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog( level_bits, tile_size) print("\tRect size: {}".format(level_data_tiles.shape)) adj_rules = None # Get the patterns # Pattern catalog maps tiles to patterns? # Input is not periodic print("\tGetting pattern catalog") pattern_catalog, pattern_weights, pattern_list, pattern_grid = wfc_patterns.make_pattern_catalog( level_data_tiles, pattern_size, False) number_of_patterns = len(pattern_catalog) print("\tNumber of patterns: {}".format(number_of_patterns)) # Get the adjacencies print("\tGetting adjacencies") directions = list(enumerate([(0, -1), (1, 0), (0, 1), (-1, 0)])) adjacency_relations = wfc_adjacency.adjacency_extraction( pattern_grid, pattern_catalog, directions) # Create functions to make pattern arrays and decode them decode_patterns = np.vectorize({x: i for i, x in enumerate(pattern_list)}.get) encode_patterns = np.vectorize({i: x for i, x in enumerate(pattern_list)}.get) decode_dict = {x: i for i, x in enumerate(pattern_list)} # Direction -> list of sets adjacency_list = {} for i, d in directions: adjacency_list[d] = [set() for i in pattern_weights] for direction, pattern1, pattern2 in adjacency_relations: adjacency_list[direction][decode_dict[pattern1]].add( decode_dict[pattern2]) # The original level as pattern ids pattern_id_grid = decode_patterns(pattern_grid) # ASP print("\tStarting ASP instance") ctl = clingo.Control([], logger=print) # WFC constraints ctl.add( "wfc", ["h", "w", "p"], """ cell(0..h-1,0..w-1). pattern(0..p-1). wave(I,J,K) :- (I,J,K) = @constrained_tiles. 1 { wave(I,J,P):pattern(P) } 1 :- cell(I,J). :- wave(I,J,A), J < w-1, not 1 { wave(I,J+1,@v_adj(A)) }. :- wave(I,J,A), I < h-1, not 1 { wave(I+1,J,@h_adj(A)) }. %:- pattern(P), not 1 { wave(I,J,P) }. % Every tile must be used at least once #show wave/3. """) ctx = Context(width, height, adjacency_list) #TODO: swap axes? for t in fixed_tiles: ctx.pin_tile(t[1], t[0], pattern_id_grid) print("\tGrounding ASP Model") ctl.ground([ ("wfc", list(map(clingo.Number, [height, width, number_of_patterns]))) ], context=ctx) print("\tSolving ASP Model") #TODO: --restart_on_model, Config object? #TODO: Specify seed to ASP config? #TODO: Constrain the amount of plagiarism? ctl.solve(on_model=ctx.on_model) solution = ctx.solution # Convert to pattern array solution_as_ids = encode_patterns(solution) # Convert to tile hash array solution_tile_grid = wfc_patterns.pattern_grid_to_tiles( solution_as_ids, pattern_catalog) output = np.zeros((height, width, level_bits.shape[2])) # Convert back to bit array #TODO: swap axes? for y in range(height): for x in range(width): output[y, x, :] = tile_catalog[solution_tile_grid[y, x]] output = np.swapaxes(output, 0, 1) assert output.shape == level_bits.shape # Convert to int output = output.astype("int64") return output