diff --git a/requirements.txt b/requirements.txt index 0136aea..ab6a79a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,3 +27,5 @@ wincertstore==0.2 moviepy==1.0.3 tracery==0.1.1 arrdem.datalog==2.0.1 +scipy +numpy diff --git a/wfc b/wfc deleted file mode 160000 index 524d284..0000000 --- a/wfc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 524d28483b20a92c99836f717cd5578b0a678612 diff --git a/wfc/.gitignore b/wfc/.gitignore new file mode 100644 index 0000000..97594ab --- /dev/null +++ b/wfc/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +/output/* +/build diff --git a/wfc/LICENSE b/wfc/LICENSE new file mode 100644 index 0000000..5842edf --- /dev/null +++ b/wfc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Isaac Karth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/wfc/README.md b/wfc/README.md new file mode 100644 index 0000000..7987d27 --- /dev/null +++ b/wfc/README.md @@ -0,0 +1,50 @@ +# wfc_2019f + +This is my research implementation of WaveFunctionCollapse in Python. It has two goals: + +* Make it easier to understand how the algorithm operates +* Provide a testbed for experimenting with alternate heuristics and features + +For more general-purpose WFC information, the original reference repository remains the best resource: https://github.com/mxgmn/WaveFunctionCollapse + +## Running WFC + +If you want direct control over running WFC, call `wfc_control.execute_wfc()`. + +The arguments it accepts are: + +- `filename`: path to the input image file +- `tile_size=1`: size of the tiles it uses (1 is fine for pixel images, larger is for things like a Super Metroid map) +- `pattern_width=2`: size of the patterns; usually 2 or 3 because bigger gets slower and +- `rotations=8`: how many reflections and/or rotations to use with the patterns +- `output_size=[48,48]`: how big the output image is +- `ground=None`: which patterns should be placed along the bottom-most line +- `attempt_limit=10`: stop after this many tries +- `output_periodic=True`: the output wraps at the edges +- `input_periodic=True`: the input wraps at the edges +- `loc_heuristic="lexical"`: what location heuristic to use; `entropy` is the original WFC behavior. The heuristics that are implemented are `lexical`, `hilbert`, `spiral`, `entropy`, `anti-entropy`, `simple`, `random`, but when in doubt stick with `entropy`. +- `choice_heuristic="lexical"`: what choice heuristic to use; `weighted` is the original WFC behavior. +- `visualize=True`: write intermediate images to disk? +- `global_constraint=False`: what global constraint to use. Currently the only one implemented is `allpatterns` +- `backtracking=False`: do we use backtracking if we run into a contradiction? +- `log_filename="log"`: what should the log file be named? +- `logging=True`: should we write to a log file? +- `log_stats_to_output=None` + +## Test + +``` +pytest +``` + +## Documentation + +``` +python setup.py build_sphinx +``` + +With linux the doculentation can be displayed with: + +``` +xdg-open build/sphinx/index.html +``` diff --git a/wfc/doc/conf.py b/wfc/doc/conf.py new file mode 100644 index 0000000..8a44009 --- /dev/null +++ b/wfc/doc/conf.py @@ -0,0 +1,56 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'wfc_python' +copyright = '2020, Isaac Karth' +author = 'Isaac Karth' + +# The full version, including alpha/beta/rc tags +release = '0.1' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.graphviz', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['static'] \ No newline at end of file diff --git a/wfc/doc/dot/chain.dot b/wfc/doc/dot/chain.dot new file mode 100644 index 0000000..025a6b9 --- /dev/null +++ b/wfc/doc/dot/chain.dot @@ -0,0 +1,16 @@ +digraph { + read_xml_command -> import_image -> make_tile_catalog -> make_pattern_catalog -> make_adjacency_matrix -> solve_constraint_problem -> output_solution_image + make_tile_catalog -> output_solution_image + make_tile_catalog -> instrumentation [color=gray] + make_pattern_catalog -> instrumentation [color=gray] + make_adjacency_matrix -> instrumentation [color=gray] + solve_constraint_problem -> instrumentation [color=gray] + output_solution_image -> visualization [color=gray] + make_tile_catalog -> visualization [color=gray] + make_pattern_catalog -> visualization [color=gray] + make_adjacency_matrix -> visualization [color=gray] + solve_constraint_problem -> visualization [color=gray] + visualization [color=gray, fontcolor=gray] + instrumentation [color=gray, fontcolor=gray] + visualization -> make_tile_catalog [color=magenta] +} \ No newline at end of file diff --git a/wfc/doc/dot/dependency.dot b/wfc/doc/dot/dependency.dot new file mode 100644 index 0000000..642eb85 --- /dev/null +++ b/wfc/doc/dot/dependency.dot @@ -0,0 +1,37 @@ +digraph { + wfc_run -> wfc_control + wfc_control -> wfc_utilities + wfc_control -> wfc_solver + wfc_solver -> numpy + wfc_tiles -> numpy + wfc_patterns -> numpy + wfc_tiles -> wfc_utilities + wfc_control -> wfc_tiles + wfc_control -> wfc_patterns + wfc_patterns -> wfc_utilities + wfc_tiles -> imageio + wfc_control -> wfc_adjacency + wfc_control -> wfc_visualize + wfc_visualize -> matplotlib + wfc_visualize -> wfc_utilities + wfc_adjacency -> wfc_utilities + wfc_adjacency -> numpy + wfc_control -> wfc_instrumentation + + implemented [style=filled, fillcolor=gray] + partial [style=filled, fillcolor=cyan] + unimplemented [style=filled, fillcolor=firebrick] + wfc_run + wfc_control [] + wfc_solver + numpy [color=gray, fontcolor=gray] + wfc_tiles + wfc_patterns [style=filled, fillcolor=cyan] + wfc_utilities + imageio [color=gray, fontcolor=gray] + wfc_adjacency + wfc_visualize [style=filled, fillcolor=cyan] + matplotlib [color=gray, fontcolor=gray] + wfc_instrumentation [style=filled, fillcolor=firebrick] + label="Modules in WFC 19f" +} \ No newline at end of file diff --git a/wfc/doc/dot/design.dot b/wfc/doc/dot/design.dot new file mode 100644 index 0000000..a5c11a3 --- /dev/null +++ b/wfc/doc/dot/design.dot @@ -0,0 +1,89 @@ +digraph { + things_to_implement [label="{Things that aren't implemented yet|Intermediate visualization|timing and profiling|performance statistics|outputting images|most heuristics|removing ground patterns|rotated patterns}", shape=record, fillcolor="cyan", style=filled] + read_data [label="Read data from XML", fillcolor="cyan", shape=box, style=filled] + read_data -> input_data + input_data [shape=record, label="XML"] + input_data -> execute_wfc + solver [label="Solver", shape=house] + solver -> make_wave + make_wave -> remove_patterns + remove_patterns [label="Remove ground patterns", fillcolor="cyan", style=filled] + input_data -> remove_patterns + input_data -> solver + remove_patterns -> solver_run [headport=n] + subgraph cluster_solver_run { + label="wfc_solver.py" + + solver_run [label="solver.run()"] + solver_observe [label="solver.observe()"] + solver_propagate [label="solver.propagate()"] + solver_on_backtrack [label="solver.onBacktrack()", shape=invhouse] + solver_on_choice [label="solver.onChoice()", shape=invhouse] + on_choice [label="onChoice()", shape=note] + on_backtrack [label="onBacktrack()", shape=note, fillcolor="cyan", style=filled] + solver_if_backtracking [label="if backtracking", shape=diamond] + pattern_heuristic [label="pattern heuristic", shape=note] + location_heuristic [label="location heuristic", shape=note] + + + {rank=same pattern_heuristic location_heuristic} + solver_run -> solver_check_feasible + solver_check_feasible -> solver_propagate + solver_propagate -> solver_observe + solver_observe -> pattern_heuristic + solver_observe -> location_heuristic + solver_observe -> solver_on_choice + solver_on_choice -> on_choice + solver_recurse -> except_contradictions [color=red] + solver_on_choice -> solver_if_finished + solver_recurse -> solver_run [headport=n, tailport=w] + solver_if_finished [shape=diamond] + solver_if_finished -> solver_recurse [splines=polyline, dir=both, arrowhead=dotvee, arrowtail=dot, tailport=s, headport=n, color="black:green:black"] + except_contradictions -> solver_if_backtracking + solver_if_backtracking -> solver_on_backtrack [label="Yes"] + solver_on_backtrack -> on_backtrack + on_backtrack -> solver_run [headport=n] + solver_if_backtracking -> cant_solve [splines=curved, label="No", dir=both, arrowhead=dotvee, arrowtail=dot, tailport=e, headport=ne, color="grey"] + } + solver_if_finished -> solver_solution [tailport=w, color="black:blue:black"] + + execute_wfc [shape=invhouse, fillcolor="cyan", style=filled] + execute_wfc -> import_image + import_image [shape=box] + import_image -> make_tile_catalog + subgraph cluster_tile_py { + label="wfc_tiles.py" + make_tile_catalog -> image_to_tiles + } + image_to_tiles -> tile_catalog + tile_catalog [label="Tile Catalog|{dictionary of tiles|image in tile IDs|set of tiles|frequency of tile occurance}", shape=record] + subgraph cluster_patterns { + label="wfc_patterns.py" + tile_catalog -> make_pattern_catalog + {rank=same unique_patterns_2d rotate_or_reflect} + make_pattern_catalog -> unique_patterns_2d -> rotate_or_reflect -> unique_patterns_2d + make_pattern_catalog [fillcolor="cyan", style=filled] + rotate_or_reflect [fillcolor="cyan", style=filled] + } + unique_patterns_2d -> pattern_catalog + pattern_catalog [label="Pattern Catalog|{dictionary of patterns|ordered list of pattern weights|ordered list of pattern contents}", shape=record] + pattern_catalog -> extract_adjacency + subgraph cluster_adjacency { + extract_adjacency -> is_valid_overlap + } + extract_adjacency -> adjacency_relations + adjacency_relations [label="{Adjacency Relations|tuples of (edge,pattern,pattern)}", shape=record] + adjacency_relations -> combine_inputs + combine_inputs -> adjacency_matrix + adjacency_matrix [label="{Adjacency Matrix|boolean matrix of pattern x pattern x direction}", shape=record] + adjacency_matrix -> solver + pattern_catalog -> solver + cant_solve [label="Can't Solve", shape=box] + solver_solution [shape=record, label="Solution|grid of pattern IDs"] + solver_solution -> visualizer + visualizer -> output_image + output_image [shape=box, label="Output Image", style=filled, fillcolor=cyan] + pattern_catalog -> visualizer + tile_catalog -> visualizer + visualizer [fillcolor=cyan, style=filled] +} diff --git a/wfc/doc/index.rst b/wfc/doc/index.rst new file mode 100644 index 0000000..f2413fd --- /dev/null +++ b/wfc/doc/index.rst @@ -0,0 +1,17 @@ +Documentation +============= + +Module dependencies +------------------- + +.. graphviz:: dot/dependency.dot + +Design +------ + +.. graphviz:: dot/design.dot + +Chain +----- + +.. graphviz:: dot/chain.dot diff --git a/wfc/images/samples/3Bricks.png b/wfc/images/samples/3Bricks.png new file mode 100644 index 0000000..33205e5 Binary files /dev/null and b/wfc/images/samples/3Bricks.png differ diff --git a/wfc/images/samples/Angular.png b/wfc/images/samples/Angular.png new file mode 100644 index 0000000..7b8efd6 Binary files /dev/null and b/wfc/images/samples/Angular.png differ diff --git a/wfc/images/samples/Castle/bridge.png b/wfc/images/samples/Castle/bridge.png new file mode 100644 index 0000000..737c692 Binary files /dev/null and b/wfc/images/samples/Castle/bridge.png differ diff --git a/wfc/images/samples/Castle/data.xml b/wfc/images/samples/Castle/data.xml new file mode 100644 index 0000000..0fdaf8d --- /dev/null +++ b/wfc/images/samples/Castle/data.xml @@ -0,0 +1,150 @@ +<<<<<<< HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94 + \ No newline at end of file diff --git a/wfc/images/samples/Castle/ground.png b/wfc/images/samples/Castle/ground.png new file mode 100644 index 0000000..8eff3f2 Binary files /dev/null and b/wfc/images/samples/Castle/ground.png differ diff --git a/wfc/images/samples/Castle/river.png b/wfc/images/samples/Castle/river.png new file mode 100644 index 0000000..5c5f5f6 Binary files /dev/null and b/wfc/images/samples/Castle/river.png differ diff --git a/wfc/images/samples/Castle/riverturn.png b/wfc/images/samples/Castle/riverturn.png new file mode 100644 index 0000000..b6637e3 Binary files /dev/null and b/wfc/images/samples/Castle/riverturn.png differ diff --git a/wfc/images/samples/Castle/road.png b/wfc/images/samples/Castle/road.png new file mode 100644 index 0000000..453f0d7 Binary files /dev/null and b/wfc/images/samples/Castle/road.png differ diff --git a/wfc/images/samples/Castle/roadturn.png b/wfc/images/samples/Castle/roadturn.png new file mode 100644 index 0000000..77a0f33 Binary files /dev/null and b/wfc/images/samples/Castle/roadturn.png differ diff --git a/wfc/images/samples/Castle/t.png b/wfc/images/samples/Castle/t.png new file mode 100644 index 0000000..77aa8e7 Binary files /dev/null and b/wfc/images/samples/Castle/t.png differ diff --git a/wfc/images/samples/Castle/tower.png b/wfc/images/samples/Castle/tower.png new file mode 100644 index 0000000..75ddff2 Binary files /dev/null and b/wfc/images/samples/Castle/tower.png differ diff --git a/wfc/images/samples/Castle/wall.png b/wfc/images/samples/Castle/wall.png new file mode 100644 index 0000000..7872819 Binary files /dev/null and b/wfc/images/samples/Castle/wall.png differ diff --git a/wfc/images/samples/Castle/wallriver.png b/wfc/images/samples/Castle/wallriver.png new file mode 100644 index 0000000..a17adce Binary files /dev/null and b/wfc/images/samples/Castle/wallriver.png differ diff --git a/wfc/images/samples/Castle/wallroad.png b/wfc/images/samples/Castle/wallroad.png new file mode 100644 index 0000000..a306816 Binary files /dev/null and b/wfc/images/samples/Castle/wallroad.png differ diff --git a/wfc/images/samples/Cat.png b/wfc/images/samples/Cat.png new file mode 100644 index 0000000..fc3f220 Binary files /dev/null and b/wfc/images/samples/Cat.png differ diff --git a/wfc/images/samples/Cats.png b/wfc/images/samples/Cats.png new file mode 100644 index 0000000..5351262 Binary files /dev/null and b/wfc/images/samples/Cats.png differ diff --git a/wfc/images/samples/Cave.png b/wfc/images/samples/Cave.png new file mode 100644 index 0000000..4a9e21a Binary files /dev/null and b/wfc/images/samples/Cave.png differ diff --git a/wfc/images/samples/Chess.png b/wfc/images/samples/Chess.png new file mode 100644 index 0000000..090ce47 Binary files /dev/null and b/wfc/images/samples/Chess.png differ diff --git a/wfc/images/samples/Circles/b.png b/wfc/images/samples/Circles/b.png new file mode 100644 index 0000000..e0aaf6e Binary files /dev/null and b/wfc/images/samples/Circles/b.png differ diff --git a/wfc/images/samples/Circles/b_half.png b/wfc/images/samples/Circles/b_half.png new file mode 100644 index 0000000..b93fd3f Binary files /dev/null and b/wfc/images/samples/Circles/b_half.png differ diff --git a/wfc/images/samples/Circles/b_i.png b/wfc/images/samples/Circles/b_i.png new file mode 100644 index 0000000..cc5e205 Binary files /dev/null and b/wfc/images/samples/Circles/b_i.png differ diff --git a/wfc/images/samples/Circles/b_quarter.png b/wfc/images/samples/Circles/b_quarter.png new file mode 100644 index 0000000..5932768 Binary files /dev/null and b/wfc/images/samples/Circles/b_quarter.png differ diff --git a/wfc/images/samples/Circles/data.xml b/wfc/images/samples/Circles/data.xml new file mode 100644 index 0000000..d109eb0 --- /dev/null +++ b/wfc/images/samples/Circles/data.xml @@ -0,0 +1,238 @@ +<<<<<<< HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94 + \ No newline at end of file diff --git a/wfc/images/samples/Circles/w.png b/wfc/images/samples/Circles/w.png new file mode 100644 index 0000000..4804c6c Binary files /dev/null and b/wfc/images/samples/Circles/w.png differ diff --git a/wfc/images/samples/Circles/w_half.png b/wfc/images/samples/Circles/w_half.png new file mode 100644 index 0000000..b83f2fe Binary files /dev/null and b/wfc/images/samples/Circles/w_half.png differ diff --git a/wfc/images/samples/Circles/w_i.png b/wfc/images/samples/Circles/w_i.png new file mode 100644 index 0000000..c8ffb56 Binary files /dev/null and b/wfc/images/samples/Circles/w_i.png differ diff --git a/wfc/images/samples/Circles/w_quarter.png b/wfc/images/samples/Circles/w_quarter.png new file mode 100644 index 0000000..3ce9433 Binary files /dev/null and b/wfc/images/samples/Circles/w_quarter.png differ diff --git a/wfc/images/samples/Circuit/bridge.png b/wfc/images/samples/Circuit/bridge.png new file mode 100644 index 0000000..1123db8 Binary files /dev/null and b/wfc/images/samples/Circuit/bridge.png differ diff --git a/wfc/images/samples/Circuit/component.png b/wfc/images/samples/Circuit/component.png new file mode 100644 index 0000000..fbb5fa1 Binary files /dev/null and b/wfc/images/samples/Circuit/component.png differ diff --git a/wfc/images/samples/Circuit/connection.png b/wfc/images/samples/Circuit/connection.png new file mode 100644 index 0000000..3061705 Binary files /dev/null and b/wfc/images/samples/Circuit/connection.png differ diff --git a/wfc/images/samples/Circuit/corner.png b/wfc/images/samples/Circuit/corner.png new file mode 100644 index 0000000..643cf27 Binary files /dev/null and b/wfc/images/samples/Circuit/corner.png differ diff --git a/wfc/images/samples/Circuit/data.xml b/wfc/images/samples/Circuit/data.xml new file mode 100644 index 0000000..f58039f --- /dev/null +++ b/wfc/images/samples/Circuit/data.xml @@ -0,0 +1,366 @@ +<<<<<<< HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94 + \ No newline at end of file diff --git a/wfc/images/samples/Circuit/dskew.png b/wfc/images/samples/Circuit/dskew.png new file mode 100644 index 0000000..f9a07f4 Binary files /dev/null and b/wfc/images/samples/Circuit/dskew.png differ diff --git a/wfc/images/samples/Circuit/skew.png b/wfc/images/samples/Circuit/skew.png new file mode 100644 index 0000000..260df54 Binary files /dev/null and b/wfc/images/samples/Circuit/skew.png differ diff --git a/wfc/images/samples/Circuit/substrate.png b/wfc/images/samples/Circuit/substrate.png new file mode 100644 index 0000000..2c3aec0 Binary files /dev/null and b/wfc/images/samples/Circuit/substrate.png differ diff --git a/wfc/images/samples/Circuit/t.png b/wfc/images/samples/Circuit/t.png new file mode 100644 index 0000000..084c360 Binary files /dev/null and b/wfc/images/samples/Circuit/t.png differ diff --git a/wfc/images/samples/Circuit/track.png b/wfc/images/samples/Circuit/track.png new file mode 100644 index 0000000..01158cb Binary files /dev/null and b/wfc/images/samples/Circuit/track.png differ diff --git a/wfc/images/samples/Circuit/transition.png b/wfc/images/samples/Circuit/transition.png new file mode 100644 index 0000000..42ea71f Binary files /dev/null and b/wfc/images/samples/Circuit/transition.png differ diff --git a/wfc/images/samples/Circuit/turn.png b/wfc/images/samples/Circuit/turn.png new file mode 100644 index 0000000..ff10c5f Binary files /dev/null and b/wfc/images/samples/Circuit/turn.png differ diff --git a/wfc/images/samples/Circuit/viad.png b/wfc/images/samples/Circuit/viad.png new file mode 100644 index 0000000..eddd987 Binary files /dev/null and b/wfc/images/samples/Circuit/viad.png differ diff --git a/wfc/images/samples/Circuit/vias.png b/wfc/images/samples/Circuit/vias.png new file mode 100644 index 0000000..eb7a35e Binary files /dev/null and b/wfc/images/samples/Circuit/vias.png differ diff --git a/wfc/images/samples/Circuit/wire.png b/wfc/images/samples/Circuit/wire.png new file mode 100644 index 0000000..958d10c Binary files /dev/null and b/wfc/images/samples/Circuit/wire.png differ diff --git a/wfc/images/samples/City.png b/wfc/images/samples/City.png new file mode 100644 index 0000000..3b9c4c3 Binary files /dev/null and b/wfc/images/samples/City.png differ diff --git a/wfc/images/samples/Colored City.png b/wfc/images/samples/Colored City.png new file mode 100644 index 0000000..f0c5a5f Binary files /dev/null and b/wfc/images/samples/Colored City.png differ diff --git a/wfc/images/samples/Dungeon.png b/wfc/images/samples/Dungeon.png new file mode 100644 index 0000000..64ce90d Binary files /dev/null and b/wfc/images/samples/Dungeon.png differ diff --git a/wfc/images/samples/Fabric.png b/wfc/images/samples/Fabric.png new file mode 100644 index 0000000..538259d Binary files /dev/null and b/wfc/images/samples/Fabric.png differ diff --git a/wfc/images/samples/Flowers.png b/wfc/images/samples/Flowers.png new file mode 100644 index 0000000..b67d8ba Binary files /dev/null and b/wfc/images/samples/Flowers.png differ diff --git a/wfc/images/samples/Forest.png b/wfc/images/samples/Forest.png new file mode 100644 index 0000000..dc00bc6 Binary files /dev/null and b/wfc/images/samples/Forest.png differ diff --git a/wfc/images/samples/Hogs.png b/wfc/images/samples/Hogs.png new file mode 100644 index 0000000..789c8d3 Binary files /dev/null and b/wfc/images/samples/Hogs.png differ diff --git a/wfc/images/samples/Knot.png b/wfc/images/samples/Knot.png new file mode 100644 index 0000000..cc132f5 Binary files /dev/null and b/wfc/images/samples/Knot.png differ diff --git a/wfc/images/samples/Knots/corner.png b/wfc/images/samples/Knots/corner.png new file mode 100644 index 0000000..bcc6ef7 Binary files /dev/null and b/wfc/images/samples/Knots/corner.png differ diff --git a/wfc/images/samples/Knots/cross.png b/wfc/images/samples/Knots/cross.png new file mode 100644 index 0000000..49752d8 Binary files /dev/null and b/wfc/images/samples/Knots/cross.png differ diff --git a/wfc/images/samples/Knots/data.xml b/wfc/images/samples/Knots/data.xml new file mode 100644 index 0000000..f4ece3f --- /dev/null +++ b/wfc/images/samples/Knots/data.xml @@ -0,0 +1,180 @@ +<<<<<<< HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94 + \ No newline at end of file diff --git a/wfc/images/samples/Knots/empty.png b/wfc/images/samples/Knots/empty.png new file mode 100644 index 0000000..ccae147 Binary files /dev/null and b/wfc/images/samples/Knots/empty.png differ diff --git a/wfc/images/samples/Knots/line.png b/wfc/images/samples/Knots/line.png new file mode 100644 index 0000000..c315294 Binary files /dev/null and b/wfc/images/samples/Knots/line.png differ diff --git a/wfc/images/samples/Knots/t.png b/wfc/images/samples/Knots/t.png new file mode 100644 index 0000000..cdaba5a Binary files /dev/null and b/wfc/images/samples/Knots/t.png differ diff --git a/wfc/images/samples/Lake.png b/wfc/images/samples/Lake.png new file mode 100644 index 0000000..56c9ceb Binary files /dev/null and b/wfc/images/samples/Lake.png differ diff --git a/wfc/images/samples/Less Rooms.png b/wfc/images/samples/Less Rooms.png new file mode 100644 index 0000000..8f36e62 Binary files /dev/null and b/wfc/images/samples/Less Rooms.png differ diff --git a/wfc/images/samples/Link 2.png b/wfc/images/samples/Link 2.png new file mode 100644 index 0000000..8a8b337 Binary files /dev/null and b/wfc/images/samples/Link 2.png differ diff --git a/wfc/images/samples/Link.png b/wfc/images/samples/Link.png new file mode 100644 index 0000000..57f5e28 Binary files /dev/null and b/wfc/images/samples/Link.png differ diff --git a/wfc/images/samples/Magic Office.png b/wfc/images/samples/Magic Office.png new file mode 100644 index 0000000..5765b8c Binary files /dev/null and b/wfc/images/samples/Magic Office.png differ diff --git a/wfc/images/samples/Maze.png b/wfc/images/samples/Maze.png new file mode 100644 index 0000000..6cfd1b7 Binary files /dev/null and b/wfc/images/samples/Maze.png differ diff --git a/wfc/images/samples/Mazelike.png b/wfc/images/samples/Mazelike.png new file mode 100644 index 0000000..bda57e1 Binary files /dev/null and b/wfc/images/samples/Mazelike.png differ diff --git a/wfc/images/samples/More Flowers.png b/wfc/images/samples/More Flowers.png new file mode 100644 index 0000000..bc781e4 Binary files /dev/null and b/wfc/images/samples/More Flowers.png differ diff --git a/wfc/images/samples/Mountains.png b/wfc/images/samples/Mountains.png new file mode 100644 index 0000000..ec27119 Binary files /dev/null and b/wfc/images/samples/Mountains.png differ diff --git a/wfc/images/samples/Nested.png b/wfc/images/samples/Nested.png new file mode 100644 index 0000000..8e524f2 Binary files /dev/null and b/wfc/images/samples/Nested.png differ diff --git a/wfc/images/samples/Office 2.png b/wfc/images/samples/Office 2.png new file mode 100644 index 0000000..f3e23cd Binary files /dev/null and b/wfc/images/samples/Office 2.png differ diff --git a/wfc/images/samples/Office.png b/wfc/images/samples/Office.png new file mode 100644 index 0000000..a4912f2 Binary files /dev/null and b/wfc/images/samples/Office.png differ diff --git a/wfc/images/samples/Paths.png b/wfc/images/samples/Paths.png new file mode 100644 index 0000000..a7a6084 Binary files /dev/null and b/wfc/images/samples/Paths.png differ diff --git a/wfc/images/samples/Platformer.png b/wfc/images/samples/Platformer.png new file mode 100644 index 0000000..e129381 Binary files /dev/null and b/wfc/images/samples/Platformer.png differ diff --git a/wfc/images/samples/Qud.png b/wfc/images/samples/Qud.png new file mode 100644 index 0000000..561f5b3 Binary files /dev/null and b/wfc/images/samples/Qud.png differ diff --git a/wfc/images/samples/Red Dot.png b/wfc/images/samples/Red Dot.png new file mode 100644 index 0000000..44e5afb Binary files /dev/null and b/wfc/images/samples/Red Dot.png differ diff --git a/wfc/images/samples/Red Maze.png b/wfc/images/samples/Red Maze.png new file mode 100644 index 0000000..f716db2 Binary files /dev/null and b/wfc/images/samples/Red Maze.png differ diff --git a/wfc/images/samples/Rooms.png b/wfc/images/samples/Rooms.png new file mode 100644 index 0000000..4210deb Binary files /dev/null and b/wfc/images/samples/Rooms.png differ diff --git a/wfc/images/samples/Rooms/bend.png b/wfc/images/samples/Rooms/bend.png new file mode 100644 index 0000000..fd27d07 Binary files /dev/null and b/wfc/images/samples/Rooms/bend.png differ diff --git a/wfc/images/samples/Rooms/corner.png b/wfc/images/samples/Rooms/corner.png new file mode 100644 index 0000000..5d9bf27 Binary files /dev/null and b/wfc/images/samples/Rooms/corner.png differ diff --git a/wfc/images/samples/Rooms/corridor.png b/wfc/images/samples/Rooms/corridor.png new file mode 100644 index 0000000..2fd942f Binary files /dev/null and b/wfc/images/samples/Rooms/corridor.png differ diff --git a/wfc/images/samples/Rooms/data.xml b/wfc/images/samples/Rooms/data.xml new file mode 100644 index 0000000..d6c23dd --- /dev/null +++ b/wfc/images/samples/Rooms/data.xml @@ -0,0 +1,112 @@ +<<<<<<< HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94 + \ No newline at end of file diff --git a/wfc/images/samples/Rooms/door.png b/wfc/images/samples/Rooms/door.png new file mode 100644 index 0000000..6cd0500 Binary files /dev/null and b/wfc/images/samples/Rooms/door.png differ diff --git a/wfc/images/samples/Rooms/empty.png b/wfc/images/samples/Rooms/empty.png new file mode 100644 index 0000000..c2eea5a Binary files /dev/null and b/wfc/images/samples/Rooms/empty.png differ diff --git a/wfc/images/samples/Rooms/side.png b/wfc/images/samples/Rooms/side.png new file mode 100644 index 0000000..2fe2883 Binary files /dev/null and b/wfc/images/samples/Rooms/side.png differ diff --git a/wfc/images/samples/Rooms/t.png b/wfc/images/samples/Rooms/t.png new file mode 100644 index 0000000..47b93c7 Binary files /dev/null and b/wfc/images/samples/Rooms/t.png differ diff --git a/wfc/images/samples/Rooms/turn.png b/wfc/images/samples/Rooms/turn.png new file mode 100644 index 0000000..9db18d7 Binary files /dev/null and b/wfc/images/samples/Rooms/turn.png differ diff --git a/wfc/images/samples/Rooms/wall.png b/wfc/images/samples/Rooms/wall.png new file mode 100644 index 0000000..90aefbe Binary files /dev/null and b/wfc/images/samples/Rooms/wall.png differ diff --git a/wfc/images/samples/Rule 126.png b/wfc/images/samples/Rule 126.png new file mode 100644 index 0000000..e24796c Binary files /dev/null and b/wfc/images/samples/Rule 126.png differ diff --git a/wfc/images/samples/Scaled Maze.png b/wfc/images/samples/Scaled Maze.png new file mode 100644 index 0000000..537c506 Binary files /dev/null and b/wfc/images/samples/Scaled Maze.png differ diff --git a/wfc/images/samples/Sewers.png b/wfc/images/samples/Sewers.png new file mode 100644 index 0000000..e26765c Binary files /dev/null and b/wfc/images/samples/Sewers.png differ diff --git a/wfc/images/samples/Simple Knot.png b/wfc/images/samples/Simple Knot.png new file mode 100644 index 0000000..0e805cd Binary files /dev/null and b/wfc/images/samples/Simple Knot.png differ diff --git a/wfc/images/samples/Simple Maze.png b/wfc/images/samples/Simple Maze.png new file mode 100644 index 0000000..c6c9bd4 Binary files /dev/null and b/wfc/images/samples/Simple Maze.png differ diff --git a/wfc/images/samples/Simple Wall.png b/wfc/images/samples/Simple Wall.png new file mode 100644 index 0000000..9369ff6 Binary files /dev/null and b/wfc/images/samples/Simple Wall.png differ diff --git a/wfc/images/samples/Skew 1.png b/wfc/images/samples/Skew 1.png new file mode 100644 index 0000000..65f27ea Binary files /dev/null and b/wfc/images/samples/Skew 1.png differ diff --git a/wfc/images/samples/Skew 2.png b/wfc/images/samples/Skew 2.png new file mode 100644 index 0000000..8c60b39 Binary files /dev/null and b/wfc/images/samples/Skew 2.png differ diff --git a/wfc/images/samples/Skyline 2.png b/wfc/images/samples/Skyline 2.png new file mode 100644 index 0000000..a7cd1e5 Binary files /dev/null and b/wfc/images/samples/Skyline 2.png differ diff --git a/wfc/images/samples/Skyline.png b/wfc/images/samples/Skyline.png new file mode 100644 index 0000000..4589876 Binary files /dev/null and b/wfc/images/samples/Skyline.png differ diff --git a/wfc/images/samples/Smile City.png b/wfc/images/samples/Smile City.png new file mode 100644 index 0000000..8681f9f Binary files /dev/null and b/wfc/images/samples/Smile City.png differ diff --git a/wfc/images/samples/Spirals.png b/wfc/images/samples/Spirals.png new file mode 100644 index 0000000..55de4c4 Binary files /dev/null and b/wfc/images/samples/Spirals.png differ diff --git a/wfc/images/samples/Summer/cliff 0.png b/wfc/images/samples/Summer/cliff 0.png new file mode 100644 index 0000000..a921d79 Binary files /dev/null and b/wfc/images/samples/Summer/cliff 0.png differ diff --git a/wfc/images/samples/Summer/cliff 1.png b/wfc/images/samples/Summer/cliff 1.png new file mode 100644 index 0000000..6f36731 Binary files /dev/null and b/wfc/images/samples/Summer/cliff 1.png differ diff --git a/wfc/images/samples/Summer/cliff 2.png b/wfc/images/samples/Summer/cliff 2.png new file mode 100644 index 0000000..c3b96f2 Binary files /dev/null and b/wfc/images/samples/Summer/cliff 2.png differ diff --git a/wfc/images/samples/Summer/cliff 3.png b/wfc/images/samples/Summer/cliff 3.png new file mode 100644 index 0000000..4c45cfe Binary files /dev/null and b/wfc/images/samples/Summer/cliff 3.png differ diff --git a/wfc/images/samples/Summer/cliffcorner 0.png b/wfc/images/samples/Summer/cliffcorner 0.png new file mode 100644 index 0000000..2a52d32 Binary files /dev/null and b/wfc/images/samples/Summer/cliffcorner 0.png differ diff --git a/wfc/images/samples/Summer/cliffcorner 1.png b/wfc/images/samples/Summer/cliffcorner 1.png new file mode 100644 index 0000000..d94efed Binary files /dev/null and b/wfc/images/samples/Summer/cliffcorner 1.png differ diff --git a/wfc/images/samples/Summer/cliffcorner 2.png b/wfc/images/samples/Summer/cliffcorner 2.png new file mode 100644 index 0000000..41f4f5a Binary files /dev/null and b/wfc/images/samples/Summer/cliffcorner 2.png differ diff --git a/wfc/images/samples/Summer/cliffcorner 3.png b/wfc/images/samples/Summer/cliffcorner 3.png new file mode 100644 index 0000000..14e3035 Binary files /dev/null and b/wfc/images/samples/Summer/cliffcorner 3.png differ diff --git a/wfc/images/samples/Summer/cliffturn 0.png b/wfc/images/samples/Summer/cliffturn 0.png new file mode 100644 index 0000000..1dadb94 Binary files /dev/null and b/wfc/images/samples/Summer/cliffturn 0.png differ diff --git a/wfc/images/samples/Summer/cliffturn 1.png b/wfc/images/samples/Summer/cliffturn 1.png new file mode 100644 index 0000000..f9e9ad2 Binary files /dev/null and b/wfc/images/samples/Summer/cliffturn 1.png differ diff --git a/wfc/images/samples/Summer/cliffturn 2.png b/wfc/images/samples/Summer/cliffturn 2.png new file mode 100644 index 0000000..d37cefb Binary files /dev/null and b/wfc/images/samples/Summer/cliffturn 2.png differ diff --git a/wfc/images/samples/Summer/cliffturn 3.png b/wfc/images/samples/Summer/cliffturn 3.png new file mode 100644 index 0000000..ba08391 Binary files /dev/null and b/wfc/images/samples/Summer/cliffturn 3.png differ diff --git a/wfc/images/samples/Summer/data.xml b/wfc/images/samples/Summer/data.xml new file mode 100644 index 0000000..f9a9b73 --- /dev/null +++ b/wfc/images/samples/Summer/data.xml @@ -0,0 +1,138 @@ +<<<<<<< HEAD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94 + \ No newline at end of file diff --git a/wfc/images/samples/Summer/grass 0.png b/wfc/images/samples/Summer/grass 0.png new file mode 100644 index 0000000..d8cbedb Binary files /dev/null and b/wfc/images/samples/Summer/grass 0.png differ diff --git a/wfc/images/samples/Summer/grasscorner 0.png b/wfc/images/samples/Summer/grasscorner 0.png new file mode 100644 index 0000000..94054b1 Binary files /dev/null and b/wfc/images/samples/Summer/grasscorner 0.png differ diff --git a/wfc/images/samples/Summer/grasscorner 1.png b/wfc/images/samples/Summer/grasscorner 1.png new file mode 100644 index 0000000..15e7bd0 Binary files /dev/null and b/wfc/images/samples/Summer/grasscorner 1.png differ diff --git a/wfc/images/samples/Summer/grasscorner 2.png b/wfc/images/samples/Summer/grasscorner 2.png new file mode 100644 index 0000000..f8438d8 Binary files /dev/null and b/wfc/images/samples/Summer/grasscorner 2.png differ diff --git a/wfc/images/samples/Summer/grasscorner 3.png b/wfc/images/samples/Summer/grasscorner 3.png new file mode 100644 index 0000000..7936dd9 Binary files /dev/null and b/wfc/images/samples/Summer/grasscorner 3.png differ diff --git a/wfc/images/samples/Summer/road 0.png b/wfc/images/samples/Summer/road 0.png new file mode 100644 index 0000000..3c5ea96 Binary files /dev/null and b/wfc/images/samples/Summer/road 0.png differ diff --git a/wfc/images/samples/Summer/road 1.png b/wfc/images/samples/Summer/road 1.png new file mode 100644 index 0000000..3116b68 Binary files /dev/null and b/wfc/images/samples/Summer/road 1.png differ diff --git a/wfc/images/samples/Summer/road 2.png b/wfc/images/samples/Summer/road 2.png new file mode 100644 index 0000000..67566c7 Binary files /dev/null and b/wfc/images/samples/Summer/road 2.png differ diff --git a/wfc/images/samples/Summer/road 3.png b/wfc/images/samples/Summer/road 3.png new file mode 100644 index 0000000..f889273 Binary files /dev/null and b/wfc/images/samples/Summer/road 3.png differ diff --git a/wfc/images/samples/Summer/roadturn 0.png b/wfc/images/samples/Summer/roadturn 0.png new file mode 100644 index 0000000..5dd1fb1 Binary files /dev/null and b/wfc/images/samples/Summer/roadturn 0.png differ diff --git a/wfc/images/samples/Summer/roadturn 1.png b/wfc/images/samples/Summer/roadturn 1.png new file mode 100644 index 0000000..5191e19 Binary files /dev/null and b/wfc/images/samples/Summer/roadturn 1.png differ diff --git a/wfc/images/samples/Summer/roadturn 2.png b/wfc/images/samples/Summer/roadturn 2.png new file mode 100644 index 0000000..11b9be7 Binary files /dev/null and b/wfc/images/samples/Summer/roadturn 2.png differ diff --git a/wfc/images/samples/Summer/roadturn 3.png b/wfc/images/samples/Summer/roadturn 3.png new file mode 100644 index 0000000..b80c01b Binary files /dev/null and b/wfc/images/samples/Summer/roadturn 3.png differ diff --git a/wfc/images/samples/Summer/water_a 0.png b/wfc/images/samples/Summer/water_a 0.png new file mode 100644 index 0000000..b1e9eeb Binary files /dev/null and b/wfc/images/samples/Summer/water_a 0.png differ diff --git a/wfc/images/samples/Summer/water_b 0.png b/wfc/images/samples/Summer/water_b 0.png new file mode 100644 index 0000000..1e0ec81 Binary files /dev/null and b/wfc/images/samples/Summer/water_b 0.png differ diff --git a/wfc/images/samples/Summer/water_c 0.png b/wfc/images/samples/Summer/water_c 0.png new file mode 100644 index 0000000..5fd11e0 Binary files /dev/null and b/wfc/images/samples/Summer/water_c 0.png differ diff --git a/wfc/images/samples/Summer/watercorner 0.png b/wfc/images/samples/Summer/watercorner 0.png new file mode 100644 index 0000000..c629e2d Binary files /dev/null and b/wfc/images/samples/Summer/watercorner 0.png differ diff --git a/wfc/images/samples/Summer/watercorner 1.png b/wfc/images/samples/Summer/watercorner 1.png new file mode 100644 index 0000000..c7d5766 Binary files /dev/null and b/wfc/images/samples/Summer/watercorner 1.png differ diff --git a/wfc/images/samples/Summer/watercorner 2.png b/wfc/images/samples/Summer/watercorner 2.png new file mode 100644 index 0000000..20fb34a Binary files /dev/null and b/wfc/images/samples/Summer/watercorner 2.png differ diff --git a/wfc/images/samples/Summer/watercorner 3.png b/wfc/images/samples/Summer/watercorner 3.png new file mode 100644 index 0000000..9e19f70 Binary files /dev/null and b/wfc/images/samples/Summer/watercorner 3.png differ diff --git a/wfc/images/samples/Summer/waterside 0.png b/wfc/images/samples/Summer/waterside 0.png new file mode 100644 index 0000000..608e8bb Binary files /dev/null and b/wfc/images/samples/Summer/waterside 0.png differ diff --git a/wfc/images/samples/Summer/waterside 1.png b/wfc/images/samples/Summer/waterside 1.png new file mode 100644 index 0000000..10bb864 Binary files /dev/null and b/wfc/images/samples/Summer/waterside 1.png differ diff --git a/wfc/images/samples/Summer/waterside 2.png b/wfc/images/samples/Summer/waterside 2.png new file mode 100644 index 0000000..d7a1faf Binary files /dev/null and b/wfc/images/samples/Summer/waterside 2.png differ diff --git a/wfc/images/samples/Summer/waterside 3.png b/wfc/images/samples/Summer/waterside 3.png new file mode 100644 index 0000000..417c24e Binary files /dev/null and b/wfc/images/samples/Summer/waterside 3.png differ diff --git a/wfc/images/samples/Summer/waterturn 0.png b/wfc/images/samples/Summer/waterturn 0.png new file mode 100644 index 0000000..3d34449 Binary files /dev/null and b/wfc/images/samples/Summer/waterturn 0.png differ diff --git a/wfc/images/samples/Summer/waterturn 1.png b/wfc/images/samples/Summer/waterturn 1.png new file mode 100644 index 0000000..1f9319f Binary files /dev/null and b/wfc/images/samples/Summer/waterturn 1.png differ diff --git a/wfc/images/samples/Summer/waterturn 2.png b/wfc/images/samples/Summer/waterturn 2.png new file mode 100644 index 0000000..baf8eae Binary files /dev/null and b/wfc/images/samples/Summer/waterturn 2.png differ diff --git a/wfc/images/samples/Summer/waterturn 3.png b/wfc/images/samples/Summer/waterturn 3.png new file mode 100644 index 0000000..bf26d7c Binary files /dev/null and b/wfc/images/samples/Summer/waterturn 3.png differ diff --git a/wfc/images/samples/Town.png b/wfc/images/samples/Town.png new file mode 100644 index 0000000..925de00 Binary files /dev/null and b/wfc/images/samples/Town.png differ diff --git a/wfc/images/samples/Trick Knot.png b/wfc/images/samples/Trick Knot.png new file mode 100644 index 0000000..eb9c6e6 Binary files /dev/null and b/wfc/images/samples/Trick Knot.png differ diff --git a/wfc/images/samples/Village.png b/wfc/images/samples/Village.png new file mode 100644 index 0000000..b0389c0 Binary files /dev/null and b/wfc/images/samples/Village.png differ diff --git a/wfc/images/samples/Water.png b/wfc/images/samples/Water.png new file mode 100644 index 0000000..9af51fc Binary files /dev/null and b/wfc/images/samples/Water.png differ diff --git a/wfc/images/samples/blackdots.png b/wfc/images/samples/blackdots.png new file mode 100644 index 0000000..6feedb6 Binary files /dev/null and b/wfc/images/samples/blackdots.png differ diff --git a/wfc/images/samples/blackdotsladder.png b/wfc/images/samples/blackdotsladder.png new file mode 100644 index 0000000..b53b376 Binary files /dev/null and b/wfc/images/samples/blackdotsladder.png differ diff --git a/wfc/images/samples/blackdotsred.png b/wfc/images/samples/blackdotsred.png new file mode 100644 index 0000000..6c863fe Binary files /dev/null and b/wfc/images/samples/blackdotsred.png differ diff --git a/wfc/images/samples/blackdotsstripe.png b/wfc/images/samples/blackdotsstripe.png new file mode 100644 index 0000000..663bd44 Binary files /dev/null and b/wfc/images/samples/blackdotsstripe.png differ diff --git a/wfc/images/samples/dash.png b/wfc/images/samples/dash.png new file mode 100644 index 0000000..eaac7c2 Binary files /dev/null and b/wfc/images/samples/dash.png differ diff --git a/wfc/images/samples/extrapolate_1a.png b/wfc/images/samples/extrapolate_1a.png new file mode 100644 index 0000000..bfef84e Binary files /dev/null and b/wfc/images/samples/extrapolate_1a.png differ diff --git a/wfc/images/samples/forest1.png b/wfc/images/samples/forest1.png new file mode 100644 index 0000000..fa29795 Binary files /dev/null and b/wfc/images/samples/forest1.png differ diff --git a/wfc/images/samples/forest3b.png b/wfc/images/samples/forest3b.png new file mode 100644 index 0000000..eea4ee3 Binary files /dev/null and b/wfc/images/samples/forest3b.png differ diff --git a/wfc/images/samples/forest4c.png b/wfc/images/samples/forest4c.png new file mode 100644 index 0000000..41f5edd Binary files /dev/null and b/wfc/images/samples/forest4c.png differ diff --git a/wfc/images/samples/redpath1.png b/wfc/images/samples/redpath1.png new file mode 100644 index 0000000..ff1bf5c Binary files /dev/null and b/wfc/images/samples/redpath1.png differ diff --git a/wfc/images/samples/redpath2.png b/wfc/images/samples/redpath2.png new file mode 100644 index 0000000..32065b0 Binary files /dev/null and b/wfc/images/samples/redpath2.png differ diff --git a/wfc/images/samples/trees1.png b/wfc/images/samples/trees1.png new file mode 100644 index 0000000..6d3cd35 Binary files /dev/null and b/wfc/images/samples/trees1.png differ diff --git a/wfc/images/samples/village2.png b/wfc/images/samples/village2.png new file mode 100644 index 0000000..7b7f2e4 Binary files /dev/null and b/wfc/images/samples/village2.png differ diff --git a/wfc/images/samples/village3.png b/wfc/images/samples/village3.png new file mode 100644 index 0000000..0ea2013 Binary files /dev/null and b/wfc/images/samples/village3.png differ diff --git a/wfc/logs/logs.txt b/wfc/logs/logs.txt new file mode 100644 index 0000000..66f6b63 --- /dev/null +++ b/wfc/logs/logs.txt @@ -0,0 +1 @@ +Log files will be placed in this directory. diff --git a/wfc/pyproject.toml b/wfc/pyproject.toml new file mode 100644 index 0000000..3d4ad49 --- /dev/null +++ b/wfc/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "wfc_python" +version = "2019f" +description = "Implementation of wave function collapse in Python." +authors = [ + "Isaac Karth " +] +license = "MIT" +readme = "README.md" +python = "^3.5" +repository = "https://github.com/ikarth/wfc_python" +keywords = ["sample", "wfc", "wave function collapse"] + +classifiers = [ + "Development Status :: 3 - Alpha", + "Topic :: Utilities", + "License :: OSI Approved :: MIT License", +] + +[build-system] + +requires = [ + "setuptools", +] +build-backend = "setuptools.build_meta" + +[dependencies] +numpy +imageio + +[dev-dependencies] +pytest +sphinx diff --git a/wfc/samples.xml b/wfc/samples.xml new file mode 100644 index 0000000..6e9c8e0 --- /dev/null +++ b/wfc/samples.xml @@ -0,0 +1,3 @@ + + + diff --git a/wfc/samples_cats.xml b/wfc/samples_cats.xml new file mode 100644 index 0000000..1a41e62 --- /dev/null +++ b/wfc/samples_cats.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wfc/samples_original.xml b/wfc/samples_original.xml new file mode 100644 index 0000000..04be934 --- /dev/null +++ b/wfc/samples_original.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wfc/samples_reference.xml b/wfc/samples_reference.xml new file mode 100644 index 0000000..a5e566a --- /dev/null +++ b/wfc/samples_reference.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wfc/samples_reference_continue.xml b/wfc/samples_reference_continue.xml new file mode 100644 index 0000000..e126c90 --- /dev/null +++ b/wfc/samples_reference_continue.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wfc/samples_reference_nohogs.xml b/wfc/samples_reference_nohogs.xml new file mode 100644 index 0000000..f354a4b --- /dev/null +++ b/wfc/samples_reference_nohogs.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wfc/samples_test.xml b/wfc/samples_test.xml new file mode 100644 index 0000000..5c3d16b --- /dev/null +++ b/wfc/samples_test.xml @@ -0,0 +1,3 @@ + + + diff --git a/wfc/samples_test_ground.xml b/wfc/samples_test_ground.xml new file mode 100644 index 0000000..2132117 --- /dev/null +++ b/wfc/samples_test_ground.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/wfc/samples_test_vis.xml b/wfc/samples_test_vis.xml new file mode 100644 index 0000000..91945d8 --- /dev/null +++ b/wfc/samples_test_vis.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/wfc/setup.py b/wfc/setup.py new file mode 100644 index 0000000..bac24a4 --- /dev/null +++ b/wfc/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/wfc/wfc/__init__.py b/wfc/wfc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wfc/wfc/wfc_adjacency.py b/wfc/wfc/wfc_adjacency.py new file mode 100644 index 0000000..caed14a --- /dev/null +++ b/wfc/wfc/wfc_adjacency.py @@ -0,0 +1,48 @@ +"""Convert input data to adjacency information""" +import numpy as np + + +def adjacency_extraction( + pattern_grid, pattern_catalog, direction_offsets, pattern_size=[2, 2] +): + """Takes a pattern grid and returns a list of all of the legal adjacencies found in it.""" + + def is_valid_overlap_xy(adjacency_direction, pattern_1, pattern_2): + """Given a direction and two patterns, find the overlap of the two patterns + and return True if the intersection matches.""" + dimensions = (1, 0) + not_a_number = -1 + + # TODO: can probably speed this up by using the right slices, rather than rolling the whole pattern... + shifted = np.roll( + np.pad( + pattern_catalog[pattern_2], + max(pattern_size), + mode="constant", + constant_values=not_a_number, + ), + adjacency_direction, + dimensions, + ) + compare = shifted[ + pattern_size[0] : pattern_size[0] + pattern_size[0], + pattern_size[1] : pattern_size[1] + pattern_size[1], + ] + + left = max(0, 0, +adjacency_direction[0]) + right = min(pattern_size[0], pattern_size[0] + adjacency_direction[0]) + top = max(0, 0 + adjacency_direction[1]) + bottom = min(pattern_size[1], pattern_size[1] + adjacency_direction[1]) + a = pattern_catalog[pattern_1][top:bottom, left:right] + b = compare[top:bottom, left:right] + res = np.array_equal(a, b) + return res + + pattern_list = list(pattern_catalog.keys()) + legal = [] + for pattern_1 in pattern_list: + for pattern_2 in pattern_list: + for _direction_index, direction in direction_offsets: + if is_valid_overlap_xy(direction, pattern_1, pattern_2): + legal.append((direction, pattern_1, pattern_2)) + return legal diff --git a/wfc/wfc/wfc_control.py b/wfc/wfc/wfc_control.py new file mode 100644 index 0000000..864612a --- /dev/null +++ b/wfc/wfc/wfc_control.py @@ -0,0 +1,451 @@ +from .wfc_tiles import make_tile_catalog +from .wfc_patterns import ( + pattern_grid_to_tiles, + make_pattern_catalog_with_rotations, +) +from .wfc_adjacency import adjacency_extraction +from .wfc_solver import ( + run, + makeWave, + makeAdj, + lexicalLocationHeuristic, + lexicalPatternHeuristic, + makeWeightedPatternHeuristic, + Contradiction, + StopEarly, + makeEntropyLocationHeuristic, + make_global_use_all_patterns, + makeRandomLocationHeuristic, + makeRandomPatternHeuristic, + TimedOut, + simpleLocationHeuristic, + makeSpiralLocationHeuristic, + makeHilbertLocationHeuristic, + makeAntiEntropyLocationHeuristic, + makeRarestPatternHeuristic, +) +from .wfc_visualize import ( + figure_list_of_tiles, + figure_false_color_tile_grid, + figure_pattern_catalog, + render_tiles_to_output, + figure_adjacencies, + make_solver_visualizers, + make_solver_loggers, +) +import imageio +import numpy as np +import time + + +def visualize_tiles(unique_tiles, tile_catalog, tile_grid): + if False: + figure_list_of_tiles(unique_tiles, tile_catalog) + figure_false_color_tile_grid(tile_grid) + + +def visualize_patterns(pattern_catalog, tile_catalog, pattern_weights, pattern_width): + if False: + figure_pattern_catalog( + pattern_catalog, tile_catalog, pattern_weights, pattern_width + ) + + +def make_log_stats(): + log_line = 0 + + def log_stats(stats, filename): + nonlocal log_line + if stats: + log_line += 1 + with open(filename, "a", encoding="utf_8") as logf: + if log_line < 2: + for s in stats.keys(): + print(str(s), end="\t", file=logf) + print("", file=logf) + for s in stats.keys(): + print(str(stats[s]), end="\t", file=logf) + print("", file=logf) + + return log_stats + + +def execute_wfc( + filename, + tile_size=0, + pattern_width=2, + rotations=8, + output_size=[48, 48], + ground=None, + attempt_limit=10, + output_periodic=True, + input_periodic=True, + loc_heuristic="lexical", + choice_heuristic="lexical", + visualize=True, + global_constraint=False, + backtracking=False, + log_filename="log", + logging=True, + global_constraints=None, + log_stats_to_output=None, +): + timecode = f"{time.time()}" + time_begin = time.time() + output_destination = r"./output/" + input_folder = r"./images/samples/" + + rotations -= 1 # change to zero-based + + input_stats = { + "filename": filename, + "tile_size": tile_size, + "pattern_width": pattern_width, + "rotations": rotations, + "output_size": output_size, + "ground": ground, + "attempt_limit": attempt_limit, + "output_periodic": output_periodic, + "input_periodic": input_periodic, + "location heuristic": loc_heuristic, + "choice heuristic": choice_heuristic, + "global constraint": global_constraint, + "backtracking": backtracking, + } + + # Load the image + img = imageio.imread(input_folder + filename + ".png") + img = img[:, :, :3] # TODO: handle alpha channels + + # TODO: generalize this to more than the four cardinal directions + direction_offsets = list(enumerate([(0, -1), (1, 0), (0, 1), (-1, 0)])) + + tile_catalog, tile_grid, _code_list, _unique_tiles = make_tile_catalog(img, tile_size) + ( + pattern_catalog, + pattern_weights, + pattern_list, + pattern_grid, + ) = make_pattern_catalog_with_rotations( + tile_grid, pattern_width, input_is_periodic=input_periodic, rotations=rotations + ) + + print("pattern catalog") + + # visualize_tiles(unique_tiles, tile_catalog, tile_grid) + # visualize_patterns(pattern_catalog, tile_catalog, pattern_weights, pattern_width) + # figure_list_of_tiles(unique_tiles, tile_catalog, output_filename=f"visualization/tilelist_{filename}_{timecode}") + # figure_false_color_tile_grid(tile_grid, output_filename=f"visualization/tile_falsecolor_{filename}_{timecode}") + if visualize: + figure_pattern_catalog( + pattern_catalog, + tile_catalog, + pattern_weights, + pattern_width, + output_filename=f"visualization/pattern_catalog_{filename}_{timecode}", + ) + + print("profiling adjacency relations") + adjacency_relations = None + + if False: + import pprofile + profiler = pprofile.Profile() + with profiler: + adjacency_relations = adjacency_extraction( + pattern_grid, + pattern_catalog, + direction_offsets, + [pattern_width, pattern_width], + ) + profiler.dump_stats(f"logs/profile_adj_{filename}_{timecode}.txt") + else: + adjacency_relations = adjacency_extraction( + pattern_grid, + pattern_catalog, + direction_offsets, + [pattern_width, pattern_width], + ) + + print("adjacency_relations") + + if visualize: + figure_adjacencies( + adjacency_relations, + direction_offsets, + tile_catalog, + pattern_catalog, + pattern_width, + [tile_size, tile_size], + output_filename=f"visualization/adjacency_{filename}_{timecode}_A", + ) + # figure_adjacencies(adjacency_relations, direction_offsets, tile_catalog, pattern_catalog, pattern_width, [tile_size, tile_size], output_filename=f"visualization/adjacency_{filename}_{timecode}_B", render_b_first=True) + + print(f"output size: {output_size}\noutput periodic: {output_periodic}") + number_of_patterns = len(pattern_weights) + print(f"# patterns: {number_of_patterns}") + decode_patterns = dict(enumerate(pattern_list)) + encode_patterns = {x: i for i, x in enumerate(pattern_list)} + _encode_directions = {j: i for i, j in direction_offsets} + + adjacency_list = {} + for i, d in direction_offsets: + adjacency_list[d] = [set() for i in pattern_weights] + # print(adjacency_list) + for i in adjacency_relations: + # print(i) + # print(decode_patterns[i[1]]) + adjacency_list[i[0]][encode_patterns[i[1]]].add(encode_patterns[i[2]]) + + print(f"adjacency: {len(adjacency_list)}") + + time_adjacency = time.time() + + ### Ground ### + + ground_list = [] + if ground != 0: + ground_list = np.vectorize(lambda x: encode_patterns[x])( + pattern_grid.flat[(ground - 1) :] + ) + if len(ground_list) < 1: + ground_list = None + + if not (ground_list is None): + ground_catalog = { + encode_patterns[k]: v + for k, v in pattern_catalog.items() + if encode_patterns[k] in ground_list + } + if visualize: + figure_pattern_catalog( + ground_catalog, + tile_catalog, + pattern_weights, + pattern_width, + output_filename=f"visualization/patterns_ground_{filename}_{timecode}", + ) + + wave = makeWave( + number_of_patterns, output_size[0], output_size[1], ground=ground_list + ) + adjacency_matrix = makeAdj(adjacency_list) + + ### Heuristics ### + + encoded_weights = np.zeros((number_of_patterns), dtype=np.float64) + for w_id, w_val in pattern_weights.items(): + encoded_weights[encode_patterns[w_id]] = w_val + choice_random_weighting = np.random.random(wave.shape[1:]) * 0.1 + + pattern_heuristic = lexicalPatternHeuristic + if choice_heuristic == "rarest": + pattern_heuristic = makeRarestPatternHeuristic(encoded_weights) + if choice_heuristic == "weighted": + pattern_heuristic = makeWeightedPatternHeuristic(encoded_weights) + if choice_heuristic == "random": + pattern_heuristic = makeRandomPatternHeuristic(encoded_weights) + + print(loc_heuristic) + location_heuristic = lexicalLocationHeuristic + if loc_heuristic == "anti-entropy": + location_heuristic = makeAntiEntropyLocationHeuristic(choice_random_weighting) + if loc_heuristic == "entropy": + location_heuristic = makeEntropyLocationHeuristic(choice_random_weighting) + if loc_heuristic == "random": + location_heuristic = makeRandomLocationHeuristic(choice_random_weighting) + if loc_heuristic == "simple": + location_heuristic = simpleLocationHeuristic + if loc_heuristic == "spiral": + location_heuristic = makeSpiralLocationHeuristic(choice_random_weighting) + if loc_heuristic == "hilbert": + location_heuristic = makeHilbertLocationHeuristic(choice_random_weighting) + + ### Visualization ### + + ( + visualize_choice, + visualize_wave, + visualize_backtracking, + visualize_propagate, + visualize_final, + visualize_after, + ) = (None, None, None, None, None, None) + if visualize: + ( + visualize_choice, + visualize_wave, + visualize_backtracking, + visualize_propagate, + visualize_final, + visualize_after, + ) = make_solver_visualizers( + f"{filename}_{timecode}", + wave, + decode_patterns=decode_patterns, + pattern_catalog=pattern_catalog, + tile_catalog=tile_catalog, + tile_size=[tile_size, tile_size], + ) + if logging: + ( + visualize_choice, + visualize_wave, + visualize_backtracking, + visualize_propagate, + visualize_final, + visualize_after, + ) = make_solver_loggers(f"{filename}_{timecode}", input_stats.copy()) + if logging and visualize: + vis = make_solver_visualizers( + f"{filename}_{timecode}", + wave, + decode_patterns=decode_patterns, + pattern_catalog=pattern_catalog, + tile_catalog=tile_catalog, + tile_size=[tile_size, tile_size], + ) + log = make_solver_loggers(f"{filename}_{timecode}", input_stats.copy()) + + def visfunc(idx): + def vf(*args, **kwargs): + if vis[idx]: + vis[idx](*args, **kwargs) + if log[idx]: + return log[idx](*args, **kwargs) + + return vf + + ( + visualize_choice, + visualize_wave, + visualize_backtracking, + visualize_propagate, + visualize_final, + visualize_after, + ) = [visfunc(x) for x in range(len(vis))] + + ### Global Constraints ### + active_global_constraint = lambda wave: True + if global_constraint == "allpatterns": + active_global_constraint = make_global_use_all_patterns() + print(active_global_constraint) + + ### Search Depth Limit + def makeSearchLengthLimit(max_limit): + search_length_counter = 0 + + def searchLengthLimit(wave): + nonlocal search_length_counter + search_length_counter += 1 + return search_length_counter <= max_limit + + return searchLengthLimit + + combined_constraints = [active_global_constraint, makeSearchLengthLimit(1200)] + + def combinedConstraints(wave): + print + return all([fn(wave) for fn in combined_constraints]) + + ### Solving ### + + time_solve_start = None + time_solve_end = None + + solution_tile_grid = None + print("solving...") + attempts = 0 + while attempts < attempt_limit: + attempts += 1 + end_early = False + time_solve_start = time.time() + stats = {} + # profiler = pprofile.Profile() + if True: + # with profiler: + # with PyCallGraph(output=GraphvizOutput(output_file=f"visualization/pycallgraph_{filename}_{timecode}.png")): + try: + solution = run( + wave.copy(), + adjacency_matrix, + locationHeuristic=location_heuristic, + patternHeuristic=pattern_heuristic, + periodic=output_periodic, + backtracking=backtracking, + onChoice=visualize_choice, + onBacktrack=visualize_backtracking, + onObserve=visualize_wave, + onPropagate=visualize_propagate, + onFinal=visualize_final, + checkFeasible=combinedConstraints, + ) + if visualize_after: + stats = visualize_after() + # print(solution) + # print(stats) + solution_as_ids = np.vectorize(lambda x: decode_patterns[x])(solution) + solution_tile_grid = pattern_grid_to_tiles( + solution_as_ids, pattern_catalog + ) + + print("Solution:") + # print(solution_tile_grid) + render_tiles_to_output( + solution_tile_grid, + tile_catalog, + [tile_size, tile_size], + output_destination + filename + "_" + timecode + ".png", + ) + + time_solve_end = time.time() + stats.update({"outcome": "success"}) + except StopEarly: + print("Skipping...") + end_early = True + stats.update({"outcome": "skipped"}) + except TimedOut: + print("Timed Out") + if visualize_after: + stats = visualize_after() + stats.update({"outcome": "timed_out"}) + except Contradiction: + print("Contradiction") + if visualize_after: + stats = visualize_after() + stats.update({"outcome": "contradiction"}) + # profiler.dump_stats(f"logs/profile_{filename}_{timecode}.txt") + + outstats = {} + outstats.update(input_stats) + solve_duration = time.time() - time_solve_start + try: + solve_duration = time_solve_end - time_solve_start + except TypeError: + pass + adjacency_duration = 0 + try: + adjacency_duration = time_solve_start - time_adjacency + except TypeError: + pass + outstats.update( + { + "attempts": attempts, + "time_start": time_begin, + "time_adjacency": time_adjacency, + "adjacency_duration": adjacency_duration, + "time solve start": time_solve_start, + "time solve end": time_solve_end, + "solve duration": solve_duration, + "pattern count": number_of_patterns, + } + ) + outstats.update(stats) + if not log_stats_to_output is None: + log_stats_to_output(outstats, output_destination + log_filename + ".tsv") + if not solution_tile_grid is None: + return solution_tile_grid + if end_early: + return None + + return None diff --git a/wfc/wfc/wfc_patterns.py b/wfc/wfc/wfc_patterns.py new file mode 100644 index 0000000..90dd3ed --- /dev/null +++ b/wfc/wfc/wfc_patterns.py @@ -0,0 +1,179 @@ +"Extract patterns from grids of tiles." +from .wfc_utilities import hash_downto +from collections import Counter +import numpy as np + + +def unique_patterns_2d(agrid, ksize, periodic_input): + assert ksize >= 1 + if periodic_input: + agrid = np.pad( + agrid, + ((0, ksize - 1), (0, ksize - 1), *(((0, 0),) * (len(agrid.shape) - 2))), + mode="wrap", + ) + else: + # TODO: implement non-wrapped image handling + # a = np.pad(a, ((0,k-1),(0,k-1),*(((0,0),)*(len(a.shape)-2))), mode='constant', constant_values=None) + agrid = np.pad( + agrid, + ((0, ksize - 1), (0, ksize - 1), *(((0, 0),) * (len(agrid.shape) - 2))), + mode="wrap", + ) + + patches = np.lib.stride_tricks.as_strided( + agrid, + ( + agrid.shape[0] - ksize + 1, + agrid.shape[1] - ksize + 1, + ksize, + ksize, + *agrid.shape[2:], + ), + agrid.strides[:2] + agrid.strides[:2] + agrid.strides[2:], + writeable=False, + ) + patch_codes = hash_downto(patches, 2) + uc, ui = np.unique(patch_codes, return_index=True) + locs = np.unravel_index(ui, patch_codes.shape) + up = patches[locs[0], locs[1]] + ids = np.vectorize({code: ind for ind, code in enumerate(uc)}.get)(patch_codes) + return ids, up, patch_codes + + +def unique_patterns_brute_force(grid, size, periodic_input): + padded_grid = np.pad( + grid, + ((0, size - 1), (0, size - 1), *(((0, 0),) * (len(grid.shape) - 2))), + mode="wrap", + ) + patches = [] + for x in range(grid.shape[0]): + row_patches = [] + for y in range(grid.shape[1]): + row_patches.append( + np.ndarray.tolist(padded_grid[x : x + size, y : y + size]) + ) + patches.append(row_patches) + patches = np.array(patches) + patch_codes = hash_downto(patches, 2) + uc, ui = np.unique(patch_codes, return_index=True) + locs = np.unravel_index(ui, patch_codes.shape) + up = patches[locs[0], locs[1]] + ids = np.vectorize({c: i for i, c in enumerate(uc)}.get)(patch_codes) + return ids, up + + +def make_pattern_catalog(tile_grid, pattern_width, input_is_periodic=True): + """Returns a pattern catalog (dictionary of pattern hashes to consituent tiles), +an ordered list of pattern weights, and an ordered list of pattern contents.""" + _patterns_in_grid, pattern_contents_list, patch_codes = unique_patterns_2d( + tile_grid, pattern_width, input_is_periodic + ) + dict_of_pattern_contents = {} + for pat_idx in range(pattern_contents_list.shape[0]): + p_hash = hash_downto(pattern_contents_list[pat_idx], 0) + dict_of_pattern_contents.update( + {p_hash.item(): pattern_contents_list[pat_idx]} + ) + pattern_frequency = Counter(hash_downto(pattern_contents_list, 1)) + return ( + dict_of_pattern_contents, + pattern_frequency, + hash_downto(pattern_contents_list, 1), + patch_codes, + ) + + +def identity_grid(grid): + """Do nothing to the grid""" + # return np.array([[7,5,5,5],[5,0,0,0],[5,0,1,0],[5,0,0,0]]) + return grid + + +def reflect_grid(grid): + """Reflect the grid left/right""" + return np.fliplr(grid) + + +def rotate_grid(grid): + """Rotate the grid""" + return np.rot90(grid, axes=(1, 0)) + + +def make_pattern_catalog_with_rotations( + tile_grid, pattern_width, rotations=7, input_is_periodic=True +): + rotated_tile_grid = tile_grid.copy() + merged_dict_of_pattern_contents = {} + merged_pattern_frequency = Counter() + merged_pattern_contents_list = None + merged_patch_codes = None + + def _make_catalog(): + nonlocal rotated_tile_grid, merged_dict_of_pattern_contents, merged_pattern_contents_list, merged_pattern_frequency, merged_patch_codes + ( + dict_of_pattern_contents, + pattern_frequency, + pattern_contents_list, + patch_codes, + ) = make_pattern_catalog(rotated_tile_grid, pattern_width, input_is_periodic) + merged_dict_of_pattern_contents.update(dict_of_pattern_contents) + merged_pattern_frequency.update(pattern_frequency) + if merged_pattern_contents_list is None: + merged_pattern_contents_list = pattern_contents_list.copy() + else: + merged_pattern_contents_list = np.unique( + np.concatenate((merged_pattern_contents_list, pattern_contents_list)) + ) + if merged_patch_codes is None: + merged_patch_codes = patch_codes.copy() + + counter = 0 + grid_ops = [ + identity_grid, + reflect_grid, + rotate_grid, + reflect_grid, + rotate_grid, + reflect_grid, + rotate_grid, + reflect_grid, + ] + while counter <= (rotations): + # print(rotated_tile_grid.shape) + # print(np.array_equiv(reflect_grid(rotated_tile_grid.copy()), rotate_grid(rotated_tile_grid.copy()))) + + # print(counter) + # print(grid_ops[counter].__name__) + rotated_tile_grid = grid_ops[counter](rotated_tile_grid.copy()) + # print(rotated_tile_grid) + # print("---") + _make_catalog() + counter += 1 + + # assert False + return ( + merged_dict_of_pattern_contents, + merged_pattern_frequency, + merged_pattern_contents_list, + merged_patch_codes, + ) + + +def pattern_grid_to_tiles(pattern_grid, pattern_catalog): + anchor_x = 0 + anchor_y = 0 + + def pattern_to_tile(pattern): + # if isinstance(pattern, list): + # ptrns = [] + # for p in pattern: + # print(p) + # ptrns.push(pattern_to_tile(p)) + # print(ptrns) + # assert False + # return ptrns + return pattern_catalog[pattern][anchor_x][anchor_y] + + return np.vectorize(pattern_to_tile)(pattern_grid) diff --git a/wfc/wfc/wfc_solver.py b/wfc/wfc/wfc_solver.py new file mode 100644 index 0000000..2eea139 --- /dev/null +++ b/wfc/wfc/wfc_solver.py @@ -0,0 +1,439 @@ +from scipy import sparse +import numpy +import sys +import math +import itertools + +# By default Python has a very low recursion limit. +# Might still be better to rewrite te recursion as a loop, of course +sys.setrecursionlimit(5500) + + +class Contradiction(Exception): + """Solving could not proceed without backtracking/restarting.""" + + pass + + +class TimedOut(Exception): + """Solve timed out.""" + + pass + + +class StopEarly(Exception): + """Aborting solve early.""" + + pass + + +def makeWave(n, w, h, ground=None): + wave = numpy.ones((n, w, h), dtype=bool) + if ground is not None: + wave[:, :, h - 1] = 0 + for g in ground: + wave[g, :,] = 0 + wave[g, :, h - 1] = 1 + # print(wave) + # for i in range(wave.shape[0]): + # print(wave[i]) + return wave + + +def makeAdj(adjLists): + adjMatrices = {} + # print(adjLists) + num_patterns = len(list(adjLists.values())[0]) + for d in adjLists: + m = numpy.zeros((num_patterns, num_patterns), dtype=bool) + for i, js in enumerate(adjLists[d]): + # print(js) + for j in js: + m[i, j] = 1 + adjMatrices[d] = sparse.csr_matrix(m) + return adjMatrices + + +###################################### +# Location Heuristics + + +def makeRandomLocationHeuristic(preferences): + def randomLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where(unresolved_cell_mask, preferences, numpy.inf) + row, col = numpy.unravel_index(numpy.argmin(cell_weights), cell_weights.shape) + return [row, col] + + return randomLocationHeuristic + + +def makeEntropyLocationHeuristic(preferences): + def entropyLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where( + unresolved_cell_mask, + preferences + numpy.count_nonzero(wave, axis=0), + numpy.inf, + ) + row, col = numpy.unravel_index(numpy.argmin(cell_weights), cell_weights.shape) + return [row, col] + + return entropyLocationHeuristic + + +def makeAntiEntropyLocationHeuristic(preferences): + def antiEntropyLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where( + unresolved_cell_mask, + preferences + numpy.count_nonzero(wave, axis=0), + -numpy.inf, + ) + row, col = numpy.unravel_index(numpy.argmax(cell_weights), cell_weights.shape) + return [row, col] + + return antiEntropyLocationHeuristic + + +def spiral_transforms(): + for N in itertools.count(start=1): + if N % 2 == 0: + yield (0, 1) # right + for _ in range(N): + yield (1, 0) # down + for _ in range(N): + yield (0, -1) # left + else: + yield (0, -1) # left + for _ in range(N): + yield (-1, 0) # up + for _ in range(N): + yield (0, 1) # right + + +def spiral_coords(x, y): + yield x, y + for transform in spiral_transforms(): + x += transform[0] + y += transform[1] + yield x, y + + +def fill_with_curve(arr, curve_gen): + arr_len = numpy.prod(arr.shape) + fill = 0 + for _, coord in enumerate(curve_gen): + # print(fill, idx, coord) + if fill < arr_len: + try: + arr[coord[0], coord[1]] = fill / arr_len + fill += 1 + except IndexError: + pass + else: + break + # print(arr) + return arr + + +def makeSpiralLocationHeuristic(preferences): + # https://stackoverflow.com/a/23707273/5562922 + + spiral_gen = ( + sc for sc in spiral_coords(preferences.shape[0] // 2, preferences.shape[1] // 2) + ) + + cell_order = fill_with_curve(preferences, spiral_gen) + + def spiralLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where(unresolved_cell_mask, cell_order, numpy.inf) + row, col = numpy.unravel_index(numpy.argmin(cell_weights), cell_weights.shape) + return [row, col] + + return spiralLocationHeuristic + + +from hilbertcurve.hilbertcurve import HilbertCurve + + +def makeHilbertLocationHeuristic(preferences): + curve_size = math.ceil(math.sqrt(max(preferences.shape[0], preferences.shape[1]))) + print(curve_size) + curve_size = 4 + h_curve = HilbertCurve(curve_size, 2) + + def h_coords(): + for i in range(100000): + # print(i) + try: + coords = h_curve.coordinates_from_distance(i) + except ValueError: + coords = [0, 0] + # print(coords) + yield coords + + cell_order = fill_with_curve(preferences, h_coords()) + # print(cell_order) + + def hilbertLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where(unresolved_cell_mask, cell_order, numpy.inf) + row, col = numpy.unravel_index(numpy.argmin(cell_weights), cell_weights.shape) + return [row, col] + + return hilbertLocationHeuristic + + +def simpleLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where( + unresolved_cell_mask, numpy.count_nonzero(wave, axis=0), numpy.inf + ) + row, col = numpy.unravel_index(numpy.argmin(cell_weights), cell_weights.shape) + return [row, col] + + +def lexicalLocationHeuristic(wave): + unresolved_cell_mask = numpy.count_nonzero(wave, axis=0) > 1 + cell_weights = numpy.where(unresolved_cell_mask, 1.0, numpy.inf) + row, col = numpy.unravel_index(numpy.argmin(cell_weights), cell_weights.shape) + return [row, col] + + +##################################### +# Pattern Heuristics + + +def lexicalPatternHeuristic(weights, wave): + return numpy.nonzero(weights)[0][0] + + +def makeWeightedPatternHeuristic(weights): + num_of_patterns = len(weights) + + def weightedPatternHeuristic(wave, _): + # TODO: there's maybe a faster, more controlled way to do this sampling... + weighted_wave = weights * wave + weighted_wave /= weighted_wave.sum() + result = numpy.random.choice(num_of_patterns, p=weighted_wave) + return result + + return weightedPatternHeuristic + + +def makeRarestPatternHeuristic(weights): + """Return a function that chooses the rarest (currently least-used) pattern.""" + def weightedPatternHeuristic(wave, total_wave): + print(total_wave.shape) + # [print(e) for e in wave] + wave_sums = numpy.sum(total_wave, (1, 2)) + # print(wave_sums) + selected_pattern = numpy.random.choice( + numpy.where(wave_sums == wave_sums.max())[0] + ) + return selected_pattern + + return weightedPatternHeuristic + + +def makeMostCommonPatternHeuristic(weights): + """Return a function that chooses the most common (currently most-used) pattern.""" + def weightedPatternHeuristic(wave, total_wave): + print(total_wave.shape) + # [print(e) for e in wave] + wave_sums = numpy.sum(total_wave, (1, 2)) + selected_pattern = numpy.random.choice( + numpy.where(wave_sums == wave_sums.min())[0] + ) + return selected_pattern + + return weightedPatternHeuristic + + +def makeRandomPatternHeuristic(weights): + num_of_patterns = len(weights) + + def randomPatternHeuristic(wave, _): + # TODO: there's maybe a faster, more controlled way to do this sampling... + weighted_wave = 1.0 * wave + weighted_wave /= weighted_wave.sum() + result = numpy.random.choice(num_of_patterns, p=weighted_wave) + return result + + return randomPatternHeuristic + + +###################################### +# Global Constraints + + +def make_global_use_all_patterns(): + def global_use_all_patterns(wave): + """Returns true if at least one instance of each pattern is still possible.""" + return numpy.all(numpy.any(wave, axis=(1, 2))) + + return global_use_all_patterns + + +##################################### +# Solver + + +def propagate(wave, adj, periodic=False, onPropagate=None): + last_count = wave.sum() + + while True: + supports = {} + if periodic: + padded = numpy.pad(wave, ((0, 0), (1, 1), (1, 1)), mode="wrap") + else: + padded = numpy.pad( + wave, ((0, 0), (1, 1), (1, 1)), mode="constant", constant_values=True + ) + + for d in adj: + dx, dy = d + shifted = padded[ + :, 1 + dx : 1 + wave.shape[1] + dx, 1 + dy : 1 + wave.shape[2] + dy + ] + # print(f"shifted: {shifted.shape} | adj[d]: {adj[d].shape} | d: {d}") + # raise StopEarly + # supports[d] = numpy.einsum('pwh,pq->qwh', shifted, adj[d]) > 0 + supports[d] = (adj[d] @ shifted.reshape(shifted.shape[0], -1)).reshape( + shifted.shape + ) > 0 + + for d in adj: + wave *= supports[d] + + if wave.sum() == last_count: + break + else: + last_count = wave.sum() + + if onPropagate: + onPropagate(wave) + + if wave.sum() == 0: + raise Contradiction + + +def observe(wave, locationHeuristic, patternHeuristic): + i, j = locationHeuristic(wave) + pattern = patternHeuristic(wave[:, i, j], wave) + return pattern, i, j + + +# def run_loop(wave, adj, locationHeuristic, patternHeuristic, periodic=False, backtracking=False, onBacktrack=None, onChoice=None, checkFeasible=None): +# stack = [] +# while True: +# if checkFeasible: +# if not checkFeasible(wave): +# raise Contradiction +# stack.append(wave.copy()) +# propagate(wave, adj, periodic=periodic) +# try: +# pattern, i, j = observe(wave, locationHeuristic, patternHeuristic) +# if onChoice: +# onChoice(pattern, i, j) +# wave[:, i, j] = False +# wave[pattern, i, j] = True +# propagate(wave, adj, periodic=periodic) +# if wave.sum() > wave.shape[1] * wave.shape[2]: +# pass +# else: +# return numpy.argmax(wave, 0) +# except Contradiction: +# if backtracking: +# if onBacktrack: +# onBacktrack() +# wave = stack.pop() +# wave[pattern, i, j] = False +# else: +# raise + + +def run( + wave, + adj, + locationHeuristic, + patternHeuristic, + periodic=False, + backtracking=False, + onBacktrack=None, + onChoice=None, + onObserve=None, + onPropagate=None, + checkFeasible=None, + onFinal=None, + depth=0, + depth_limit=None, +): + # print("run.") + if checkFeasible: + if not checkFeasible(wave): + raise Contradiction + if depth_limit: + if depth > depth_limit: + raise TimedOut + if depth % 50 == 0: + print(depth) + original = wave.copy() + propagate(wave, adj, periodic=periodic, onPropagate=onPropagate) + try: + pattern, i, j = observe(wave, locationHeuristic, patternHeuristic) + if onChoice: + onChoice(pattern, i, j) + wave[:, i, j] = False + wave[pattern, i, j] = True + if onObserve: + onObserve(wave) + propagate(wave, adj, periodic=periodic, onPropagate=onPropagate) + if wave.sum() > wave.shape[1] * wave.shape[2]: + # return run(wave, adj, locationHeuristic, patternHeuristic, periodic, backtracking, onBacktrack) + return run( + wave, + adj, + locationHeuristic, + patternHeuristic, + periodic=periodic, + backtracking=backtracking, + onBacktrack=onBacktrack, + onChoice=onChoice, + onObserve=onObserve, + onPropagate=onPropagate, + checkFeasible=checkFeasible, + depth=depth + 1, + depth_limit=depth_limit, + ) + else: + if onFinal: + onFinal(wave) + return numpy.argmax(wave, 0) + except Contradiction: + if backtracking: + if onBacktrack: + onBacktrack() + wave = original + wave[pattern, i, j] = False + return run( + wave, + adj, + locationHeuristic, + patternHeuristic, + periodic=periodic, + backtracking=backtracking, + onBacktrack=onBacktrack, + onChoice=onChoice, + onObserve=onObserve, + onPropagate=onPropagate, + checkFeasible=checkFeasible, + depth=depth + 1, + depth_limit=depth_limit, + ) + else: + if onFinal: + onFinal(wave) + raise diff --git a/wfc/wfc/wfc_tiles.py b/wfc/wfc/wfc_tiles.py new file mode 100644 index 0000000..df51a83 --- /dev/null +++ b/wfc/wfc/wfc_tiles.py @@ -0,0 +1,55 @@ +"""Breaks an image into consituant tiles.""" +import numpy as np +from .wfc_utilities import hash_downto + + +def image_to_tiles(img, tile_size): + """ + Takes an images, divides it into tiles, return an array of tiles. + """ + padding_argument = [(0, 0), (0, 0), (0, 0)] + for input_dim in [0, 1]: + padding_argument[input_dim] = ( + 0, + (tile_size - img.shape[input_dim]) % tile_size, + ) + img = np.pad(img, padding_argument, mode="constant") + tiles = img.reshape( + ( + img.shape[0] // tile_size, + tile_size, + img.shape[1] // tile_size, + tile_size, + img.shape[2], + ) + ).swapaxes(1, 2) + return tiles + + +def make_tile_catalog(image_data, tile_size): + """ + Takes an image and tile size and returns the following: + tile_catalog is a dictionary tiles, with the hashed ID as the key + tile_grid is the original image, expressed in terms of hashed tile IDs + code_list is the original image, expressed in terms of hashed tile IDs and reduced to one dimension + unique_tiles is the set of tiles, plus the frequency of occurance + """ + channels = image_data.shape[2] # Number of color channels in the image + tiles = image_to_tiles(image_data, tile_size) + tile_list = np.array(tiles, dtype=np.int64).reshape( + (tiles.shape[0] * tiles.shape[1], tile_size, tile_size, channels) + ) + code_list = np.array(hash_downto(tiles, 2), dtype=np.int64).reshape( + (tiles.shape[0] * tiles.shape[1]) + ) + tile_grid = np.array(hash_downto(tiles, 2), dtype=np.int64) + unique_tiles = np.unique(tile_grid, return_counts=True) + + tile_catalog = {} + for i, j in enumerate(code_list): + tile_catalog[j] = tile_list[i] + return tile_catalog, tile_grid, code_list, unique_tiles + + +def tiles_to_images(tile_grid, tile_catalog): + return diff --git a/wfc/wfc/wfc_utilities.py b/wfc/wfc/wfc_utilities.py new file mode 100644 index 0000000..e2f63b6 --- /dev/null +++ b/wfc/wfc/wfc_utilities.py @@ -0,0 +1,58 @@ +"""Utility data and functions for WFC""" + +import collections +import numpy as np + + +CoordXY = collections.namedtuple("coords_xy", ["x", "y"]) +CoordRC = collections.namedtuple("coords_rc", ["row", "column"]) + + +def hash_downto(a, rank, seed=0): + state = np.random.RandomState(seed) + assert rank < len(a.shape) + # print((np.prod(a.shape[:rank]),-1)) + # print(np.array([np.prod(a.shape[:rank]),-1], dtype=np.int64).dtype) + u = a.reshape( + np.array([np.prod(a.shape[:rank]), -1], dtype=np.int64) + ) # change because lists are by default float64? + # u = a.reshape((np.prod(a.shape[:rank]),-1)) + v = state.randint(1 - (1 << 63), 1 << 63, np.prod(a.shape[rank:]), dtype="int64") + return np.inner(u, v).reshape(a.shape[:rank]).astype("int64") + + +try: + import google.colab + + IN_COLAB = True +except: + IN_COLAB = False + + +def load_visualizer(wfc_ns): + if IN_COLAB: + from google.colab import files + + uploaded = files.upload() + for fn in uploaded.keys(): + print( + 'User uploaded file "{name}" with length {length} bytes'.format( + name=fn, length=len(uploaded[fn]) + ) + ) + else: + import matplotlib + import matplotlib.pylab + from matplotlib.pyplot import figure + from matplotlib.pyplot import subplot + from matplotlib.pyplot import title + from matplotlib.pyplot import matshow + + wfc_ns.img_filename = f"images/{wfc_ns.img_filename}" + return wfc_ns + + +def find_pattern_center(wfc_ns): + # wfc_ns.pattern_center = (math.floor((wfc_ns.pattern_width - 1) / 2), math.floor((wfc_ns.pattern_width - 1) / 2)) + wfc_ns.pattern_center = (0, 0) + return wfc_ns diff --git a/wfc/wfc/wfc_visualize.py b/wfc/wfc/wfc_visualize.py new file mode 100644 index 0000000..ab86e5b --- /dev/null +++ b/wfc/wfc/wfc_visualize.py @@ -0,0 +1,733 @@ +"Visualize the patterns into tiles and so on." + +import math +import pathlib +import itertools +import imageio +import matplotlib +import struct +import matplotlib.pyplot as plt +import numpy as np +from .wfc_patterns import pattern_grid_to_tiles + +## Helper functions +RGB_CHANNELS = 3 + + +def rgb_to_int(rgb_in): + """"Takes RGB triple, returns integer representation.""" + return struct.unpack( + "I", struct.pack("<" + "B" * 4, *(rgb_in + [0] * (4 - len(rgb_in)))) + )[0] + + +def int_to_rgb(val): + """Convert hashed int to RGB values""" + return [x for x in val.to_bytes(RGB_CHANNELS, "little")] + + +WFC_PARTIAL_BLANK = np.nan + + +def tile_to_image(tile, tile_catalog, tile_size, visualize=False): + """ + Takes a single tile and returns the pixel image representation. + """ + new_img = np.zeros((tile_size[0], tile_size[1], 3), dtype=np.int64) + for u in range(tile_size[0]): + for v in range(tile_size[1]): + ## If we want to display a partial pattern, it is helpful to + ## be able to show empty cells. Therefore, in visualize mode, + ## we use -1 as a magic number for a non-existant tile. + pixel = [200, 0, 200] + if (visualize) and ((-1 == tile) or (WFC_PARTIAL_BLANK == tile)): + if 0 == (u + v) % 2: + pixel = [255, 0, 255] + else: + if (visualize) and -2 == tile: + pixel = [0, 255, 255] + else: + pixel = tile_catalog[tile][u, v] + new_img[u, v] = pixel + return new_img + + +def argmax_unique(arr, axis): + """Return a mask so that we can exclude the nonunique maximums, i.e. the nodes that aren't completely resolved""" + arrm = np.argmax(arr, axis) + arrs = np.sum(arr, axis) + nonunique_mask = np.ma.make_mask((arrs == 1) is False) + uni_argmax = np.ma.masked_array(arrm, mask=nonunique_mask, fill_value=-1) + return uni_argmax, nonunique_mask + + +def make_solver_loggers(filename, stats={}): + counter_choices = 0 + counter_wave = 0 + counter_backtracks = 0 + counter_propagate = 0 + + def choice_count(pattern, i, j, wave=None): + nonlocal counter_choices + counter_choices += 1 + + def wave_count(wave): + nonlocal counter_wave + counter_wave += 1 + + def backtrack_count(): + nonlocal counter_backtracks + counter_backtracks += 1 + + def propagate_count(wave): + nonlocal counter_propagate + counter_propagate += 1 + + def final_count(wave): + print( + f"{filename}: choices: {counter_choices}, wave:{counter_wave}, backtracks: {counter_backtracks}, propagations: {counter_propagate}" + ) + stats.update( + { + "choices": counter_choices, + "wave": counter_wave, + "backtracks": counter_backtracks, + "propagations": counter_propagate, + } + ) + return stats + + def report_count(): + stats.update( + { + "choices": counter_choices, + "wave": counter_wave, + "backtracks": counter_backtracks, + "propagations": counter_propagate, + } + ) + return stats + + return ( + choice_count, + wave_count, + backtrack_count, + propagate_count, + final_count, + report_count, + ) + + +def make_solver_visualizers( + filename, + wave, + decode_patterns=None, + pattern_catalog=None, + tile_catalog=None, + tile_size=[1, 1], +): + """Construct visualizers for displaying the intermediate solver status""" + print(wave.shape) + pattern_total_count = wave.shape[0] + resolution_order = np.full( + wave.shape[1:], np.nan + ) # pattern_wave = when was this resolved? + backtracking_order = np.full( + wave.shape[1:], np.nan + ) # on which iternation was this resolved? + pattern_solution = np.full(wave.shape[1:], np.nan) # what is the resolved result? + resolution_method = np.zeros( + wave.shape[1:] + ) # did we set this via observation or propagation? + choice_count = 0 + vis_count = 0 + backtracking_count = 0 + max_choices = math.floor((wave.shape[1] * wave.shape[2]) / 3) + output_individual_visualizations = False + + tile_wave = np.zeros(wave.shape, dtype=np.int64) + for i in range(wave.shape[0]): + local_solution_as_ids = np.full(wave.shape[1:], decode_patterns[i]) + local_solution_tile_grid = pattern_grid_to_tiles( + local_solution_as_ids, pattern_catalog + ) + tile_wave[i] = local_solution_tile_grid + + def choice_vis(pattern, i, j, wave=None): + nonlocal choice_count + nonlocal resolution_order + nonlocal resolution_method + choice_count += 1 + resolution_order[i][j] = choice_count + pattern_solution[i][j] = pattern + resolution_method[i][j] = 2 + if output_individual_visualizations: + figure_solver_data( + f"visualization/{filename}_choice_{choice_count}.png", + "order of resolution", + resolution_order, + 0, + max_choices, + "gist_ncar", + ) + figure_solver_data( + f"visualization/{filename}_solution_{choice_count}.png", + "chosen pattern", + pattern_solution, + 0, + pattern_total_count, + "viridis", + ) + figure_solver_data( + f"visualization/{filename}_resolution_{choice_count}.png", + "resolution method", + resolution_method, + 0, + 2, + "inferno", + ) + if wave: + _assigned_patterns, nonunique_mask = argmax_unique(wave, 0) + resolved_by_propagation = ( + np.ma.mask_or(nonunique_mask, resolution_method != 0) == 0 + ) + resolution_method[resolved_by_propagation] = 1 + resolution_order[resolved_by_propagation] = choice_count + if output_individual_visualizations: + figure_solver_data( + f"visualization/{filename}_wave_{choice_count}.png", + "patterns remaining", + np.count_nonzero(wave > 0, axis=0), + 0, + wave.shape[0], + "plasma", + ) + + def wave_vis(wave): + nonlocal vis_count + nonlocal resolution_method + nonlocal resolution_order + vis_count += 1 + pattern_left_count = np.count_nonzero(wave > 0, axis=0) + # assigned_patterns, nonunique_mask = argmax_unique(wave, 0) + resolved_by_propagation = ( + np.ma.mask_or(pattern_left_count > 1, resolution_method != 0) != 1 + ) + # print(resolved_by_propagation) + resolution_method[resolved_by_propagation] = 1 + resolution_order[resolved_by_propagation] = choice_count + backtracking_order[resolved_by_propagation] = backtracking_count + if output_individual_visualizations: + figure_wave_patterns(filename, pattern_left_count, pattern_total_count) + figure_solver_data( + f"visualization/{filename}_wave_patterns_{choice_count}.png", + "patterns remaining", + pattern_left_count, + 0, + pattern_total_count, + "magma", + ) + if decode_patterns and pattern_catalog and tile_catalog: + solution_as_ids = np.vectorize(lambda x: decode_patterns[x])( + np.argmax(wave, 0) + ) + solution_tile_grid = pattern_grid_to_tiles(solution_as_ids, pattern_catalog) + if output_individual_visualizations: + figure_solver_data( + f"visualization/{filename}_tiles_assigned_{choice_count}.png", + "tiles assigned", + solution_tile_grid, + 0, + pattern_total_count, + "plasma", + ) + img = tile_grid_to_image(solution_tile_grid.T, tile_catalog, tile_size) + + masked_tile_wave = np.ma.MaskedArray( + data=tile_wave, mask=(wave == False), dtype=np.int64 + ) + masked_img = tile_grid_to_average( + np.transpose(masked_tile_wave, (0, 2, 1)), tile_catalog, tile_size + ) + + if output_individual_visualizations: + figure_solver_image( + f"visualization/{filename}_solution_partial_{choice_count}.png", + "solved_tiles", + img.astype(np.uint8), + ) + imageio.imwrite( + f"visualization/{filename}_solution_partial_img_{choice_count}.png", + img.astype(np.uint8), + ) + fig_list = [ + # {"title": "resolved by propagation", "data": resolved_by_propagation.T, "vmin": 0, "vmax": 2, "cmap": "inferno", "datatype":"figure"}, + { + "title": "order of resolution", + "data": resolution_order.T, + "vmin": 0, + "vmax": max_choices / 4, + "cmap": "hsv", + "datatype": "figure", + }, + { + "title": "chosen pattern", + "data": pattern_solution.T, + "vmin": 0, + "vmax": pattern_total_count, + "cmap": "viridis", + "datatype": "figure", + }, + { + "title": "resolution method", + "data": resolution_method.T, + "vmin": 0, + "vmax": 2, + "cmap": "magma", + "datatype": "figure", + }, + { + "title": "patterns remaining", + "data": pattern_left_count.T, + "vmin": 0, + "vmax": pattern_total_count, + "cmap": "viridis", + "datatype": "figure", + }, + { + "title": "tiles assigned", + "data": solution_tile_grid.T, + "vmin": None, + "vmax": None, + "cmap": "prism", + "datatype": "figure", + }, + { + "title": "solved tiles", + "data": masked_img.astype(np.uint8), + "datatype": "image", + }, + ] + figure_unified( + "Solver Readout", + f"visualization/{filename}_readout_{choice_count:03}_{vis_count:03}.png", + fig_list, + ) + + def backtrack_vis(): + nonlocal vis_count + nonlocal pattern_solution + nonlocal backtracking_count + backtracking_count += 1 + vis_count += 1 + pattern_solution = np.full(wave.shape[1:], -1) + + return choice_vis, wave_vis, backtrack_vis, None, wave_vis, None + + +def figure_unified(figure_name_overall, filename, data): + matfig, axs = plt.subplots( + 1, len(data), sharey="row", gridspec_kw={"hspace": 0, "wspace": 0} + ) + + for idx, _data_obj in enumerate(data): + if "image" == data[idx]["datatype"]: + axs[idx].imshow(data[idx]["data"], interpolation="nearest") + else: + axs[idx].matshow( + data[idx]["data"], + vmin=data[idx]["vmin"], + vmax=data[idx]["vmax"], + cmap=data[idx]["cmap"], + ) + axs[idx].get_xaxis().set_visible(False) + axs[idx].get_yaxis().set_visible(False) + axs[idx].label_outer() + + plt.savefig(filename, bbox_inches="tight", pad_inches=0, dpi=600) + plt.close(fig=matfig) + plt.close("all") + + +vis_count = 0 + + +def visualize_solver(wave): + pattern_left_count = np.count_nonzero(wave > 0, axis=0) + pattern_total_count = wave.shape[0] + figure_wave_patterns(pattern_left_count, pattern_total_count) + + +def make_figure_solver_image(plot_title, img): + visfig = plt.figure(figsize=(4, 4), edgecolor="k", frameon=True) + plt.imshow(img, interpolation="nearest") + plt.title(plot_title) + plt.grid(None) + plt.grid(None) + an_ax = plt.gca() + an_ax.get_xaxis().set_visible(False) + an_ax.get_yaxis().set_visible(False) + return visfig + + +def figure_solver_image(filename, plot_title, img): + visfig = make_figure_solver_image(plot_title, img) + plt.savefig(filename, bbox_inches="tight", pad_inches=0) + plt.close(fig=visfig) + plt.close("all") + + +def make_figure_solver_data(plot_title, data, min_count, max_count, cmap_name): + visfig = plt.figure(figsize=(4, 4), edgecolor="k", frameon=True) + plt.title(plot_title) + plt.matshow(data, vmin=min_count, vmax=max_count, cmap=cmap_name) + plt.grid(None) + plt.grid(None) + ax = plt.gca() + ax.get_xaxis().set_visible(False) + ax.get_yaxis().set_visible(False) + return visfig + + +def figure_solver_data(filename, plot_title, data, min_count, max_count, cmap_name): + visfig = make_figure_solver_data(plot_title, data, min_count, max_count, cmap_name) + plt.savefig(filename, bbox_inches="tight", pad_inches=0) + plt.close(fig=visfig) + plt.close("all") + + +def figure_wave_patterns(filename, pattern_left_count, max_count): + global vis_count + vis_count += 1 + visfig = plt.figure(figsize=(4, 4), edgecolor="k", frameon=True) + + plt.title("wave") + plt.matshow(pattern_left_count, vmin=0, vmax=max_count, cmap="plasma") + plt.grid(None) + + plt.grid(None) + plt.savefig(f"{filename}_wave_patterns_{vis_count}.png") + plt.close(fig=visfig) + + +def tile_grid_to_average(tile_grid, tile_catalog, tile_size, color_channels=3): + """ + Takes a masked array of tile grid stacks and transforms it into an image, taking + the average colors of the tiles in tile_catalog. + """ + new_img = np.zeros( + ( + tile_grid.shape[1] * tile_size[0], + tile_grid.shape[2] * tile_size[1], + color_channels, + ), + dtype=np.int64, + ) + for i in range(tile_grid.shape[1]): + for j in range(tile_grid.shape[2]): + tile_stack = tile_grid[:, i, j] + for u in range(tile_size[0]): + for v in range(tile_size[1]): + pixel = [200, 0, 200] + pixel_list = np.array( + [ + tile_catalog[t][u, v] + for t in tile_stack[tile_stack.mask == False] + ], + dtype=np.int64, + ) + pixel = np.mean(pixel_list, axis=0) + # TODO: will need to change if using an image with more than 3 channels + new_img[(i * tile_size[0]) + u, (j * tile_size[1]) + v] = np.resize( + pixel, + new_img[(i * tile_size[0]) + u, (j * tile_size[1]) + v].shape, + ) + return new_img + + +def tile_grid_to_image( + tile_grid, tile_catalog, tile_size, visualize=False, partial=False, color_channels=3 +): + """ + Takes a tile_grid and transforms it into an image, using the information + in tile_catalog. We use tile_size to figure out the size the new image + should be, and visualize for displaying partial tile patterns. + """ + new_img = np.zeros( + ( + tile_grid.shape[0] * tile_size[0], + tile_grid.shape[1] * tile_size[1], + color_channels, + ), + dtype=np.int64, + ) + if partial and (len(tile_grid.shape)) > 2: + # TODO: implement rendering partially completed solution + # Call tile_grid_to_average() instead. + assert False + else: + for i in range(tile_grid.shape[0]): + for j in range(tile_grid.shape[1]): + tile = tile_grid[i, j] + for u in range(tile_size[0]): + for v in range(tile_size[1]): + pixel = [200, 0, 200] + ## If we want to display a partial pattern, it is helpful to + ## be able to show empty cells. Therefore, in visualize mode, + ## we use -1 as a magic number for a non-existant tile. + if visualize and ((-1 == tile) or (-2 == tile)): + if -1 == tile: + if 0 == (i + j) % 2: + pixel = [255, 0, 255] + if -2 == tile: + pixel = [0, 255, 255] + else: + pixel = tile_catalog[tile][u, v] + # TODO: will need to change if using an image with more than 3 channels + new_img[ + (i * tile_size[0]) + u, (j * tile_size[1]) + v + ] = np.resize( + pixel, + new_img[ + (i * tile_size[0]) + u, (j * tile_size[1]) + v + ].shape, + ) + return new_img + + +def figure_list_of_tiles(unique_tiles, tile_catalog, output_filename="list_of_tiles"): + plt.figure(figsize=(4, 4), edgecolor="k", frameon=True) + plt.title("Extracted Tiles") + s = math.ceil(math.sqrt(len(unique_tiles))) + 1 + for i, tcode in enumerate(unique_tiles[0]): + sp = plt.subplot(s, s, i + 1).imshow(tile_catalog[tcode]) + sp.axes.tick_params(labelleft=False, labelbottom=False, length=0) + plt.title(f"{i}\n{tcode}", fontsize=10) + sp.axes.grid(False) + fp = pathlib.Path(output_filename + ".pdf") + plt.savefig(fp, bbox_inches="tight") + plt.close() + + +def figure_false_color_tile_grid(tile_grid, output_filename="./false_color_tiles"): + figure_plot = plt.matshow( + tile_grid, + cmap="gist_ncar", + extent=(0, tile_grid.shape[1], tile_grid.shape[0], 0), + ) + plt.title("False Color Map of Tiles in Input Image") + figure_plot.axes.grid(None) + plt.savefig(output_filename + ".png", bbox_inches="tight") + plt.close() + + +def figure_tile_grid(tile_grid, tile_catalog, tile_size): + tile_grid_to_image(tile_grid, tile_catalog, tile_size) + + +def render_pattern(render_pattern, tile_catalog): + """Turn a pattern into an image""" + rp_iter = np.nditer(render_pattern, flags=["multi_index"]) + output = np.zeros(render_pattern.shape + (3,), dtype=np.uint32) + while not rp_iter.finished: + # Note that this truncates images with more than 3 channels down to just the channels in the output. + # If we want to have alpha channels, we'll need a different way to handle this. + output[rp_iter.multi_index] = np.resize( + tile_catalog[render_pattern[rp_iter.multi_index]], + output[rp_iter.multi_index].shape, + ) + rp_iter.iternext() + return output + + +def figure_pattern_catalog( + pattern_catalog, + tile_catalog, + pattern_weights, + pattern_width, + output_filename="pattern_catalog", +): + s_columns = 24 // min(24, pattern_width) + s_rows = 1 + (int(len(pattern_catalog)) // s_columns) + _fig = plt.figure(figsize=(s_columns, s_rows * 1.5)) + plt.title("Extracted Patterns") + counter = 0 + for i, _tcode in pattern_catalog.items(): + pat_cat = pattern_catalog[i] + ptr = render_pattern(pat_cat, tile_catalog).astype(np.uint8) + sp = plt.subplot(s_rows, s_columns, counter + 1) + spi = sp.imshow(ptr) + spi.axes.xaxis.set_label_text(f"({pattern_weights[i]})") + sp.set_title(f"{counter}\n{i}", fontsize=3) + spi.axes.tick_params( + labelleft=False, labelbottom=False, left=False, bottom=False + ) + spi.axes.grid(False) + counter += 1 + plt.savefig(output_filename + "_patterns.pdf", bbox_inches="tight") + plt.close() + + +def render_tiles_to_output(tile_grid, tile_catalog, tile_size, output_filename): + img = tile_grid_to_image(tile_grid.T, tile_catalog, tile_size) + imageio.imwrite(output_filename, img.astype(np.uint8)) + + +def blit(destination, sprite, upper_left, layer=False, check=False): + """ + Blits one multidimensional array into another numpy array. + """ + lower_right = [ + ((a + b) if ((a + b) < c) else c) + for a, b, c in zip(upper_left, sprite.shape, destination.shape) + ] + if min(lower_right) < 0: + return + + for i_index, i in enumerate(range(upper_left[0], lower_right[0])): + for j_index, j in enumerate(range(upper_left[1], lower_right[1])): + if (i >= 0) and (j >= 0): + if len(destination.shape) > 2: + destination[i, j, layer] = sprite[i_index, j_index] + else: + if check: + if ( + (destination[i, j] == sprite[i_index, j_index]) + or (destination[i, j] == -1) + or {sprite[i_index, j_index] == -1} + ): + destination[i, j] = sprite[i_index, j_index] + else: + print( + "ERROR, mismatch: destination[{i},{j}] = {destination[i, j]}, sprite[{i_index}, {j_index}] = {sprite[i_index, j_index]}" + ) + else: + destination[i, j] = sprite[i_index, j_index] + return destination + + +class InvalidAdjacency(Exception): + """The combination of patterns and offsets results in pattern combinations that don't match.""" + + pass + + +def validate_adjacency( + pattern_a, pattern_b, preview_size, upper_left_of_center, adj_rel +): + preview_adj_a_first = np.full((preview_size, preview_size), -1, dtype=np.int64) + preview_adj_b_first = np.full((preview_size, preview_size), -1, dtype=np.int64) + blit( + preview_adj_b_first, + pattern_b, + ( + upper_left_of_center[1] + adj_rel[0][1], + upper_left_of_center[0] + adj_rel[0][0], + ), + check=True, + ) + blit(preview_adj_b_first, pattern_a, upper_left_of_center, check=True) + + blit(preview_adj_a_first, pattern_a, upper_left_of_center, check=True) + blit( + preview_adj_a_first, + pattern_b, + ( + upper_left_of_center[1] + adj_rel[0][1], + upper_left_of_center[0] + adj_rel[0][0], + ), + check=True, + ) + if not np.array_equiv(preview_adj_a_first, preview_adj_b_first): + print(adj_rel) + print(pattern_a) + print(pattern_b) + print(preview_adj_a_first) + print(preview_adj_b_first) + raise InvalidAdjacency + + +def figure_adjacencies( + adjacency_relations_list, + adjacency_directions, + tile_catalog, + patterns, + pattern_width, + tile_size, + output_filename="adjacency", + render_b_first=False, +): + # try: + adjacency_directions_list = list(dict(adjacency_directions).values()) + _figadj = plt.figure( + figsize=(12, 1 + len(adjacency_relations_list[:64])), edgecolor="b" + ) + plt.title("Adjacencies") + max_offset = max( + [abs(x) for x in list(itertools.chain.from_iterable(adjacency_directions_list))] + ) + + for i, adj_rel in enumerate(adjacency_relations_list[:64]): + preview_size = pattern_width + max_offset * 2 + preview_adj = np.full((preview_size, preview_size), -1, dtype=np.int64) + upper_left_of_center = [max_offset, max_offset] + + pattern_a = patterns[adj_rel[1]] + pattern_b = patterns[adj_rel[2]] + validate_adjacency( + pattern_a, pattern_b, preview_size, upper_left_of_center, adj_rel + ) + if render_b_first: + blit( + preview_adj, + pattern_b, + ( + upper_left_of_center[1] + adj_rel[0][1], + upper_left_of_center[0] + adj_rel[0][0], + ), + check=True, + ) + blit(preview_adj, pattern_a, upper_left_of_center, check=True) + else: + blit(preview_adj, pattern_a, upper_left_of_center, check=True) + blit( + preview_adj, + pattern_b, + ( + upper_left_of_center[1] + adj_rel[0][1], + upper_left_of_center[0] + adj_rel[0][0], + ), + check=True, + ) + + ptr = tile_grid_to_image( + preview_adj, tile_catalog, tile_size, visualize=True + ).astype(np.uint8) + + subp = plt.subplot(math.ceil(len(adjacency_relations_list[:64]) / 4), 4, i + 1) + spi = subp.imshow(ptr) + spi.axes.tick_params( + left=False, bottom=False, labelleft=False, labelbottom=False + ) + plt.title( + f"{i}:\n({adj_rel[1]} +\n{adj_rel[2]})\n by {adj_rel[0]}", fontsize=10 + ) + + indicator_rect = matplotlib.patches.Rectangle( + (upper_left_of_center[1] - 0.51, upper_left_of_center[0] - 0.51), + pattern_width, + pattern_width, + Fill=False, + edgecolor="b", + linewidth=3.0, + linestyle=":", + ) + + spi.axes.add_artist(indicator_rect) + spi.axes.grid(False) + plt.savefig(output_filename + "_adjacency.pdf", bbox_inches="tight") + plt.close() + + +# except ValueError as e: +# print(e) diff --git a/wfc/wfc_run.py b/wfc/wfc_run.py new file mode 100644 index 0000000..e293acb --- /dev/null +++ b/wfc/wfc_run.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- +"""Base code to load commands from xml and run them.""" + +import time +import wfc.wfc_control as wfc_control +import xml.etree.ElementTree as ET +import os + + +def string2bool(strn): + if isinstance(strn, bool): + return strn + return strn.lower() in ["true"] + + +def run_default(run_experiment=False): + log_filename = f"log_{time.time()}" + xdoc = ET.ElementTree(file="samples_reference.xml") + default_allowed_attempts = 10 + default_backtracking = False + log_stats_to_output = wfc_control.make_log_stats() + + for xnode in xdoc.getroot(): + name = xnode.get("name", "NAME") + if "overlapping" == xnode.tag: + # seed = 3262 + tile_size = int(xnode.get("tile_size", 1)) + # 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. + + symmetry = int(xnode.get("symmetry", 8)) + ground = int(xnode.get("ground", 0)) + periodic_input = string2bool( + xnode.get("periodic", False) + ) # Does the input wrap? + periodic_output = string2bool( + xnode.get("periodic", False) + ) # Do we want the output to wrap? + generated_size = (int(xnode.get("width", 48)), int(xnode.get("height", 48))) + 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 + backtracking = string2bool(xnode.get("backtracking", default_backtracking)) + visualize_experiment = False + + run_instructions = [ + { + "loc": "entropy", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + } + ] + # run_instructions = [{"loc": "entropy", "choice": "weighted", "backtracking": True, "global": "allpatterns"}] + if run_experiment: + run_instructions = [ + { + "loc": "lexical", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "random", + "choice": "weighted", + "backtracking": False, + "global": None, + }, + { + "loc": "lexical", + "choice": "random", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "entropy", + "choice": "random", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "random", + "choice": "random", + "backtracking": False, + "global": None, + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": True, + "global": None, + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": True, + "global": None, + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": True, + "global": "allpatterns", + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": True, + "global": "allpatterns", + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": False, + "global": "allpatterns", + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": False, + "global": "allpatterns", + }, + ] + if run_experiment == "heuristic": + run_instructions = [ + { + "loc": "hilbert", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "spiral", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "anti-entropy", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "simple", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + { + "loc": "random", + "choice": "weighted", + "backtracking": backtracking, + "global": None, + }, + ] + if run_experiment == "backtracking": + run_instructions = [ + { + "loc": "entropy", + "choice": "weighted", + "backtracking": True, + "global": "allpatterns", + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": False, + "global": "allpatterns", + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": True, + "global": None, + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": False, + "global": None, + }, + ] + if run_experiment == "backtracking_heuristic": + run_instructions = [ + { + "loc": "lexical", + "choice": "weighted", + "backtracking": True, + "global": "allpatterns", + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": False, + "global": "allpatterns", + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": True, + "global": None, + }, + { + "loc": "lexical", + "choice": "weighted", + "backtracking": False, + "global": None, + }, + { + "loc": "random", + "choice": "weighted", + "backtracking": True, + "global": "allpatterns", + }, + { + "loc": "random", + "choice": "weighted", + "backtracking": False, + "global": "allpatterns", + }, + { + "loc": "random", + "choice": "weighted", + "backtracking": True, + "global": None, + }, + { + "loc": "random", + "choice": "weighted", + "backtracking": False, + "global": None, + }, + ] + if run_experiment == "choices": + run_instructions = [ + { + "loc": "entropy", + "choice": "rarest", + "backtracking": False, + "global": None, + }, + { + "loc": "entropy", + "choice": "weighted", + "backtracking": False, + "global": None, + }, + { + "loc": "entropy", + "choice": "random", + "backtracking": False, + "global": None, + }, + ] + + for experiment in run_instructions: + for x in range(screenshots): + print(f"-: {name} > {x}") + solution = wfc_control.execute_wfc( + name, + tile_size=tile_size, + pattern_width=pattern_width, + rotations=symmetry, + output_size=generated_size, + ground=ground, + attempt_limit=allowed_attempts, + output_periodic=periodic_output, + input_periodic=periodic_input, + loc_heuristic=experiment["loc"], + choice_heuristic=experiment["choice"], + backtracking=experiment["backtracking"], + global_constraint=experiment["global"], + log_filename=log_filename, + log_stats_to_output=log_stats_to_output, + visualize=visualize_experiment, + logging=True, + ) + if solution is None: + print(None) + else: + print(solution) + + # These are included for my colab experiments, remove them if you're not me + os.system( + 'cp -rf "/content/wfc/output/*.tsv" "/content/drive/My Drive/wfc_exper/2"' + ) + os.system( + 'cp -r "/content/wfc/output" "/content/drive/My Drive/wfc_exper/2"' + ) + + +run_default("choice") +run_default("backtracking") +run_default("heuristic") +run_default() +run_default("choices") +run_default("backtracking")