Repository: ikarth/wfc_2019f
Branch: master
Commit: 3a937fed1393
Files: 45
Total size: 180.0 KB
Directory structure:
gitextract_a9o9q6ic/
├── .github/
│ └── workflows/
│ └── python-package.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── doc/
│ ├── conf.py
│ ├── dot/
│ │ ├── chain.dot
│ │ ├── dependency.dot
│ │ └── design.dot
│ └── index.rst
├── images/
│ └── samples/
│ ├── Castle/
│ │ └── data.xml
│ ├── Circles/
│ │ └── data.xml
│ ├── Circuit/
│ │ └── data.xml
│ ├── Knots/
│ │ └── data.xml
│ ├── Rooms/
│ │ └── data.xml
│ └── Summer/
│ └── data.xml
├── pyproject.toml
├── requirements.txt
├── samples.xml
├── samples_cats.xml
├── samples_original.xml
├── samples_reference.xml
├── samples_reference_continue.xml
├── samples_reference_nohogs.xml
├── samples_test.xml
├── samples_test_ground.xml
├── samples_test_vis.xml
├── setup.cfg
├── setup.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_wfc_adjacency.py
│ ├── test_wfc_patterns.py
│ ├── test_wfc_solver.py
│ └── test_wfc_tiles.py
├── wfc/
│ ├── __init__.py
│ ├── py.typed
│ ├── wfc_adjacency.py
│ ├── wfc_control.py
│ ├── wfc_patterns.py
│ ├── wfc_solver.py
│ ├── wfc_tiles.py
│ ├── wfc_utilities.py
│ └── wfc_visualize.py
└── wfc_run.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/python-package.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python package
on:
push:
pull_request:
defaults:
run:
shell: bash
jobs:
mypy:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install Python dependencies
run: pip install mypy -r requirements.txt
- name: Mypy
uses: liskin/gh-problem-matcher-wrap@v1
with:
linters: mypy
run: mypy --show-column-numbers .
pytest:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install Python dependencies
run: pip install -r requirements.txt
- name: Test with pytest
run: pytest
wfc_run:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Build package
run: pip install --editable .
- name: Run all experiments
run: |
python ./wfc_run.py -e simple -s samples.xml
python ./wfc_run.py -e choice -s samples.xml
python ./wfc_run.py -e choices -s samples.xml
python ./wfc_run.py -e heuristic -s samples.xml
python ./wfc_run.py -e backtracking -s samples.xml
python ./wfc_run.py -e backtracking_heuristic -s samples.xml
- name: Package output folder
run: tar -cf wfc-output.tar --format=ustar output/
- uses: actions/upload-artifact@v2
with:
name: wfc-output
path: wfc-output.tar
retention-days: 7
================================================
FILE: .gitignore
================================================
__pycache__
/output/*
/build
logs/
*.egg-info/
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: MANIFEST.in
================================================
include wfc/py.typed
================================================
FILE: README.md
================================================
# 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
## Installing
```
git clone https://github.com/ikarth/wfc_2019f.git
cd wfc_2019f
conda create -n wfc2019 python=3.10
conda activate wfc2019
pip install -r requirements.txt
python wfc_run.py -s samples_reference.xml
```
## Running WFC
If you want direct control over running WFC, call `wfc_control.execute_wfc()`.
The arguments it accepts are:
- `filename=None`: path to the input image file, this is mostly for internal use and should be left as `None`, set `image` instead.
- `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="entropy"`: 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="weighted"`: what choice heuristic to use; `weighted` is the original WFC behavior, other options are `random`, `rarest`, and `lexical`.
- `visualize=False`: write intermediate images to disk? requires `filename`.
- `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=False`: should we write to a log file? requires `filename`.
- `log_stats_to_output=None`
- `image`: an array of pixel data, typically in the shape: (height, width, rgb)
## Test
```
pytest
```
## Documentation
```
python setup.py build_sphinx
```
With linux the documentation can be displayed with:
```
xdg-open build/sphinx/index.html
```
================================================
FILE: doc/conf.py
================================================
# 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 = [] # type: ignore[var-annotated]
# -- 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']
================================================
FILE: doc/dot/chain.dot
================================================
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]
}
================================================
FILE: doc/dot/dependency.dot
================================================
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"
}
================================================
FILE: doc/dot/design.dot
================================================
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]
}
================================================
FILE: doc/index.rst
================================================
Documentation
=============
Module dependencies
-------------------
.. graphviz:: dot/dependency.dot
Design
------
.. graphviz:: dot/design.dot
Chain
-----
.. graphviz:: dot/chain.dot
================================================
FILE: images/samples/Castle/data.xml
================================================
<<<<<<< HEAD
<set size="7">
<tiles>
<tile name="bridge" symmetry="I"/>
<tile name="ground" symmetry="X"/>
<tile name="river" symmetry="I"/>
<tile name="riverturn" symmetry="L"/>
<tile name="road" symmetry="I"/>
<tile name="roadturn" symmetry="L"/>
<tile name="t" symmetry="T"/>
<tile name="tower" symmetry="L"/>
<tile name="wall" symmetry="I"/>
<tile name="wallriver" symmetry="I"/>
<tile name="wallroad" symmetry="I"/>
</tiles>
<neighbors>
<neighbor left="bridge 1" right="river 1"/>
<neighbor left="bridge 1" right="riverturn 1"/>
<neighbor left="bridge" right="road 1"/>
<neighbor left="bridge" right="roadturn 1"/>
<neighbor left="bridge" right="t"/>
<neighbor left="bridge" right="t 3"/>
<neighbor left="bridge" right="wallroad"/>
<neighbor left="ground" right="ground"/>
<neighbor left="ground" right="river"/>
<neighbor left="ground" right="riverturn"/>
<neighbor left="ground" right="road"/>
<neighbor left="ground" right="roadturn"/>
<neighbor left="ground" right="t 1"/>
<neighbor left="ground" right="tower"/>
<neighbor left="ground" right="wall"/>
<neighbor left="river 1" right="river 1"/>
<neighbor left="river 1" right="riverturn 1"/>
<neighbor left="river" right="road"/>
<neighbor left="river" right="roadturn"/>
<neighbor left="river" right="t 1"/>
<neighbor left="river" right="tower"/>
<neighbor left="river" right="wall"/>
<neighbor left="river 1" right="wallriver"/>
<neighbor left="riverturn" right="riverturn 2"/>
<neighbor left="road" right="riverturn"/>
<neighbor left="roadturn 1" right="riverturn"/>
<neighbor left="roadturn 2" right="riverturn"/>
<neighbor left="t 3" right="riverturn"/>
<neighbor left="tower 1" right="riverturn"/>
<neighbor left="tower 2" right="riverturn"/>
<neighbor left="wall" right="riverturn"/>
<neighbor left="riverturn" right="wallriver"/>
<neighbor left="road 1" right="road 1"/>
<neighbor left="roadturn" right="road 1"/>
<neighbor left="road 1" right="t"/>
<neighbor left="road 1" right="t 3"/>
<neighbor left="road" right="tower"/>
<neighbor left="road" right="wall"/>
<neighbor left="road 1" right="wallroad"/>
<neighbor left="roadturn" right="roadturn 2"/>
<neighbor left="roadturn" right="t"/>
<neighbor left="roadturn 1" right="tower"/>
<neighbor left="roadturn 2" right="tower"/>
<neighbor left="roadturn 1" right="wall"/>
<neighbor left="roadturn" right="wallroad"/>
<neighbor left="t" right="t 2"/>
<neighbor left="t 3" right="tower"/>
<neighbor left="t 3" right="wall"/>
<neighbor left="t" right="wallroad"/>
<neighbor left="t 1" right="wallroad"/>
<neighbor left="tower" right="wall 1"/>
<neighbor left="tower" right="wallriver 1"/>
<neighbor left="tower" right="wallroad 1"/>
<neighbor left="wall 1" right="wall 1"/>
<neighbor left="wall 1" right="wallriver 1"/>
<neighbor left="wall 1" right="wallroad 1"/>
<neighbor left="wallriver 1" right="wallroad 1"/>
</neighbors>
=======
<set size="7">
<tiles>
<tile name="bridge" symmetry="I"/>
<tile name="ground" symmetry="X"/>
<tile name="river" symmetry="I"/>
<tile name="riverturn" symmetry="L"/>
<tile name="road" symmetry="I"/>
<tile name="roadturn" symmetry="L"/>
<tile name="t" symmetry="T"/>
<tile name="tower" symmetry="L"/>
<tile name="wall" symmetry="I"/>
<tile name="wallriver" symmetry="I"/>
<tile name="wallroad" symmetry="I"/>
</tiles>
<neighbors>
<neighbor left="bridge 1" right="river 1"/>
<neighbor left="bridge 1" right="riverturn 1"/>
<neighbor left="bridge" right="road 1"/>
<neighbor left="bridge" right="roadturn 1"/>
<neighbor left="bridge" right="t"/>
<neighbor left="bridge" right="t 3"/>
<neighbor left="bridge" right="wallroad"/>
<neighbor left="ground" right="ground"/>
<neighbor left="ground" right="river"/>
<neighbor left="ground" right="riverturn"/>
<neighbor left="ground" right="road"/>
<neighbor left="ground" right="roadturn"/>
<neighbor left="ground" right="t 1"/>
<neighbor left="ground" right="tower"/>
<neighbor left="ground" right="wall"/>
<neighbor left="river 1" right="river 1"/>
<neighbor left="river 1" right="riverturn 1"/>
<neighbor left="river" right="road"/>
<neighbor left="river" right="roadturn"/>
<neighbor left="river" right="t 1"/>
<neighbor left="river" right="tower"/>
<neighbor left="river" right="wall"/>
<neighbor left="river 1" right="wallriver"/>
<neighbor left="riverturn" right="riverturn 2"/>
<neighbor left="road" right="riverturn"/>
<neighbor left="roadturn 1" right="riverturn"/>
<neighbor left="roadturn 2" right="riverturn"/>
<neighbor left="t 3" right="riverturn"/>
<neighbor left="tower 1" right="riverturn"/>
<neighbor left="tower 2" right="riverturn"/>
<neighbor left="wall" right="riverturn"/>
<neighbor left="riverturn" right="wallriver"/>
<neighbor left="road 1" right="road 1"/>
<neighbor left="roadturn" right="road 1"/>
<neighbor left="road 1" right="t"/>
<neighbor left="road 1" right="t 3"/>
<neighbor left="road" right="tower"/>
<neighbor left="road" right="wall"/>
<neighbor left="road 1" right="wallroad"/>
<neighbor left="roadturn" right="roadturn 2"/>
<neighbor left="roadturn" right="t"/>
<neighbor left="roadturn 1" right="tower"/>
<neighbor left="roadturn 2" right="tower"/>
<neighbor left="roadturn 1" right="wall"/>
<neighbor left="roadturn" right="wallroad"/>
<neighbor left="t" right="t 2"/>
<neighbor left="t 3" right="tower"/>
<neighbor left="t 3" right="wall"/>
<neighbor left="t" right="wallroad"/>
<neighbor left="t 1" right="wallroad"/>
<neighbor left="tower" right="wall 1"/>
<neighbor left="tower" right="wallriver 1"/>
<neighbor left="tower" right="wallroad 1"/>
<neighbor left="wall 1" right="wall 1"/>
<neighbor left="wall 1" right="wallriver 1"/>
<neighbor left="wall 1" right="wallroad 1"/>
<neighbor left="wallriver 1" right="wallroad 1"/>
</neighbors>
>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94
</set>
================================================
FILE: images/samples/Circles/data.xml
================================================
<<<<<<< HEAD
<set size="32">
<tiles>
<tile name="b_half" symmetry="T"/>
<tile name="b_i" symmetry="I"/>
<tile name="b_quarter" symmetry="L"/>
<tile name="w_half" symmetry="T"/>
<tile name="w_i" symmetry="I"/>
<tile name="w_quarter" symmetry="L"/>
<tile name="b" symmetry="X"/>
<tile name="w" symmetry="X"/>
</tiles>
<neighbors>
<neighbor left="b_half" right="b_half"/>
<neighbor left="b_half 1" right="b_half 3"/>
<neighbor left="b_half 3" right="b_half 1"/>
<neighbor left="b_half" right="b_half 3"/>
<neighbor left="b_half" right="b_half 2"/>
<neighbor left="b_half" right="b_i"/>
<neighbor left="b_half 3" right="b_i 3"/>
<neighbor left="b_half 1" right="b_i"/>
<neighbor left="b_half" right="b_quarter"/>
<neighbor left="b_half 1" right="b_quarter"/>
<neighbor left="b_half 2" right="b_quarter"/>
<neighbor left="b_half 3" right="b_quarter 1"/>
<neighbor left="b_i" right="b_i"/>
<neighbor left="b_i 1" right="b_i 1"/>
<neighbor left="b_i" right="b_quarter"/>
<neighbor left="b_i 1" right="b_quarter 1"/>
<neighbor left="b_quarter" right="b_quarter 1"/>
<neighbor left="b_quarter 1" right="b_quarter"/>
<neighbor left="b_quarter 2" right="b_quarter"/>
<neighbor left="b_quarter" right="b_quarter 2"/>
<neighbor left="b_half 1" right="w_half 1"/>
<neighbor left="b_half" right="w_half 1"/>
<neighbor left="b_half 3" right="w_half"/>
<neighbor left="b_half 3" right="w_half 3"/>
<neighbor left="b_half" right="w_i 1"/>
<neighbor left="b_half 1" right="w_i 1"/>
<neighbor left="b_half 3" right="w_i"/>
<neighbor left="b_half" right="w_quarter 1"/>
<neighbor left="b_half" right="w_quarter 2"/>
<neighbor left="b_half 1" right="w_quarter 1"/>
<neighbor left="b_half 3" right="w_quarter"/>
<neighbor left="b_i" right="w_half 1"/>
<neighbor left="b_i 1" right="w_half"/>
<neighbor left="b_i 1" right="w_half 3"/>
<neighbor left="b_i" right="w_i 1"/>
<neighbor left="b_i 1" right="w_i"/>
<neighbor left="b_i" right="w_quarter 1"/>
<neighbor left="b_i 1" right="w_quarter"/>
<neighbor left="b_quarter" right="w_half"/>
<neighbor left="b_quarter" right="w_half 3"/>
<neighbor left="b_quarter" right="w_half 2"/>
<neighbor left="b_quarter 1" right="w_half 1"/>
<neighbor left="b_quarter" right="w_i"/>
<neighbor left="b_quarter 1" right="w_i 1"/>
<neighbor left="b_quarter" right="w_quarter"/>
<neighbor left="b_quarter" right="w_quarter 3"/>
<neighbor left="b_quarter 1" right="w_quarter 1"/>
<neighbor left="b_quarter 1" right="w_quarter 2"/>
<neighbor left="w_half" right="w_half"/>
<neighbor left="w_half 1" right="w_half 3"/>
<neighbor left="w_half 3" right="w_half 1"/>
<neighbor left="w_half" right="w_half 3"/>
<neighbor left="w_half" right="w_half 2"/>
<neighbor left="w_half" right="w_i"/>
<neighbor left="w_half 3" right="w_i 3"/>
<neighbor left="w_half 1" right="w_i"/>
<neighbor left="w_half" right="w_quarter"/>
<neighbor left="w_half 1" right="w_quarter"/>
<neighbor left="w_half 2" right="w_quarter"/>
<neighbor left="w_half 3" right="w_quarter 1"/>
<neighbor left="w_i" right="w_i"/>
<neighbor left="w_i 1" right="w_i 1"/>
<neighbor left="w_i" right="w_quarter"/>
<neighbor left="w_i 1" right="w_quarter 1"/>
<neighbor left="w_quarter" right="w_quarter 1"/>
<neighbor left="w_quarter 1" right="w_quarter"/>
<neighbor left="w_quarter 2" right="w_quarter"/>
<neighbor left="w_quarter" right="w_quarter 2"/>
<neighbor left="b" right="b"/>
<neighbor left="b" right="b_half 1"/>
<neighbor left="b" right="b_i 1"/>
<neighbor left="b" right="b_quarter 1"/>
<neighbor left="b" right="w_half"/>
<neighbor left="b" right="w_half 3"/>
<neighbor left="b" right="w_i"/>
<neighbor left="b" right="w_quarter"/>
<neighbor left="w" right="w"/>
<neighbor left="w" right="w_half 1"/>
<neighbor left="w" right="w_i 1"/>
<neighbor left="w" right="w_quarter 1"/>
<neighbor left="w" right="b_half"/>
<neighbor left="w" right="b_half 3"/>
<neighbor left="w" right="b_i"/>
<neighbor left="w" right="b_quarter"/>
</neighbors>
<subsets>
<subset name="Large Circles">
<tile name="b_quarter"/>
<tile name="w_quarter"/>
</subset>
<subset name="Large Circles and Solid">
<tile name="b_quarter"/>
<tile name="w_quarter"/>
<tile name="b"/>
<tile name="w"/>
</subset>
<subset name="No Solid">
<tile name="b_half" symmetry="T"/>
<tile name="b_i" symmetry="I"/>
<tile name="b_quarter" symmetry="L"/>
<tile name="w_half" symmetry="T"/>
<tile name="w_i" symmetry="I"/>
<tile name="w_quarter" symmetry="L"/>
</subset>
</subsets>
=======
<set size="32">
<tiles>
<tile name="b_half" symmetry="T"/>
<tile name="b_i" symmetry="I"/>
<tile name="b_quarter" symmetry="L"/>
<tile name="w_half" symmetry="T"/>
<tile name="w_i" symmetry="I"/>
<tile name="w_quarter" symmetry="L"/>
<tile name="b" symmetry="X"/>
<tile name="w" symmetry="X"/>
</tiles>
<neighbors>
<neighbor left="b_half" right="b_half"/>
<neighbor left="b_half 1" right="b_half 3"/>
<neighbor left="b_half 3" right="b_half 1"/>
<neighbor left="b_half" right="b_half 3"/>
<neighbor left="b_half" right="b_half 2"/>
<neighbor left="b_half" right="b_i"/>
<neighbor left="b_half 3" right="b_i 3"/>
<neighbor left="b_half 1" right="b_i"/>
<neighbor left="b_half" right="b_quarter"/>
<neighbor left="b_half 1" right="b_quarter"/>
<neighbor left="b_half 2" right="b_quarter"/>
<neighbor left="b_half 3" right="b_quarter 1"/>
<neighbor left="b_i" right="b_i"/>
<neighbor left="b_i 1" right="b_i 1"/>
<neighbor left="b_i" right="b_quarter"/>
<neighbor left="b_i 1" right="b_quarter 1"/>
<neighbor left="b_quarter" right="b_quarter 1"/>
<neighbor left="b_quarter 1" right="b_quarter"/>
<neighbor left="b_quarter 2" right="b_quarter"/>
<neighbor left="b_quarter" right="b_quarter 2"/>
<neighbor left="b_half 1" right="w_half 1"/>
<neighbor left="b_half" right="w_half 1"/>
<neighbor left="b_half 3" right="w_half"/>
<neighbor left="b_half 3" right="w_half 3"/>
<neighbor left="b_half" right="w_i 1"/>
<neighbor left="b_half 1" right="w_i 1"/>
<neighbor left="b_half 3" right="w_i"/>
<neighbor left="b_half" right="w_quarter 1"/>
<neighbor left="b_half" right="w_quarter 2"/>
<neighbor left="b_half 1" right="w_quarter 1"/>
<neighbor left="b_half 3" right="w_quarter"/>
<neighbor left="b_i" right="w_half 1"/>
<neighbor left="b_i 1" right="w_half"/>
<neighbor left="b_i 1" right="w_half 3"/>
<neighbor left="b_i" right="w_i 1"/>
<neighbor left="b_i 1" right="w_i"/>
<neighbor left="b_i" right="w_quarter 1"/>
<neighbor left="b_i 1" right="w_quarter"/>
<neighbor left="b_quarter" right="w_half"/>
<neighbor left="b_quarter" right="w_half 3"/>
<neighbor left="b_quarter" right="w_half 2"/>
<neighbor left="b_quarter 1" right="w_half 1"/>
<neighbor left="b_quarter" right="w_i"/>
<neighbor left="b_quarter 1" right="w_i 1"/>
<neighbor left="b_quarter" right="w_quarter"/>
<neighbor left="b_quarter" right="w_quarter 3"/>
<neighbor left="b_quarter 1" right="w_quarter 1"/>
<neighbor left="b_quarter 1" right="w_quarter 2"/>
<neighbor left="w_half" right="w_half"/>
<neighbor left="w_half 1" right="w_half 3"/>
<neighbor left="w_half 3" right="w_half 1"/>
<neighbor left="w_half" right="w_half 3"/>
<neighbor left="w_half" right="w_half 2"/>
<neighbor left="w_half" right="w_i"/>
<neighbor left="w_half 3" right="w_i 3"/>
<neighbor left="w_half 1" right="w_i"/>
<neighbor left="w_half" right="w_quarter"/>
<neighbor left="w_half 1" right="w_quarter"/>
<neighbor left="w_half 2" right="w_quarter"/>
<neighbor left="w_half 3" right="w_quarter 1"/>
<neighbor left="w_i" right="w_i"/>
<neighbor left="w_i 1" right="w_i 1"/>
<neighbor left="w_i" right="w_quarter"/>
<neighbor left="w_i 1" right="w_quarter 1"/>
<neighbor left="w_quarter" right="w_quarter 1"/>
<neighbor left="w_quarter 1" right="w_quarter"/>
<neighbor left="w_quarter 2" right="w_quarter"/>
<neighbor left="w_quarter" right="w_quarter 2"/>
<neighbor left="b" right="b"/>
<neighbor left="b" right="b_half 1"/>
<neighbor left="b" right="b_i 1"/>
<neighbor left="b" right="b_quarter 1"/>
<neighbor left="b" right="w_half"/>
<neighbor left="b" right="w_half 3"/>
<neighbor left="b" right="w_i"/>
<neighbor left="b" right="w_quarter"/>
<neighbor left="w" right="w"/>
<neighbor left="w" right="w_half 1"/>
<neighbor left="w" right="w_i 1"/>
<neighbor left="w" right="w_quarter 1"/>
<neighbor left="w" right="b_half"/>
<neighbor left="w" right="b_half 3"/>
<neighbor left="w" right="b_i"/>
<neighbor left="w" right="b_quarter"/>
</neighbors>
<subsets>
<subset name="Large Circles">
<tile name="b_quarter"/>
<tile name="w_quarter"/>
</subset>
<subset name="Large Circles and Solid">
<tile name="b_quarter"/>
<tile name="w_quarter"/>
<tile name="b"/>
<tile name="w"/>
</subset>
<subset name="No Solid">
<tile name="b_half" symmetry="T"/>
<tile name="b_i" symmetry="I"/>
<tile name="b_quarter" symmetry="L"/>
<tile name="w_half" symmetry="T"/>
<tile name="w_i" symmetry="I"/>
<tile name="w_quarter" symmetry="L"/>
</subset>
</subsets>
>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94
</set>
================================================
FILE: images/samples/Circuit/data.xml
================================================
<<<<<<< HEAD
<set size="14">
<tiles>
<tile name="bridge" symmetry="I" weight="1.0"/>
<tile name="component" symmetry="X" weight="20.0"/>
<tile name="connection" symmetry="T" weight="10.0"/>
<tile name="corner" symmetry="L" weight="10.0"/>
<tile name="substrate" symmetry="X" weight="2.0"/>
<tile name="t" symmetry="T" weight="0.1"/>
<tile name="track" symmetry="I" weight="2.0"/>
<tile name="transition" symmetry="T" weight="0.4"/>
<tile name="turn" symmetry="L" weight="1.0"/>
<tile name="viad" symmetry="I" weight="0.1"/>
<tile name="vias" symmetry="T" weight="0.3"/>
<tile name="wire" symmetry="I" weight="0.5"/>
<tile name="skew" symmetry="L" weight="2.0"/>
<tile name="dskew" symmetry="\" weight="2.0"/>
</tiles>
<neighbors>
<neighbor left="bridge" right="bridge"/>
<neighbor left="bridge 1" right="bridge 1"/>
<neighbor left="bridge 1" right="connection 1"/>
<neighbor left="bridge 1" right="t 2"/>
<neighbor left="bridge 1" right="t 3"/>
<neighbor left="bridge 1" right="track 1"/>
<neighbor left="bridge" right="transition 1"/>
<neighbor left="bridge 1" right="turn 1"/>
<neighbor left="bridge 1" right="viad"/>
<neighbor left="bridge 1" right="vias 1"/>
<neighbor left="bridge" right="wire"/>
<neighbor left="component" right="component"/>
<neighbor left="connection 1" right="component"/>
<neighbor left="connection" right="connection"/>
<neighbor left="connection" right="corner"/>
<neighbor left="t 1" right="connection 1"/>
<neighbor left="t 2" right="connection 1"/>
<neighbor left="track 1" right="connection 1"/>
<neighbor left="turn" right="connection 1"/>
<neighbor left="substrate" right="corner 1"/>
<neighbor left="t 3" right="corner 1"/>
<neighbor left="track" right="corner 1"/>
<neighbor left="transition 2" right="corner 1"/>
<neighbor left="transition" right="corner 1"/>
<neighbor left="turn 1" right="corner 1"/>
<neighbor left="turn 2" right="corner 1"/>
<neighbor left="viad 1" right="corner 1"/>
<neighbor left="vias 1" right="corner 1"/>
<neighbor left="vias 2" right="corner 1"/>
<neighbor left="vias" right="corner 1"/>
<neighbor left="wire 1" right="corner 1"/>
<neighbor left="substrate" right="substrate"/>
<neighbor left="substrate" right="t 1"/>
<neighbor left="substrate" right="track"/>
<neighbor left="substrate" right="transition 2"/>
<neighbor left="substrate" right="turn"/>
<neighbor left="substrate" right="viad 1"/>
<neighbor left="substrate" right="vias 2"/>
<neighbor left="substrate" right="vias 3"/>
<neighbor left="substrate" right="wire 1"/>
<neighbor left="t 1" right="t 3"/>
<neighbor left="t 3" right="t 1"/>
<neighbor left="t 1" right="t 2"/>
<neighbor left="t 2" right="t 2"/>
<neighbor left="t 2" right="t"/>
<neighbor left="t 3" right="track"/>
<neighbor left="t 1" right="track 1"/>
<neighbor left="t 2" right="track 1"/>
<neighbor left="t 1" right="transition 3"/>
<neighbor left="t 3" right="transition 2"/>
<neighbor left="t 2" right="transition 3"/>
<neighbor left="t 3" right="turn"/>
<neighbor left="t 1" right="turn 1"/>
<neighbor left="t 2" right="turn 1"/>
<neighbor left="t 2" right="turn 2"/>
<neighbor left="t 3" right="viad 1"/>
<neighbor left="t 1" right="viad"/>
<neighbor left="t 2" right="viad"/>
<neighbor left="t 2" right="vias 1"/>
<neighbor left="t 1" right="vias 1"/>
<neighbor left="vias 1" right="t 1"/>
<neighbor left="vias 2" right="t 1"/>
<neighbor left="wire 1" right="t 1"/>
<neighbor left="track" right="track"/>
<neighbor left="track 1" right="track 1"/>
<neighbor left="track 1" right="transition 3"/>
<neighbor left="track" right="transition 2"/>
<neighbor left="track" right="turn"/>
<neighbor left="track 1" right="turn 1"/>
<neighbor left="track" right="viad 1"/>
<neighbor left="track 1" right="viad"/>
<neighbor left="track" right="vias 2"/>
<neighbor left="track" right="vias 3"/>
<neighbor left="track 1" right="vias 1"/>
<neighbor left="track" right="wire 1"/>
<neighbor left="transition 2" right="turn"/>
<neighbor left="transition" right="turn"/>
<neighbor left="transition 1" right="turn 1"/>
<neighbor left="transition 2" right="viad 1"/>
<neighbor left="transition 2" right="vias 2"/>
<neighbor left="transition 2" right="vias 3"/>
<neighbor left="transition 2" right="vias"/>
<neighbor left="wire" right="transition 1"/>
<neighbor left="transition 2" right="wire 1"/>
<neighbor left="turn 1" right="turn"/>
<neighbor left="turn 2" right="turn"/>
<neighbor left="turn" right="turn 1"/>
<neighbor left="turn" right="turn 2"/>
<neighbor left="turn 1" right="viad 1"/>
<neighbor left="turn" right="viad"/>
<neighbor left="turn 1" right="vias 2"/>
<neighbor left="turn 1" right="vias 3"/>
<neighbor left="turn 1" right="vias"/>
<neighbor left="turn" right="vias 1"/>
<neighbor left="turn 1" right="wire 1"/>
<neighbor left="viad 1" right="viad 1"/>
<neighbor left="viad 1" right="vias 2"/>
<neighbor left="viad 1" right="vias 3"/>
<neighbor left="viad 1" right="wire 1"/>
<neighbor left="vias 1" right="wire 1"/>
<neighbor left="vias 2" right="wire 1"/>
<neighbor left="vias 1" right="vias 3"/>
<neighbor left="vias 2" right="vias 2"/>
<neighbor left="vias 2" right="vias"/>
<neighbor left="wire" right="wire"/>
<neighbor left="wire 1" right="wire 1"/>
<neighbor left="bridge 1" right="dskew"/>
<neighbor left="connection 3" right="dskew"/>
<neighbor left="dskew" right="dskew"/>
<neighbor left="skew" right="dskew"/>
<neighbor left="t" right="dskew"/>
<neighbor left="t 2" right="dskew"/>
<neighbor left="t 1" right="dskew"/>
<neighbor left="track 1" right="dskew"/>
<neighbor left="transition 1" right="dskew"/>
<neighbor left="turn 3" right="dskew"/>
<neighbor left="viad" right="dskew"/>
<neighbor left="vias 3" right="dskew"/>
<neighbor left="skew" right="bridge 1"/>
<neighbor left="skew" right="connection 1"/>
<neighbor left="corner" right="skew"/>
<neighbor left="corner 3" right="skew"/>
<neighbor left="skew" right="dskew"/>
<neighbor left="skew" right="skew 2"/>
<neighbor left="skew 1" right="skew"/>
<neighbor left="skew 1" right="skew 3"/>
<neighbor left="substrate" right="skew"/>
<neighbor left="t 3" right="skew"/>
<neighbor left="t" right="skew 2"/>
<neighbor left="t 2" right="skew 2"/>
<neighbor left="t 1" right="skew 2"/>
<neighbor left="track" right="skew"/>
<neighbor left="track 1" right="skew 2"/>
<neighbor left="transition" right="skew"/>
<neighbor left="transition 1" right="skew 2"/>
<neighbor left="turn 1" right="skew"/>
<neighbor left="turn 2" right="skew"/>
<neighbor left="turn 3" right="skew 2"/>
<neighbor left="viad 1" right="skew"/>
<neighbor left="viad" right="skew 2"/>
<neighbor left="vias" right="skew"/>
<neighbor left="vias 1" right="skew"/>
<neighbor left="vias 2" right="skew"/>
<neighbor left="vias 3" right="skew 2"/>
<neighbor left="wire 1" right="skew"/>
</neighbors>
<subsets>
<subset name="Turnless">
<tile name="bridge"/>
<tile name="component"/>
<tile name="connection"/>
<tile name="corner"/>
<tile name="substrate"/>
<tile name="t"/>
<tile name="track"/>
<tile name="transition"/>
<tile name="viad"/>
<tile name="vias"/>
<tile name="wire"/>
<tile name="skew"/>
<tile name="dskew"/>
</subset>
</subsets>
=======
<set size="14">
<tiles>
<tile name="bridge" symmetry="I" weight="1.0"/>
<tile name="component" symmetry="X" weight="20.0"/>
<tile name="connection" symmetry="T" weight="10.0"/>
<tile name="corner" symmetry="L" weight="10.0"/>
<tile name="substrate" symmetry="X" weight="2.0"/>
<tile name="t" symmetry="T" weight="0.1"/>
<tile name="track" symmetry="I" weight="2.0"/>
<tile name="transition" symmetry="T" weight="0.4"/>
<tile name="turn" symmetry="L" weight="1.0"/>
<tile name="viad" symmetry="I" weight="0.1"/>
<tile name="vias" symmetry="T" weight="0.3"/>
<tile name="wire" symmetry="I" weight="0.5"/>
<tile name="skew" symmetry="L" weight="2.0"/>
<tile name="dskew" symmetry="\" weight="2.0"/>
</tiles>
<neighbors>
<neighbor left="bridge" right="bridge"/>
<neighbor left="bridge 1" right="bridge 1"/>
<neighbor left="bridge 1" right="connection 1"/>
<neighbor left="bridge 1" right="t 2"/>
<neighbor left="bridge 1" right="t 3"/>
<neighbor left="bridge 1" right="track 1"/>
<neighbor left="bridge" right="transition 1"/>
<neighbor left="bridge 1" right="turn 1"/>
<neighbor left="bridge 1" right="viad"/>
<neighbor left="bridge 1" right="vias 1"/>
<neighbor left="bridge" right="wire"/>
<neighbor left="component" right="component"/>
<neighbor left="connection 1" right="component"/>
<neighbor left="connection" right="connection"/>
<neighbor left="connection" right="corner"/>
<neighbor left="t 1" right="connection 1"/>
<neighbor left="t 2" right="connection 1"/>
<neighbor left="track 1" right="connection 1"/>
<neighbor left="turn" right="connection 1"/>
<neighbor left="substrate" right="corner 1"/>
<neighbor left="t 3" right="corner 1"/>
<neighbor left="track" right="corner 1"/>
<neighbor left="transition 2" right="corner 1"/>
<neighbor left="transition" right="corner 1"/>
<neighbor left="turn 1" right="corner 1"/>
<neighbor left="turn 2" right="corner 1"/>
<neighbor left="viad 1" right="corner 1"/>
<neighbor left="vias 1" right="corner 1"/>
<neighbor left="vias 2" right="corner 1"/>
<neighbor left="vias" right="corner 1"/>
<neighbor left="wire 1" right="corner 1"/>
<neighbor left="substrate" right="substrate"/>
<neighbor left="substrate" right="t 1"/>
<neighbor left="substrate" right="track"/>
<neighbor left="substrate" right="transition 2"/>
<neighbor left="substrate" right="turn"/>
<neighbor left="substrate" right="viad 1"/>
<neighbor left="substrate" right="vias 2"/>
<neighbor left="substrate" right="vias 3"/>
<neighbor left="substrate" right="wire 1"/>
<neighbor left="t 1" right="t 3"/>
<neighbor left="t 3" right="t 1"/>
<neighbor left="t 1" right="t 2"/>
<neighbor left="t 2" right="t 2"/>
<neighbor left="t 2" right="t"/>
<neighbor left="t 3" right="track"/>
<neighbor left="t 1" right="track 1"/>
<neighbor left="t 2" right="track 1"/>
<neighbor left="t 1" right="transition 3"/>
<neighbor left="t 3" right="transition 2"/>
<neighbor left="t 2" right="transition 3"/>
<neighbor left="t 3" right="turn"/>
<neighbor left="t 1" right="turn 1"/>
<neighbor left="t 2" right="turn 1"/>
<neighbor left="t 2" right="turn 2"/>
<neighbor left="t 3" right="viad 1"/>
<neighbor left="t 1" right="viad"/>
<neighbor left="t 2" right="viad"/>
<neighbor left="t 2" right="vias 1"/>
<neighbor left="t 1" right="vias 1"/>
<neighbor left="vias 1" right="t 1"/>
<neighbor left="vias 2" right="t 1"/>
<neighbor left="wire 1" right="t 1"/>
<neighbor left="track" right="track"/>
<neighbor left="track 1" right="track 1"/>
<neighbor left="track 1" right="transition 3"/>
<neighbor left="track" right="transition 2"/>
<neighbor left="track" right="turn"/>
<neighbor left="track 1" right="turn 1"/>
<neighbor left="track" right="viad 1"/>
<neighbor left="track 1" right="viad"/>
<neighbor left="track" right="vias 2"/>
<neighbor left="track" right="vias 3"/>
<neighbor left="track 1" right="vias 1"/>
<neighbor left="track" right="wire 1"/>
<neighbor left="transition 2" right="turn"/>
<neighbor left="transition" right="turn"/>
<neighbor left="transition 1" right="turn 1"/>
<neighbor left="transition 2" right="viad 1"/>
<neighbor left="transition 2" right="vias 2"/>
<neighbor left="transition 2" right="vias 3"/>
<neighbor left="transition 2" right="vias"/>
<neighbor left="wire" right="transition 1"/>
<neighbor left="transition 2" right="wire 1"/>
<neighbor left="turn 1" right="turn"/>
<neighbor left="turn 2" right="turn"/>
<neighbor left="turn" right="turn 1"/>
<neighbor left="turn" right="turn 2"/>
<neighbor left="turn 1" right="viad 1"/>
<neighbor left="turn" right="viad"/>
<neighbor left="turn 1" right="vias 2"/>
<neighbor left="turn 1" right="vias 3"/>
<neighbor left="turn 1" right="vias"/>
<neighbor left="turn" right="vias 1"/>
<neighbor left="turn 1" right="wire 1"/>
<neighbor left="viad 1" right="viad 1"/>
<neighbor left="viad 1" right="vias 2"/>
<neighbor left="viad 1" right="vias 3"/>
<neighbor left="viad 1" right="wire 1"/>
<neighbor left="vias 1" right="wire 1"/>
<neighbor left="vias 2" right="wire 1"/>
<neighbor left="vias 1" right="vias 3"/>
<neighbor left="vias 2" right="vias 2"/>
<neighbor left="vias 2" right="vias"/>
<neighbor left="wire" right="wire"/>
<neighbor left="wire 1" right="wire 1"/>
<neighbor left="bridge 1" right="dskew"/>
<neighbor left="connection 3" right="dskew"/>
<neighbor left="dskew" right="dskew"/>
<neighbor left="skew" right="dskew"/>
<neighbor left="t" right="dskew"/>
<neighbor left="t 2" right="dskew"/>
<neighbor left="t 1" right="dskew"/>
<neighbor left="track 1" right="dskew"/>
<neighbor left="transition 1" right="dskew"/>
<neighbor left="turn 3" right="dskew"/>
<neighbor left="viad" right="dskew"/>
<neighbor left="vias 3" right="dskew"/>
<neighbor left="skew" right="bridge 1"/>
<neighbor left="skew" right="connection 1"/>
<neighbor left="corner" right="skew"/>
<neighbor left="corner 3" right="skew"/>
<neighbor left="skew" right="dskew"/>
<neighbor left="skew" right="skew 2"/>
<neighbor left="skew 1" right="skew"/>
<neighbor left="skew 1" right="skew 3"/>
<neighbor left="substrate" right="skew"/>
<neighbor left="t 3" right="skew"/>
<neighbor left="t" right="skew 2"/>
<neighbor left="t 2" right="skew 2"/>
<neighbor left="t 1" right="skew 2"/>
<neighbor left="track" right="skew"/>
<neighbor left="track 1" right="skew 2"/>
<neighbor left="transition" right="skew"/>
<neighbor left="transition 1" right="skew 2"/>
<neighbor left="turn 1" right="skew"/>
<neighbor left="turn 2" right="skew"/>
<neighbor left="turn 3" right="skew 2"/>
<neighbor left="viad 1" right="skew"/>
<neighbor left="viad" right="skew 2"/>
<neighbor left="vias" right="skew"/>
<neighbor left="vias 1" right="skew"/>
<neighbor left="vias 2" right="skew"/>
<neighbor left="vias 3" right="skew 2"/>
<neighbor left="wire 1" right="skew"/>
</neighbors>
<subsets>
<subset name="Turnless">
<tile name="bridge"/>
<tile name="component"/>
<tile name="connection"/>
<tile name="corner"/>
<tile name="substrate"/>
<tile name="t"/>
<tile name="track"/>
<tile name="transition"/>
<tile name="viad"/>
<tile name="vias"/>
<tile name="wire"/>
<tile name="skew"/>
<tile name="dskew"/>
</subset>
</subsets>
>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94
</set>
================================================
FILE: images/samples/Knots/data.xml
================================================
<<<<<<< HEAD
<set size="10">
<tiles>
<tile name="corner" symmetry="L"/>
<tile name="cross" symmetry="I"/>
<tile name="empty" symmetry="X"/>
<tile name="line" symmetry="I"/>
<tile name="t" symmetry="T"/>
</tiles>
<neighbors>
<neighbor left="corner 1" right="empty"/>
<neighbor left="corner" right="cross"/>
<neighbor left="corner" right="cross 1"/>
<neighbor left="corner" right="line"/>
<neighbor left="corner 1" right="line 1"/>
<neighbor left="corner" right="t 2"/>
<neighbor left="corner" right="t 3"/>
<neighbor left="corner" right="t"/>
<neighbor left="corner 1" right="t 1"/>
<neighbor left="corner 1" right="corner 3"/>
<neighbor left="corner 1" right="corner"/>
<neighbor left="corner" right="corner 1"/>
<neighbor left="corner" right="corner 2"/>
<neighbor left="cross" right="cross"/>
<neighbor left="cross" right="cross 1"/>
<neighbor left="cross 1" right="cross 1"/>
<neighbor left="cross" right="line"/>
<neighbor left="cross 1" right="line"/>
<neighbor left="cross" right="t"/>
<neighbor left="cross" right="t 3"/>
<neighbor left="cross 1" right="t"/>
<neighbor left="cross 1" right="t 3"/>
<neighbor left="empty" right="empty"/>
<neighbor left="empty" right="line 1"/>
<neighbor left="empty" right="t 1"/>
<neighbor left="line" right="line"/>
<neighbor left="line 1" right="line 1"/>
<neighbor left="line" right="t"/>
<neighbor left="line 1" right="t 1"/>
<neighbor left="line" right="t 3"/>
<neighbor left="t 1" right="t 3"/>
<neighbor left="t" right="t"/>
<neighbor left="t 2" right="t"/>
<neighbor left="t 1" right="t"/>
<neighbor left="t 3" right="t 1"/>
</neighbors>
<subsets>
<subset name="Standard">
<tile name="corner"/>
<tile name="cross"/>
<tile name="empty"/>
<tile name="line"/>
</subset>
<subset name="Dense">
<tile name="corner"/>
<tile name="cross"/>
<tile name="line"/>
</subset>
<subset name="Crossless">
<tile name="corner"/>
<tile name="empty"/>
<tile name="line"/>
</subset>
<subset name="TE">
<tile name="t"/>
<tile name="empty"/>
</subset>
<subset name="T">
<tile name="t"/>
</subset>
<subset name="CL">
<tile name="corner"/>
<tile name="line"/>
</subset>
<subset name="CE">
<tile name="corner"/>
<tile name="empty"/>
</subset>
<subset name="C">
<tile name="corner"/>
</subset>
<subset name="Fabric">
<tile name="cross"/>
<tile name="line"/>
</subset>
<subset name="Dense Fabric">
<tile name="cross"/>
</subset>
</subsets>
=======
<set size="10">
<tiles>
<tile name="corner" symmetry="L"/>
<tile name="cross" symmetry="I"/>
<tile name="empty" symmetry="X"/>
<tile name="line" symmetry="I"/>
<tile name="t" symmetry="T"/>
</tiles>
<neighbors>
<neighbor left="corner 1" right="empty"/>
<neighbor left="corner" right="cross"/>
<neighbor left="corner" right="cross 1"/>
<neighbor left="corner" right="line"/>
<neighbor left="corner 1" right="line 1"/>
<neighbor left="corner" right="t 2"/>
<neighbor left="corner" right="t 3"/>
<neighbor left="corner" right="t"/>
<neighbor left="corner 1" right="t 1"/>
<neighbor left="corner 1" right="corner 3"/>
<neighbor left="corner 1" right="corner"/>
<neighbor left="corner" right="corner 1"/>
<neighbor left="corner" right="corner 2"/>
<neighbor left="cross" right="cross"/>
<neighbor left="cross" right="cross 1"/>
<neighbor left="cross 1" right="cross 1"/>
<neighbor left="cross" right="line"/>
<neighbor left="cross 1" right="line"/>
<neighbor left="cross" right="t"/>
<neighbor left="cross" right="t 3"/>
<neighbor left="cross 1" right="t"/>
<neighbor left="cross 1" right="t 3"/>
<neighbor left="empty" right="empty"/>
<neighbor left="empty" right="line 1"/>
<neighbor left="empty" right="t 1"/>
<neighbor left="line" right="line"/>
<neighbor left="line 1" right="line 1"/>
<neighbor left="line" right="t"/>
<neighbor left="line 1" right="t 1"/>
<neighbor left="line" right="t 3"/>
<neighbor left="t 1" right="t 3"/>
<neighbor left="t" right="t"/>
<neighbor left="t 2" right="t"/>
<neighbor left="t 1" right="t"/>
<neighbor left="t 3" right="t 1"/>
</neighbors>
<subsets>
<subset name="Standard">
<tile name="corner"/>
<tile name="cross"/>
<tile name="empty"/>
<tile name="line"/>
</subset>
<subset name="Dense">
<tile name="corner"/>
<tile name="cross"/>
<tile name="line"/>
</subset>
<subset name="Crossless">
<tile name="corner"/>
<tile name="empty"/>
<tile name="line"/>
</subset>
<subset name="TE">
<tile name="t"/>
<tile name="empty"/>
</subset>
<subset name="T">
<tile name="t"/>
</subset>
<subset name="CL">
<tile name="corner"/>
<tile name="line"/>
</subset>
<subset name="CE">
<tile name="corner"/>
<tile name="empty"/>
</subset>
<subset name="C">
<tile name="corner"/>
</subset>
<subset name="Fabric">
<tile name="cross"/>
<tile name="line"/>
</subset>
<subset name="Dense Fabric">
<tile name="cross"/>
</subset>
</subsets>
>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94
</set>
================================================
FILE: images/samples/Rooms/data.xml
================================================
<<<<<<< HEAD
<set size="3">
<tiles>
<tile name="bend" symmetry="L" weight="0.5"/>
<tile name="corner" symmetry="L" weight="0.5"/>
<tile name="corridor" symmetry="I" weight="1.0"/>
<tile name="door" symmetry="T" weight="0.5"/>
<tile name="empty" symmetry="X"/>
<tile name="side" symmetry="T" weight="2.0"/>
<tile name="t" symmetry="T" weight="0.5"/>
<tile name="turn" symmetry="L" weight="0.25"/>
<tile name="wall" symmetry="X"/>
</tiles>
<neighbors>
<neighbor left="corner 1" right="corner"/>
<neighbor left="corner 2" right="corner"/>
<neighbor left="corner" right="door"/>
<neighbor left="corner" right="side 2"/>
<neighbor left="corner 1" right="side 1"/>
<neighbor left="corner 1" right="t 1"/>
<neighbor left="corner 1" right="turn"/>
<neighbor left="corner 2" right="turn"/>
<neighbor left="wall" right="corner"/>
<neighbor left="corridor 1" right="corridor 1"/>
<neighbor left="corridor 1" right="door 3"/>
<neighbor left="corridor" right="side 1"/>
<neighbor left="corridor 1" right="t"/>
<neighbor left="corridor 1" right="t 3"/>
<neighbor left="corridor 1" right="turn 1"/>
<neighbor left="corridor" right="wall"/>
<neighbor left="door 1" right="door 3"/>
<neighbor left="door 3" right="empty"/>
<neighbor left="door" right="side 2"/>
<neighbor left="door 1" right="t"/>
<neighbor left="door 1" right="t 3"/>
<neighbor left="door 1" right="turn 1"/>
<neighbor left="empty" right="empty"/>
<neighbor left="empty" right="side 3"/>
<neighbor left="side" right="side"/>
<neighbor left="side 3" right="side 1"/>
<neighbor left="side 3" right="t 1"/>
<neighbor left="side 3" right="turn"/>
<neighbor left="side 3" right="wall"/>
<neighbor left="t" right="t 2"/>
<neighbor left="t" right="turn 1"/>
<neighbor left="t 3" right="wall"/>
<neighbor left="turn" right="turn 2"/>
<neighbor left="turn 1" right="wall"/>
<neighbor left="wall" right="wall"/>
<neighbor left="bend" right="bend 1"/>
<neighbor left="corner" right="bend 2"/>
<neighbor left="door" right="bend 2"/>
<neighbor left="empty" right="bend"/>
<neighbor left="side" right="bend 1"/>
</neighbors>
=======
<set size="3">
<tiles>
<tile name="bend" symmetry="L" weight="0.5"/>
<tile name="corner" symmetry="L" weight="0.5"/>
<tile name="corridor" symmetry="I" weight="1.0"/>
<tile name="door" symmetry="T" weight="0.5"/>
<tile name="empty" symmetry="X"/>
<tile name="side" symmetry="T" weight="2.0"/>
<tile name="t" symmetry="T" weight="0.5"/>
<tile name="turn" symmetry="L" weight="0.25"/>
<tile name="wall" symmetry="X"/>
</tiles>
<neighbors>
<neighbor left="corner 1" right="corner"/>
<neighbor left="corner 2" right="corner"/>
<neighbor left="corner" right="door"/>
<neighbor left="corner" right="side 2"/>
<neighbor left="corner 1" right="side 1"/>
<neighbor left="corner 1" right="t 1"/>
<neighbor left="corner 1" right="turn"/>
<neighbor left="corner 2" right="turn"/>
<neighbor left="wall" right="corner"/>
<neighbor left="corridor 1" right="corridor 1"/>
<neighbor left="corridor 1" right="door 3"/>
<neighbor left="corridor" right="side 1"/>
<neighbor left="corridor 1" right="t"/>
<neighbor left="corridor 1" right="t 3"/>
<neighbor left="corridor 1" right="turn 1"/>
<neighbor left="corridor" right="wall"/>
<neighbor left="door 1" right="door 3"/>
<neighbor left="door 3" right="empty"/>
<neighbor left="door" right="side 2"/>
<neighbor left="door 1" right="t"/>
<neighbor left="door 1" right="t 3"/>
<neighbor left="door 1" right="turn 1"/>
<neighbor left="empty" right="empty"/>
<neighbor left="empty" right="side 3"/>
<neighbor left="side" right="side"/>
<neighbor left="side 3" right="side 1"/>
<neighbor left="side 3" right="t 1"/>
<neighbor left="side 3" right="turn"/>
<neighbor left="side 3" right="wall"/>
<neighbor left="t" right="t 2"/>
<neighbor left="t" right="turn 1"/>
<neighbor left="t 3" right="wall"/>
<neighbor left="turn" right="turn 2"/>
<neighbor left="turn 1" right="wall"/>
<neighbor left="wall" right="wall"/>
<neighbor left="bend" right="bend 1"/>
<neighbor left="corner" right="bend 2"/>
<neighbor left="door" right="bend 2"/>
<neighbor left="empty" right="bend"/>
<neighbor left="side" right="bend 1"/>
</neighbors>
>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94
</set>
================================================
FILE: images/samples/Summer/data.xml
================================================
<<<<<<< HEAD
<set size="48" unique="True">
<tiles>
<tile name="cliff" symmetry="T"/>
<tile name="cliffcorner" symmetry="L"/>
<tile name="cliffturn" symmetry="L"/>
<tile name="grass" symmetry="X"/>
<tile name="grasscorner" symmetry="L" weight="0.0001"/>
<tile name="road" symmetry="T" weight="0.1"/>
<tile name="roadturn" symmetry="L" weight="0.1"/>
<tile name="water_a" symmetry="X"/>
<tile name="water_b" symmetry="X"/>
<tile name="water_c" symmetry="X"/>
<tile name="watercorner" symmetry="L"/>
<tile name="waterside" symmetry="T"/>
<tile name="waterturn" symmetry="L"/>
</tiles>
<neighbors>
<neighbor left="cliff 0" right="cliff 0"/>
<neighbor left="cliff 2" right="cliffcorner 1"/>
<neighbor left="cliff 2" right="cliffturn 2"/>
<neighbor left="cliff 1" right="grass 0"/>
<neighbor left="grass 0" right="cliff 1"/>
<neighbor left="cliff 1" right="road 3"/>
<neighbor left="road 1" right="cliff 1"/>
<neighbor left="cliff 1" right="roadturn 0"/>
<neighbor left="roadturn 1" right="cliff 1"/>
<neighbor left="cliffcorner 0" right="cliffturn 2"/>
<neighbor left="cliffcorner 1" right="road 3"/>
<neighbor left="cliffcorner 1" right="roadturn 0"/>
<neighbor left="cliffcorner 1" right="roadturn 3"/>
<neighbor left="cliffcorner 1" right="grass 0"/>
<neighbor left="cliffturn 1" right="grass 0"/>
<neighbor left="cliffturn 1" right="road 3"/>
<neighbor left="cliffturn 1" right="roadturn 0"/>
<neighbor left="cliffturn 1" right="roadturn 3"/>
<neighbor left="grass 0" right="grass 0"/>
<neighbor left="grass 0" right="road 3"/>
<neighbor left="grass 0" right="roadturn 0"/>
<neighbor left="grass 0" right="watercorner 0"/>
<neighbor left="grass 0" right="waterside 3"/>
<neighbor left="grasscorner 1" right="grasscorner 0"/>
<neighbor left="grasscorner 1" right="grasscorner 3"/>
<neighbor left="grasscorner 1" right="road 1"/>
<neighbor left="grasscorner 3" right="road 0"/>
<neighbor left="grasscorner 3" right="roadturn 1"/>
<neighbor left="road 3" right="road 1"/>
<neighbor left="road 0" right="road 0"/>
<neighbor left="road 0" right="roadturn 1"/>
<neighbor left="road 1" right="watercorner 0"/>
<neighbor left="road 1" right="waterside 3"/>
<neighbor left="roadturn 1" right="watercorner 0"/>
<neighbor left="roadturn 1" right="watercorner 3"/>
<neighbor left="roadturn 1" right="waterside 3"/>
<neighbor left="water_a 0" right="water_a 0"/>
<neighbor left="water_a 0" right="water_b 0"/>
<neighbor left="water_a 0" right="water_c 0"/>
<neighbor left="water_a 0" right="waterside 1"/>
<neighbor left="water_a 0" right="waterturn 1"/>
<neighbor left="water_b 0" right="waterside 1"/>
<neighbor left="water_b 0" right="waterturn 1"/>
<neighbor left="water_c 0" right="waterside 1"/>
<neighbor left="water_c 0" right="waterturn 1"/>
<neighbor left="watercorner 0" right="waterside 0"/>
<neighbor left="watercorner 0" right="waterturn 0"/>
<neighbor left="waterside 0" right="waterside 0"/>
<neighbor left="waterside 0" right="waterturn 0"/>
</neighbors>
=======
<set size="48" unique="True">
<tiles>
<tile name="cliff" symmetry="T"/>
<tile name="cliffcorner" symmetry="L"/>
<tile name="cliffturn" symmetry="L"/>
<tile name="grass" symmetry="X"/>
<tile name="grasscorner" symmetry="L" weight="0.0001"/>
<tile name="road" symmetry="T" weight="0.1"/>
<tile name="roadturn" symmetry="L" weight="0.1"/>
<tile name="water_a" symmetry="X"/>
<tile name="water_b" symmetry="X"/>
<tile name="water_c" symmetry="X"/>
<tile name="watercorner" symmetry="L"/>
<tile name="waterside" symmetry="T"/>
<tile name="waterturn" symmetry="L"/>
</tiles>
<neighbors>
<neighbor left="cliff 0" right="cliff 0"/>
<neighbor left="cliff 2" right="cliffcorner 1"/>
<neighbor left="cliff 2" right="cliffturn 2"/>
<neighbor left="cliff 1" right="grass 0"/>
<neighbor left="grass 0" right="cliff 1"/>
<neighbor left="cliff 1" right="road 3"/>
<neighbor left="road 1" right="cliff 1"/>
<neighbor left="cliff 1" right="roadturn 0"/>
<neighbor left="roadturn 1" right="cliff 1"/>
<neighbor left="cliffcorner 0" right="cliffturn 2"/>
<neighbor left="cliffcorner 1" right="road 3"/>
<neighbor left="cliffcorner 1" right="roadturn 0"/>
<neighbor left="cliffcorner 1" right="roadturn 3"/>
<neighbor left="cliffcorner 1" right="grass 0"/>
<neighbor left="cliffturn 1" right="grass 0"/>
<neighbor left="cliffturn 1" right="road 3"/>
<neighbor left="cliffturn 1" right="roadturn 0"/>
<neighbor left="cliffturn 1" right="roadturn 3"/>
<neighbor left="grass 0" right="grass 0"/>
<neighbor left="grass 0" right="road 3"/>
<neighbor left="grass 0" right="roadturn 0"/>
<neighbor left="grass 0" right="watercorner 0"/>
<neighbor left="grass 0" right="waterside 3"/>
<neighbor left="grasscorner 1" right="grasscorner 0"/>
<neighbor left="grasscorner 1" right="grasscorner 3"/>
<neighbor left="grasscorner 1" right="road 1"/>
<neighbor left="grasscorner 3" right="road 0"/>
<neighbor left="grasscorner 3" right="roadturn 1"/>
<neighbor left="road 3" right="road 1"/>
<neighbor left="road 0" right="road 0"/>
<neighbor left="road 0" right="roadturn 1"/>
<neighbor left="road 1" right="watercorner 0"/>
<neighbor left="road 1" right="waterside 3"/>
<neighbor left="roadturn 1" right="watercorner 0"/>
<neighbor left="roadturn 1" right="watercorner 3"/>
<neighbor left="roadturn 1" right="waterside 3"/>
<neighbor left="water_a 0" right="water_a 0"/>
<neighbor left="water_a 0" right="water_b 0"/>
<neighbor left="water_a 0" right="water_c 0"/>
<neighbor left="water_a 0" right="waterside 1"/>
<neighbor left="water_a 0" right="waterturn 1"/>
<neighbor left="water_b 0" right="waterside 1"/>
<neighbor left="water_b 0" right="waterturn 1"/>
<neighbor left="water_c 0" right="waterside 1"/>
<neighbor left="water_c 0" right="waterturn 1"/>
<neighbor left="watercorner 0" right="waterside 0"/>
<neighbor left="watercorner 0" right="waterturn 0"/>
<neighbor left="waterside 0" right="waterside 0"/>
<neighbor left="waterside 0" right="waterturn 0"/>
</neighbors>
>>>>>>> 1fe4f1f60ebd57b99ca7148fb003edefa7979d94
</set>
================================================
FILE: pyproject.toml
================================================
[project]
name = "wfc_python"
version = "0.0.0"
description = "Implementation of wave function collapse in Python."
readme = "README.md"
requires-python = ">=3.5"
license = {file = "LICENSE"}
keywords = ["sample", "wfc", "wave function collapse"]
authors = [{name = "Isaac Karth", email = "isaac@isaackarth.com"}]
classifiers = [
"Development Status :: 3 - Alpha",
"Topic :: Utilities",
"License :: OSI Approved :: MIT License",
]
dependencies = [
"hilbertcurve",
"imageio",
"matplotlib",
"numpy",
"scipy"
]
[project.optional-dependencies]
tests = ["pytest"]
docs = ["sphinx"]
[project.urls]
homepage = "https://github.com/ikarth/wfc_python"
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
================================================
FILE: requirements.txt
================================================
numpy
hilbertcurve>=2
imageio
matplotlib
scipy
# Testing
pytest
types-setuptools
# Documentation.
sphinx
================================================
FILE: samples.xml
================================================
<samples>
<overlapping name="Red Maze" N="2" periodic="True" width="8" height="6"/>
</samples>
================================================
FILE: samples_cats.xml
================================================
<samples>
<overlapping name="Cat" N="3" symmetry="2" periodic="True" width="80" height="80"/>
<overlapping name="Cats" N="3" symmetry="2" periodic="True"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="True" ground="-1"/>
<overlapping name="Angular" N="3" periodic="True"/>
<overlapping name="City" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Colored City" N="3" periodic="True"/>
<overlapping name="Dungeon" N="3" periodic="True"/>
<overlapping name="Lake" N="3" periodic="True" width="60" height="60"/>
<overlapping name="Link" N="3" periodic="True"/>
<overlapping name="Link 2" N="3" periodic="True"/>
<overlapping name="Mazelike" N="3" periodic="True"/>
<overlapping name="Nested" N="3" periodic="True"/>
<overlapping name="Magic Office" N="3" periodic="True"/>
<overlapping name="Office 2" N="3" periodic="True"/>
<overlapping name="Qud" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Red Dot" N="3" periodic="True"/>
<overlapping name="Scaled Maze" N="2" periodic="True"/>
<overlapping name="Sewers" N="3" periodic="True"/>
<overlapping name="Skew 1" N="3" periodic="True"/>
<overlapping name="Skew 2" N="3" periodic="True"/>
<overlapping name="Smile City" N="3" periodic="True"/>
<overlapping name="Spirals" N="3" periodic="True"/>
<overlapping name="Town" N="3" periodic="True"/>
<overlapping name="3Bricks" N="3" symmetry="1" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" limit="50" periodic="True"/>
<simpletiled name="Summer" width="15" height="15" periodic="False" limit="15"/>
<simpletiled name="Knots" subset="Standard" width="5" height="5" periodic="True" textOutput="True"/>
<simpletiled name="Summer" width="6" height="6" periodic="False" textOutput="True"/>
<simpletiled name="Circuit" subset="Turnless" width="7" height="7" periodic="True" textOutput="True"/>
</samples>
================================================
FILE: samples_original.xml
================================================
<samples>
<overlapping name="Chess" N="2" periodic="True"/>
<overlapping name="Chess" N="2" width="47" height="47" periodic="True" screenshots="1"/>
<overlapping name="Hogs" N="3" periodic="True"/>
<overlapping name="Hogs" N="2" periodic="True"/>
<overlapping name="Knot" N="3" periodic="True"/>
<overlapping name="Less Rooms" N="3" periodic="True"/>
<overlapping name="Mountains" N="3" symmetry="2" periodic="True"/>
<overlapping name="Office" N="3" periodic="True"/>
<overlapping name="Paths" N="3" periodic="True"/>
<overlapping name="Platformer" N="2" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Platformer" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Red Maze" N="2"/>
<overlapping name="Rooms" N="3" screenshots="3" periodic="True"/>
<overlapping name="Rule 126" N="3" symmetry="2" periodicInput="False" periodic="False"/>
<overlapping name="Simple Knot" N="3" periodic="True"/>
<overlapping name="Simple Maze" N="2"/>
<overlapping name="Simple Wall" N="3" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="3" periodic="True"/>
<overlapping name="Simple Wall" N="2" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="2" periodic="True"/>
<overlapping name="Trick Knot" N="3" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" periodic="True"/>
<overlapping name="Water" N="3" symmetry="1" periodic="True"/>
<simpletiled name="Summer" width="15" height="15"/>
<simpletiled name="Castle" width="20" height="20"/>
<simpletiled name="Circuit" subset="Turnless" width="34" height="34" periodic="True" screenshots="3"/>
<simpletiled name="Knots" subset="Standard" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Crossless" width="24" height="24"/>
<simpletiled name="Knots" subset="TE" width="24" height="24"/>
<simpletiled name="Knots" subset="T" width="24" height="24"/>
<simpletiled name="Knots" subset="CL" width="24" height="24"/>
<simpletiled name="Knots" subset="CE" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="C" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Rooms" width="30" height="30"/>
<simpletiled name="Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles and Solid" width="24" height="24"/>
<simpletiled name="Circles" subset="No Solid" width="24" height="24"/>
<overlapping name="Cat" N="3" symmetry="2" periodic="True" width="80" height="80"/>
<overlapping name="Cats" N="3" symmetry="2" periodic="True"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="True" ground="-1"/>
<overlapping name="Angular" N="3" periodic="True"/>
<overlapping name="City" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Colored City" N="3" periodic="True"/>
<overlapping name="Dungeon" N="3" periodic="True"/>
<overlapping name="Lake" N="3" periodic="True" width="60" height="60"/>
<overlapping name="Link" N="3" periodic="True"/>
<overlapping name="Link 2" N="3" periodic="True"/>
<overlapping name="Mazelike" N="3" periodic="True"/>
<overlapping name="Nested" N="3" periodic="True"/>
<overlapping name="Magic Office" N="3" periodic="True"/>
<overlapping name="Office 2" N="3" periodic="True"/>
<overlapping name="Qud" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Red Dot" N="3" periodic="True"/>
<overlapping name="Scaled Maze" N="2" periodic="True"/>
<overlapping name="Sewers" N="3" periodic="True"/>
<overlapping name="Skew 1" N="3" periodic="True"/>
<overlapping name="Skew 2" N="3" periodic="True"/>
<overlapping name="Smile City" N="3" periodic="True"/>
<overlapping name="Spirals" N="3" periodic="True"/>
<overlapping name="Town" N="3" periodic="True"/>
<overlapping name="3Bricks" N="3" symmetry="1" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" limit="50" periodic="True"/>
<simpletiled name="Summer" width="15" height="15" periodic="False" limit="15"/>
<simpletiled name="Knots" subset="Standard" width="5" height="5" periodic="True" textOutput="True"/>
<simpletiled name="Summer" width="6" height="6" periodic="False" textOutput="True"/>
<simpletiled name="Circuit" subset="Turnless" width="7" height="7" periodic="True" textOutput="True"/>
</samples>
================================================
FILE: samples_reference.xml
================================================
<samples>
<comment name="Skyline" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Chess" N="2" periodic="True"/>
<overlapping name="Chess" N="2" width="47" height="47" periodic="True" screenshots="1"/>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="True"/>
<overlapping name="Hogs" N="3" periodic="True"/>
<overlapping name="Hogs" N="2" periodic="True"/>
<overlapping name="Knot" N="3" periodic="True"/>
<overlapping name="Less Rooms" N="3" periodic="True"/>
<overlapping name="Mountains" N="3" symmetry="2" periodic="True"/>
<overlapping name="Office" N="3" periodic="True"/>
<overlapping name="Paths" N="3" periodic="True"/>
<overlapping name="Platformer" N="2" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Platformer" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Red Maze" N="2"/>
<overlapping name="Rooms" N="3" screenshots="3" periodic="True"/>
<overlapping name="Rule 126" N="3" symmetry="2" periodicInput="False" periodic="False"/>
<overlapping name="Simple Knot" N="3" periodic="True"/>
<overlapping name="Simple Maze" N="2"/>
<overlapping name="Simple Wall" N="3" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="3" periodic="True"/>
<overlapping name="Simple Wall" N="2" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="2" periodic="True"/>
<overlapping name="Trick Knot" N="3" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" periodic="True"/>
<overlapping name="Water" N="3" symmetry="1" periodic="True"/>
<overlapping name="Skyline" N="3" symmetry="2" ground="-1" periodic="True"/>
<simpletiled name="Summer" width="15" height="15"/>
<simpletiled name="Castle" width="20" height="20"/>
<simpletiled name="Circuit" subset="Turnless" width="34" height="34" periodic="True" screenshots="3"/>
<simpletiled name="Knots" subset="Standard" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Crossless" width="24" height="24"/>
<simpletiled name="Knots" subset="TE" width="24" height="24"/>
<simpletiled name="Knots" subset="T" width="24" height="24"/>
<simpletiled name="Knots" subset="CL" width="24" height="24"/>
<simpletiled name="Knots" subset="CE" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="C" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Rooms" width="30" height="30"/>
<simpletiled name="Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles and Solid" width="24" height="24"/>
<simpletiled name="Circles" subset="No Solid" width="24" height="24"/>
<overlapping name="Cat" N="3" symmetry="2" periodic="True" width="80" height="80"/>
<overlapping name="Cats" N="3" symmetry="2" periodic="True"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="True" ground="-1"/>
<overlapping name="Angular" N="3" periodic="True"/>
<overlapping name="City" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Colored City" N="3" periodic="True"/>
<overlapping name="Dungeon" N="3" periodic="True"/>
<overlapping name="Lake" N="3" periodic="True" width="60" height="60"/>
<overlapping name="Link" N="3" periodic="True"/>
<overlapping name="Link 2" N="3" periodic="True"/>
<overlapping name="Mazelike" N="3" periodic="True"/>
<overlapping name="Nested" N="3" periodic="True"/>
<overlapping name="Magic Office" N="3" periodic="True"/>
<overlapping name="Office 2" N="3" periodic="True"/>
<overlapping name="Qud" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Red Dot" N="3" periodic="True"/>
<overlapping name="Scaled Maze" N="2" periodic="True"/>
<overlapping name="Sewers" N="3" periodic="True"/>
<overlapping name="Skew 1" N="3" periodic="True"/>
<overlapping name="Skew 2" N="3" periodic="True"/>
<overlapping name="Smile City" N="3" periodic="True"/>
<overlapping name="Spirals" N="3" periodic="True"/>
<overlapping name="Town" N="3" periodic="True"/>
<overlapping name="3Bricks" N="3" symmetry="1" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" limit="50" periodic="True"/>
<simpletiled name="Summer" width="15" height="15" periodic="False" limit="15"/>
<simpletiled name="Knots" subset="Standard" width="5" height="5" periodic="True" textOutput="True"/>
<simpletiled name="Summer" width="6" height="6" periodic="False" textOutput="True"/>
<simpletiled name="Circuit" subset="Turnless" width="7" height="7" periodic="True" textOutput="True"/>
</samples>
================================================
FILE: samples_reference_continue.xml
================================================
<samples>
<overlapping name="Knot" N="3" periodic="True"/>
<overlapping name="Less Rooms" N="3" periodic="True"/>
<overlapping name="Mountains" N="3" symmetry="2" periodic="True"/>
<overlapping name="Office" N="3" periodic="True"/>
<overlapping name="Paths" N="3" periodic="True"/>
<overlapping name="Platformer" N="2" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Platformer" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Red Maze" N="2"/>
<overlapping name="Rooms" N="3" screenshots="3" periodic="True"/>
<overlapping name="Rule 126" N="3" symmetry="2" periodicInput="False" periodic="False"/>
<overlapping name="Simple Knot" N="3" periodic="True"/>
<overlapping name="Simple Maze" N="2"/>
<overlapping name="Simple Wall" N="3" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="3" periodic="True"/>
<overlapping name="Simple Wall" N="2" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="2" periodic="True"/>
<overlapping name="Trick Knot" N="3" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" periodic="True"/>
<overlapping name="Water" N="3" symmetry="1" periodic="True"/>
<simpletiled name="Summer" width="15" height="15"/>
<simpletiled name="Castle" width="20" height="20"/>
<simpletiled name="Circuit" subset="Turnless" width="34" height="34" periodic="True" screenshots="3"/>
<simpletiled name="Knots" subset="Standard" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Crossless" width="24" height="24"/>
<simpletiled name="Knots" subset="TE" width="24" height="24"/>
<simpletiled name="Knots" subset="T" width="24" height="24"/>
<simpletiled name="Knots" subset="CL" width="24" height="24"/>
<simpletiled name="Knots" subset="CE" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="C" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Rooms" width="30" height="30"/>
<simpletiled name="Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles and Solid" width="24" height="24"/>
<simpletiled name="Circles" subset="No Solid" width="24" height="24"/>
<overlapping name="Cat" N="3" symmetry="2" periodic="True" width="80" height="80"/>
<overlapping name="Cats" N="3" symmetry="2" periodic="True"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="True" ground="-1"/>
<overlapping name="Angular" N="3" periodic="True"/>
<overlapping name="City" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Colored City" N="3" periodic="True"/>
<overlapping name="Dungeon" N="3" periodic="True"/>
<overlapping name="Lake" N="3" periodic="True" width="60" height="60"/>
<overlapping name="Link" N="3" periodic="True"/>
<overlapping name="Link 2" N="3" periodic="True"/>
<overlapping name="Mazelike" N="3" periodic="True"/>
<overlapping name="Nested" N="3" periodic="True"/>
<overlapping name="Magic Office" N="3" periodic="True"/>
<overlapping name="Office 2" N="3" periodic="True"/>
<overlapping name="Qud" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Red Dot" N="3" periodic="True"/>
<overlapping name="Scaled Maze" N="2" periodic="True"/>
<overlapping name="Sewers" N="3" periodic="True"/>
<overlapping name="Skew 1" N="3" periodic="True"/>
<overlapping name="Skew 2" N="3" periodic="True"/>
<overlapping name="Smile City" N="3" periodic="True"/>
<overlapping name="Spirals" N="3" periodic="True"/>
<overlapping name="Town" N="3" periodic="True"/>
<overlapping name="3Bricks" N="3" symmetry="1" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" limit="50" periodic="True"/>
<simpletiled name="Summer" width="15" height="15" periodic="False" limit="15"/>
<simpletiled name="Knots" subset="Standard" width="5" height="5" periodic="True" textOutput="True"/>
<simpletiled name="Summer" width="6" height="6" periodic="False" textOutput="True"/>
<simpletiled name="Circuit" subset="Turnless" width="7" height="7" periodic="True" textOutput="True"/>
</samples>
================================================
FILE: samples_reference_nohogs.xml
================================================
<samples>
<overlapping name="Chess" N="2" periodic="True"/>
<overlapping name="Chess" N="2" width="47" height="47" periodic="True" screenshots="1"/>
<overlapping name="Skyline" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="True"/>
<overlapping name="Hogs" N="3" periodic="True"/>
<overlapping name="Knot" N="3" periodic="True"/>
<overlapping name="Less Rooms" N="3" periodic="True"/>
<overlapping name="Mountains" N="3" symmetry="2" periodic="True"/>
<overlapping name="Office" N="3" periodic="True"/>
<overlapping name="Paths" N="3" periodic="True"/>
<overlapping name="Platformer" N="2" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Platformer" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Red Maze" N="2"/>
<overlapping name="Rooms" N="3" screenshots="3" periodic="True"/>
<overlapping name="Rule 126" N="3" symmetry="2" periodicInput="False" periodic="False"/>
<overlapping name="Simple Knot" N="3" periodic="True"/>
<overlapping name="Simple Maze" N="2"/>
<overlapping name="Simple Wall" N="3" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="3" periodic="True"/>
<overlapping name="Simple Wall" N="2" symmetry="2" periodic="True"/>
<overlapping name="Simple Wall" N="2" periodic="True"/>
<overlapping name="Trick Knot" N="3" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" periodic="True"/>
<overlapping name="Water" N="3" symmetry="1" periodic="True"/>
<simpletiled name="Summer" width="15" height="15"/>
<simpletiled name="Castle" width="20" height="20"/>
<simpletiled name="Circuit" subset="Turnless" width="34" height="34" periodic="True" screenshots="3"/>
<simpletiled name="Knots" subset="Standard" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Crossless" width="24" height="24"/>
<simpletiled name="Knots" subset="TE" width="24" height="24"/>
<simpletiled name="Knots" subset="T" width="24" height="24"/>
<simpletiled name="Knots" subset="CL" width="24" height="24"/>
<simpletiled name="Knots" subset="CE" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="C" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Knots" subset="Dense Fabric" width="24" height="24" periodic="True"/>
<simpletiled name="Rooms" width="30" height="30"/>
<simpletiled name="Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles" width="24" height="24"/>
<simpletiled name="Circles" subset="Large Circles and Solid" width="24" height="24"/>
<simpletiled name="Circles" subset="No Solid" width="24" height="24"/>
<overlapping name="Cat" N="3" symmetry="2" periodic="True" width="80" height="80"/>
<overlapping name="Cats" N="3" symmetry="2" periodic="True"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="True" ground="-1"/>
<overlapping name="Angular" N="3" periodic="True"/>
<overlapping name="City" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Colored City" N="3" periodic="True"/>
<overlapping name="Dungeon" N="3" periodic="True"/>
<overlapping name="Lake" N="3" periodic="True" width="60" height="60"/>
<overlapping name="Link" N="3" periodic="True"/>
<overlapping name="Link 2" N="3" periodic="True"/>
<overlapping name="Mazelike" N="3" periodic="True"/>
<overlapping name="Nested" N="3" periodic="True"/>
<overlapping name="Magic Office" N="3" periodic="True"/>
<overlapping name="Office 2" N="3" periodic="True"/>
<overlapping name="Qud" N="3" periodic="True" width="80" height="80"/>
<overlapping name="Red Dot" N="3" periodic="True"/>
<overlapping name="Scaled Maze" N="2" periodic="True"/>
<overlapping name="Sewers" N="3" periodic="True"/>
<overlapping name="Skew 1" N="3" periodic="True"/>
<overlapping name="Skew 2" N="3" periodic="True"/>
<overlapping name="Smile City" N="3" periodic="True"/>
<overlapping name="Spirals" N="3" periodic="True"/>
<overlapping name="Town" N="3" periodic="True"/>
<overlapping name="3Bricks" N="3" symmetry="1" periodic="True"/>
<overlapping name="Village" N="3" symmetry="2" limit="50" periodic="True"/>
<simpletiled name="Summer" width="15" height="15" periodic="False" limit="15"/>
<simpletiled name="Knots" subset="Standard" width="5" height="5" periodic="True" textOutput="True"/>
<simpletiled name="Summer" width="6" height="6" periodic="False" textOutput="True"/>
<simpletiled name="Circuit" subset="Turnless" width="7" height="7" periodic="True" textOutput="True"/>
</samples>
================================================
FILE: samples_test.xml
================================================
<samples>
<overlapping name="test_pattern" N="2" symmetry="1" periodic="False" width="11" height="9"/>
</samples>
================================================
FILE: samples_test_ground.xml
================================================
<samples>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="False" width="8" height="12"/>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="False" width="16" height="12"/>
<overlapping name="Platformer" N="2" symmetry="2" ground="-1" periodic="False" width="12" height="8"/>
<overlapping name="Platformer" N="3" symmetry="2" ground="-1" periodic="False" width="12" height="8"/>
<overlapping name="Skyline" N="3" symmetry="2" ground="-1" periodic="False" width="12" height="8"/>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="False" width="12" height="8"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="False" ground="-1" width="12" height="8"/>
<overlapping name="Red Maze" N="2" width="16" height="11"/>
<overlapping name="Dungeon" N="3" periodic="True" width="28" height="31"/>
<overlapping name="Platformer" N="2" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Platformer" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Skyline" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="True"/>
<overlapping name="Skyline 2" N="3" symmetry="2" periodic="True" ground="-1"/>
</samples>
================================================
FILE: samples_test_vis.xml
================================================
<samples>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="True"/>
<overlapping name="Skyline" N="3" symmetry="2" ground="-1" periodic="True"/>
<overlapping name="Red Maze" N="2" width="36" height="48"/>
<overlapping name="Red Maze" N="2" width="16" height="13"/>
<overlapping name="Flowers" N="3" symmetry="2" ground="-4" periodic="False" width="8" height="12"/>
<overlapping name="Dungeon" N="3" periodic="True" width="18" height="7"/>
<overlapping name="f_test" N="2" width="4" height="4" screenshots="3" symmetry="1"/>
<overlapping name="f_test" N="2" width="16" height="16" screenshots="3" symmetry="1"/>
<overlapping name="f_test" N="2" width="32" height="32" screenshots="3" symmetry="1"/>
<overlapping name="tilesize_test" N="2" width="10" height="8" tile_size="2" screenshots="1"/>
</samples>
================================================
FILE: setup.cfg
================================================
[metadata]
name = wfc_python
version = 0.0.0
[options]
packages = wfc
include_package_data = True
install_requires =
hilbertcurve>=2
imageio
matplotlib
numpy
scipy
[options.package_data]
wfc = py.typed
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
import setuptools
if __name__ == "__main__":
setuptools.setup()
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/conftest.py
================================================
from __future__ import annotations
import os.path
import pytest
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
class Resources:
def get_image(self, image: str) -> str:
return os.path.join(PROJECT_ROOT, "images", image)
@pytest.fixture(scope="session")
def resources() -> Resources:
return Resources()
================================================
FILE: tests/test_wfc_adjacency.py
================================================
"""Convert input data to adjacency information"""
from __future__ import annotations
import imageio # type: ignore
from tests.conftest import Resources
from wfc import wfc_tiles
from wfc import wfc_patterns
from wfc import wfc_adjacency
def test_adjacency_extraction(resources: Resources) -> None:
# TODO: generalize this to more than the four cardinal directions
direction_offsets = list(enumerate([(0, -1), (1, 0), (0, 1), (-1, 0)]))
filename = resources.get_image("samples/Red Maze.png")
img = imageio.imread(filename)
tile_size = 1
pattern_width = 2
periodic = False
_tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog(img, tile_size)
pattern_catalog, _pattern_weights, _pattern_list, pattern_grid = wfc_patterns.make_pattern_catalog(
tile_grid, pattern_width, periodic
)
adjacency_relations = wfc_adjacency.adjacency_extraction(
pattern_grid, pattern_catalog, direction_offsets
)
assert ((0, -1), -6150964001204120324, -4042134092912931260) in adjacency_relations
assert ((-1, 0), -4042134092912931260, 3069048847358774683) in adjacency_relations
assert ((1, 0), -3950451988873469076, -3950451988873469076) in adjacency_relations
assert ((-1, 0), -3950451988873469076, -3950451988873469076) in adjacency_relations
assert ((0, 1), -3950451988873469076, 3336256675067683735) in adjacency_relations
assert (
not ((0, -1), -3950451988873469076, -3950451988873469076) in adjacency_relations
)
assert (
not ((0, 1), -3950451988873469076, -3950451988873469076) in adjacency_relations
)
================================================
FILE: tests/test_wfc_patterns.py
================================================
from __future__ import annotations
import imageio # type: ignore
import numpy as np
from tests.conftest import Resources
from wfc import wfc_patterns
from wfc import wfc_tiles
def test_unique_patterns_2d(resources: Resources) -> None:
filename = resources.get_image("samples/Red Maze.png")
img = imageio.imread(filename)
tile_size = 1
pattern_width = 2
_tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog(img, tile_size)
_patterns_in_grid, pattern_contents_list, patch_codes = wfc_patterns.unique_patterns_2d(
tile_grid, pattern_width, True
)
assert patch_codes[1][2] == 4867810695119132864
assert pattern_contents_list[7][1][1] == 8253868773529191888
def test_make_pattern_catalog(resources: Resources) -> None:
filename = resources.get_image("samples/Red Maze.png")
img = imageio.imread(filename)
tile_size = 1
pattern_width = 2
_tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog(img, tile_size)
pattern_catalog, pattern_weights, pattern_list, _pattern_grid = wfc_patterns.make_pattern_catalog(
tile_grid, pattern_width
)
assert pattern_weights[-6150964001204120324] == 1
assert pattern_list[3] == 2800765426490226432
assert pattern_catalog[5177878755649963747][0][1] == -8754995591521426669
def test_pattern_to_tile(resources: Resources) -> None:
filename = resources.get_image("samples/Red Maze.png")
img = imageio.imread(filename)
tile_size = 1
pattern_width = 2
_tile_catalog, tile_grid, _code_list, _unique_tiles = wfc_tiles.make_tile_catalog(img, tile_size)
pattern_catalog, _pattern_weights, _pattern_list, pattern_grid = wfc_patterns.make_pattern_catalog(
tile_grid, pattern_width
)
new_tile_grid = wfc_patterns.pattern_grid_to_tiles(pattern_grid, pattern_catalog)
assert np.array_equal(tile_grid, new_tile_grid)
================================================
FILE: tests/test_wfc_solver.py
================================================
from __future__ import annotations
from typing import Any, Dict, List, Set, Tuple
from numpy.typing import NDArray
import imageio # type: ignore
import numpy
import numpy as np
from tests.conftest import Resources
from wfc import wfc_solver
from wfc import wfc_tiles
from wfc import wfc_patterns
from wfc import wfc_adjacency
def test_makeWave() -> None:
wave = wfc_solver.makeWave(3, 10, 20, ground=[-1])
# print(wave)
# print(wave.sum())
# print((2*10*19) + (1*10*1))
assert wave.sum() == (2 * 10 * 19) + (1 * 10 * 1)
assert wave[2, 5, 19] == True
assert wave[1, 5, 19] == False
def test_entropyLocationHeuristic() -> None:
wave = numpy.ones((5, 3, 4), dtype=bool) # everthing is possible
wave[1:, 0, 0] = False # first cell is fully observed
wave[4, :, 2] = False
preferences: NDArray[np.float_] = numpy.ones((3, 4), dtype=np.float_) * 0.5
preferences[1, 2] = 0.3
preferences[1, 1] = 0.1
heu = wfc_solver.makeEntropyLocationHeuristic(preferences)
result = heu(wave)
assert (1, 2) == result
def test_observe() -> None:
my_wave = numpy.ones((5, 3, 4), dtype=np.bool_)
my_wave[0, 1, 2] = False
def locHeu(wave: NDArray[np.bool_]) -> Tuple[int, int]:
assert numpy.array_equal(wave, my_wave)
return 1, 2
def patHeu(weights: NDArray[np.bool_], wave: NDArray[np.bool_]) -> int:
assert numpy.array_equal(weights, my_wave[:, 1, 2])
return 3
assert wfc_solver.observe(my_wave, locationHeuristic=locHeu, patternHeuristic=patHeu) == (
3,
1,
2,
)
def test_propagate() -> None:
wave = numpy.ones((3, 3, 4), dtype=bool)
adjLists = {}
# checkerboard #0/#1 or solid fill #2
adjLists[(+1, 0)] = adjLists[(-1, 0)] = adjLists[(0, +1)] = adjLists[(0, -1)] = [
[1],
[0],
[2],
]
wave[:, 0, 0] = False
wave[0, 0, 0] = True
adj = wfc_solver.makeAdj(adjLists)
wfc_solver.propagate(wave, adj, periodic=False)
expected_result = numpy.array(
[
[
[True, False, True, False],
[False, True, False, True],
[True, False, True, False],
],
[
[False, True, False, True],
[True, False, True, False],
[False, True, False, True],
],
[
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
],
]
)
assert numpy.array_equal(wave, expected_result)
def test_run() -> None:
wave = wfc_solver.makeWave(3, 3, 4)
adjLists = {}
adjLists[(+1, 0)] = adjLists[(-1, 0)] = adjLists[(0, +1)] = adjLists[(0, -1)] = [
[1],
[0],
[2],
]
adj = wfc_solver.makeAdj(adjLists)
first_result = wfc_solver.run(
wave.copy(),
adj,
locationHeuristic=wfc_solver.lexicalLocationHeuristic,
patternHeuristic=wfc_solver.lexicalPatternHeuristic,
periodic=False,
)
expected_first_result = numpy.array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 0, 1]])
assert numpy.array_equal(first_result, expected_first_result)
event_log: List[Any] = []
def onChoice(pattern: int, i: int, j: int) -> None:
event_log.append((pattern, i, j))
def onBacktrack() -> None:
event_log.append("backtrack")
second_result = wfc_solver.run(
wave.copy(),
adj,
locationHeuristic=wfc_solver.lexicalLocationHeuristic,
patternHeuristic=wfc_solver.lexicalPatternHeuristic,
periodic=True,
backtracking=True,
onChoice=onChoice,
onBacktrack=onBacktrack,
)
expected_second_result = numpy.array([[2, 2, 2, 2], [2, 2, 2, 2], [2, 2, 2, 2]])
assert numpy.array_equal(second_result, expected_second_result)
print(event_log)
assert event_log == [(0, 0, 0), "backtrack", (2, 0, 0)]
class Infeasible(Exception):
pass
def explode(wave: NDArray[np.bool_]) -> bool:
if wave.sum() < 20:
raise Infeasible
return False
try:
result = wfc_solver.run(
wave.copy(),
adj,
locationHeuristic=wfc_solver.lexicalLocationHeuristic,
patternHeuristic=wfc_solver.lexicalPatternHeuristic,
periodic=True,
backtracking=True,
checkFeasible=explode,
)
print(result)
happy = False
except wfc_solver.Contradiction:
happy = True
assert happy
================================================
FILE: tests/test_wfc_tiles.py
================================================
"""Breaks an image into consituant tiles."""
from __future__ import annotations
import imageio # type: ignore
from tests.conftest import Resources
from wfc import wfc_tiles
def test_image_to_tile(resources: Resources) -> None:
filename = resources.get_image("samples/Red Maze.png")
img = imageio.imread(filename)
tiles = wfc_tiles.image_to_tiles(img, 1)
assert tiles[2][2][0][0][0] == 255
assert tiles[2][2][0][0][1] == 0
def test_make_tile_catalog(resources: Resources) -> None:
filename = resources.get_image("samples/Red Maze.png")
img = imageio.imread(filename)
print(img)
tc, tg, cl, ut = wfc_tiles.make_tile_catalog(img, 1)
print("tile catalog")
print(tc)
print("tile grid")
print(tg)
print("code list")
print(cl)
print("unique tiles")
print(ut)
assert ut[1][0] == 7
================================================
FILE: wfc/__init__.py
================================================
================================================
FILE: wfc/py.typed
================================================
================================================
FILE: wfc/wfc_adjacency.py
================================================
"""Convert input data to adjacency information"""
from __future__ import annotations
from typing import Dict, List, Tuple
import numpy as np
from numpy.typing import NDArray
def adjacency_extraction(
pattern_grid: NDArray[np.int64],
pattern_catalog: Dict[int, NDArray[np.int64]],
direction_offsets: List[Tuple[int, Tuple[int, int]]],
pattern_size: Tuple[int, int] = (2, 2),
) -> List[Tuple[Tuple[int, int], int, int]]:
"""Takes a pattern grid and returns a list of all of the legal adjacencies found in it."""
def is_valid_overlap_xy(adjacency_direction: Tuple[int, int], pattern_1: int, pattern_2: int) -> bool:
"""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
================================================
FILE: wfc/wfc_control.py
================================================
from __future__ import annotations
import datetime
from typing import Any, Callable, Dict, List, Literal, Optional, Set, Tuple
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,
tile_grid_to_image,
)
import imageio # type: ignore
import numpy as np
import time
import logging
from numpy.typing import NDArray
logger = logging.getLogger(__name__)
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() -> Callable[[Dict[str, Any], str], None]:
log_line = 0
def log_stats(stats: Dict[str, Any], filename: str) -> None:
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: Optional[str] = None,
tile_size: int = 1,
pattern_width: int = 2,
rotations: int = 8,
output_size: Tuple[int, int] = (48, 48),
ground: Optional[int] = None,
attempt_limit: int = 10,
output_periodic: bool = True,
input_periodic: bool = True,
loc_heuristic: Literal["lexical", "hilbert", "spiral", "entropy", "anti-entropy", "simple", "random"] = "entropy",
choice_heuristic: Literal["lexical", "rarest", "weighted", "random"] = "weighted",
visualize: bool = False,
global_constraint: Literal[False, "allpatterns"] = False,
backtracking: bool = False,
log_filename: str = "log",
logging: bool = False,
global_constraints: None = None,
log_stats_to_output: Optional[Callable[[Dict[str, Any], str], None]] = None,
*,
image: Optional[NDArray[np.integer]] = None,
) -> NDArray[np.integer]:
timecode = datetime.datetime.now().isoformat().replace(":", ".")
time_begin = time.perf_counter()
output_destination = r"./output/"
input_folder = r"./images/samples/"
rotations -= 1 # change to zero-based
input_stats = {
"filename": str(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
if filename:
if image is not None:
raise TypeError("Only filename or image can be provided, not both.")
image = imageio.imread(input_folder + filename + ".png")[:, :, :3] # TODO: handle alpha channels
if image is None:
raise TypeError("An image must be given.")
# 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(image, 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
)
logger.debug("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 and filename:
figure_pattern_catalog(
pattern_catalog,
tile_catalog,
pattern_weights,
pattern_width,
output_filename=f"visualization/pattern_catalog_{filename}_{timecode}",
)
logger.debug("profiling adjacency relations")
if False:
import pprofile # type: ignore
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),
)
logger.debug("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)
logger.debug(f"output size: {output_size}\noutput periodic: {output_periodic}")
number_of_patterns = len(pattern_weights)
logger.debug(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: Dict[Tuple[int, int], List[Set[int]]] = {}
for _, adjacency in direction_offsets:
adjacency_list[adjacency] = [set() for _ in pattern_weights]
# logger.debug(adjacency_list)
for adjacency, pattern1, pattern2 in adjacency_relations:
# logger.debug(adjacency)
# logger.debug(decode_patterns[pattern1])
adjacency_list[adjacency][encode_patterns[pattern1]].add(encode_patterns[pattern2])
logger.debug(f"adjacency: {len(adjacency_list)}")
time_adjacency = time.perf_counter()
### Ground ###
ground_list: Optional[NDArray[np.int64]] = None
if ground:
ground_list = np.vectorize(lambda x: encode_patterns[x])(
pattern_grid.flat[(ground - 1) :]
)
if ground_list is None or ground_list.size == 0:
ground_list = None
if ground_list is not 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: NDArray[np.float64] = 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: NDArray[np.float64] = np.random.random_sample(wave.shape[1:]) * 0.1
pattern_heuristic: Callable[[NDArray[np.bool_], NDArray[np.bool_]], int] = 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)
logger.debug(loc_heuristic)
location_heuristic: Callable[[NDArray[np.bool_]], Tuple[int, int]] = 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 filename and 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 filename and logging:
(
visualize_choice,
visualize_wave,
visualize_backtracking,
visualize_propagate,
visualize_final,
visualize_after,
) = make_solver_loggers(f"{filename}_{timecode}", input_stats.copy())
if filename and 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: int):
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()
logger.debug(active_global_constraint)
combined_constraints = [active_global_constraint]
def combinedConstraints(wave: NDArray[np.bool_]) -> bool:
return all(fn(wave) for fn in combined_constraints)
### Solving ###
time_solve_start = None
time_solve_end = None
solution_tile_grid = None
logger.debug("solving...")
attempts = 0
while attempts < attempt_limit:
attempts += 1
time_solve_start = time.perf_counter()
stats = {}
# profiler = pprofile.Profile()
# 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()
# logger.debug(solution)
# logger.debug(stats)
solution_as_ids = np.vectorize(lambda x: decode_patterns[x])(solution)
solution_tile_grid = pattern_grid_to_tiles(
solution_as_ids, pattern_catalog
)
logger.debug("Solution:")
# logger.debug(solution_tile_grid)
if filename:
render_tiles_to_output(
solution_tile_grid,
tile_catalog,
(tile_size, tile_size),
output_destination + filename + "_" + timecode + ".png",
)
time_solve_end = time.perf_counter()
stats.update({"outcome": "success"})
except StopEarly:
logger.debug("Skipping...")
stats.update({"outcome": "skipped"})
raise
except TimedOut:
logger.debug("Timed Out")
if visualize_after:
stats = visualize_after()
stats.update({"outcome": "timed_out"})
except Contradiction as exc:
logger.warning(f"Contradiction: {exc}")
if visualize_after:
stats = visualize_after()
stats.update({"outcome": "contradiction"})
finally:
# profiler.dump_stats(f"logs/profile_{filename}_{timecode}.txt")
outstats = {}
outstats.update(input_stats)
solve_duration = time.perf_counter() - time_solve_start
if time_solve_end is not None:
solve_duration = time_solve_end - time_solve_start
adjacency_duration = time_solve_start - time_adjacency
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 log_stats_to_output is not None:
log_stats_to_output(outstats, output_destination + log_filename + ".tsv")
if solution_tile_grid is not None:
return tile_grid_to_image(solution_tile_grid, tile_catalog, (tile_size, tile_size))
raise TimedOut("Attempt limit exceeded.")
================================================
FILE: wfc/wfc_patterns.py
================================================
"Extract patterns from grids of tiles."
from __future__ import annotations
import logging
from typing import Any, Dict, Mapping, Optional, Tuple
from .wfc_utilities import hash_downto
from collections import Counter
import numpy as np
from numpy.typing import NDArray
logger = logging.getLogger(__name__)
def unique_patterns_2d(agrid: NDArray[np.int64], ksize: int, periodic_input: bool) -> Tuple[NDArray[np.int64], NDArray[np.int64], NDArray[np.int64]]:
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: NDArray[np.int64] = 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: NDArray[np.int64] = patches[locs[0], locs[1]]
ids: NDArray[np.int64] = 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: NDArray[np.int64], pattern_width: int, input_is_periodic: bool = True
) -> Tuple[Dict[int, NDArray[np.int64]], Counter, NDArray[np.int64], NDArray[np.int64]]:
"""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: Dict[int, NDArray[np.int64]] = {}
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: NDArray[np.int64], pattern_width: int, rotations: int = 7, input_is_periodic: bool = True
) -> Tuple[Dict[int, NDArray[np.int64]], Counter, NDArray[np.int64], NDArray[np.int64]]:
rotated_tile_grid = tile_grid.copy()
merged_dict_of_pattern_contents: Dict[int, NDArray[np.int64]] = {}
merged_pattern_frequency: Counter = Counter()
merged_pattern_contents_list: Optional[NDArray[np.int64]] = None
merged_patch_codes: Optional[NDArray[np.int64]] = None
def _make_catalog() -> None:
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):
# logger.debug(rotated_tile_grid.shape)
# logger.debug(np.array_equiv(reflect_grid(rotated_tile_grid.copy()), rotate_grid(rotated_tile_grid.copy())))
# logger.debug(counter)
# logger.debug(grid_ops[counter].__name__)
rotated_tile_grid = grid_ops[counter](rotated_tile_grid.copy())
# logger.debug(rotated_tile_grid)
# logger.debug("---")
_make_catalog()
counter += 1
# assert False
assert merged_pattern_contents_list is not None
assert merged_patch_codes is not None
return (
merged_dict_of_pattern_contents,
merged_pattern_frequency,
merged_pattern_contents_list,
merged_patch_codes,
)
def pattern_grid_to_tiles(
pattern_grid: NDArray[np.int64], pattern_catalog: Mapping[int, NDArray[np.int64]]
) -> NDArray[np.int64]:
anchor_x = 0
anchor_y = 0
def pattern_to_tile(pattern: int) -> Any:
# if isinstance(pattern, list):
# ptrns = []
# for p in pattern:
# logger.debug(p)
# ptrns.push(pattern_to_tile(p))
# logger.debug(ptrns)
# assert False
# return ptrns
return pattern_catalog[pattern][anchor_x][anchor_y]
return np.vectorize(pattern_to_tile)(pattern_grid)
================================================
FILE: wfc/wfc_solver.py
================================================
from __future__ import annotations
import logging
from typing import Any, Callable, Collection, Dict, Iterable, Iterator, List, Mapping, Optional, Tuple, TypeVar
from scipy import sparse # type: ignore
import numpy
import numpy as np
import sys
import math
import itertools
from numpy.typing import NBitBase, NDArray
from hilbertcurve.hilbertcurve import HilbertCurve # type: ignore
logger = logging.getLogger(__name__)
T = TypeVar("T", bound=NBitBase)
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
class Solver:
"""WFC Solver which can hold wave and backtracking state."""
def __init__(
self,
*,
wave: NDArray[np.bool_],
adj: Mapping[Tuple[int, int], NDArray[numpy.bool_]],
periodic: bool = False,
backtracking: bool = False,
on_backtrack: Optional[Callable[[], None]] = None,
on_choice: Optional[Callable[[int, int, int], None]] = None,
on_observe: Optional[Callable[[NDArray[numpy.bool_]], None]] = None,
on_propagate: Optional[Callable[[NDArray[numpy.bool_]], None]] = None,
check_feasible: Optional[Callable[[NDArray[numpy.bool_]], bool]] = None
) -> None:
self.wave = wave
self.adj = adj
self.periodic = periodic
self.backtracking = backtracking
self.history: List[NDArray[np.bool_]] = [] # An undo history for backtracking.
self.on_backtrack = on_backtrack
self.on_choice = on_choice
self.on_observe = on_observe
self.on_propagate = on_propagate
self.check_feasible = check_feasible
@property
def is_solved(self) -> bool:
"""Is True if the wave has been fully resolved."""
return self.wave.sum() == self.wave.shape[1] * self.wave.shape[2] and (self.wave.sum(axis=0) == 1).all()
def solve_next(
self,
location_heuristic: Callable[[NDArray[numpy.bool_]], Tuple[int, int]],
pattern_heuristic: Callable[[NDArray[np.bool_], NDArray[np.bool_]], int],
) -> bool:
"""Attempt to collapse one wave. Returns True if no more steps remain."""
if self.is_solved:
return True
if self.check_feasible and not self.check_feasible(self.wave):
raise Contradiction("Not feasible.")
if self.backtracking:
self.history.append(self.wave.copy())
propagate(self.wave, self.adj, periodic=self.periodic, onPropagate=self.on_propagate)
try:
pattern, i, j = observe(self.wave, location_heuristic, pattern_heuristic)
if self.on_choice:
self.on_choice(pattern, i, j)
self.wave[:, i, j] = False
self.wave[pattern, i, j] = True
if self.on_observe:
self.on_observe(self.wave)
propagate(self.wave, self.adj, periodic=self.periodic, onPropagate=self.on_propagate)
return False # Assume there is remaining steps, if not then the next call will return True.
except Contradiction:
if not self.backtracking:
raise
if not self.history:
raise Contradiction("Every permutation has been attempted.")
if self.on_backtrack:
self.on_backtrack()
self.wave = self.history.pop()
self.wave[pattern, i, j] = False
return False
def solve(
self,
location_heuristic: Callable[[NDArray[numpy.bool_]], Tuple[int, int]],
pattern_heuristic: Callable[[NDArray[np.bool_], NDArray[np.bool_]], int],
) -> NDArray[np.int64]:
"""Attempts to solve all waves and returns the solution."""
while not self.solve_next(location_heuristic=location_heuristic, pattern_heuristic=pattern_heuristic):
pass
return numpy.argmax(self.wave, axis=0)
def makeWave(n: int, w: int, h: int, ground: Optional[Iterable[int]] = None) -> NDArray[numpy.bool_]:
wave: NDArray[numpy.bool_] = numpy.ones((n, w, h), dtype=numpy.bool_)
if ground is not None:
wave[:, :, h - 1] = False
for g in ground:
wave[g, :,] = False
wave[g, :, h - 1] = True
# logger.debug(wave)
# for i in range(wave.shape[0]):
# logger.debug(wave[i])
return wave
def makeAdj(
adjLists: Mapping[Tuple[int, int], Collection[Iterable[int]]]
) -> Dict[Tuple[int, int], NDArray[numpy.bool_]]:
adjMatrices = {}
# logger.debug(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]):
# logger.debug(js)
for j in js:
m[i, j] = 1
adjMatrices[d] = sparse.csr_matrix(m)
return adjMatrices
######################################
# Location Heuristics
def makeRandomLocationHeuristic(preferences: NDArray[np.floating[Any]]) -> Callable[[NDArray[np.bool_]], Tuple[int, int]]:
def randomLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
return randomLocationHeuristic
def makeEntropyLocationHeuristic(preferences: NDArray[np.floating[Any]]) -> Callable[[NDArray[np.bool_]], Tuple[int, int]]:
def entropyLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
return entropyLocationHeuristic
def makeAntiEntropyLocationHeuristic(
preferences: NDArray[np.floating[Any]]
) -> Callable[[NDArray[np.bool_]], Tuple[int, int]]:
def antiEntropyLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
return antiEntropyLocationHeuristic
def spiral_transforms() -> Iterator[Tuple[int, int]]:
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: int, y: int) -> Iterator[Tuple[int, int]]:
yield x, y
for transform in spiral_transforms():
x += transform[0]
y += transform[1]
yield x, y
def fill_with_curve(arr: NDArray[np.floating[T]], curve_gen: Iterable[Iterable[int]]) -> NDArray[np.floating[T]]:
arr_len = numpy.prod(arr.shape)
fill = 0
for coord in curve_gen:
# logger.debug(fill, idx, coord)
if fill < arr_len:
try:
arr[tuple(coord)] = fill / arr_len
fill += 1
except IndexError:
pass
else:
break
# logger.debug(arr)
return arr
def makeSpiralLocationHeuristic(preferences: NDArray[np.floating[Any]]) -> Callable[[NDArray[np.bool_]], Tuple[int, int]]:
# 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: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
return spiralLocationHeuristic
def makeHilbertLocationHeuristic(preferences: NDArray[np.floating[Any]]) -> Callable[[NDArray[np.bool_]], Tuple[int, int]]:
curve_size = math.ceil(math.sqrt(max(preferences.shape[0], preferences.shape[1])))
logger.debug(curve_size)
curve_size = 4
h_curve = HilbertCurve(curve_size, 2)
h_coords = (h_curve.point_from_distance(i) for i in itertools.count())
cell_order = fill_with_curve(preferences, h_coords)
# logger.debug(cell_order)
def hilbertLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
return hilbertLocationHeuristic
def simpleLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
def lexicalLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
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.item(), col.item()
#####################################
# Pattern Heuristics
def lexicalPatternHeuristic(weights: NDArray[np.bool_], wave: NDArray[np.bool_]) -> int:
return numpy.nonzero(weights)[0][0].item()
def makeWeightedPatternHeuristic(weights: NDArray[np.floating[Any]]):
num_of_patterns = len(weights)
def weightedPatternHeuristic(wave: NDArray[np.bool_], _: NDArray[np.bool_]) -> int:
# TODO: there's maybe a faster, more controlled way to do this sampling...
weighted_wave: NDArray[np.floating[Any]] = weights * wave
weighted_wave /= weighted_wave.sum()
result = numpy.random.choice(num_of_patterns, p=weighted_wave)
return result
return weightedPatternHeuristic
def makeRarestPatternHeuristic(weights: NDArray[np.floating[Any]]) -> Callable[[NDArray[np.bool_], NDArray[np.bool_]], int]:
"""Return a function that chooses the rarest (currently least-used) pattern."""
def weightedPatternHeuristic(wave: NDArray[np.bool_], total_wave: NDArray[np.bool_]) -> int:
logger.debug(total_wave.shape)
# [logger.debug(e) for e in wave]
wave_sums = numpy.sum(total_wave, (1, 2))
# logger.debug(wave_sums)
selected_pattern = numpy.random.choice(
numpy.where(wave_sums == wave_sums.max())[0]
)
return selected_pattern
return weightedPatternHeuristic
def makeMostCommonPatternHeuristic(
weights: NDArray[np.floating[Any]]
) -> Callable[[NDArray[np.bool_], NDArray[np.bool_]], int]:
"""Return a function that chooses the most common (currently most-used) pattern."""
def weightedPatternHeuristic(wave: NDArray[np.bool_], total_wave: NDArray[np.bool_]) -> int:
logger.debug(total_wave.shape)
# [logger.debug(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: NDArray[np.floating[Any]]) -> Callable[[NDArray[np.bool_], NDArray[np.bool_]], int]:
num_of_patterns = len(weights)
def randomPatternHeuristic(wave: NDArray[np.bool_], _: NDArray[np.bool_]) -> int:
# 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() -> Callable[[NDArray[np.bool_]], bool]:
def global_use_all_patterns(wave: NDArray[np.bool_]) -> bool:
"""Returns true if at least one instance of each pattern is still possible."""
return numpy.all(numpy.any(wave, axis=(1, 2))).item()
return global_use_all_patterns
#####################################
# Solver
def propagate(
wave: NDArray[np.bool_],
adj: Mapping[Tuple[int, int], NDArray[numpy.bool_]],
periodic: bool = False,
onPropagate: Optional[Callable[[NDArray[numpy.bool_]], None]] = None,
) -> None:
"""Completely probagate any newly collapsed waves to all areas."""
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
)
# adj is the list of adjacencies. For each direction d in adjacency,
# check which patterns are still valid...
for d in adj:
dx, dy = d
# padded[] is a version of the adjacency matrix with the values wrapped around
# shifted[] is the padded version with the values shifted over in one direction
# because my code stores the directions as relative (x,y) coordinates, we can find
# the adjacent cell for each direction by simply shifting the matrix in that direction,
# which allows for arbitrary adjacency directions. This is somewhat excessive, but elegant.
shifted = padded[
:, 1 + dx : 1 + wave.shape[1] + dx, 1 + dy : 1 + wave.shape[2] + dy
]
# logger.debug(f"shifted: {shifted.shape} | adj[d]: {adj[d].shape} | d: {d}")
# raise StopEarly
# supports[d] = numpy.einsum('pwh,pq->qwh', shifted, adj[d]) > 0
# The adjacency matrix is a boolean matrix, indexed by the direction and the two patterns.
# If the value for (direction, pattern1, pattern2) is True, then this is a valid adjacency.
# This gives us a rapid way to compare: True is 1, False is 0, so multiplying the matrices
# gives us the adjacency compatibility.
supports[d] = (adj[d] @ shifted.reshape(shifted.shape[0], -1)).reshape(
shifted.shape
) > 0
# supports[d] = ( <- for each cell in the matrix
# adj[d] <- the adjacency matrix [sliced by the direction d]
# @ <- Matrix multiplication
# shifted.reshape(shifted.shape[0], -1)) <- change the shape of the shifted matrix to 2-dimensions, to make the matrix multiplication easier
# .reshape( <- reshape our matrix-multiplied result...
# shifted.shape) <- ...to match the original shape of the shifted matrix
# > 0 <- is not false
# multiply the wave matrix by the support matrix to find which patterns are still in the domain
for d in adj:
wave *= supports[d]
if wave.sum() == last_count:
break # No changes since the last loop, changed waves have been fully propagated.
last_count = wave.sum()
if onPropagate:
onPropagate(wave)
if (wave.sum(axis=0) == 0).any():
raise Contradiction("Wave is in a contradictory state and can not be solved.")
def observe(
wave: NDArray[np.bool_],
locationHeuristic: Callable[[NDArray[np.bool_]], Tuple[int, int]],
patternHeuristic: Callable[[NDArray[np.bool_], NDArray[np.bool_]], int],
) -> Tuple[int, int, int]:
"""Return the next best wave to collapse based on the provided heuristics."""
i, j = locationHeuristic(wave)
pattern = patternHeuristic(wave[:, i, j], wave)
return pattern, i, j
def run(
wave: NDArray[np.bool_],
adj: Mapping[Tuple[int, int], NDArray[numpy.bool_]],
locationHeuristic: Callable[[NDArray[numpy.bool_]], Tuple[int, int]],
patternHeuristic: Callable[[NDArray[np.bool_], NDArray[np.bool_]], int],
periodic: bool = False,
backtracking: bool = False,
onBacktrack: Optional[Callable[[], None]] = None,
onChoice: Optional[Callable[[int, int, int], None]] = None,
onObserve: Optional[Callable[[NDArray[numpy.bool_]], None]] = None,
onPropagate: Optional[Callable[[NDArray[numpy.bool_]], None]] = None,
checkFeasible: Optional[Callable[[NDArray[numpy.bool_]], bool]] = None,
onFinal: Optional[Callable[[NDArray[numpy.bool_]], None]] = None,
depth: int = 0,
depth_limit: Optional[int] = None,
) -> NDArray[numpy.int64]:
solver = Solver(
wave=wave,
adj=adj,
periodic=periodic,
backtracking=backtracking,
on_backtrack=onBacktrack,
on_choice=onChoice,
on_observe=onObserve,
on_propagate=onPropagate,
check_feasible=checkFeasible
)
while not solver.solve_next(location_heuristic=locationHeuristic, pattern_heuristic=patternHeuristic):
pass
if onFinal:
onFinal(solver.wave)
return numpy.argmax(solver.wave, axis=0)
================================================
FILE: wfc/wfc_tiles.py
================================================
"""Breaks an image into consituant tiles."""
from __future__ import annotations
from typing import Dict, Tuple
import numpy as np
from numpy.typing import NDArray
from .wfc_utilities import hash_downto
def image_to_tiles(img: NDArray[np.integer], tile_size: int) -> NDArray[np.integer]:
"""
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: NDArray[np.integer], tile_size: int
) -> Tuple[Dict[int, NDArray[np.integer]], NDArray[np.int64], NDArray[np.int64], Tuple[NDArray[np.int64], NDArray[np.int64]]]:
"""
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 their occurrence
"""
channels = image_data.shape[2] # Number of color channels in the image
tiles = image_to_tiles(image_data, tile_size)
tile_list: NDArray[np.integer] = tiles.reshape((tiles.shape[0] * tiles.shape[1], tile_size, tile_size, channels))
code_list: NDArray[np.int64] = hash_downto(tiles, 2).reshape((tiles.shape[0] * tiles.shape[1]))
tile_grid: NDArray[np.int64] = hash_downto(tiles, 2)
unique_tiles: Tuple[NDArray[np.int64], NDArray[np.int64]] = np.unique(tile_grid, return_counts=True)
tile_catalog: Dict[int, NDArray[np.integer]] = {}
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
================================================
FILE: wfc/wfc_utilities.py
================================================
"""Utility data and functions for WFC"""
from __future__ import annotations
import collections
import logging
from typing import Any
import numpy as np
from numpy.typing import NDArray
logger = logging.getLogger(__name__)
CoordXY = collections.namedtuple("CoordXY", ["x", "y"])
CoordRC = collections.namedtuple("CoordRC", ["row", "column"])
def hash_downto(a: NDArray[np.integer], rank: int, seed: Any=0) -> NDArray[np.int64]:
state = np.random.RandomState(seed)
assert rank < len(a.shape)
# logger.debug((np.prod(a.shape[:rank]),-1))
# logger.debug(np.array([np.prod(a.shape[:rank]),-1], dtype=np.int64).dtype)
u: NDArray[np.integer] = a.reshape((np.prod(a.shape[:rank], dtype=np.int64), -1))
# u = a.reshape((np.prod(a.shape[:rank]),-1))
v = state.randint(1 - (1 << 63), 1 << 63, np.prod(a.shape[rank:]), dtype=np.int64)
return np.asarray(np.inner(u, v).reshape(a.shape[:rank]), dtype=np.int64)
try:
import google.colab # type: ignore
IN_COLAB = True
except:
IN_COLAB = False
def load_visualizer(wfc_ns):
if IN_COLAB:
from google.colab import files # type: ignore
uploaded = files.upload()
for fn in uploaded.keys():
logger.debug(
'User uploaded file "{name}" with length {length} bytes'.format(
name=fn, length=len(uploaded[fn])
)
)
else:
import matplotlib # type: ignore
import matplotlib.pylab # type: ignore
from matplotlib.pyplot import figure, subplot, title, matshow # type: ignore
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
================================================
FILE: wfc/wfc_visualize.py
================================================
"Visualize the patterns into tiles and so on."
from __future__ import annotations
import logging
import math
import pathlib
import itertools
from typing import Dict, Tuple
import imageio # type: ignore
import matplotlib # type: ignore
import struct
import matplotlib.pyplot as plt # type: ignore
import numpy as np
from numpy.typing import NDArray
from .wfc_patterns import pattern_grid_to_tiles
logger = logging.getLogger(__name__)
## 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() -> None:
nonlocal counter_backtracks
counter_backtracks += 1
def propagate_count(wave):
nonlocal counter_propagate
counter_propagate += 1
def final_count(wave):
logger.info(
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: str,
wave: NDArray[np.bool_],
decode_patterns=None,
pattern_catalog=None,
tile_catalog=None,
tile_size=[1, 1],
):
"""Construct visualizers for displaying the intermediate solver status"""
logger.debug(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
)
# logger.debug(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 = 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() -> None:
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: np.ma.MaskedArray,
tile_catalog: Dict[int, NDArray[np.int64]],
tile_size: Tuple[int, int],
color_channels: int = 3,
) -> NDArray[np.int64]:
"""
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: NDArray[np.int64],
tile_catalog: Dict[int, NDArray[np.integer]],
tile_size: Tuple[int, int],
visualize: bool = False,
partial: bool = False,
color_channels: int = 3,
) -> NDArray[np.integer]:
"""
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.
"""
tile_dtype = next(iter(tile_catalog.values())).dtype
new_img = np.zeros(
(
tile_grid.shape[0] * tile_size[0],
tile_grid.shape[1] * tile_size[1],
color_channels,
),
dtype=tile_dtype,
)
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: NDArray[np.int64],
tile_catalog: Dict[int, NDArray[np.integer]],
tile_size: Tuple[int, int],
output_filename: str,
) -> None:
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:
logger.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):
logger.debug(adj_rel)
logger.debug(pattern_a)
logger.debug(pattern_b)
logger.debug(preview_adj_a_first)
logger.debug(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:
# logger.exception(e)
================================================
FILE: wfc_run.py
================================================
# -*- coding: utf-8 -*-
"""Base code to load commands from xml and run them."""
from __future__ import annotations
import argparse
import datetime
import logging
from typing import List, Literal, TypedDict, Union
import wfc.wfc_control as wfc_control
import xml.etree.ElementTree as ET
import os
class RunInstructions(TypedDict):
loc: Literal["lexical", "hilbert", "spiral", "entropy", "anti-entropy", "simple", "random"]
choice: Literal["lexical", "rarest", "weighted", "random"]
backtracking: bool
global_constraint: Literal[False, "allpatterns"]
def string2bool(strn: Union[bool, str]) -> bool:
if isinstance(strn, bool):
return strn
return strn.lower() in ["true"]
def run_default(run_experiment: str = "simple", samples: str = "samples_reference.xml") -> None:
log_filename = f"log_{datetime.datetime.now().isoformat()}".replace(":", ".")
xdoc = ET.ElementTree(file=samples)
default_allowed_attempts = 10
default_backtracking = str(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", 1)
) # 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: List[RunInstructions] = [ # simple
{
"loc": "entropy",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
}
]
# run_instructions = [{"loc": "entropy", "choice": "weighted", "backtracking": True, "global_constraint": "allpatterns"}]
if run_experiment == "choice":
run_instructions = [
{
"loc": "lexical",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "random",
"choice": "weighted",
"backtracking": False,
"global_constraint": False,
},
{
"loc": "lexical",
"choice": "random",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "random",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "random",
"choice": "random",
"backtracking": False,
"global_constraint": False,
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": True,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": True,
"global_constraint": False,
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": True,
"global_constraint": "allpatterns",
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": True,
"global_constraint": "allpatterns",
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": False,
"global_constraint": "allpatterns",
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": False,
"global_constraint": "allpatterns",
},
]
if run_experiment == "heuristic":
run_instructions = [
{
"loc": "hilbert",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "spiral",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "anti-entropy",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "simple",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
{
"loc": "random",
"choice": "weighted",
"backtracking": backtracking,
"global_constraint": False,
},
]
if run_experiment == "backtracking":
run_instructions = [
{
"loc": "entropy",
"choice": "weighted",
"backtracking": True,
"global_constraint": "allpatterns",
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": False,
"global_constraint": "allpatterns",
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": True,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": False,
"global_constraint": False,
},
]
if run_experiment == "backtracking_heuristic":
run_instructions = [
{
"loc": "lexical",
"choice": "weighted",
"backtracking": True,
"global_constraint": "allpatterns",
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": False,
"global_constraint": "allpatterns",
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": True,
"global_constraint": False,
},
{
"loc": "lexical",
"choice": "weighted",
"backtracking": False,
"global_constraint": False,
},
{
"loc": "random",
"choice": "weighted",
"backtracking": True,
"global_constraint": "allpatterns",
},
{
"loc": "random",
"choice": "weighted",
"backtracking": False,
"global_constraint": "allpatterns",
},
{
"loc": "random",
"choice": "weighted",
"backtracking": True,
"global_constraint": False,
},
{
"loc": "random",
"choice": "weighted",
"backtracking": False,
"global_constraint": False,
},
]
if run_experiment == "choices":
run_instructions = [
{
"loc": "entropy",
"choice": "rarest",
"backtracking": False,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "weighted",
"backtracking": False,
"global_constraint": False,
},
{
"loc": "entropy",
"choice": "random",
"backtracking": False,
"global_constraint": False,
},
]
for experiment in run_instructions:
for x in range(screenshots):
print(f"-: {name} > {x}")
try:
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_constraint"],
log_filename=log_filename,
log_stats_to_output=log_stats_to_output,
visualize=visualize_experiment,
logging=True,
)
print(solution)
except Exception as exc:
print(f"Skipped because: {exc}")
if False: # 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"'
)
def main() -> None:
logging.basicConfig(level=logging.DEBUG)
parser = argparse.ArgumentParser(
description="Geneates examples from bundled samples which will be saved to the output/ directory.",
)
parser.add_argument(
"-e", "--experiment",
type=str,
default="simple",
choices=["simple", "choice", "choices", "heuristic", "backtracking", "backtracking_heuristic"],
help="Which experiment to run, defaults to simple.",
)
parser.add_argument(
"-s", "--samples",
type=str,
required=True,
metavar="XML_FILE",
default="samples_reference.xml",
help="An XML file with input data. If unsure then use '-s samples_reference.xml'",
)
args = parser.parse_args()
run_default(run_experiment=args.experiment, samples=args.samples)
if __name__ == "__main__":
main()
gitextract_a9o9q6ic/ ├── .github/ │ └── workflows/ │ └── python-package.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── doc/ │ ├── conf.py │ ├── dot/ │ │ ├── chain.dot │ │ ├── dependency.dot │ │ └── design.dot │ └── index.rst ├── images/ │ └── samples/ │ ├── Castle/ │ │ └── data.xml │ ├── Circles/ │ │ └── data.xml │ ├── Circuit/ │ │ └── data.xml │ ├── Knots/ │ │ └── data.xml │ ├── Rooms/ │ │ └── data.xml │ └── Summer/ │ └── data.xml ├── pyproject.toml ├── requirements.txt ├── samples.xml ├── samples_cats.xml ├── samples_original.xml ├── samples_reference.xml ├── samples_reference_continue.xml ├── samples_reference_nohogs.xml ├── samples_test.xml ├── samples_test_ground.xml ├── samples_test_vis.xml ├── setup.cfg ├── setup.py ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── test_wfc_adjacency.py │ ├── test_wfc_patterns.py │ ├── test_wfc_solver.py │ └── test_wfc_tiles.py ├── wfc/ │ ├── __init__.py │ ├── py.typed │ ├── wfc_adjacency.py │ ├── wfc_control.py │ ├── wfc_patterns.py │ ├── wfc_solver.py │ ├── wfc_tiles.py │ ├── wfc_utilities.py │ └── wfc_visualize.py └── wfc_run.py
SYMBOL INDEX (91 symbols across 13 files)
FILE: tests/conftest.py
class Resources (line 8) | class Resources:
method get_image (line 9) | def get_image(self, image: str) -> str:
function resources (line 14) | def resources() -> Resources:
FILE: tests/test_wfc_adjacency.py
function test_adjacency_extraction (line 11) | def test_adjacency_extraction(resources: Resources) -> None:
FILE: tests/test_wfc_patterns.py
function test_unique_patterns_2d (line 10) | def test_unique_patterns_2d(resources: Resources) -> None:
function test_make_pattern_catalog (line 24) | def test_make_pattern_catalog(resources: Resources) -> None:
function test_pattern_to_tile (line 39) | def test_pattern_to_tile(resources: Resources) -> None:
FILE: tests/test_wfc_solver.py
function test_makeWave (line 15) | def test_makeWave() -> None:
function test_entropyLocationHeuristic (line 25) | def test_entropyLocationHeuristic() -> None:
function test_observe (line 37) | def test_observe() -> None:
function test_propagate (line 57) | def test_propagate() -> None:
function test_run (line 92) | def test_run() -> None:
FILE: tests/test_wfc_tiles.py
function test_image_to_tile (line 9) | def test_image_to_tile(resources: Resources) -> None:
function test_make_tile_catalog (line 17) | def test_make_tile_catalog(resources: Resources) -> None:
FILE: wfc/wfc_adjacency.py
function adjacency_extraction (line 8) | def adjacency_extraction(
FILE: wfc/wfc_control.py
function visualize_tiles (line 50) | def visualize_tiles(unique_tiles, tile_catalog, tile_grid):
function visualize_patterns (line 56) | def visualize_patterns(pattern_catalog, tile_catalog, pattern_weights, p...
function make_log_stats (line 63) | def make_log_stats() -> Callable[[Dict[str, Any], str], None]:
function execute_wfc (line 82) | def execute_wfc(
FILE: wfc/wfc_patterns.py
function unique_patterns_2d (line 14) | def unique_patterns_2d(agrid: NDArray[np.int64], ksize: int, periodic_in...
function unique_patterns_brute_force (line 51) | def unique_patterns_brute_force(grid, size, periodic_input):
function make_pattern_catalog (line 74) | def make_pattern_catalog(
function identity_grid (line 97) | def identity_grid(grid):
function reflect_grid (line 103) | def reflect_grid(grid):
function rotate_grid (line 108) | def rotate_grid(grid):
function make_pattern_catalog_with_rotations (line 113) | def make_pattern_catalog_with_rotations(
function pattern_grid_to_tiles (line 175) | def pattern_grid_to_tiles(
FILE: wfc/wfc_solver.py
class Contradiction (line 19) | class Contradiction(Exception):
class TimedOut (line 25) | class TimedOut(Exception):
class StopEarly (line 31) | class StopEarly(Exception):
class Solver (line 37) | class Solver:
method __init__ (line 40) | def __init__(
method is_solved (line 65) | def is_solved(self) -> bool:
method solve_next (line 69) | def solve_next(
method solve (line 103) | def solve(
function makeWave (line 114) | def makeWave(n: int, w: int, h: int, ground: Optional[Iterable[int]] = N...
function makeAdj (line 127) | def makeAdj(
function makeRandomLocationHeuristic (line 147) | def makeRandomLocationHeuristic(preferences: NDArray[np.floating[Any]]) ...
function makeEntropyLocationHeuristic (line 157) | def makeEntropyLocationHeuristic(preferences: NDArray[np.floating[Any]])...
function makeAntiEntropyLocationHeuristic (line 171) | def makeAntiEntropyLocationHeuristic(
function spiral_transforms (line 187) | def spiral_transforms() -> Iterator[Tuple[int, int]]:
function spiral_coords (line 203) | def spiral_coords(x: int, y: int) -> Iterator[Tuple[int, int]]:
function fill_with_curve (line 210) | def fill_with_curve(arr: NDArray[np.floating[T]], curve_gen: Iterable[It...
function makeSpiralLocationHeuristic (line 227) | def makeSpiralLocationHeuristic(preferences: NDArray[np.floating[Any]]) ...
function makeHilbertLocationHeuristic (line 245) | def makeHilbertLocationHeuristic(preferences: NDArray[np.floating[Any]])...
function simpleLocationHeuristic (line 263) | def simpleLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
function lexicalLocationHeuristic (line 272) | def lexicalLocationHeuristic(wave: NDArray[np.bool_]) -> Tuple[int, int]:
function lexicalPatternHeuristic (line 283) | def lexicalPatternHeuristic(weights: NDArray[np.bool_], wave: NDArray[np...
function makeWeightedPatternHeuristic (line 287) | def makeWeightedPatternHeuristic(weights: NDArray[np.floating[Any]]):
function makeRarestPatternHeuristic (line 300) | def makeRarestPatternHeuristic(weights: NDArray[np.floating[Any]]) -> Ca...
function makeMostCommonPatternHeuristic (line 315) | def makeMostCommonPatternHeuristic(
function makeRandomPatternHeuristic (line 331) | def makeRandomPatternHeuristic(weights: NDArray[np.floating[Any]]) -> Ca...
function make_global_use_all_patterns (line 348) | def make_global_use_all_patterns() -> Callable[[NDArray[np.bool_]], bool]:
function propagate (line 360) | def propagate(
function observe (line 425) | def observe(
function run (line 436) | def run(
FILE: wfc/wfc_tiles.py
function image_to_tiles (line 10) | def image_to_tiles(img: NDArray[np.integer], tile_size: int) -> NDArray[...
function make_tile_catalog (line 33) | def make_tile_catalog(
function tiles_to_images (line 56) | def tiles_to_images(tile_grid, tile_catalog):
FILE: wfc/wfc_utilities.py
function hash_downto (line 16) | def hash_downto(a: NDArray[np.integer], rank: int, seed: Any=0) -> NDArr...
function load_visualizer (line 35) | def load_visualizer(wfc_ns):
function find_pattern_center (line 55) | def find_pattern_center(wfc_ns):
FILE: wfc/wfc_visualize.py
function rgb_to_int (line 23) | def rgb_to_int(rgb_in):
function int_to_rgb (line 30) | def int_to_rgb(val):
function tile_to_image (line 38) | def tile_to_image(tile, tile_catalog, tile_size, visualize=False):
function argmax_unique (line 61) | def argmax_unique(arr, axis):
function make_solver_loggers (line 70) | def make_solver_loggers(filename, stats={}):
function make_solver_visualizers (line 127) | def make_solver_visualizers(
function figure_unified (line 334) | def figure_unified(figure_name_overall, filename, data):
function visualize_solver (line 361) | def visualize_solver(wave):
function make_figure_solver_image (line 367) | def make_figure_solver_image(plot_title, img):
function figure_solver_image (line 379) | def figure_solver_image(filename, plot_title, img):
function make_figure_solver_data (line 386) | def make_figure_solver_data(plot_title, data, min_count, max_count, cmap...
function figure_solver_data (line 398) | def figure_solver_data(filename, plot_title, data, min_count, max_count,...
function figure_wave_patterns (line 405) | def figure_wave_patterns(filename, pattern_left_count, max_count):
function tile_grid_to_average (line 419) | def tile_grid_to_average(
function tile_grid_to_image (line 459) | def tile_grid_to_image(
function figure_list_of_tiles (line 515) | def figure_list_of_tiles(unique_tiles, tile_catalog, output_filename="li...
function figure_false_color_tile_grid (line 529) | def figure_false_color_tile_grid(tile_grid, output_filename="./false_col...
function figure_tile_grid (line 541) | def figure_tile_grid(tile_grid, tile_catalog, tile_size):
function render_pattern (line 545) | def render_pattern(render_pattern, tile_catalog):
function figure_pattern_catalog (line 560) | def figure_pattern_catalog(
function render_tiles_to_output (line 588) | def render_tiles_to_output(
function blit (line 598) | def blit(destination, sprite, upper_left, layer=False, check=False):
class InvalidAdjacency (line 631) | class InvalidAdjacency(Exception):
function validate_adjacency (line 637) | def validate_adjacency(
function figure_adjacencies (line 672) | def figure_adjacencies(
FILE: wfc_run.py
class RunInstructions (line 14) | class RunInstructions(TypedDict):
function string2bool (line 21) | def string2bool(strn: Union[bool, str]) -> bool:
function run_default (line 27) | def run_default(run_experiment: str = "simple", samples: str = "samples_...
function main (line 329) | def main() -> None:
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (204K chars).
[
{
"path": ".github/workflows/python-package.yml",
"chars": 1757,
"preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
},
{
"path": ".gitignore",
"chars": 52,
"preview": "__pycache__\r\n/output/*\r\n/build\r\nlogs/\r\n*.egg-info/\r\n"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2020 Isaac Karth\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "MANIFEST.in",
"chars": 21,
"preview": "include wfc/py.typed\n"
},
{
"path": "README.md",
"chars": 2546,
"preview": "# wfc_2019f\n\nThis is my research implementation of WaveFunctionCollapse in Python. It has two goals:\n\n* Make it easier t"
},
{
"path": "doc/conf.py",
"chars": 1945,
"preview": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common op"
},
{
"path": "doc/dot/chain.dot",
"chars": 948,
"preview": "digraph {\r\n read_xml_command -> import_image -> make_tile_catalog -> make_pattern_catalog -> make_adjacency_matri"
},
{
"path": "doc/dot/dependency.dot",
"chars": 1285,
"preview": "digraph {\r\n wfc_run -> wfc_control\r\n wfc_control -> wfc_utilities\r\n wfc_control -> wfc_solver\r\n "
},
{
"path": "doc/dot/design.dot",
"chars": 5206,
"preview": "digraph {\r\n things_to_implement [label=\"{Things that aren't implemented yet|Intermediate visualization|timing and"
},
{
"path": "doc/index.rst",
"chars": 190,
"preview": "Documentation\n=============\n\nModule dependencies\n-------------------\n\n.. graphviz:: dot/dependency.dot\n\nDesign\n------\n\n."
},
{
"path": "images/samples/Castle/data.xml",
"chars": 6085,
"preview": "<<<<<<< HEAD\n<set size=\"7\">\r\n\t<tiles>\r\n\t\t<tile name=\"bridge\" symmetry=\"I\"/>\r\n\t\t<tile name=\"ground\" symmetry=\"X\"/>\r\n\t\t<ti"
},
{
"path": "images/samples/Circles/data.xml",
"chars": 9445,
"preview": "<<<<<<< HEAD\n<set size=\"32\">\r\n\t<tiles>\r\n\t\t<tile name=\"b_half\" symmetry=\"T\"/>\r\n\t\t<tile name=\"b_i\" symmetry=\"I\"/>\r\n\t\t<tile"
},
{
"path": "images/samples/Circuit/data.xml",
"chars": 15095,
"preview": "<<<<<<< HEAD\n<set size=\"14\">\r\n\t<tiles>\r\n\t\t<tile name=\"bridge\" symmetry=\"I\" weight=\"1.0\"/>\r\n\t\t<tile name=\"component\" symm"
},
{
"path": "images/samples/Knots/data.xml",
"chars": 5236,
"preview": "<<<<<<< HEAD\n<set size=\"10\">\r\n\t<tiles>\r\n\t\t<tile name=\"corner\" symmetry=\"L\"/>\r\n\t\t<tile name=\"cross\" symmetry=\"I\"/>\r\n\t\t<ti"
},
{
"path": "images/samples/Rooms/data.xml",
"chars": 4432,
"preview": "<<<<<<< HEAD\n<set size=\"3\">\r\n\t<tiles>\r\n\t\t<tile name=\"bend\" symmetry=\"L\" weight=\"0.5\"/>\r\n\t\t<tile name=\"corner\" symmetry=\""
},
{
"path": "images/samples/Summer/data.xml",
"chars": 6289,
"preview": "<<<<<<< HEAD\n<set size=\"48\" unique=\"True\">\r\n\t<tiles>\r\n\t\t<tile name=\"cliff\" symmetry=\"T\"/>\r\n\t\t<tile name=\"cliffcorner\" sy"
},
{
"path": "pyproject.toml",
"chars": 769,
"preview": "[project]\nname = \"wfc_python\"\nversion = \"0.0.0\"\ndescription = \"Implementation of wave function collapse in Python.\"\nread"
},
{
"path": "requirements.txt",
"chars": 107,
"preview": "numpy\nhilbertcurve>=2\nimageio\nmatplotlib\nscipy\n\n# Testing\npytest\ntypes-setuptools\n\n# Documentation.\nsphinx\n"
},
{
"path": "samples.xml",
"chars": 96,
"preview": "<samples>\n\t<overlapping name=\"Red Maze\" N=\"2\" periodic=\"True\" width=\"8\" height=\"6\"/>\n</samples>\n"
},
{
"path": "samples_cats.xml",
"chars": 1894,
"preview": "<samples>\n\t<overlapping name=\"Cat\" N=\"3\" symmetry=\"2\" periodic=\"True\" width=\"80\" height=\"80\"/>\n\t<overlapping name=\"Cats\""
},
{
"path": "samples_original.xml",
"chars": 4624,
"preview": "<samples>\n\t<overlapping name=\"Chess\" N=\"2\" periodic=\"True\"/>\n\t<overlapping name=\"Chess\" N=\"2\" width=\"47\" height=\"47\" per"
},
{
"path": "samples_reference.xml",
"chars": 4854,
"preview": "<samples>\n\t<comment name=\"Skyline\" N=\"3\" symmetry=\"2\" ground=\"-1\" periodic=\"True\"/>\n\t<overlapping name=\"Chess\" N=\"2\" per"
},
{
"path": "samples_reference_continue.xml",
"chars": 4387,
"preview": "<samples>\n\t<overlapping name=\"Knot\" N=\"3\" periodic=\"True\"/>\n\t<overlapping name=\"Less Rooms\" N=\"3\" periodic=\"True\"/>\n\t<ov"
},
{
"path": "samples_reference_nohogs.xml",
"chars": 4734,
"preview": "<samples>\n\t<overlapping name=\"Chess\" N=\"2\" periodic=\"True\"/>\n\t<overlapping name=\"Chess\" N=\"2\" width=\"47\" height=\"47\" per"
},
{
"path": "samples_test.xml",
"chars": 115,
"preview": "<samples>\n\t<overlapping name=\"test_pattern\" N=\"2\" symmetry=\"1\" periodic=\"False\" width=\"11\" height=\"9\"/>\n</samples>\n"
},
{
"path": "samples_test_ground.xml",
"chars": 1293,
"preview": "<samples>\n\t<overlapping name=\"Flowers\" N=\"3\" symmetry=\"2\" ground=\"-4\" periodic=\"False\" width=\"8\" height=\"12\"/>\n\t<overlap"
},
{
"path": "samples_test_vis.xml",
"chars": 861,
"preview": "<samples>\n <overlapping name=\"Flowers\" N=\"3\" symmetry=\"2\" ground=\"-4\" periodic=\"True\"/>\n <overlapping name=\"Skyline\" N"
},
{
"path": "setup.cfg",
"chars": 224,
"preview": "[metadata]\nname = wfc_python\nversion = 0.0.0\n\n[options]\npackages = wfc\ninclude_package_data = True\ninstall_requires =\n "
},
{
"path": "setup.py",
"chars": 92,
"preview": "#!/usr/bin/env python\n\nimport setuptools\n\nif __name__ == \"__main__\":\n setuptools.setup()\n"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/conftest.py",
"chars": 332,
"preview": "from __future__ import annotations\n\nimport os.path\nimport pytest\n\nPROJECT_ROOT = os.path.dirname(os.path.dirname(__file_"
},
{
"path": "tests/test_wfc_adjacency.py",
"chars": 1635,
"preview": "\"\"\"Convert input data to adjacency information\"\"\"\nfrom __future__ import annotations\n\nimport imageio # type: ignore\nfro"
},
{
"path": "tests/test_wfc_patterns.py",
"chars": 1931,
"preview": "from __future__ import annotations\n\nimport imageio # type: ignore\nimport numpy as np\nfrom tests.conftest import Resourc"
},
{
"path": "tests/test_wfc_solver.py",
"chars": 4762,
"preview": "from __future__ import annotations\r\n\r\nfrom typing import Any, Dict, List, Set, Tuple\r\nfrom numpy.typing import NDArray\r\n"
},
{
"path": "tests/test_wfc_tiles.py",
"chars": 851,
"preview": "\"\"\"Breaks an image into consituant tiles.\"\"\"\nfrom __future__ import annotations\n\nimport imageio # type: ignore\nfrom tes"
},
{
"path": "wfc/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "wfc/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "wfc/wfc_adjacency.py",
"chars": 2183,
"preview": "\"\"\"Convert input data to adjacency information\"\"\"\nfrom __future__ import annotations\n\nfrom typing import Dict, List, Tup"
},
{
"path": "wfc/wfc_control.py",
"chars": 16066,
"preview": "from __future__ import annotations\n\nimport datetime\nfrom typing import Any, Callable, Dict, List, Literal, Optional, Set"
},
{
"path": "wfc/wfc_patterns.py",
"chars": 6831,
"preview": "\"Extract patterns from grids of tiles.\"\nfrom __future__ import annotations\n\nimport logging\nfrom typing import Any, Dict,"
},
{
"path": "wfc/wfc_solver.py",
"chars": 18495,
"preview": "from __future__ import annotations\r\n\r\nimport logging\r\nfrom typing import Any, Callable, Collection, Dict, Iterable, Iter"
},
{
"path": "wfc/wfc_tiles.py",
"chars": 2231,
"preview": "\"\"\"Breaks an image into consituant tiles.\"\"\"\nfrom __future__ import annotations\n\nfrom typing import Dict, Tuple\nimport n"
},
{
"path": "wfc/wfc_utilities.py",
"chars": 1867,
"preview": "\"\"\"Utility data and functions for WFC\"\"\"\nfrom __future__ import annotations\n\nimport collections\nimport logging\nfrom typi"
},
{
"path": "wfc/wfc_visualize.py",
"chars": 27230,
"preview": "\"Visualize the patterns into tiles and so on.\"\r\nfrom __future__ import annotations\r\n\r\nimport logging\r\nimport math\r\nimpor"
},
{
"path": "wfc_run.py",
"chars": 14208,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"Base code to load commands from xml and run them.\"\"\"\nfrom __future__ import annotations\n\nimpo"
}
]
About this extraction
This page contains the full source code of the ikarth/wfc_2019f GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (180.0 KB), approximately 50.6k tokens, and a symbol index with 91 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.