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_unique_patterns_2d(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) _patterns_in_grid, pattern_contents_list, patch_codes = wfc_patterns.unique_patterns_2d( tile_grid, pattern_width, True) assert patch_codes[1][2] == 4867810695119132864 assert pattern_contents_list[7][1][1] == 8253868773529191888
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_make_tile_catalog(resources: Resources) -> None: filename = resources.get_image("samples/Red Maze.png") img = imageio.imread(filename) print(img) tc, tg, cl, ut = wfc_tiles.make_tile_catalog(img, 1) print("tile catalog") print(tc) print("tile grid") print(tg) print("code list") print(cl) print("unique tiles") print(ut) assert ut[1][0] == 7
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 wfc_execute(WFC_VISUALIZE=False, WFC_PROFILE=False, WFC_LOGGING=False): solver_to_use = "default" #"minizinc" wfc_stats_tracking = { "observations": 0, "propagations": 0, "time_start": None, "time_end": None, "choices_before_success": 0, "choices_per_run": [], "success": False } wfc_stats_data = [] stats_file_name = f"output/stats_{time.time()}.tsv" with open(stats_file_name, "a+") as stats_file: stats_file.write( "id\tname\tsuccess?\tattempts\tobservations\tpropagations\tchoices_to_solution\ttotal_observations_before_solution_in_last_restart\ttotal_choices_before_success_across_restarts\tbacktracking_total\ttime_passed\ttime_start\ttime_end\tfinal_time_end\tgenerated_size\tpattern_count\tseed\tbacktracking?\tallowed_restarts\tforce_the_use_of_all_patterns?\toutput_filename\n" ) default_backtracking = False default_allowed_attempts = 10 default_force_use_all_patterns = False xdoc = ET.ElementTree(file="samples_original.xml") counter = 0 choices_before_success = 0 for xnode in xdoc.getroot(): counter += 1 choices_before_success = 0 if ("#comment" == xnode.tag): continue name = xnode.get('name', "NAME") global hackstring hackstring = name print("< {0} ".format(name), end='') if "backtracking_on" == xnode.tag: default_backtracking = True if "backtracking_off" == xnode.tag: default_backtracking = False if "one_allowed_attempts" == xnode.tag: default_allowed_attempts = 1 if "ten_allowed_attempts" == xnode.tag: default_allowed_attempts = 10 if "force_use_all_patterns" == xnode.tag: default_force_use_all_patterns = True if "overlapping" == xnode.tag: choices_before_success = 0 print("beginning...") print(xnode.attrib) current_output_file_number = 97000 + (counter * 10) wfc_ns = types.SimpleNamespace( output_path="output/", img_filename="samples/" + xnode.get('name', "NAME") + ".png", # name of the input file output_file_number=current_output_file_number, operation_name=xnode.get('name', "NAME"), output_filename="output/" + xnode.get('name', "NAME") + "_" + str(current_output_file_number) + "_" + str(time.time()) + ".png", # name of the output file debug_log_filename="output/" + xnode.get('name', "NAME") + "_" + str(current_output_file_number) + "_" + str(time.time()) + ".log", seed=11975, # seed for random generation, can be any number tile_size=int(xnode.get('tile_size', 1)), # size of tile, in pixels pattern_width=int( xnode.get('N', 2) ), # Size of the patterns we want. 2x2 is the minimum, larger scales get slower fast. channels=3, # Color channels in the image (usually 3 for RGB) symmetry=int(xnode.get('symmetry', 8)), ground=int(xnode.get('ground', 0)), adjacency_directions=dict( enumerate([ CoordXY(x=0, y=-1), CoordXY(x=1, y=0), CoordXY(x=0, y=1), CoordXY(x=-1, y=0) ]) ), # The list of adjacencies that we care about - these will be turned into the edges of the graph periodic_input=string2bool(xnode.get( 'periodicInput', True)), # Does the input wrap? periodic_output=string2bool( xnode.get('periodicOutput', False)), # Do we want the output to wrap? generated_size=(int(xnode.get('width', 48)), int(xnode.get('height', 48))), #Size of the final image screenshots=int( xnode.get('screenshots', 3) ), # Number of times to run the algorithm, will produce this many distinct outputs iteration_limit=int( xnode.get('iteration_limit', 0) ), # After this many iterations, time out. 0 = never time out. allowed_attempts=int( xnode.get('allowed_attempts', default_allowed_attempts) ), # Give up after this many contradictions stats_tracking=wfc_stats_tracking.copy(), backtracking=string2bool( xnode.get('backtracking', default_backtracking)), force_use_all_patterns=default_force_use_all_patterns, force_fail_first_solution=False) wfc_ns.stats_tracking[ "choices_before_success"] += choices_before_success wfc_ns.stats_tracking["time_start"] = time.time() pr = cProfile.Profile() pr.enable() wfc_ns = find_pattern_center(wfc_ns) wfc_ns = wfc.wfc_utilities.load_visualizer(wfc_ns) ## ## Load image and make tile data structures ## wfc_ns.img = load_source_image(wfc_ns.img_filename) wfc_ns.channels = wfc_ns.img.shape[ -1] # detect if it uses channels other than RGB... wfc_ns.tiles = image_to_tiles(wfc_ns.img, wfc_ns.tile_size) wfc_ns.tile_catalog, wfc_ns.tile_grid, wfc_ns.code_list, wfc_ns.unique_tiles = make_tile_catalog( wfc_ns) wfc_ns.tile_ids = { v: k for k, v in dict(enumerate(wfc_ns.unique_tiles[0])).items() } wfc_ns.tile_weights = { a: b for a, b in zip(wfc_ns.unique_tiles[0], wfc_ns.unique_tiles[1]) } if WFC_VISUALIZE: show_input_to_output(wfc_ns) show_extracted_tiles(wfc_ns) show_false_color_tile_grid(wfc_ns) wfc_ns.pattern_catalog, wfc_ns.pattern_weights, wfc_ns.patterns, wfc_ns.pattern_grid = make_pattern_catalog_with_symmetry( wfc_ns.tile_grid, wfc_ns.pattern_width, wfc_ns.symmetry, wfc_ns.periodic_input) if WFC_VISUALIZE: show_pattern_catalog(wfc_ns) adjacency_relations = adjacency_extraction_consistent( wfc_ns, wfc_ns.patterns) if WFC_VISUALIZE: show_adjacencies(wfc_ns, adjacency_relations[:256]) wfc_ns = wfc.wfc_patterns.detect_ground(wfc_ns) pr.disable() screenshots_collected = 0 while screenshots_collected < wfc_ns.screenshots: wfc_logger.info(f"Starting solver #{screenshots_collected}") screenshots_collected += 1 wfc_ns.seed += 100 choice_before_success = 0 #wfc_ns.stats_tracking["choices_before_success"] = 0# += choices_before_success wfc_ns.stats_tracking["time_start"] = time.time() wfc_ns.stats_tracking["final_time_end"] = None # update output name so each iteration has a unique filename output_filename = "output/" + xnode.get( 'name', "NAME" ) + "_" + str(current_output_file_number) + "_" + str( time.time()) + "_" + str( wfc_ns.seed) + ".png", # name of the output file profile_filename = "" + str( wfc_ns.output_path) + "setup_" + str( wfc_ns.output_file_number) + "_" + str( wfc_ns.seed) + "_" + str(time.time()) + "_" + str( wfc_ns.seed) + ".profile" if WFC_PROFILE: with open(profile_filename, 'w') as profile_file: ps = pstats.Stats(pr, stream=profile_file) ps.sort_stats('cumtime', 'ncalls') ps.print_stats(20) solution = None if "minizinc" == solver_to_use: attempt_count = 0 #while attempt_count < wfc_ns.allowed_attempts: # attempt_count += 1 # solution = mz_run(wfc_ns) # solution.wfc_ns.stats_tracking["attempt_count"] = attempt_count # solution.wfc_ns.stats_tracking["choices_before_success"] += solution.wfc_ns.stats_tracking["observations"] else: if True: attempt_count = 0 #print("allowed attempts: " + str(wfc_ns.allowed_attempts)) attempt_wfc_ns = copy.deepcopy(wfc_ns) attempt_wfc_ns.stats_tracking[ "time_start"] = time.time() attempt_wfc_ns.stats_tracking[ "choices_before_success"] = 0 attempt_wfc_ns.stats_tracking[ "total_observations_before_success"] = 0 wfc.wfc_solver.reset_backtracking_count( ) # reset the count of how many times we've backtracked, because multiple attempts are handled here instead of there while attempt_count < wfc_ns.allowed_attempts: attempt_count += 1 print(attempt_count, end=' ') attempt_wfc_ns.seed += 7 # change seed for each attempt... solution = wfc_run(attempt_wfc_ns, visualize=WFC_VISUALIZE, logging=WFC_LOGGING) solution.wfc_ns.stats_tracking[ "attempt_count"] = attempt_count solution.wfc_ns.stats_tracking[ "choices_before_success"] += solution.wfc_ns.stats_tracking[ "observations"] attempt_wfc_ns.stats_tracking[ "total_observations_before_success"] += solution.wfc_ns.stats_tracking[ 'total_observations'] wfc_logger.info("result: {} is {}".format( attempt_count, solution.result)) if solution.result == -2: attempt_count = wfc_ns.allowed_attempts solution.wfc_ns.stats_tracking[ "time_end"] = time.time() wfc_stats_data.append( solution.wfc_ns.stats_tracking.copy()) solution.wfc_ns.stats_tracking[ "final_time_end"] = time.time() print("tracking choices before success...") choices_before_success = solution.wfc_ns.stats_tracking[ "choices_before_success"] time_passed = None if None != solution.wfc_ns.stats_tracking["time_end"]: time_passed = solution.wfc_ns.stats_tracking[ "time_end"] - solution.wfc_ns.stats_tracking[ "time_start"] else: if None != solution.wfc_ns.stats_tracking[ "final_time_end"]: time_passed = solution.wfc_ns.stats_tracking[ "final_time_end"] - solution.wfc_ns.stats_tracking[ "time_start"] print("...finished calculating time passed") #print(wfc_stats_data) print("writing stats...", end='') with open(stats_file_name, "a+") as stats_file: stats_file.write( f"{solution.wfc_ns.output_file_number}\t{solution.wfc_ns.operation_name}\t{solution.wfc_ns.stats_tracking['success']}\t{solution.wfc_ns.stats_tracking['attempt_count']}\t{solution.wfc_ns.stats_tracking['observations']}\t{solution.wfc_ns.stats_tracking['propagations']}\t{solution.wfc_ns.stats_tracking['choices_before_success']}\t{solution.wfc_ns.stats_tracking['total_observations']}\t{attempt_wfc_ns.stats_tracking['total_observations_before_success']}\t{solution.backtracking_total}\t{time_passed}\t{solution.wfc_ns.stats_tracking['time_start']}\t{solution.wfc_ns.stats_tracking['time_end']}\t{solution.wfc_ns.stats_tracking['final_time_end']}\t{solution.wfc_ns.generated_size}\t{len(solution.wfc_ns.pattern_weights.keys())}\t{solution.wfc_ns.seed}\t{solution.wfc_ns.backtracking}\t{solution.wfc_ns.allowed_attempts}\t{solution.wfc_ns.force_use_all_patterns}\t{solution.wfc_ns.output_filename}\n" ) print("done") if WFC_VISUALIZE: print("visualize") if None == solution: print("n u l l") #print(solution) print(1) solution_vis = wfc.wfc_solver.render_recorded_visualization( solution.recorded_vis) #print(solution) print(2) video_fn = f"{solution.wfc_ns.output_path}/crystal_example_{solution.wfc_ns.output_file_number}_{time.time()}.mp4" wfc_logger.info("*****************************") wfc_logger.warning(video_fn) print( f"solver recording stack len - {len(solution_vis.solver_recording_stack)}" ) print(solution_vis.solver_recording_stack[0].shape) if len(solution_vis.solver_recording_stack) > 0: wfc_logger.info( solution_vis.solver_recording_stack[0].shape) writer = FFMPEG_VideoWriter(video_fn, [ solution_vis.solver_recording_stack[0].shape[0], solution_vis.solver_recording_stack[0].shape[1] ], 12.0) for img_data in solution_vis.solver_recording_stack: writer.write_frame(img_data) print('!', end='') writer.close() mpy.ipython_display(video_fn, height=700) print("recording done") if WFC_VISUALIZE: solution = wfc_partial_output(solution) show_rendered_patterns(solution, True) print("render to output") render_patterns_to_output(solution, True, False) print("completed") print("\n{0} >".format(name)) elif "simpletiled" == xnode.tag: print("> ", end="\n") continue else: continue
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