Merge pull request #2052 from almarklein/mc_lewiner

FEAT: Lewiner marching cubes algorithm
This commit is contained in:
Josh Warner
2016-07-26 18:26:29 -05:00
committed by GitHub
16 changed files with 6301 additions and 57 deletions
+5 -1
View File
@@ -35,7 +35,8 @@
and more.
- Almar Klein
Binary heap class for graph algorithms
Binary heap class and other improvements for graph algorithms
Lewiner variant of marching cubes algorithm
- Lee Kamentsky and Thouis Jones of the CellProfiler team, Broad Institute, MIT
Constant time per pixel median filter, edge detectors, and more.
@@ -229,6 +230,9 @@
- Alex Izvorski
Color spaces for YUV and related spaces
- Thomas Lewiner
Design and original implementation of the Lewiner marching cubes algorithm
- Jeff Hemmelgarn
Minimum threshold
+5 -2
View File
@@ -50,9 +50,12 @@ Library:
Extension: skimage.measure._moments_cy
Sources:
skimage/measure/_moments_cy.pyx
Extension: skimage.measure._marching_cubes_cy
Extension: skimage.measure._marching_cubes_classic_cy
Sources:
skimage/measure/_marching_cubes_cy.pyx
skimage/measure/_marching_cubes_classic_cy.pyx
Extension: skimage.measure._marching_cubes_lewiner_cy
Sources:
skimage/measure/_marching_cubes_lewiner_cy.pyx
Extension: skimage.graph._mcp
Sources:
skimage/graph/_mcp.pyx
+2 -2
View File
@@ -33,10 +33,10 @@ ellip_double = np.concatenate((ellip_base[:-1, ...],
ellip_base[2:, ...]), axis=0)
# Use marching cubes to obtain the surface mesh of these ellipsoids
verts, faces = measure.marching_cubes(ellip_double, 0)
verts, faces, normals, values = measure.marching_cubes(ellip_double, 0)
# Display resulting triangular mesh using Matplotlib. This can also be done
# with mayavi (see skimage.measure.marching_cubes docstring).
# with mayavi or visvis (see skimage.measure.marching_cubes docstring).
fig = plt.figure(figsize=(10, 12))
ax = fig.add_subplot(111, projection='3d')
+6 -2
View File
@@ -1,6 +1,8 @@
from ._find_contours import find_contours
from ._marching_cubes import (marching_cubes, mesh_surface_area,
correct_mesh_orientation)
from ._marching_cubes_lewiner import marching_cubes, marching_cubes_lewiner
from ._marching_cubes_classic import (marching_cubes_classic,
mesh_surface_area,
correct_mesh_orientation)
from ._regionprops import regionprops, perimeter
from .simple_metrics import compare_mse, compare_nrmse, compare_psnr
from ._structural_similarity import compare_ssim, structural_similarity
@@ -29,6 +31,8 @@ __all__ = ['find_contours',
'moments_normalized',
'moments_hu',
'marching_cubes',
'marching_cubes_lewiner',
'marching_cubes_classic',
'mesh_surface_area',
'correct_mesh_orientation',
'profile_line',
@@ -1,20 +1,24 @@
import numpy as np
import scipy.ndimage as ndi
from .._shared.utils import warn
from . import _marching_cubes_cy
from . import _marching_cubes_classic_cy
def marching_cubes(volume, level, spacing=(1., 1., 1.),
gradient_direction='descent'):
def marching_cubes_classic(volume, level=None, spacing=(1., 1., 1.),
gradient_direction='descent'):
"""
Marching cubes algorithm to find iso-valued surfaces in 3d volumetric data
Classic marching cubes algorithm to find surfaces in 3d volumetric data.
Note that the ``marching_cubes()`` algorithm is recommended over
this algorithm, because it's faster and produces better results.
Parameters
----------
volume : (M, N, P) array of doubles
Input data volume to find isosurfaces. Will be cast to `np.float64`.
level : float
Contour value to search for isosurfaces in `volume`.
Contour value to search for isosurfaces in `volume`. If not
given or None, the average of the min and max of vol is used.
spacing : length-3 tuple of floats
Voxel spacing in spatial dimensions corresponding to numpy array
indexing dimensions (M, N, P) as in `volume`.
@@ -82,34 +86,27 @@ def marching_cubes(volume, level, spacing=(1., 1., 1.),
To quantify the area of an isosurface generated by this algorithm, pass
outputs directly into `skimage.measure.mesh_surface_area`.
Regarding visualization of algorithm output, the ``mayavi`` package
is recommended. To contour a volume named `myvolume` about the level 0.0::
>>> from mayavi import mlab # doctest: +SKIP
>>> verts, faces = marching_cubes(myvolume, 0.0, (1., 1., 2.)) # doctest: +SKIP
>>> mlab.triangular_mesh([vert[0] for vert in verts],
... [vert[1] for vert in verts],
... [vert[2] for vert in verts],
... faces) # doctest: +SKIP
>>> mlab.show() # doctest: +SKIP
References
----------
.. [1] Lorensen, William and Harvey E. Cline. Marching Cubes: A High
Resolution 3D Surface Construction Algorithm. Computer Graphics
(SIGGRAPH 87 Proceedings) 21(4) July 1987, p. 163-170).
DOI: 10.1145/37401.37422
See Also
--------
skimage.measure.correct_mesh_orientation
skimage.measure.marching_cubes
skimage.measure.mesh_surface_area
"""
# Check inputs and ensure `volume` is C-contiguous for memoryviews
if volume.ndim != 3:
raise ValueError("Input volume must have 3 dimensions.")
if level < volume.min() or level > volume.max():
raise ValueError("Contour level must be within volume data range.")
if level is None:
level = 0.5 * (volume.min() + volume.max())
else:
level = float(level)
if level < volume.min() or level > volume.max():
raise ValueError("Surface level must be within volume data range.")
if len(spacing) != 3:
raise ValueError("`spacing` must consist of three floats.")
@@ -121,18 +118,16 @@ def marching_cubes(volume, level, spacing=(1., 1., 1.),
# Note: this algorithm is fast, but returns degenerate "triangles" which
# have repeated vertices - and equivalent vertices are redundantly
# placed in every triangle they connect with.
raw_faces = _marching_cubes_cy.iterate_and_store_3d(volume, float(level))
raw_faces = _marching_cubes_classic_cy.iterate_and_store_3d(volume,
float(level))
# Find and collect unique vertices, storing triangle verts as indices.
# Returns a true mesh with no degenerate faces.
verts, faces = _marching_cubes_cy.unpack_unique_verts(raw_faces)
verts, faces = _marching_cubes_classic_cy.unpack_unique_verts(raw_faces)
verts = np.asarray(verts)
faces = np.asarray(faces)
# Calculate gradient of `volume`, then interpolate to vertices in `verts`
grad_x, grad_y, grad_z = np.gradient(volume)
# Fancy indexing to define two vector arrays from triangle vertices
faces = _correct_mesh_orientation(volume, verts[faces], faces, spacing,
gradient_direction)
@@ -160,7 +155,7 @@ def mesh_surface_area(verts, faces):
Notes
-----
The arguments expected by this function are the exact outputs from
The arguments expected by this function are the first two outputs from
`skimage.measure.marching_cubes`. For unit correct output, ensure correct
`spacing` was passed to `skimage.measure.marching_cubes`.
@@ -170,6 +165,7 @@ def mesh_surface_area(verts, faces):
See Also
--------
skimage.measure.marching_cubes
skimage.measure.marching_cubes_classic
skimage.measure.correct_mesh_orientation
"""
@@ -217,7 +213,7 @@ def correct_mesh_orientation(volume, verts, faces, spacing=(1., 1., 1.),
Certain applications and mesh processing algorithms require all faces
to be oriented in a consistent way. Generally, this means a normal vector
points "out" of the meshed shapes. This algorithm corrects the output from
`skimage.measure.marching_cubes` by flipping the orientation of
`skimage.measure.marching_cubes_classic` by flipping the orientation of
mis-oriented faces.
Because marching cubes could be used to find isosurfaces either on
@@ -228,21 +224,21 @@ def correct_mesh_orientation(volume, verts, faces, spacing=(1., 1., 1.),
completely incorrectly, try changing this option.
The arguments expected by this function are the exact outputs from
`skimage.measure.marching_cubes`. Only `faces` is corrected and returned,
as the vertices do not change; only the order in which they are
`skimage.measure.marching_cubes_classic`. Only `faces` is corrected and
returned, as the vertices do not change; only the order in which they are
referenced.
This algorithm assumes ``faces`` provided are all triangles.
See Also
--------
skimage.measure.marching_cubes
skimage.measure.marching_cubes_classic
skimage.measure.mesh_surface_area
"""
warn(DeprecationWarning("`correct_mesh_orientation` is deprecated for "
"removal as `marching_cubes` now guarantess "
"correct mesh orientation."))
"removal as `marching_cubes_classic` now "
"guarantees correct mesh orientation."))
verts = verts.copy()
verts[:, 0] /= spacing[0]
@@ -291,7 +287,7 @@ def _correct_mesh_orientation(volume, actual_verts, faces,
Certain applications and mesh processing algorithms require all faces
to be oriented in a consistent way. Generally, this means a normal vector
points "out" of the meshed shapes. This algorithm corrects the output from
`skimage.measure.marching_cubes` by flipping the orientation of
`skimage.measure.marching_cubes_classic` by flipping the orientation of
mis-oriented faces.
Because marching cubes could be used to find isosurfaces either on
@@ -302,7 +298,7 @@ def _correct_mesh_orientation(volume, actual_verts, faces,
completely incorrectly, try changing this option.
The arguments expected by this function are the exact outputs from
`skimage.measure.marching_cubes` except `actual_verts`, which is an
`skimage.measure.marching_cubes_classic` except `actual_verts`, which is an
uncorrected version of the fancy indexing operation `verts[faces]`.
Only `faces` is corrected and returned as the vertices do not change,
only the order in which they are referenced.
@@ -311,7 +307,7 @@ def _correct_mesh_orientation(volume, actual_verts, faces,
See Also
--------
skimage.measure.marching_cubes
skimage.measure.marching_cubes_classic
skimage.measure.mesh_surface_area
"""
+278
View File
@@ -0,0 +1,278 @@
import sys
import base64
import dis
import inspect
import numpy as np
if sys.version_info >= (3, ):
base64decode = base64.decodebytes
ordornot = lambda x:x
else:
base64decode = base64.decodestring
ordornot = ord
from . import _marching_cubes_lewiner_luts as mcluts
from . import _marching_cubes_lewiner_cy
from .._shared.utils import skimage_deprecation, warn
def _expected_output_args():
""" Get number of expected output args.
Please don't use this to influence the algorithmic bahaviour of a function.
For ``a, b, rest*, c = ...`` syntax, returns n + 0.1 (3.1 in this example).
"""
f = inspect.currentframe().f_back.f_back
i = f.f_lasti + 3
bytecode = f.f_code.co_code
instruction = ordornot(bytecode[i])
while True:
if instruction == dis.opmap['DUP_TOP']:
if ordornot(bytecode[i + 1]) == dis.opmap['UNPACK_SEQUENCE']:
return ordornot(bytecode[i + 2])
i += 4
instruction = ordornot(bytecode[i])
continue
if instruction == dis.opmap['STORE_NAME']:
return 1
if instruction == dis.opmap['UNPACK_SEQUENCE']:
return ordornot(bytecode[i + 1])
if instruction == dis.opmap.get('UNPACK_EX', -1): # py3k
return ordornot(bytecode[i + 1]) + ordornot(bytecode[i + 2]) + 0.1
return 0
def marching_cubes(volume, level=None, spacing=(1., 1., 1.),
gradient_direction='descent', step_size=1,
allow_degenerate=True, use_classic=False):
"""
Lewiner marching cubes algorithm to find surfaces in 3d volumetric data.
In contrast to ``marching_cubes_classic()``, this algorithm is faster,
resolves ambiguities, and guarantees topologically correct results.
Therefore, this algorithm generally a better choice, unless there
is a specific need for the classic algorithm.
Parameters
----------
volume : (M, N, P) array
Input data volume to find isosurfaces. Will internally be
converted to float32 if necessary.
level : float
Contour value to search for isosurfaces in `volume`. If not
given or None, the average of the min and max of vol is used.
spacing : length-3 tuple of floats
Voxel spacing in spatial dimensions corresponding to numpy array
indexing dimensions (M, N, P) as in `volume`.
gradient_direction : string
Controls if the mesh was generated from an isosurface with gradient
descent toward objects of interest (the default), or the opposite,
considering the *left-hand* rule.
The two options are:
* descent : Object was greater than exterior
* ascent : Exterior was greater than object
step_size : int
Step size in voxels. Default 1. Larger steps yield faster but
coarser results. The result will always be topologically correct
though.
allow_degenerate : bool
Whether to allow degenerate (i.e. zero-area) triangles in the
end-result. Default True. If False, degenerate triangles are
removed, at the cost of making the algorithm slower.
use_classic : bool
If given and True, the classic marching cubes by Lorensen (1987)
is used. This option is included for reference purposes. Note
that this algorithm has ambiguities and is not guaranteed to
produce a topologically correct result. The results with using
this option are *not* generally the same as the
``marching_cubes_classic()`` function.
Returns
-------
verts : (V, 3) array
Spatial coordinates for V unique mesh vertices. Coordinate order
matches input `volume` (M, N, P).
faces : (F, 3) array
Define triangular faces via referencing vertex indices from ``verts``.
This algorithm specifically outputs triangles, so each face has
exactly three indices.
normals : (V, 3) array
The normal direction at each vertex, as calculated from the
data.
values : (V, ) array
Gives a measure for the maximum value of the data in the local region
near each vertex. This can be used by visualization tools to apply
a colormap to the mesh.
Notes
-----
The algorithm [1] is an improved version of Chernyaev's Marching
Cubes 33 algorithm. It is an efficient algorithm that relies on
heavy use of lookup tables to handle the many different cases,
keeping the algorithm relatively easy. This implementation is
written in Cython, ported from Lewiner's C++ implementation.
To quantify the area of an isosurface generated by this algorithm, pass
verts and faces to `skimage.measure.mesh_surface_area`.
Regarding visualization of algorithm output, to contour a volume
named `myvolume` about the level 0.0, using the ``mayavi`` package::
>>> from mayavi import mlab # doctest: +SKIP
>>> verts, faces, normals, values = marching_cubes(myvolume, 0.0) # doctest: +SKIP
>>> mlab.triangular_mesh([vert[0] for vert in verts],
... [vert[1] for vert in verts],
... [vert[2] for vert in verts],
... faces) # doctest: +SKIP
>>> mlab.show() # doctest: +SKIP
Similarly using the ``visvis`` package::
>>> import visvis as vv # doctest: +SKIP
>>> verts, faces, normals, values = marching_cubes_classic(myvolume, 0.0) # doctest: +SKIP
>>> vv.mesh(np.fliplr(verts), faces, normals, values) # doctest: +SKIP
>>> vv.use().Run() # doctest: +SKIP
References
----------
.. [1] Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan
Tavares. Efficient implementation of Marching Cubes' cases with
topological guarantees. Journal of Graphics Tools 8(2)
pp. 1-15 (december 2003).
DOI: 10.1080/10867651.2003.10487582
See Also
--------
skimage.measure.marching_cubes_classic
skimage.measure.mesh_surface_area
"""
# This signature (output args) of this func changed after 0.12
try:
nout = _expected_output_args()
except Exception:
nout = 0 # always warn if, for whaterver reason, the black magic in above call fails
if nout <= 2:
warn(skimage_deprecation('`marching_cubes` now uses a better and '
'faster algorithm, and returns four instead '
'of two outputs (see docstring for details). '
'Backwards compatibility with 0.12 and prior '
'is available with `marching_cubes_classic`.'))
return marching_cubes_lewiner(volume, level, spacing, gradient_direction,
step_size, allow_degenerate, use_classic)
def marching_cubes_lewiner(volume, level=None, spacing=(1., 1., 1.),
gradient_direction='descent', step_size=1,
allow_degenerate=True, use_classic=False):
""" Alias for ``marching_cubes()``.
"""
# Check volume and ensure its in the format that the alg needs
if not isinstance(volume, np.ndarray) or (volume.ndim != 3):
raise ValueError('Input volume should be a 3D numpy array.')
if volume.shape[0] < 2 or volume.shape[1] < 2 or volume.shape[2] < 2:
raise ValueError("Input array must be at least 2x2x2.")
volume = np.ascontiguousarray(volume, np.float32) # no copy if not necessary
# Check/convert other inputs:
# level
if level is None:
level = 0.5 * (volume.min() + volume.max())
else:
level = float(level)
if level < volume.min() or level > volume.max():
raise ValueError("Surface level must be within volume data range.")
# spacing
if len(spacing) != 3:
raise ValueError("`spacing` must consist of three floats.")
# step_size
step_size = int(step_size)
if step_size < 1:
raise ValueError('step_size must be at least one.')
# use_classic
use_classic = bool(use_classic)
# Get LutProvider class (reuse if possible)
L = _get_mc_luts()
# Apply algorithm
func = _marching_cubes_lewiner_cy.marching_cubes
vertices, faces , normals, values = func(volume, level, L, step_size, use_classic)
if not len(vertices):
raise RuntimeError('No surface found at the given iso value.')
# Output in z-y-x order, as is common in skimage
vertices = np.fliplr(vertices)
normals = np.fliplr(normals)
# Finishing touches to output
faces.shape = -1, 3
if gradient_direction == 'descent':
# MC implementation is right-handed, but gradient_direction is left-handed
faces = np.fliplr(faces)
elif not gradient_direction == 'ascent':
raise ValueError("Incorrect input %s in `gradient_direction`, see "
"docstring." % (gradient_direction))
if spacing != (1, 1, 1):
vertices = vertices * np.r_[spacing]
if allow_degenerate:
return vertices, faces, normals, values
else:
fun = _marching_cubes_lewiner_cy.remove_degenerate_faces
return fun(vertices, faces, normals, values)
def _to_array(args):
shape, text = args
byts = base64decode(text.encode('utf-8'))
ar = np.frombuffer(byts, dtype='int8')
ar.shape = shape
return ar
# Map an edge-index to two relative pixel positions. The ege index
# represents a point that lies somewhere in between these pixels.
# Linear interpolation should be used to determine where it is exactly.
# 0
# 3 1 -> 0x
# 2 xx
EDGETORELATIVEPOSX = np.array([ [0,1],[1,1],[1,0],[0,0], [0,1],[1,1],[1,0],[0,0], [0,0],[1,1],[1,1],[0,0] ], 'int8')
EDGETORELATIVEPOSY = np.array([ [0,0],[0,1],[1,1],[1,0], [0,0],[0,1],[1,1],[1,0], [0,0],[0,0],[1,1],[1,1] ], 'int8')
EDGETORELATIVEPOSZ = np.array([ [0,0],[0,0],[0,0],[0,0], [1,1],[1,1],[1,1],[1,1], [0,1],[0,1],[0,1],[0,1] ], 'int8')
def _get_mc_luts():
""" Kind of lazy obtaining of the luts.
"""
if not hasattr(mcluts, 'THE_LUTS'):
mcluts.THE_LUTS = _marching_cubes_lewiner_cy.LutProvider(
EDGETORELATIVEPOSX, EDGETORELATIVEPOSY, EDGETORELATIVEPOSZ,
_to_array(mcluts.CASESCLASSIC), _to_array(mcluts.CASES),
_to_array(mcluts.TILING1), _to_array(mcluts.TILING2), _to_array(mcluts.TILING3_1), _to_array(mcluts.TILING3_2),
_to_array(mcluts.TILING4_1), _to_array(mcluts.TILING4_2), _to_array(mcluts.TILING5), _to_array(mcluts.TILING6_1_1),
_to_array(mcluts.TILING6_1_2), _to_array(mcluts.TILING6_2), _to_array(mcluts.TILING7_1),
_to_array(mcluts.TILING7_2), _to_array(mcluts.TILING7_3), _to_array(mcluts.TILING7_4_1),
_to_array(mcluts.TILING7_4_2), _to_array(mcluts.TILING8), _to_array(mcluts.TILING9),
_to_array(mcluts.TILING10_1_1), _to_array(mcluts.TILING10_1_1_), _to_array(mcluts.TILING10_1_2),
_to_array(mcluts.TILING10_2), _to_array(mcluts.TILING10_2_), _to_array(mcluts.TILING11),
_to_array(mcluts.TILING12_1_1), _to_array(mcluts.TILING12_1_1_), _to_array(mcluts.TILING12_1_2),
_to_array(mcluts.TILING12_2), _to_array(mcluts.TILING12_2_), _to_array(mcluts.TILING13_1),
_to_array(mcluts.TILING13_1_), _to_array(mcluts.TILING13_2), _to_array(mcluts.TILING13_2_),
_to_array(mcluts.TILING13_3), _to_array(mcluts.TILING13_3_), _to_array(mcluts.TILING13_4),
_to_array(mcluts.TILING13_5_1), _to_array(mcluts.TILING13_5_2), _to_array(mcluts.TILING14),
_to_array(mcluts.TEST3), _to_array(mcluts.TEST4), _to_array(mcluts.TEST6),
_to_array(mcluts.TEST7), _to_array(mcluts.TEST10), _to_array(mcluts.TEST12),
_to_array(mcluts.TEST13), _to_array(mcluts.SUBCONFIG13),
)
return mcluts.THE_LUTS
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,528 @@
# -*- coding: utf-8 -*-
# This file was auto-generated from LookUpTable.h by createluts.py.
# The luts are Copyright (C) 2002 by Thomas Lewiner
#static const char casesClassic[256][16]
CASESCLASSIC = (256, 16), """
/////////////////////wAIA/////////////////8AAQn/////////////////AQgDCQgB////
/////////wECCv////////////////8ACAMBAgr/////////////CQIKAAIJ/////////////wII
AwIKCAoJCP////////8DCwL/////////////////AAsCCAsA/////////////wEJAAIDC///////
//////8BCwIBCQsJCAv/////////AwoBCwoD/////////////wAKAQAICggLCv////////8DCQAD
CwkLCgn/////////CQgKCggL/////////////wQHCP////////////////8EAwAHAwT/////////
////AAEJCAQH/////////////wQBCQQHAQcDAf////////8BAgoIBAf/////////////AwQHAwAE
AQIK/////////wkCCgkAAggEB/////////8CCgkCCQcCBwMHCQT/////CAQHAwsC////////////
/wsEBwsCBAIABP////////8JAAEIBAcCAwv/////////BAcLCQQLCQsCCQIB/////wMKAQMLCgcI
BP////////8BCwoBBAsBAAQHCwT/////BAcICQALCQsKCwAD/////wQHCwQLCQkLCv////////8J
BQT/////////////////CQUEAAgD/////////////wAFBAEFAP////////////8IBQQIAwUDAQX/
////////AQIKCQUE/////////////wMACAECCgQJBf////////8FAgoFBAIEAAL/////////AgoF
AwIFAwUEAwQI/////wkFBAIDC/////////////8ACwIACAsECQX/////////AAUEAAEFAgML////
/////wIBBQIFCAIICwQIBf////8KAwsKAQMJBQT/////////BAkFAAgBCAoBCAsK/////wUEAAUA
CwULCgsAA/////8FBAgFCAoKCAv/////////CQcIBQcJ/////////////wkDAAkFAwUHA///////
//8ABwgAAQcBBQf/////////AQUDAwUH/////////////wkHCAkFBwoBAv////////8KAQIJBQAF
AwAFBwP/////CAACCAIFCAUHCgUC/////wIKBQIFAwMFB/////////8HCQUHCAkDCwL/////////
CQUHCQcCCQIAAgcL/////wIDCwABCAEHCAEFB/////8LAgELAQcHAQX/////////CQUICAUHCgED
CgML/////wUHAAUACQcLAAEACgsKAP8LCgALAAMKBQAIAAcFBwD/CwoFBwsF/////////////woG
Bf////////////////8ACAMFCgb/////////////CQABBQoG/////////////wEIAwEJCAUKBv//
//////8BBgUCBgH/////////////AQYFAQIGAwAI/////////wkGBQkABgACBv////////8FCQgF
CAIFAgYDAgj/////AgMLCgYF/////////////wsACAsCAAoGBf////////8AAQkCAwsFCgb/////
////BQoGAQkCCQsCCQgL/////wYDCwYFAwUBA/////////8ACAsACwUABQEFCwb/////AwsGAAMG
AAYFAAUJ/////wYFCQYJCwsJCP////////8FCgYEBwj/////////////BAMABAcDBgUK////////
/wEJAAUKBggEB/////////8KBgUBCQcBBwMHCQT/////BgECBgUBBAcI/////////wECBQUCBgMA
BAMEB/////8IBAcJAAUABgUAAgb/////BwMJBwkEAwIJBQkGAgYJ/wMLAgcIBAoGBf////////8F
CgYEBwIEAgACBwv/////AAEJBAcIAgMLBQoG/////wkCAQkLAgkECwcLBAUKBv8IBAcDCwUDBQEF
Cwb/////BQELBQsGAQALBwsEAAQL/wAFCQAGBQADBgsGAwgEB/8GBQkGCQsEBwkHCwn/////CgQJ
BgQK/////////////wQKBgQJCgAIA/////////8KAAEKBgAGBAD/////////CAMBCAEGCAYEBgEK
/////wEECQECBAIGBP////////8DAAgBAgkCBAkCBgT/////AAIEBAIG/////////////wgDAggC
BAQCBv////////8KBAkKBgQLAgP/////////AAgCAggLBAkKBAoG/////wMLAgABBgAGBAYBCv//
//8GBAEGAQoECAECAQsICwH/CQYECQMGCQEDCwYD/////wgLAQgBAAsGAQkBBAYEAf8DCwYDBgAA
BgT/////////BgQICwYI/////////////wcKBgcICggJCv////////8ABwMACgcACQoGBwr/////
CgYHAQoHAQcIAQgA/////woGBwoHAQEHA/////////8BAgYBBggBCAkIBgf/////AgYJAgkBBgcJ
AAkDBwMJ/wcIAAcABgYAAv////////8HAwIGBwL/////////////AgMLCgYICggJCAYH/////wIA
BwIHCwAJBwYHCgkKB/8BCAABBwgBCgcGBwoCAwv/CwIBCwEHCgYBBgcB/////wgJBggGBwkBBgsG
AwEDBv8ACQELBgf/////////////BwgABwAGAwsACwYA/////wcLBv////////////////8HBgv/
////////////////AwAICwcG/////////////wABCQsHBv////////////8IAQkIAwELBwb/////
////CgECBgsH/////////////wECCgMACAYLB/////////8CCQACCgkGCwf/////////BgsHAgoD
CggDCgkI/////wcCAwYCB/////////////8HAAgHBgAGAgD/////////AgcGAgMHAAEJ////////
/wEGAgEIBgEJCAgHBv////8KBwYKAQcBAwf/////////CgcGAQcKAQgHAQAI/////wADBwAHCgAK
CQYKB/////8HBgoHCggICgn/////////BggECwgG/////////////wMGCwMABgAEBv////////8I
BgsIBAYJAAH/////////CQQGCQYDCQMBCwMG/////wYIBAYLCAIKAf////////8BAgoDAAsABgsA
BAb/////BAsIBAYLAAIJAgoJ/////woJAwoDAgkEAwsDBgQGA/8IAgMIBAIEBgL/////////AAQC
BAYC/////////////wEJAAIDBAIEBgQDCP////8BCQQBBAICBAb/////////CAEDCAYBCAQGBgoB
/////woBAAoABgYABP////////8EBgMEAwgGCgMAAwkKCQP/CgkEBgoE/////////////wQJBQcG
C/////////////8ACAMECQULBwb/////////BQABBQQABwYL/////////wsHBggDBAMFBAMBBf//
//8JBQQKAQIHBgv/////////BgsHAQIKAAgDBAkF/////wcGCwUECgQCCgQAAv////8DBAgDBQQD
AgUKBQILBwb/BwIDBwYCBQQJ/////////wkFBAAIBgAGAgYIB/////8DBgIDBwYBBQAFBAD/////
BgIIBggHAgEIBAgFAQUI/wkFBAoBBgEHBgEDB/////8BBgoBBwYBAAcIBwAJBQT/BAAKBAoFAAMK
BgoHAwcK/wcGCgcKCAUECgQICv////8GCQUGCwkLCAn/////////AwYLAAYDAAUGAAkF/////wAL
CAAFCwABBQUGC/////8GCwMGAwUFAwH/////////AQIKCQULCQsICwUG/////wALAwAGCwAJBgUG
CQECCv8LCAULBQYIAAUKBQIAAgX/BgsDBgMFAgoDCgUD/////wUICQUCCAUGAgMIAv////8JBQYJ
BgAABgL/////////AQUIAQgABQYIAwgCBgII/wEFBgIBBv////////////8BAwYBBgoDCAYFBgkI
CQb/CgEACgAGCQUABQYA/////wADCAUGCv////////////8KBQb/////////////////CwUKBwUL
/////////////wsFCgsHBQgDAP////////8FCwcFCgsBCQD/////////CgcFCgsHCQgBCAMB////
/wsBAgsHAQcFAf////////8ACAMBAgcBBwUHAgv/////CQcFCQIHCQACAgsH/////wcFAgcCCwUJ
AgMCCAkIAv8CBQoCAwUDBwX/////////CAIACAUCCAcFCgIF/////wkAAQUKAwUDBwMKAv////8J
CAIJAgEIBwIKAgUHBQL/AQMFAwcF/////////////wAIBwAHAQEHBf////////8JAAMJAwUFAwf/
////////CQgHBQkH/////////////wUIBAUKCAoLCP////////8FAAQFCwAFCgsLAwD/////AAEJ
CAQKCAoLCgQF/////woLBAoEBQsDBAkEAQMBBP8CBQECCAUCCwgEBQj/////AAQLAAsDBAULAgsB
BQEL/wACBQAFCQILBQQFCAsIBf8JBAUCCwP/////////////AgUKAwUCAwQFAwgE/////wUKAgUC
BAQCAP////////8DCgIDBQoDCAUEBQgAAQn/BQoCBQIEAQkCCQQC/////wgEBQgFAwMFAf//////
//8ABAUBAAX/////////////CAQFCAUDCQAFAAMF/////wkEBf////////////////8ECwcECQsJ
Cgv/////////AAgDBAkHCQsHCQoL/////wEKCwELBAEEAAcEC/////8DAQQDBAgBCgQHBAsKCwT/
BAsHCQsECQILCQEC/////wkHBAkLBwkBCwILAQAIA/8LBwQLBAICBAD/////////CwcECwQCCAME
AwIE/////wIJCgIHCQIDBwcECf////8JCgcJBwQKAgcIBwACAAf/AwcKAwoCBwQKAQoABAAK/wEK
AggHBP////////////8ECQEEAQcHAQP/////////BAkBBAEHAAgBCAcB/////wQAAwcEA///////
//////8ECAf/////////////////CQoICgsI/////////////wMACQMJCwsJCv////////8AAQoA
CggICgv/////////AwEKCwMK/////////////wECCwELCQkLCP////////8DAAkDCQsBAgkCCwn/
////AAILCAAL/////////////wMCC/////////////////8CAwgCCAoKCAn/////////CQoCAAkC
/////////////wIDCAIICgABCAEKCP////8BCgL/////////////////AQMICQEI////////////
/wAJAf////////////////8AAwj//////////////////////////////////////w==
"""
#static const char cases[256][2]
CASES = (256, 2), """
AP8BAAEBAgABAgMAAgMFAAEDAgEDAwUBAgUFBAUJCAABBAICAwQFAgQCBgIGCQsAAwgFBQcDCQEG
EA4DDAwFGAEFAwECBAUDAwYHAAUKCQAEAwYEBgsOAQYRDAQLBgUZAggFBwUMCAEGEgwFDgcFHAYV
CwQMDwUeCgUGIAYnAgwBBgQAAwUGAAIGBgMFCw4AAwkGBQcEDAEFDgsDCQQFGgMKBgYHBQwCBhMK
AQwNBhgHBwwJDQEHCQwUBiEHDQMMAgoGBwUNCwIFEAwHCAMFHQYWCgIMEQYbDgkGIgUnAg4FFA4F
CQUFIAsKBiMFKQIQDBcGJQcOAxAGLgQGAxUBCAEHAwIEAQYBAwcHAQYKDAACBwUGBgwLAQUPCQIO
BgUbAgkFCAYNDgIGFAwGCgMGGQUSCAIMEAUfCwkFIgYoAg0DCwcCBg4MAwcGDQAMDgcIBhcMCgoE
BhwMFQcKBikDDQUVCQMLCAUhDBYHCwYqAw4OCwUkBiwCEQYvAxIEBwEJAgsGCAYPCgAFEQwICwcG
GgUTDgQMEgYdCAQFIwUoAg8FFgsFDBMGHg4KBiQGKwQECQcFJQcPAxEFLAITAxYBCgUXDAsOCAYf
CQYHDAUqAw8LCwYmBi0EBQUtAxMCFQELCAUFJgUrAhIFLgMUAhYBDAUvAhQDFwENAhcBDgEPAP8=
"""
#static const char tiling1[16][3]
TILING1 = (16, 3), """
AAgDAAEJAQIKAwsCBAcICQUECgYFBwYLBwsGCgUGCQQFBAgHAwILAQoCAAkBAAMI
"""
#static const char tiling2[24][6]
TILING2 = (24, 6), """
AQgDCQgBAAsCCAsABAMABwMECQIKAAIJAAUEAQUAAwoBCwoDAQYFAgYBBwIDBgIHCQcIBQcJBggE
CwgGCgQJBgQKCwUKBwULCwoFBwsFCgkEBgoEBgQICwYICQgHBQkHBwMCBgcCAQUGAgEGAwEKCwMK
AAQFAQAFCQoCAAkCBAADBwQDAAILCAALAQMICQEI
"""
#static const char tiling3_1[24][6]
TILING3_1 = (24, 6), """
AAgDAQIKCQUEAAgDAwAICwcGAQkAAgMLAAEJCAQHCQABBQoGAQIKCQUECgECBgsHCAQHAwsCAgML
CgYFBQoGBAcIBAkFBwYLBQkECwYHBgoFCAcECwMCBQYKBwQIAgsDAgEKBwsGCgIBBAUJAQAJBgoF
CQEABwQIAAkBCwMCCAADBgcLBAUJAwgAAwgACgIB
"""
#static const char tiling3_2[24][12]
TILING3_2 = (24, 12), """
CgMCCggDCgEACAoAAwQIAwUEAwAJBQMJBggHBgAIBgsDAAYDCwADCwkACwIBCQsBBwkEBwEJBwgA
AQcABgEKBgABCQAGCQYFBAoFBAIKBAkBAgQBBwILBwECBwYKAQcKAgcLAgQHAgMIBAIIBQsGBQML
BQoCAwUCCAYHCAoGCAQFCggFCwUGCwkFCwcECQsEBgULBQkLBAcLBAsJBwYIBgoIBQQIBQgKBgsF
CwMFAgoFAgUDCwcCBwQCCAMCCAIECwIHAgEHCgYHCgcBBQoECgIEAQkEAQQCCgEGAQAGBgAJBQYJ
BAkHCQEHAAgHAAcBAwALAAkLAQILAQsJBwgGCAAGAwsGAwYACAQDBAUDCQADCQMFAgMKAwgKAAEK
AAoI
"""
#static const char tiling4_1[8][6]
TILING4_1 = (8, 6), """
AAgDBQoGAAEJCwcGAQIKCAQHCQUEAgMLBAUJCwMCCgIBBwQICQEABgcLAwgABgoF
"""
#static const char tiling4_2[8][18]
TILING4_2 = (8, 18), """
CAUABQgGAwYIBgMKAAoDCgAFCQYBBgkHAAcJBwALAQsACwEGCgcCBwoEAQQKBAEIAggBCAIHCwQD
BAsFAgULBQIJAwkCCQMEAwQLBQsECwUCCQIFAgkDBAMJAgcKBAoHCgQBCAEEAQgCBwIIAQYJBwkG
CQcACwAHAAsBBgELAAUIBggFCAYDCgMGAwoABQAK
"""
#static const char tiling5[48][9]
TILING5 = (48, 9), """
AggDAgoICgkIAQsCAQkLCQgLBAEJBAcBBwMBCAUECAMFAwEFAAoBAAgKCAsKCwQHCwIEAgAEBwAI
BwYABgIACQMACQUDBQcDAwYLAwAGAAQGAwkAAwsJCwoJBQIKBQQCBAACCQYFCQAGAAIGAAcIAAEH
AQUHCgABCgYABgQABgMLBgUDBQEDCgcGCgEHAQMHAQQJAQIEAgYECwECCwcBBwUBCAIDCAQCBAYC
AgUKAgMFAwcFBwoGBwgKCAkKBgkFBgsJCwgJBQgEBQoICgsIBAsHBAkLCQoLBAcLBAsJCQsKBQQI
BQgKCggLBgUJBgkLCwkIBwYKBwoICAoJAgoFAgUDAwUHCAMCCAIEBAIGCwIBCwEHBwEFAQkEAQQC
AgQGCgYHCgcBAQcDBgsDBgMFBQMBCgEACgAGBgAEAAgHAAcBAQcFCQUGCQYAAAYCBQoCBQIEBAIA
AwAJAwkLCwkKAwsGAwYAAAYECQADCQMFBQMHBwgABwAGBgACCwcECwQCAgQAAAEKAAoICAoLCAQF
CAUDAwUBBAkBBAEHBwEDAQILAQsJCQsIAgMIAggKCggJ
"""
#static const char tiling6_1_1[48][9]
TILING6_1_1 = (48, 9), """
BgUKAwEICQgBCwcGCQMBAwkIAQIKBwAEAAcDAwAIBQIGAgUBBQQJAgALCAsACgYFCAIAAggLCgYF
AAQDBwMEAwAIBgQKCQoECAMACgcFBwoLCAQHCgACAAoJBwYLAAIJCgkCAgMLBAEFAQQAAAEJBgMH
AwYCCQABCwQGBAsICwcGAQUABAAFAAEJBwULCgsFBAcIAQMKCwoDCQUECwEDAQsKCgECCAUHBQgJ
CAQHAgYBBQEGAQIKBAYICwgGAgMLBQcJCAkHCwIDCQYEBgkKCQUEAwcCBgIHBAUJAgcDBwIGAwIL
BAYJCgkGCwMCCQcFBwkICgIBCAYEBggLBwQIAQYCBgEFAgEKBwUICQgFBAUJAwELCgsBCAcECgMB
AwoLCQEACwUHBQsKBgcLAAUBBQAEAQAJBgQLCAsECQEABwMGAgYDCwMCBQEEAAQBCwYHCQIAAgkK
BwQIAgAKCQoAAAMIBQcKCwoHCAADCgQGBAoJBQYKAwQABAMHBQYKAAIICwgCCQQFCwACAAsICAAD
BgIFAQUCCgIBBAAHAwcABgcLAQMJCAkDCgUGCAEDAQgJ
"""
#static const char tiling6_1_2[48][27]
TILING6_1_2 = (48, 27), """
AQwDDAoDBgMKAwYIBQgGCAUMDAkIAQkMDAUKAQwDAQsMCwEGCQYBBgkHDAcJCQgMDAgDCwcMBAwA
BAEMAQQKBwoECgcCDAIHBwMMDAMAAQIMBgwCBgMMAwYIBQgGCAUADAAFBQEMDAECAwAMAAwCDAkC
BQIJAgULBAsFCwQMDAgLAAgMDAQJAAwCAAoMCgAFCAUABQgGDAYICAsMDAsCCgYMBAwADAUACgAF
AAoDBgMKAwYMDAcDBAcMDAYFBAwGDAgGAwYIBgMKAAoDCgAMDAkKBAkMDAAIBQwHBQgMCAUACgAF
AAoDDAMKCgsMDAsHCAMMAgwAAggMCAIHCgcCBwoEDAQKCgkMDAkACAQMAgwADAsABwALAAcJBgkH
CQYMDAoJAgoMDAYLBQwBBQIMAgULBAsFCwQDDAMEBAAMDAABAgMMBwwDBwAMAAcJBgkHCQYBDAEG
BgIMDAIDAAEMBgwEBgkMCQYBCwEGAQsADAALCwgMDAgECQAMBQwBDAYBCwEGAQsABwALAAcMDAQA
BQQMDAcGBQwHDAkHAAcJBwALAQsACwEMDAoLBQoMDAEJAwwBDAgBBAEIAQQKBwoECgcMDAsKAwsM
DAcIAwwBAwkMCQMECwQDBAsFDAULCwoMDAoBCQUMBwwFBwoMCgcCCAIHAggBDAEICAkMDAkFCgEM
BgwCDAcCCAIHAggBBAEIAQQMDAUBBgUMDAQHBgwEDAoEAQQKBAEIAggBCAIMDAsIBgsMDAIKBwwF
DAsFAgULBQIJAwkCCQMMDAgJBwgMDAMLBAwGBAsMCwQDCQMEAwkCDAIJCQoMDAoGCwIMBwwDDAQD
CQMEAwkCBQIJAgUMDAYCBwYMDAUEAwwHAwQMBAMJAgkDCQIFDAUCAgYMDAYHBAUMBgwEDAsEAwQL
BAMJAgkDCQIMDAoJBgoMDAILBQwHBQsMCwUCCQIFAgkDDAMJCQgMDAgHCwMMBAwGBAoMCgQBCAEE
AQgCDAIICAsMDAsGCgIMAgwGAgcMBwIIAQgCCAEEDAQBAQUMDAUGBwQMBQwHDAoHAgcKBwIIAQgC
CAEMDAkIBQkMDAEKAQwDDAkDBAMJAwQLBQsECwUMDAoLAQoMDAUJAQwDAQgMCAEECgQBBAoHDAcK
CgsMDAsDCAcMBwwFBwkMCQcACwAHAAsBDAELCwoMDAoFCQEMAQwFAQYMBgELAAsBCwAHDAcAAAQM
DAQFBgcMBAwGDAkGAQYJBgELAAsBCwAMDAgLBAgMDAAJAwwHDAAHCQcABwkGAQYJBgEMDAIGAwIM
DAEAAQwFDAIFCwUCBQsEAwQLBAMMDAAEAQAMDAMCAAwCAAsMCwAHCQcABwkGDAYJCQoMDAoCCwYM
AAwCDAgCBwIIAgcKBAoHCgQMDAkKAAkMDAQIBwwFDAgFAAUIBQAKAwoACgMMDAsKBwsMDAMIBgwE
BggMCAYDCgMGAwoADAAKCgkMDAkECAAMAAwEAAUMBQAKAwoACgMGDAYDAwcMDAcEBQYMAgwADAoA
BQAKAAUIBggFCAYMDAsIAgsMDAYKAgwAAgkMCQIFCwUCBQsEDAQLCwgMDAgACQQMAgwGDAMGCAYD
BggFAAUIBQAMDAEFAgEMDAADAAwEDAEECgQBBAoHAgcKBwIMDAMHAAMMDAIBAwwBDAsBBgELAQYJ
BwkGCQcMDAgJAwgMDAcLAwwBAwoMCgMGCAYDBggFDAUICAkMDAkBCgUM
"""
#static const char tiling6_2[48][15]
TILING6_2 = (48, 15), """
AQoDBgMKAwYIBQgGCAUJAQsDCwEGCQYBBgkHCAcJBAEAAQQKBwoECgcCAwIHBgMCAwYIBQgGCAUA
AQAFAAkCBQIJAgULBAsFCwQIAAoCCgAFCAUABQgGCwYIBAUACgAFAAoDBgMKAwYHBAgGAwYIBgMK
AAoDCgAJBQgHCAUACgAFAAoDCwMKAggACAIHCgcCBwoECQQKAgsABwALAAcJBgkHCQYKBQIBAgUL
BAsFCwQDAAMEBwADAAcJBgkHCQYBAgEGBgkECQYBCwEGAQsACAALBQYBCwEGAQsABwALAAcEBQkH
AAcJBwALAQsACwEKAwgBBAEIAQQKBwoECgcLAwkBCQMECwQDBAsFCgULBwoFCgcCCAIHAggBCQEI
BgcCCAIHAggBBAEIAQQFBgoEAQQKBAEIAggBCAILBwsFAgULBQIJAwkCCQMIBAsGCwQDCQMEAwkC
CgIJBwQDCQMEAwkCBQIJAgUGAwQHBAMJAgkDCQIFBgUCBgsEAwQLBAMJAgkDCQIKBQsHCwUCCQIF
AgkDCAMJBAoGCgQBCAEEAQgCCwIIAgcGBwIIAQgCCAEEBQQBBQoHAgcKBwIIAQgCCAEJAQkDBAMJ
AwQLBQsECwUKAQgDCAEECgQBBAoHCwcKBwkFCQcACwAHAAsBCgELAQYFBgELAAsBCwAHBAcABAkG
AQYJBgELAAsBCwAIAwAHCQcABwkGAQYJBgECAQIFCwUCBQsEAwQLBAMAAAsCCwAHCQcABwkGCgYJ
AAgCBwIIAgcKBAoHCgQJBwgFAAUIBQAKAwoACgMLBggECAYDCgMGAwoACQAKAAUEBQAKAwoACgMG
BwYDAgoABQAKAAUIBggFCAYLAgkACQIFCwUCBQsECAQLAgMGCAYDBggFAAUIBQABAAEECgQBBAoH
AgcKBwIDAwsBBgELAQYJBwkGCQcIAwoBCgMGCAYDBggFCQUI
"""
#static const char tiling7_1[16][9]
TILING7_1 = (16, 9), """
CQUECgECCAMACwcGCAMACgECAwAIBQQJBwYLCAQHCQABCwIDCgYFCwIDCQABAAEJBgUKBAcIAQIK
BwYLBQQJAgMLBAcIBgUKCwMCCAcECgUGCgIBCwYHCQQFCQEACgUGCAcEBQYKAwILAQAJBwQIAQAJ
AwILCAADCQQFCwYHBgcLAAMIAgEKBAUJAgEKAAMI
"""
#static const char tiling7_2[16][3][15]
TILING7_2 = (16, 3, 15), """
AQIKAwQIBAMFAAUDBQAJAwAICQEEAgQBBAIFCgUCCQUEAAoBCgAICggCAwIIAwAIAQYKBgEHAgcB
BwILAQIKCwMGAAYDBgAHCAcACwcGAggDCAIKCAoAAQAKCQUECwMGAAYDBgAHCAcACwcGAwQIBAMF
AAUDBQAJAwAIBAkHCwcJBQsJCwUGAAEJAgcLBwIEAwQCBAMIAgMLCAAHAQcABwEECQQBCAQHAwkA
CQMLCQsBAgELAgMLAAUJBQAGAQYABgEKAAEJCgIFAwUCBQMGCwYDBgUKAQsCCwEJCwkDAAMJBgUK
CAAHAQcABwEECQQBCAQHAAUJBQAGAQYABgEKAAEJBQoECAQKBggKCAYHCwcGCQEEAgQBBAIFCgUC
CQUEAQYKBgEHAgcBBwILAQIKBgsFCQULBwkLCQcECAQHCgIFAwUCBQMGCwYDBgUKAgcLBwIEAwQC
BAMIAgMLBwgGCgYIBAoICgQFBwQIBQIKAgUDBgMFAwYLCgUGCwcCBAIHAgQDCAMECwMCBggHCAYK
CAoEBQQKBgcLBAEJAQQCBQIEAgUKBAUJCgYBBwEGAQcCCwIHCgIBBQsGCwUJCwkHBAcJCgUGBwAI
AAcBBAEHAQQJBwQICQUABgAFAAYBCgEGCQEABAoFCgQICggGBwYICwMCCQUABgAFAAYBCgEGCQEA
BQIKAgUDBgMFAwYLCgUGAgsBCQELAwkLCQMACQEACwcCBAIHAgQDCAMECwMCBwAIAAcBBAEHAQQJ
BwQIAAkDCwMJAQsJCwECBAUJBgMLAwYABwAGAAcIBgcLCAQDBQMEAwUACQAFCAADBwkECQcLCQsF
BgULCAADCgYBBwEGAQcCCwIHCgIBBgMLAwYABwAGAAcIBgcLAwgCCgIIAAoICgABCgIBCAQDBQME
AwUACQAFCAADBAEJAQQCBQIEAgUKBAUJAQoACAAKAggKCAID
"""
#static const char tiling7_3[16][3][27]
TILING7_3 = (16, 3, 27), """
DAIKDAoFDAUEDAQIDAgDDAMADAAJDAkBDAECDAUEDAQIDAgDDAMCDAIKDAoBDAEADAAJDAkFBQQM
CgUMAgoMAwIMCAMMAAgMAQAMCQEMBAkMDAAIDAgHDAcGDAYKDAoBDAECDAILDAsDDAMADAcGDAYK
DAoBDAEADAAIDAgDDAMCDAILDAsHBwYMCAcMAAgMAQAMCgEMAgoMAwIMCwMMBgsMCQUMAAkMAwAM
CwMMBgsMBwYMCAcMBAgMBQQMAwAMCwMMBgsMBQYMCQUMBAkMBwQMCAcMAAgMDAMADAAJDAkFDAUG
DAYLDAsHDAcEDAQIDAgDDAEJDAkEDAQHDAcLDAsCDAIDDAMIDAgADAABDAQHDAcLDAsCDAIBDAEJ
DAkADAADDAMIDAgEBAcMCQQMAQkMAgEMCwIMAwsMAAMMCAAMBwgMDAMLDAsGDAYFDAUJDAkADAAB
DAEKDAoCDAIDDAYFDAUJDAkADAADDAMLDAsCDAIBDAEKDAoGBgUMCwYMAwsMAAMMCQAMAQkMAgEM
CgIMBQoMCgYMAQoMAAEMCAAMBwgMBAcMCQQMBQkMBgUMAAEMCAAMBwgMBgcMCgYMBQoMBAUMCQQM
AQkMDAABDAEKDAoGDAYHDAcIDAgEDAQFDAUJDAkACwcMAgsMAQIMCQEMBAkMBQQMCgUMBgoMBwYM
AQIMCQEMBAkMBwQMCwcMBgsMBQYMCgUMAgoMDAECDAILDAsHDAcEDAQJDAkFDAUGDAYKDAoBCAQM
AwgMAgMMCgIMBQoMBgUMCwYMBwsMBAcMAgMMCgIMBQoMBAUMCAQMBwgMBgcMCwYMAwsMDAIDDAMI
DAgEDAQFDAUKDAoGDAYHDAcLDAsCDAQIDAgDDAMCDAIKDAoFDAUGDAYLDAsHDAcEDAMCDAIKDAoF
DAUEDAQIDAgHDAcGDAYLDAsDAwIMCAMMBAgMBQQMCgUMBgoMBwYMCwcMAgsMDAcLDAsCDAIBDAEJ
DAkEDAQFDAUKDAoGDAYHDAIBDAEJDAkEDAQHDAcLDAsGDAYFDAUKDAoCAgEMCwIMBwsMBAcMCQQM
BQkMBgUMCgYMAQoMDAYKDAoBDAEADAAIDAgHDAcEDAQJDAkFDAUGDAEADAAIDAgHDAcGDAYKDAoF
DAUEDAQJDAkBAQAMCgEMBgoMBwYMCAcMBAgMBQQMCQUMAAkMCwMMBgsMBQYMCQUMAAkMAQAMCgEM
AgoMAwIMBQYMCQUMAAkMAwAMCwMMAgsMAQIMCgEMBgoMDAUGDAYLDAsDDAMADAAJDAkBDAECDAIK
DAoFCQEMBAkMBwQMCwcMAgsMAwIMCAMMAAgMAQAMBwQMCwcMAgsMAQIMCQEMAAkMAwAMCAMMBAgM
DAcEDAQJDAkBDAECDAILDAsDDAMADAAIDAgHDAUJDAkADAADDAMLDAsGDAYHDAcIDAgEDAQFDAAD
DAMLDAsGDAYFDAUJDAkEDAQHDAcIDAgAAAMMCQAMBQkMBgUMCwYMBwsMBAcMCAQMAwgMCAAMBwgM
BgcMCgYMAQoMAgEMCwIMAwsMAAMMBgcMCgYMAQoMAAEMCAAMAwgMAgMMCwIMBwsMDAYHDAcIDAgA
DAABDAEKDAoCDAIDDAMLDAsGCgIMBQoMBAUMCAQMAwgMAAMMCQAMAQkMAgEMBAUMCAQMAwgMAgMM
CgIMAQoMAAEMCQAMBQkMDAQFDAUKDAoCDAIDDAMIDAgADAABDAEJDAkE
"""
#static const char tiling7_4_1[16][15]
TILING7_4_1 = (16, 15), """
AwQIBAMKAgoDBAoFCQEAAQYKBgEIAAgBBggHCwMCCwMGCQYDBgkFAAkDBwQIAgcLBwIJAQkCBwkE
CAADAAUJBQALAwsABQsGCgIBCAAHCgcABwoGAQoABAUJCQEECwQBBAsHAgsBBQYKCgIFCAUCBQgE
AwgCBgcLBQIKAgUIBAgFAggDCwcGBAEJAQQLBwsEAQsCCgYFBwAIAAcKBgoHAAoBCQUECQUACwAF
AAsDBgsFAQIKCwcCCQIHAgkBBAkHAwAIBgMLAwYJBQkGAwkACAQHCgYBCAEGAQgABwgGAgMLCAQD
CgMEAwoCBQoEAAEJ
"""
#static const char tiling7_4_2[16][27]
TILING7_4_2 = (16, 27), """
CQQIBAkFCgUJAQoJCgECAAIBAgADCAMACQgACwYKBgsHCAcLAwgLCAMAAgADAAIBCgECCwoCCwMI
AAgDCAAJCAkEBQQJBAUHBgcFBwYLBwsICAcLBwgECQQIAAkICQABAwEAAQMCCwIDCAsDCgUJBQoG
CwYKAgsKCwIDAQMCAwEACQABCgkBCAAJAQkACQEKCQoFBgUKBQYEBwQGBAcIBAgJCQEKAgoBCgIL
CgsGBwYLBgcFBAUHBQQJBQkKCgILAwsCCwMICwgHBAcIBwQGBQYEBgUKBgoLCwIKAgsDCAMLBwgL
CAcEBgQHBAYFCgUGCwoGCgEJAQoCCwIKBgsKCwYHBQcGBwUECQQFCgkFCQAIAAkBCgEJBQoJCgUG
BAYFBgQHCAcECQgECQUKBgoFCgYLCgsCAwILAgMBAAEDAQAJAQkKCwcIBAgHCAQJCAkAAQAJAAED
AgMBAwILAwsICAMLAwgACQAIBAkICQQFBwUEBQcGCwYHCAsHCgYLBwsGCwcICwgDAAMIAwACAQIA
AgEKAgoLCAQJBQkECQUKCQoBAgEKAQIAAwACAAMIAAgJ
"""
#static const char tiling8[6][6]
TILING8 = (6, 6), """
CQgKCggLAQUDAwUHAAQCBAYCAAIEBAIGAQMFAwcFCQoICgsI
"""
#static const char tiling9[8][12]
TILING9 = (8, 12), """
AgoFAwIFAwUEAwQIBAcLCQQLCQsCCQIBCgcGAQcKAQgHAQAIAwYLAAYDAAUGAAkFAwsGAAMGAAYF
AAUJCgYHAQoHAQcIAQgABAsHCQsECQILCQECAgUKAwUCAwQFAwgE
"""
#static const char tiling10_1_1[6][12]
TILING10_1_1 = (6, 12), """
BQoHCwcKCAEJAQgDAQIFBgUCBAMAAwQHCwAIAAsCBAkGCgYJCQAKAgoABggECAYLBwIDAgcGAAEE
BQQBBwkFCQcICgELAwsB
"""
#static const char tiling10_1_1_[6][12]
TILING10_1_1_ = (6, 12), """
BQkHCAcJCwEKAQsDAwIHBgcCBAEAAQQFCgAJAAoCBAgGCwYICAALAgsABgkECQYKBQIBAgUGAAME
BwQDBwoFCgcLCQEIAwgB
"""
#static const char tiling10_1_2[6][24]
TILING10_1_2 = (6, 24), """
AwsHAwcICQgHBQkHCQUKCQoBAwEKCwMKBwYFBwUEAAQFAQAFAAECAAIDBwMCBgcCCwIKBgsKCwYE
CwQIAAgECQAEAAkKAAoCCwIKCwoGBAYKCQQKBAkABAAICwgAAgsABwYFBAcFBwQABwADAgMAAQIA
AgEFAgUGBwgDCwcDBwsKBwoFCQUKAQkKCQEDCQMI
"""
#static const char tiling10_2[6][24]
TILING10_2 = (6, 24), """
DAUJDAkIDAgDDAMBDAEKDAoLDAsHDAcFDAEADAAEDAQHDAcDDAMCDAIGDAYFDAUBBAgMBgQMCgYM
CQoMAAkMAgAMCwIMCAsMDAkEDAQGDAYLDAsIDAgADAACDAIKDAoJAAMMBAAMBQQMAQUMAgEMBgIM
BwYMAwcMCgUMCwoMAwsMAQMMCQEMCAkMBwgMBQcM
"""
#static const char tiling10_2_[6][24]
TILING10_2_ = (6, 24), """
CAcMCQgMAQkMAwEMCwMMCgsMBQoMBwUMBAUMAAQMAwAMBwMMBgcMAgYMAQIMBQEMDAsGDAYEDAQJ
DAkKDAoCDAIADAAIDAgLBgoMBAYMCAQMCwgMAgsMAAIMCQAMCgkMDAcEDAQADAABDAEFDAUGDAYC
DAIDDAMHDAcLDAsKDAoBDAEDDAMIDAgJDAkFDAUH
"""
#static const char tiling11[12][12]
TILING11 = (12, 12), """
AgoJAgkHAgcDBwkEAQYCAQgGAQkICAcGCAMBCAEGCAYEBgEKAAgLAAsFAAUBBQsGCQUHCQcCCQIA
AgcLBQAEBQsABQoLCwMABQQABQALBQsKCwADCQcFCQIHCQACAgsHAAsIAAULAAEFBQYLCAEDCAYB
CAQGBgoBAQIGAQYIAQgJCAYHAgkKAgcJAgMHBwQJ
"""
#static const char tiling12_1_1[24][12]
TILING12_1_1 = (24, 12), """
BwYLCgMCAwoICQgKBgUKCQIBAgkLCAsJCgYFBwkECQcBAwEHBwYLBAgFAwUIBQMBBQQJCAEAAQgK
CwoIAQIKAAkDBQMJAwUHCgECAAsDCwAGBAYACAMAAgkBCQIEBgQCAwAIAgsBBwELAQcFBgUKBwsE
AgQLBAIACQUEBggHCAYAAgAGCAMABwQLCQsECwkKBAcICwADAAsJCgkLBAcIBQkGAAYJBgACCwcG
BAoFCgQCAAIECwIDAQgACAEHBQcBAAEJAwgCBAIIAgQGAgMLAQoABgAKAAYECQABAwoCCgMFBwUD
CQABBAUICggFCAoLCAQHBQsGCwUDAQMFBQQJBgoHAQcKBwEDCgECBQYJCwkGCQsICwIDBgcKCAoH
CggJ
"""
#static const char tiling12_1_1_[24][12]
TILING12_1_1_ = (24, 12), """
AwILCgcGBwoICQgKAgEKCQYFBgkLCAsJCQQFBwoGCgcBAwEHBwQIBgsFAwULBQMBAQAJCAUEBQgK
CwoIAQAJAgoDBQMKAwUHCwMCAAoBCgAGBAYACQEAAggDCAIEBgQCAwILAAgBBwEIAQcFBgcLBQoE
AgQKBAIACAcEBgkFCQYAAgAGCAcEAwALCQsACwkKAAMICwQHBAsJCgkLBAUJBwgGAAYIBgACCgUG
BAsHCwQCAAIECAADAQsCCwEHBQcBAAMIAQkCBAIJAgQGAgEKAwsABgALAAYECgIBAwkACQMFBwUD
CQQFAAEICggBCAoLCwYHBQgECAUDAQMFBQYKBAkHAQcJBwEDCgUGAQIJCwkCCQsICwYHAgMKCAoD
CggJ
"""
#static const char tiling12_1_2[24][24]
TILING12_1_2 = (24, 24), """
BwMLAwcICQgHBgkHCQYKAgoGCwIGAgsDBgIKAgYLCAsGBQgGCAUJAQkFCgEFAQoCCgkFCQoBAwEK
BgMKAwYHBAcGBQQGBAUJBwgLAwsICwMBCwEGBQYBBgUEBgQHCAcEBQEJAQUKCwoFBAsFCwQIAAgE
CQAEAAkBAQkKBQoJCgUHCgcCAwIHAgMAAgABCQEACgsCCwoGBAYKAQQKBAEAAwABAgMBAwILCAkA
CQgEBgQIAwYIBgMCAQIDAAEDAQAJAwsIBwgLCAcFCAUAAQAFAAECAAIDCwMCBgsKAgoLCgIACgAF
BAUABQQHBQcGCwYHCQgECAkAAgAJBQIJAgUGBwYFBAcFBwQICAQACQAEAAkKAAoDCwMKAwsHAwcI
BAgHBAAIAAQJCgkEBwoECgcLAwsHCAMHAwgABAkIAAgJCAACCAIHBgcCBwYFBwUECQQFCwoGCgsC
AAILBwALAAcEBQQHBgUHBQYKCwgDCAsHBQcLAgULBQIBAAECAwACAAMIAAgJBAkICQQGCQYBAgEG
AQIDAQMACAADAgoLBgsKCwYECwQDAAMEAwABAwECCgIBCQoBCgkFBwUJAAcJBwADAgMAAQIAAgEK
CQUBCgEFAQoLAQsACAALAAgEAAQJBQkECAsHCwgDAQMIBAEIAQQFBgUEBwYEBgcLBQoJAQkKCQED
CQMEBwQDBAcGBAYFCgUGCgYCCwIGAgsIAggBCQEIAQkFAQUKBgoFCwcDCAMHAwgJAwkCCgIJAgoG
AgYLBwsG
"""
#static const char tiling12_2[24][24]
TILING12_2 = (24, 24), """
CQgMCgkMAgoMAwIMCwMMBgsMBwYMCAcMCAsMCQgMAQkMAgEMCgIMBQoMBgUMCwYMAwEMBwMMBAcM
CQQMBQkMBgUMCgYMAQoMDAMBDAEFDAUGDAYLDAsHDAcEDAQIDAgDCwoMCAsMAAgMAQAMCQEMBAkM
BQQMCgUMDAUHDAcDDAMCDAIKDAoBDAEADAAJDAkFBAYMAAQMAQAMCgEMAgoMAwIMCwMMBgsMBgQM
AgYMAwIMCAMMAAgMAQAMCQEMBAkMDAcFDAUBDAEADAAIDAgDDAMCDAILDAsHDAIADAAEDAQFDAUK
DAoGDAYHDAcLDAsCAgAMBgIMBwYMCAcMBAgMBQQMCQUMAAkMDAkKDAoLDAsHDAcEDAQIDAgDDAMA
DAAJCgkMCwoMBwsMBAcMCAQMAwgMAAMMCQAMDAACDAIGDAYHDAcIDAgEDAQFDAUJDAkAAAIMBAAM
BQQMCgUMBgoMBwYMCwcMAgsMBQcMAQUMAAEMCAAMAwgMAgMMCwIMBwsMDAQGDAYCDAIDDAMIDAgA
DAABDAEJDAkEDAYEDAQADAABDAEKDAoCDAIDDAMLDAsGBwUMAwcMAgMMCgIMAQoMAAEMCQAMBQkM
DAoLDAsIDAgADAABDAEJDAkEDAQFDAUKAQMMBQEMBgUMCwYMBwsMBAcMCAQMAwgMDAEDDAMHDAcE
DAQJDAkFDAUGDAYKDAoBDAsIDAgJDAkBDAECDAIKDAoFDAUGDAYLDAgJDAkKDAoCDAIDDAMLDAsG
DAYHDAcI
"""
#static const char tiling12_2_[24][24]
TILING12_2_ = (24, 24), """
DAILDAsHDAcGDAYKDAoJDAkIDAgDDAMCDAEKDAoGDAYFDAUJDAkIDAgLDAsCDAIBDAQFDAUKDAoG
DAYHDAcDDAMBDAEJDAkEBwYMCAcMBAgMBQQMAQUMAwEMCwMMBgsMDAAJDAkFDAUEDAQIDAgLDAsK
DAoBDAEAAQIMCQEMAAkMAwAMBwMMBQcMCgUMAgoMDAECDAILDAsDDAMADAAEDAQGDAYKDAoBDAMA
DAAJDAkBDAECDAIGDAYEDAQIDAgDAwAMCwMMAgsMAQIMBQEMBwUMCAcMAAgMBgUMCwYMBwsMBAcM
AAQMAgAMCgIMBQoMDAcEDAQJDAkFDAUGDAYCDAIADAAIDAgHCAcMAAgMAwAMCwMMCgsMCQoMBAkM
BwQMDAcIDAgADAADDAMLDAsKDAoJDAkEDAQHBAcMCQQMBQkMBgUMAgYMAAIMCAAMBwgMDAUGDAYL
DAsHDAcEDAQADAACDAIKDAoFDAADDAMLDAsCDAIBDAEFDAUHDAcIDAgAAAMMCQAMAQkMAgEMBgIM
BAYMCAQMAwgMAgEMCwIMAwsMAAMMBAAMBgQMCgYMAQoMDAIBDAEJDAkADAADDAMHDAcFDAUKDAoC
CQAMBQkMBAUMCAQMCwgMCgsMAQoMAAEMDAYHDAcIDAgEDAQFDAUBDAEDDAMLDAsGBQQMCgUMBgoM
BwYMAwcMAQMMCQEMBAkMCgEMBgoMBQYMCQUMCAkMCwgMAgsMAQIMCwIMBwsMBgcMCgYMCQoMCAkM
AwgMAgMM
"""
#static const char tiling13_1[2][12]
TILING13_1 = (2, 12), """
CwcGAQIKCAMACQUECAQHAgMLCQABCgYF
"""
#static const char tiling13_1_[2][12]
TILING13_1_ = (2, 12), """
BwQICwMCAQAJBQYKBgcLCgIBAAMIBAUJ
"""
#static const char tiling13_2[2][6][18]
TILING13_2 = (2, 6, 18), """
AQIKCwcGAwQIBAMFAAUDBQAJCAMACwcGCQEEAgQBBAIFCgUCCQUECAMAAQYKBgEHAgcBBwILCQUE
AQIKCwMGAAYDBgAHCAcACQUECwcGAAoBCgAICggCAwIIAQIKAwAIBAkHCwcJBQsJCwUGAgMLCAQH
AAUJBQAGAQYABgEKCQABCAQHCgIFAwUCBQMGCwYDBgUKCQABAgcLBwIEAwQCBAMIBgUKAgMLCAAH
AQcABwEECQQBBgUKCAQHAQsCCwEJCwkDAAMJAgMLAAEJBQoECAQKBggKCAYH
"""
#static const char tiling13_2_[2][6][18]
TILING13_2_ = (2, 6, 18), """
CgUGCwMCBwAIAAcBBAEHAQQJCwMCBwQICQUABgAFAAYBCgEGAQAJBwQIBQIKAgUDBgMFAwYLCgUG
AQAJCwcCBAIHAgQDCAMECgUGBwQIAgsBCQELAwkLCQMACwMCCQEABAoFCgQICggGBwYIBgcLCAAD
BAEJAQQCBQIEAgUKCAADBAUJCgYBBwEGAQcCCwIHAgEKBAUJBgMLAwYABwAGAAcIBgcLAgEKCAQD
BQMEAwUACQAFBgcLBAUJAwgCCgIIAAoICgABCAADCgIBBQsGCwUJCwkHBAcJ
"""
#static const char tiling13_3[2][12][30]
TILING13_3 = (2, 12, 30), """
CwcGDAIKDAoFDAUEDAQIDAgDDAMADAAJDAkBDAECAQIKCQUMAAkMAwAMCwMMBgsMBwYMCAcMBAgM
BQQMCwcGDAUEDAQIDAgDDAMCDAIKDAoBDAEADAAJDAkFAQIKDAMADAAJDAkFDAUGDAYLDAsHDAcE
DAQIDAgDCAMACwcMAgsMAQIMCQEMBAkMBQQMCgUMBgoMBwYMCwcGBQQMCgUMAgoMAwIMCAMMAAgM
AQAMCQEMBAkMCAMAAQIMCQEMBAkMBwQMCwcMBgsMBQYMCgUMAgoMCQUEDAAIDAgHDAcGDAYKDAoB
DAECDAILDAsDDAMACQUEDAcGDAYKDAoBDAEADAAIDAgDDAMCDAILDAsHCAMADAECDAILDAsHDAcE
DAQJDAkFDAUGDAYKDAoBCQUEBwYMCAcMAAgMAQAMCgEMAgoMAwIMCwMMBgsMAQIKAwAMCwMMBgsM
BQYMCQUMBAkMBwQMCAcMAAgMCAQHDAMLDAsGDAYFDAUJDAkADAABDAEKDAoCDAIDAgMLCgYMAQoM
AAEMCAAMBwgMBAcMCQQMBQkMBgUMCAQHDAYFDAUJDAkADAADDAMLDAsCDAIBDAEKDAoGAgMLDAAB
DAEKDAoGDAYHDAcIDAgEDAQFDAUJDAkAAAEJCAQMAwgMAgMMCgIMBQoMBgUMCwYMBwsMBAcMCAQH
BgUMCwYMAwsMAAMMCQAMAQkMAgEMCgIMBQoMCQABAgMMCgIMBQoMBAUMCAQMBwgMBgcMCwYMAwsM
BgUKDAEJDAkEDAQHDAcLDAsCDAIDDAMIDAgADAABBgUKDAQHDAcLDAsCDAIBDAEJDAkADAADDAMI
DAgECQABDAIDDAMIDAgEDAQFDAUKDAoGDAYHDAcLDAsCBgUKBAcMCQQMAQkMAgEMCwIMAwsMAAMM
CAAMBwgMAgMLAAEMCAAMBwgMBgcMCgYMBQoMBAUMCQQMAQkM
"""
#static const char tiling13_3_[2][12][30]
TILING13_3_ = (2, 12, 30), """
AwILCAcMAAgMAQAMCgEMBgoMBQYMCQUMBAkMBwQMBQYKDAILDAsHDAcEDAQJDAkBDAEADAAIDAgD
DAMCCgUGDAcEDAQJDAkBDAECDAILDAsDDAMADAAIDAgHCwMCDAEADAAIDAgHDAcGDAYKDAoFDAUE
DAQJDAkBBwQICwMMBgsMBQYMCQUMAAkMAQAMCgEMAgoMAwIMBwQIBQYMCQUMAAkMAwAMCwMMAgsM
AQIMCgEMBgoMCwMCAQAMCgEMBgoMBwYMCAcMBAgMBQQMCQUMAAkMAQAJDAQIDAgDDAMCDAIKDAoF
DAUGDAYLDAsHDAcEBwQIDAUGDAYLDAsDDAMADAAJDAkBDAECDAIKDAoFAQAJDAMCDAIKDAoFDAUE
DAQIDAgHDAcGDAYLDAsDCgUGBwQMCwcMAgsMAQIMCQEMAAkMAwAMCAMMBAgMCQEAAwIMCAMMBAgM
BQQMCgUMBgoMBwYMCwcMAgsMAAMICQQMAQkMAgEMCwIMBwsMBgcMCgYMBQoMBAUMCwYHDAMIDAgE
DAQFDAUKDAoCDAIBDAEJDAkADAADBgcLDAQFDAUKDAoCDAIDDAMIDAgADAABDAEJDAkECAADDAIB
DAEJDAkEDAQHDAcLDAsGDAYFDAUKDAoCBAUJCAAMBwgMBgcMCgYMAQoMAgEMCwIMAwsMAAMMBAUJ
BgcMCgYMAQoMAAEMCAAMAwgMAgMMCwIMBwsMCAADAgEMCwIMBwsMBAcMCQQMBQkMBgUMCgYMAQoM
AgEKDAUJDAkADAADDAMLDAsGDAYHDAcIDAgEDAQFBAUJDAYHDAcIDAgADAABDAEKDAoCDAIDDAML
DAsGAgEKDAADDAMLDAsGDAYFDAUJDAkEDAQHDAcIDAgABgcLBAUMCAQMAwgMAgMMCgIMAQoMAAEM
CQAMBQkMCgIBAAMMCQAMBQkMBgUMCwYMBwsMBAcMCAQMAwgM
"""
#static const char tiling13_4[2][4][36]
TILING13_4 = (2, 4, 36), """
DAIKDAoFDAUGDAYLDAsHDAcEDAQIDAgDDAMADAAJDAkBDAECCwMMBgsMBwYMCAcMBAgMBQQMCQUM
AAkMAQAMCgEMAgoMAwIMCQEMBAkMBQQMCgUMBgoMBwYMCwcMAgsMAwIMCAMMAAgMAQAMDAAIDAgH
DAcEDAQJDAkFDAUGDAYKDAoBDAECDAILDAsDDAMADAMLDAsGDAYHDAcIDAgEDAQFDAUJDAkADAAB
DAEKDAoCDAIDCAAMBwgMBAcMCQQMBQkMBgUMCgYMAQoMAgEMCwIMAwsMAAMMCgIMBQoMBgUMCwYM
BwsMBAcMCAQMAwgMAAMMCQAMAQkMAgEMDAEJDAkEDAQFDAUKDAoGDAYHDAcLDAsCDAIDDAMIDAgA
DAAB
"""
#static const char tiling13_5_1[2][4][18]
TILING13_5_1 = (2, 4, 18), """
BwYLAQAJCgMCAwoFAwUIBAgFAQIKBwQIAwALBgsACQYABgkFAwAIBQYKAQIJBAkCCwQCBAsHBQQJ
AwILCAEAAQgHAQcKBgoHBAcIAgEKCwADAAsGAAYJBQkGAgMLBAUJAAEIBwgBCgcBBwoGAAEJBgcL
AgMKBQoDCAUDBQgEBgUKAAMICQIBAgkEAgQLBwsE
"""
#static const char tiling13_5_2[2][4][30]
TILING13_5_2 = (2, 4, 30), """
AQAJBwQIBwgDBwMLAgsDCwIKCwoGBQYKBgUHBAcFBwQICwMCBgsCCgYCBgoFCQUKAQkKCQEAAgAB
AAIDBQYKCQEABAkACAQABAgHCwcIAwsICwMCAAIDAgABAwILBQYKBQoBBQEJAAkBCQAICQgEBAgH
BAcFBgUHAgEKBAUJBAkABAAIAwgACAMLCAsHBgcLBwYEBQQGBAUJCAADBwgDCwcDBwsGCgYLAgoL
CgIBAwECAQMABgcLCgIBBQoBCQUBBQkECAQJAAgJCAADAQMAAwECAAMIBgcLBgsCBgIKAQoCCgEJ
CgkFBQkEBQQGBwYE
"""
#static const char tiling14[12][12]
TILING14 = (12, 12), """
BQkIBQgCBQIGAwIIAgEFAgUIAggLBAgFCQQGCQYDCQMBCwMGAQsKAQQLAQAEBwsECAIACAUCCAcF
CgIFAAcDAAoHAAkKBgcKAAMHAAcKAAoJBgoHCAACCAIFCAUHCgUCAQoLAQsEAQQABwQLCQYECQMG
CQEDCwYDAgUBAggFAgsIBAUIBQgJBQIIBQYCAwgC
"""
#static const char test3[24]
TEST3 = (24,), """
BQEEBQECAgMEAwYG+vr9/P3+/v/7/P/7
"""
#static const char test4[8]
TEST4 = (8,), """
BwcHB/n5+fk=
"""
#static const char test6[48][3]
TEST6 = (48, 3), """
AgcKBAcLBQcBBQcDAQcJAwcKBgcFAQcIBAcIAQcIAwcLBQcCBQcAAQcJBgcGAgcJBAcIAgcJAgcK
BgcHAwcKBAcLAwcLBgcE+vkE/fkL/PkL/fkK+vkH/vkK/vkJ/PkI/vkJ+vkG//kJ+/kA+/kC/fkL
//kI/PkI//kI+vkF/fkK//kJ+/kD+/kB/PkL/vkK
"""
#static const char test7[16][5]
TEST7 = (16, 5), """
AQIFBwEDBAUHAwQBBgcEBAEFBwACAwUHAgECBgcFAgMGBwYDBAYHB/38+vkH/v36+Qb//vr5Bf79
+/kC/P/7+QD8//r5BP38+/kD//77+QE=
"""
#static const char test10[6][3]
TEST10 = (6, 3), """
AgQHBQYHAQMHAQMHBQYHAgQH
"""
#static const char test12[24][4]
TEST12 = (24, 4), """
BAMHCwMCBwoCBgcFBgQHBwIBBwkFAgcBBQMHAgUBBwAFBAcDBgMHBgEGBwQBBAcIBAEHCAYBBwQD
BgcGBAUHAwEFBwADBQcCAgUHAQECBwkEBgcHBgIHBQIDBwoDBAcL
"""
#static const char test13[2][7]
TEST13 = (2, 7), """
AQIDBAUGBwIDBAEFBgc=
"""
#static const char subconfig13[64]
SUBCONFIG13 = (64,), """
AAECBwP/C/8ECP//Dv///wUJDBcP/xUmERT/JBohHiwGCg0TEP8ZJRIY/yMWIB0r////Iv//HCr/
H/8pGygnLQ==
"""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
This directory contains meta-files related to the Lewiner marching cubes
algorithm. These are not used by the algorithm, but can be convenient
for development/maintenance:
* MarchingCubes.cpp - the original algorithm, this is ported to Cython
* LookupTable.h - the original LUTs, these are ported to Python
* createluts.py - scrip to generate Python luts from the .h file
* visual_test.py - script to compare visual results of marchingcubes algorithms
+173
View File
@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
""" Create lookup tables for the marching cubes algorithm, by parsing
the file "LookUpTable.h". This prints a text to the stdout wich
can then be copied to luts.py.
The luts are tuples of shape and base64 encoded bytes.
"""
import numpy as np
import sys
import base64
# Get base64 encode/decode functions
if sys.version_info >= (3, ):
base64encode = base64.encodebytes
base64decode = base64.decodebytes
else:
base64encode = base64.encodestring
base64decode = base64.decodestring
def create_luts(fname):
# Get the lines in the C header file
text = open(fname,'rb').read().decode('utf-8')
lines1 = [line.rstrip() for line in text.splitlines()]
# Init lines for Python
lines2 = []
# Get classic table
more_lines, ii = get_table(lines1, 'static const char casesClassic', 0)
lines2.extend(more_lines)
# Get cases table
more_lines, ii = get_table(lines1, 'static const char cases', 0)
lines2.extend(more_lines)
# Get tiling tables
ii = 0
for casenr in range(99):
# Get table
more_lines, ii = get_table(lines1, 'static const char tiling', ii+1)
if ii < 0:
break
else:
lines2.extend(more_lines)
# Get test tables
ii = 0
for casenr in range(99):
# Get table
more_lines, ii = get_table(lines1, 'static const char test', ii+1)
if ii < 0:
break
else:
lines2.extend(more_lines)
# Get subconfig tables
ii = 0
for casenr in range(99):
# Get table
more_lines, ii = get_table(lines1, 'static const char subconfig', ii+1)
if ii < 0:
break
else:
lines2.extend(more_lines)
return '\n'.join(lines2)
def get_table(lines1, needle, i):
# Try to find the start
ii = search_line(lines1, needle, i)
if ii < 0:
return [], -1
# Init result
lines2 = []
# Get size and name
front, dummu, back = lines1[ii].partition('[')
name = front.split(' ')[-1].upper()
size = int(back.split(']',1)[0])
cdes = lines1[ii].rstrip(' {=')
# Write name
lines2.append('%s = np.array([' % name)
# Get elements
for i in range(ii+1, ii+1+9999999):
line1 = lines1[i]
front, dummy, back = line1.partition('*/')
if not back:
front, back = back, front
line2 = ' '
line2 += back.strip().replace('{', '[').replace('}',']').replace(';','')
line2 += front.replace('/*',' #').rstrip()
lines2.append(line2)
if line1.endswith('};'):
break
# Close and return
lines2.append(" , 'int8')")
lines2.append('')
#return lines2, ii+size
# Execute code and get array as base64 text
code = '\n'.join(lines2)
code = code.split('=',1)[1]
array = eval(code)
array64 = base64encode(array.tostring()).decode('utf-8')
# Reverse: bytes = base64decode(text.encode('utf-8'))
text = '%s = %s, """\n%s"""' % (name, str(array.shape), array64)
# Build actual lines
lines2 = []
#lines2.append( '# %s -> %s %s' % (cdes, str(array.dtype), str(array.shape)) )
lines2.append( '#' + cdes)
lines2.append(text)
lines2.append('')
return lines2, ii+size
def search_line(lines, refline, start=0):
for i, line in enumerate(lines[start:]):
if line.startswith(refline):
return i + start
return -1
def getLutNames(prefix):
aa = []
for a in dir(luts):
if a.startswith(prefix): aa.append(a)
def sortkey(x):
fullnr = x.split(prefix)[1]
nr, us, subnr = fullnr.partition('_')
if len(nr) == 1:
nr = '0'+nr
return nr + us + subnr
return [a for a in sorted(aa, key=sortkey)]
if __name__ == '__main__':
import os
fname = os.path.join(os.getcwd(), 'LookUpTable.h')
if True:
with open(os.path.join(os.getcwd(), 'mcluts.py'), 'w') as f:
f.write('# -*- coding: utf-8 -*-\n')
f.write('# Copyright (C) 2012, Almar Klein\n# Copyright (C) 2002, Thomas Lewiner\n\n')
f.write('# This file was auto-generated from LookUpTable.h by createluts.py.\n\n')
f.write(create_luts(fname))
else:
for prefix in ['TILING', 'TEST']:
tmp = ['luts.'+a for a in getLutNames(prefix)]
print(', '.join(tmp))
print('')
for name in getLutNames(prefix):
print('self.%s = Lut(%s)' % (name, name))
print('')
for name in getLutNames(prefix):
print('cdef Lut %s' % name)
print('')
print('')
+72
View File
@@ -0,0 +1,72 @@
"""
Script to show the results of the two marching cubes algorithms on different
data.
"""
import time
import numpy as np
import visvis as vv
from skimage.measure import marching_cubes_classic, marching_cubes_lewiner
from skimage.draw import ellipsoid
# Create test volume
SELECT = 3
gradient_dir = 'descent' # ascent or descent
if SELECT == 1:
# Medical data
vol = vv.volread('stent')
isovalue = 800
elif SELECT == 2:
# Blocky data
vol = vv.aVolume(20, 128) # Different every time
isovalue = 0.2
elif SELECT == 3:
# Generate two donuts using a formula by Thomas Lewiner
n = 48
a, b = 2.5/n, -1.25
isovalue = 0.0
#
vol = np.empty((n,n,n), 'float32')
for iz in range(vol.shape[0]):
for iy in range(vol.shape[1]):
for ix in range(vol.shape[2]):
z, y, x = float(iz)*a+b, float(iy)*a+b, float(ix)*a+b
vol[iz,iy,ix] = ( (
(8*x)**2 + (8*y-2)**2 + (8*z)**2 + 16 - 1.85*1.85 ) * ( (8*x)**2 +
(8*y-2)**2 + (8*z)**2 + 16 - 1.85*1.85 ) - 64 * ( (8*x)**2 + (8*y-2)**2 )
) * ( ( (8*x)**2 + ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2 + 16 - 1.85*1.85 )
* ( (8*x)**2 + ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2 + 16 - 1.85*1.85 ) -
64 * ( ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2
) ) + 1025
# Uncommenting the line below will yield different results for classic MC
#vol = -vol
elif SELECT == 4:
vol = ellipsoid(4, 3, 2, levelset=True)
isovalue = 0
# Get surface meshes
t0 = time.time()
vertices1, faces1, *_ = marching_cubes_lewiner(vol, isovalue, gradient_direction=gradient_dir, use_classic=False)
print('finding surface lewiner took %1.0f ms' % (1000*(time.time()-t0)) )
t0 = time.time()
vertices2, faces2, *_ = marching_cubes_classic(vol, isovalue, gradient_direction=gradient_dir)
print('finding surface classic took %1.0f ms' % (1000*(time.time()-t0)) )
# Show
vv.figure(1); vv.clf()
a1 = vv.subplot(121); m1 = vv.mesh(np.fliplr(vertices1), faces1)
a2 = vv.subplot(122); m2 = vv.mesh(np.fliplr(vertices2), faces2)
a1.camera = a2.camera
# visvis uses right-hand rule, gradient_direction param uses left-hand rule
m1.cullFaces = m2.cullFaces = 'front' # None, front or back
vv.use().Run()
+7 -3
View File
@@ -15,7 +15,8 @@ def configuration(parent_package='', top_path=None):
cython(['_ccomp.pyx'], working_path=base_path)
cython(['_find_contours_cy.pyx'], working_path=base_path)
cython(['_moments_cy.pyx'], working_path=base_path)
cython(['_marching_cubes_cy.pyx'], working_path=base_path)
cython(['_marching_cubes_classic_cy.pyx'], working_path=base_path)
cython(['_marching_cubes_lewiner_cy.pyx'], working_path=base_path)
cython(['_pnpoly.pyx'], working_path=base_path)
config.add_extension('_ccomp', sources=['_ccomp.c'],
@@ -24,8 +25,11 @@ def configuration(parent_package='', top_path=None):
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_moments_cy', sources=['_moments_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_marching_cubes_cy',
sources=['_marching_cubes_cy.c'],
config.add_extension('_marching_cubes_classic_cy',
sources=['_marching_cubes_classic_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_marching_cubes_lewiner_cy',
sources=['_marching_cubes_lewiner_cy.c'],
include_dirs=[get_numpy_include_dirs()])
config.add_extension('_pnpoly', sources=['_pnpoly.c'],
include_dirs=[get_numpy_include_dirs(), '../_shared'])
+127 -11
View File
@@ -1,17 +1,55 @@
import sys
import numpy as np
from numpy.testing import assert_raises
from skimage.draw import ellipsoid, ellipsoid_stats
from skimage.measure import (marching_cubes, mesh_surface_area,
correct_mesh_orientation)
from skimage.measure import (marching_cubes,
marching_cubes_classic, marching_cubes_lewiner,
mesh_surface_area, correct_mesh_orientation)
from skimage.measure._marching_cubes_lewiner import _expected_output_args
def func_that_knows_about_its_outputs(r):
# Must be defined in global scope to avoid syntax error on Python 2 *sigh*
nout = _expected_output_args()
print(nout)
r.append(nout)
return [nout] * int(nout)
def test_expected_output_args():
foo = func_that_knows_about_its_outputs
res = []
foo(res)
a = foo(res)
a, b = foo(res)
a, b, c = foo(res)
assert res == [0, 1, 2, 3] or res == [0, 0, 2, 3]
# ``a = foo()`` somehow yields 0 in test, which is ok for us;
# we only want to distinguish between > 2 args or not
if sys.version_info >= (3, 3):
res = []
exec('*a, b, c = foo(res)')
exec('a, b, c, *d = foo(res)')
exec('a, b, *c, d, e = foo(res)')
assert res == [2.1, 3.1, 4.1]
def test_marching_cubes_isotropic():
ellipsoid_isotropic = ellipsoid(6, 10, 16, levelset=True)
_, surf = ellipsoid_stats(6, 10, 16)
verts, faces = marching_cubes(ellipsoid_isotropic, 0.)
# Classic
verts, faces = marching_cubes_classic(ellipsoid_isotropic, 0.)
surf_calc = mesh_surface_area(verts, faces)
# Test within 1% tolerance for isotropic. Will always underestimate.
assert surf > surf_calc and surf_calc > surf * 0.99
# Lewiner
verts, faces = marching_cubes_lewiner(ellipsoid_isotropic, 0.)[:2]
surf_calc = mesh_surface_area(verts, faces)
# Test within 1% tolerance for isotropic. Will always underestimate.
assert surf > surf_calc and surf_calc > surf * 0.99
@@ -21,20 +59,35 @@ def test_marching_cubes_anisotropic():
ellipsoid_anisotropic = ellipsoid(6, 10, 16, spacing=spacing,
levelset=True)
_, surf = ellipsoid_stats(6, 10, 16)
verts, faces = marching_cubes(ellipsoid_anisotropic, 0.,
spacing=spacing)
# Classic
verts, faces = marching_cubes_classic(ellipsoid_anisotropic, 0.,
spacing=spacing)
surf_calc = mesh_surface_area(verts, faces)
# Test within 1.5% tolerance for anisotropic. Will always underestimate.
assert surf > surf_calc and surf_calc > surf * 0.985
# Lewiner
verts, faces = marching_cubes_lewiner(ellipsoid_anisotropic, 0., spacing=spacing)[:2]
surf_calc = mesh_surface_area(verts, faces)
# Test within 1.5% tolerance for anisotropic. Will always underestimate.
assert surf > surf_calc and surf_calc > surf * 0.985
def test_invalid_input():
assert_raises(ValueError, marching_cubes, np.zeros((2, 2, 1)), 0)
assert_raises(ValueError, marching_cubes, np.zeros((2, 2, 1)), 1)
assert_raises(ValueError, marching_cubes, np.ones((3, 3, 3)), 1,
# Classic
assert_raises(ValueError, marching_cubes_classic, np.zeros((2, 2, 1)), 0)
assert_raises(ValueError, marching_cubes_classic, np.zeros((2, 2, 1)), 1)
assert_raises(ValueError, marching_cubes_classic, np.ones((3, 3, 3)), 1,
spacing=(1, 2))
assert_raises(ValueError, marching_cubes, np.zeros((20, 20)), 0)
assert_raises(ValueError, marching_cubes_classic, np.zeros((20, 20)), 0)
# Lewiner
assert_raises(ValueError, marching_cubes_lewiner, np.zeros((2, 2, 1)), 0)
assert_raises(ValueError, marching_cubes_lewiner, np.zeros((2, 2, 1)), 1)
assert_raises(ValueError, marching_cubes_lewiner, np.ones((3, 3, 3)), 1,
spacing=(1, 2))
assert_raises(ValueError, marching_cubes_lewiner, np.zeros((20, 20)), 0)
def test_correct_mesh_orientation():
@@ -74,5 +127,68 @@ def test_correct_mesh_orientation():
np.testing.assert_array_equal(expected, corrected_faces1)
def test_both_algs_same_result_ellipse():
# Performing this test on data that does not have ambiguities
sphere_small = ellipsoid(1, 1, 1, levelset=True)
vertices1, faces1 = marching_cubes_classic(sphere_small, 0)[:2]
vertices2, faces2 = marching_cubes_lewiner(sphere_small, 0, allow_degenerate=False)[:2]
vertices3, faces3 = marching_cubes_lewiner(sphere_small, 0, allow_degenerate=False, use_classic=True)[:2]
# Order is different, best we can do is test equal shape and same vertices present
assert _same_mesh(vertices1, faces1, vertices2, faces2)
assert _same_mesh(vertices1, faces1, vertices3, faces3)
def _same_mesh(vertices1, faces1, vertices2, faces2, tol=1e-10):
""" Compare two meshes, using a certain tolerance and invariant to
the order of the faces.
"""
# Unwind vertices
triangles1 = vertices1[np.array(faces1)]
triangles2 = vertices2[np.array(faces2)]
# Sort vertices within each triangle
triang1 = [np.concatenate(sorted(t, key=lambda x:tuple(x))) for t in triangles1]
triang2 = [np.concatenate(sorted(t, key=lambda x:tuple(x))) for t in triangles2]
# Sort the resulting 9-element "tuples"
triang1 = np.array(sorted([tuple(x) for x in triang1]))
triang2 = np.array(sorted([tuple(x) for x in triang2]))
return triang1.shape == triang2.shape and np.allclose(triang1, triang2, 0, tol)
def test_both_algs_same_result_donut():
# Performing this test on data that does not have ambiguities
n = 48
a, b = 2.5/n, -1.25
isovalue = 0.0
vol = np.empty((n,n,n), 'float32')
for iz in range(vol.shape[0]):
for iy in range(vol.shape[1]):
for ix in range(vol.shape[2]):
# Double-torii formula by Thomas Lewiner
z, y, x = float(iz)*a+b, float(iy)*a+b, float(ix)*a+b
vol[iz,iy,ix] = ( (
(8*x)**2 + (8*y-2)**2 + (8*z)**2 + 16 - 1.85*1.85 ) * ( (8*x)**2 +
(8*y-2)**2 + (8*z)**2 + 16 - 1.85*1.85 ) - 64 * ( (8*x)**2 + (8*y-2)**2 )
) * ( ( (8*x)**2 + ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2 + 16 - 1.85*1.85 )
* ( (8*x)**2 + ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2 + 16 - 1.85*1.85 ) -
64 * ( ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2
) ) + 1025
vertices1, faces1 = marching_cubes_classic(vol, 0)[:2]
vertices2, faces2 = marching_cubes_lewiner(vol, 0)[:2]
vertices3, faces3 = marching_cubes_lewiner(vol, 0, use_classic=True)[:2]
# Old and new alg are different
assert not _same_mesh(vertices1, faces1, vertices2, faces2)
# New classic and new Lewiner are different
assert not _same_mesh(vertices2, faces2, vertices3, faces3)
# Would have been nice if old and new classic would have been the same
# assert _same_mesh(vertices1, faces1, vertices3, faces3, 5)
if __name__ == '__main__':
np.testing.run_module_suite()