Tests

The following illustates a subset of the unit tests used to validate the code implementation. For a complete listing of the unit sets, see voxels.rs and voxel.py.

The Python script used to generate the figures is included below.

Remark: We use the convention np when importing numpy as follows:

import numpy as np

Single

The minimum working example (MWE) is a single voxel, used to create a single mesh consisting of one block consisting of a single element. The NumPy input single.npy contains the following segmentation:

segmentation = np.array(
    [
        [
            [ 11, ],
        ],
    ],
    dtype=np.uint8,
)

where the segmentation 11 denotes block 11 in the finite element mesh.

Remark: Serialization (write and read)

WriteRead
Use the np.save command to serialize the segmentation a .npy fileUse the np.load command to deserialize the segmentation from a .npy file
Example: Write the data in segmentation to a file called seg.npy

np.save("seg.npy", segmentation)Example: Read the data from the file seg.npy to a variable called loaded_array

loaded_array = np.load("seg.npy")

Equivalently, the single.spn contains a single integer:

11   #  x:1  y:1  z:1

The resulting finite element mesh is visualized is shown in the following figure:

single.png

Figure: The single.png visualization, (left) lattice node numbers, (right) mesh node numbers. Lattice node numbers appear in gray, with (x, y, z) indices in parenthesis. The right-hand rule is used. Lattice coordinates start at (0, 0, 0), and proceed along the x-axis, then the y-axis, and then the z-axis.

The finite element mesh local node numbering map to the following global node numbers identically, and local=S_global:

[1, 2, 4, 3, 5, 6, 8, 7]
->
[1, 2, 4, 3, 5, 6, 8, 7]

which is a special case not typically observed, as shown in more complex examples below.

Double

The next level of complexity example is a two-voxel domain, used to create a single block composed of two finite elements. We test propagation in both the x and y directions. The figures below show these two meshes.

Double X

11   #  x:1  y:1  z:1
11   #    2    1    1

where the segmentation 11 denotes block 11 in the finite element mesh.

double_x.png

Figure: Mesh composed of a single block with two elements, propagating along the x-axis, (left) lattice node numbers, (right) mesh node numbers.

Double Y

11   #  x:1  y:1  z:1
11   #    1    2    1

where the segmentation 11 denotes block 11 in the finite element mesh.

double_y.png

Figure: Mesh composed of a single block with two elements, propagating along the y-axis, (left) lattice node numbers, (right) mesh node numbers.

Triple

11   #  x:1  y:1  z:1
11   #    2    1    1
11   #    3    1    1

where the segmentation 11 denotes block 11 in the finite element mesh.

triple_x.png

Figure: Mesh composed of a single block with three elements, propagating along the x-axis, (left) lattice node numbers, (right) mesh node numbers.

Quadruple

11   #  x:1  y:1  z:1
11   #    2    1    1
11   #    3    1    1
11   #    4    1    1

where the segmentation 11 denotes block 11 in the finite element mesh.

quadruple_x.png

Figure: Mesh composed of a single block with four elements, propagating along the x-axis, (left) lattice node numbers, (right) mesh node numbers.

Quadruple with Voids

99   #  x:1  y:1  z:1
0    #    2    1    1
0    #    3    1    1
99   #    4    1    1

where the segmentation 99 denotes block 99 in the finite element mesh, and segmentation 0 is excluded from the mesh.

quadruple_2_voids_x.png

Figure: Mesh composed of a single block with two elements, propagating along the x-axis and two voids, (left) lattice node numbers, (right) mesh node numbers.

Quadruple with Two Blocks

100  #  x:1  y:1  z:1
101  #    2    1    1
101  #    3    1    1
100  #    4    1    1

where the segmentation 100 and 101 denotes block 100 and 101, respectively in the finite element mesh.

quadruple_2_blocks.png

Figure: Mesh composed of two blocks with two elements elements each, propagating along the x-axis, (left) lattice node numbers, (right) mesh node numbers.

Quadruple with Two Blocks and Void

102  #  x:1  y:1  z:1
103  #    2    1    1
0    #    3    1    1
102  #    4    1    1

where the segmentation 102 and 103 denotes block 102 and 103, respectively, in the finite element mesh, and segmentation 0 will be included from the finite element mesh.

quadruple_2_blocks_void.png

Figure: Mesh composed of one block with two elements, a second block with one element, and a void, propagating along the x-axis, (left) lattice node numbers, (right) mesh node numbers.

Cube

11   #  x:1  y:1  z:1
11   #  _ 2  _ 1    1
11   #    1    2    1
11   #  _ 2  _ 2  _ 1
11   #    1    1    2
11   #  _ 2  _ 1    2
11   #    1    2    2
11   #  _ 2  _ 2  _ 2

where the segmentation 11 denotes block 11 in the finite element mesh.

cube.png

Figure: Mesh composed of one block with eight elements, (left) lattice node numbers, (right) mesh node numbers.

Cube with Multi Blocks and Void

82   #  x:1  y:1  z:1
2    #  _ 2  _ 1    1
2    #    1    2    1
2    #  _ 2  _ 2  _ 1
0    #    1    1    2
31   #  _ 2  _ 1    2
0    #    1    2    2
44   #  _ 2  _ 2  _ 2

where the segmentation 82, 2, 31 and 44 denotes block 82, 2, 31 and 44, respectively, in the finite element mesh, and segmentation 0 will be included from the finite element mesh.

cube_multi.png

Figure: Mesh composed of four blocks (block 82 has one element, block 2 has three elements, block 31 has one element, and block 44 has one element), (left) lattice node numbers, (right) mesh node numbers.

Cube with Inclusion

11   #  x:1  y:1  z:1
11   #    2    1    1
11   #  _ 3  _ 1    1
11   #    1    2    1
11   #    2    2    1
11   #  _ 3  _ 2    1
11   #    1    3    1
11   #    2    3    1
11   #  _ 3  _ 3  _ 1
11   #    1    1    2
11   #    2    1    2
11   #  _ 3  _ 1    2
11   #    1    2    2
88   #    2    2    2
11   #  _ 3  _ 2    2
11   #    1    3    2
11   #    2    3    2
11   #  _ 3  _ 3  _ 2
11   #    1    1    3
11   #    2    1    3
11   #  _ 3  _ 1    3
11   #    1    2    3
11   #    2    2    3
11   #  _ 3  _ 2    3
11   #    1    3    3
11   #    2    3    3
11   #  _ 3  _ 3  _ 3

cube_with_inclusion.png

Figure: Mesh composed of 26 voxels of (block 11) and one voxel inslusion (block 88), (left) lattice node numbers, (right) mesh node numbers.

Letter F

11   #  x:1  y:1  z:1
0    #    2    1    1
0    #  _ 3  _ 1    1
11   #    1    2    1
0    #    2    2    1
0    #  _ 3  _ 2    1
11   #    1    3    1
11   #    2    3    1
0    #  _ 3  _ 3    1
11   #    1    4    1
0    #    2    4    1
0    #  _ 3  _ 4    1
11   #    1    5    1
11   #    2    5    1
11   #  _ 3  _ 5  _ 1

where the segmentation 11 denotes block 11 in the finite element mesh.

letter_f.png

Figure: Mesh composed of a single block with eight elements, (left) lattice node numbers, (right) mesh node numbers.

Letter F in 3D

1    #  x:1  y:1  z:1
1    #    2    1    1
1    #    3    1    1
1    #  _ 4  _ 1    1
1    #    1    2    1
1    #    2    2    1
1    #    3    2    1
1    #  _ 4  _ 2    1
1    #    1    3    1
1    #    2    3    1
1    #    3    3    1
1    #  _ 4  _ 3    1
1    #    1    4    1
1    #    2    4    1
1    #    3    4    1
1    #  _ 4  _ 4    1
1    #    1    5    1
1    #    2    5    1
1    #    3    5    1
1    #  _ 4  _ 5  _ 1
1    #  x:1  y:1  z:2
0    #    2    1    2
0    #    3    1    2
0    #  _ 4  _ 1    2
1    #    1    2    2
0    #    2    2    2
0    #    3    2    2
0    #  _ 4  _ 2    2
1    #    1    3    2
1    #    2    3    2
1    #    3    3    2
1    #  _ 4  _ 3    2
1    #    1    4    2
0    #    2    4    2
0    #    3    4    2
0    #  _ 4  _ 4    2
1    #    1    5    2
1    #    2    5    2
1    #    3    5    2
1    #  _ 4  _ 5  _ 2
1    #  x:1  y:1  z:3
0    #    2    1    j
0    #    3    1    2
0    #  _ 4  _ 1    2
1    #    1    2    3
0    #    2    2    3
0    #    3    2    3
0    #  _ 4  _ 2    3
1    #    1    3    3
0    #    2    3    3
0    #    3    3    3
0    #  _ 4  _ 3    3
1    #    1    4    3
0    #    2    4    3
0    #    3    4    3
0    #  _ 4  _ 4    3
1    #    1    5    3
1    #    2    5    3
1    #    3    5    3
1    #  _ 4  _ 5  _ 3

which corresponds to --nelx 4, --nely 5, and --nelz 3 in the command line interface.

letter_f_3d.png

Figure: Mesh composed of a single block with thirty-nine elements, (left) lattice node numbers, (right) mesh node numbers.

The shape of the solid segmentation is more easily seen without the lattice and element nodes, and with decreased opacity, as shown below:

letter_f_3d_alt.png

Figure: Mesh composed of a single block with thirty-nine elements, shown with decreased opacity and without lattice and element node numbers.

Sparse

0    #  x:1  y:1  z:1
0    #    2    1    1
0    #    3    1    1
0    #    4    1    1
2    #  _ 5  _ 1    1
0    #    1    2    1
1    #    2    2    1
0    #    3    2    1
0    #    4    2    1
2    #  _ 5  _ 2    1
1    #    1    3    1
2    #    2    3    1
0    #    3    3    1
2    #    4    3    1
0    #  _ 5  _ 3    1
0    #    1    4    1
1    #    2    4    1
0    #    3    4    1
2    #    4    4    1
0    #  _ 5  _ 4    1
1    #    1    5    1
0    #    2    5    1
0    #    3    5    1
0    #    4    5    1
1    #  _ 5  _ 5  _ 1
2    #  x:1  y:1  z:2
0    #    2    1    2
2    #    3    1    2
0    #    4    1    2
0    #  _ 5  _ 1    2
1    #    1    2    2
1    #    2    2    2
0    #    3    2    2
2    #    4    2    2
2    #  _ 5  _ 2    2
2    #    1    3    2
0    #    2    3    2
0    #    3    3    2
0    #    4    3    2
0    #  _ 5  _ 3    2
1    #    1    4    2
0    #    2    4    2
0    #    3    4    2
2    #    4    4    2
0    #  _ 5  _ 4    2
2    #    1    5    2
0    #    2    5    2
2    #    3    5    2
0    #    4    5    2
2    #  _ 5  _ 5  _ 2
0    #  x:1  y:1  z:3
0    #    2    1    3
1    #    3    1    3
0    #    4    1    3
2    #  _ 5  _ 1    3
0    #    1    2    3
0    #    2    2    3
0    #    3    2    3
1    #    4    2    3
2    #  _ 5  _ 2    3
0    #    1    3    3
0    #    2    3    3
2    #    3    3    3
2    #    4    3    3
2    #  _ 5  _ 3    3
0    #    1    4    3
0    #    2    4    3
1    #    3    4    3
0    #    4    4    3
1    #  _ 5  _ 4    3
0    #    1    5    3
1    #    2    5    3
0    #    3    5    3
1    #    4    5    3
0    #  _ 5  _ 5  _ 3
0    #  x:1  y:1  z:4
1    #    2    1    4
2    #    3    1    4
1    #    4    1    4
2    #  _ 5  _ 1    4
2    #    1    2    4
0    #    2    2    4
2    #    3    2    4
0    #    4    2    4
1    #  _ 5  _ 2    4
1    #    1    3    4
2    #    2    3    4
2    #    3    3    4
0    #    4    3    4
0    #  _ 5  _ 3    4
2    #    1    4    4
1    #    2    4    4
1    #    3    4    4
1    #    4    4    4
1    #  _ 5  _ 4    4
0    #    1    5    4
0    #    2    5    4
1    #    3    5    4
0    #    4    5    4
0    #  _ 5  _ 5  _ 4
0    #  x:1  y:1  z:5
1    #    2    1    5
0    #    3    1    5
2    #    4    1    5
0    #  _ 5  _ 1    5
1    #    1    2    5
0    #    2    2    5
0    #    3    2    5
0    #    4    2    5
2    #  _ 5  _ 2    5
0    #    1    3    5
1    #    2    3    5
0    #    3    3    5
0    #    4    3    5
0    #  _ 5  _ 3    5
1    #    1    4    5
0    #    2    4    5
0    #    3    4    5
0    #    4    4    5
0    #  _ 5  _ 4    5
0    #    1    5    5
0    #    2    5    5
1    #    3    5    5
2    #    4    5    5
1    #  _ 5  _ 5  _ 5

where the segmentation 1 denotes block 1 and segmentation 2 denotes block 2 in the finite eelement mesh (with segmentation 0 excluded).

sparse.png

Figure: Sparse mesh composed of two materials at random voxel locations.

sparse_alt.png

Figure: Sparse mesh composed of two materials at random voxel locations, shown with decreased opactity and without lattice and element node numbers.

Source

The figures were created with figures.py and tested with test_figures.py:

"""This module demonstrates creating a pixel slice in the (x, y)
plane, and then appending layers in the z axis, to create a 3D
voxel lattice, as a precursor for a hexahedral finite element mesh.

This module assumes the virtual environment has been loaded.

Example:

    cd ~/autotwin/automesh
    source .venv/bin/activate

    cd book/examples/unit_tests
    pip install matplotlib
    python figures.py

Ouput:

    The `output_npy` file data structure
    The `output_png` file visualization
"""

# standard library
import datetime
from pathlib import Path
from typing import Final, NamedTuple

# third-party libary
import matplotlib.pyplot as plt
from matplotlib.colors import LightSource
import numpy as np
from numpy.typing import NDArray

# module library
# import sandbox.figures_data as fd  # why doesn't this work?


def hello_world() -> str:
    """Simple example of a function hooked to a command line entry point.

    Returns:
        The canonical "Hello world!" string.
    """

    return "Hello world!"


class Example(NamedTuple):
    """A base class that has all of the fields required to specialize into a
    specific example."""

    figure_title: str = "Figure Title"
    file_stem: str = "filename"
    segmentation = np.array(
        [
            [
                [
                    1,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (1,)
    gold_lattice = None
    gold_mesh_lattice_connectivity = None
    gold_mesh_element_connectivity = None


COMMON_TITLE: Final[str] = "Lattice Index and Coordinates: "


class Single(Example):
    """A specific example of a single voxel."""

    figure_title: str = COMMON_TITLE + "Single"
    file_stem: str = "single"
    segmentation = np.array(
        [
            [
                [
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = ((1, 2, 4, 3, 5, 6, 8, 7),)
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 4, 3, 5, 6, 8, 7),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 4, 3, 5, 6, 8, 7),
        ),
    )


class DoubleX(Example):
    """A specific example of a double voxel, coursed along the x-axis."""

    figure_title: str = COMMON_TITLE + "DoubleX"
    file_stem: str = "double_x"
    segmentation = np.array(
        [
            [
                [
                    11,
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = (
        (1, 2, 5, 4, 7, 8, 11, 10),
        (2, 3, 6, 5, 8, 9, 12, 11),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 5, 4, 7, 8, 11, 10),
            (2, 3, 6, 5, 8, 9, 12, 11),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 5, 4, 7, 8, 11, 10),
            (2, 3, 6, 5, 8, 9, 12, 11),
        ),
    )


class DoubleY(Example):
    """A specific example of a double voxel, coursed along the y-axis."""

    figure_title: str = COMMON_TITLE + "DoubleY"
    file_stem: str = "double_y"
    segmentation = np.array(
        [
            [
                [
                    11,
                ],
                [
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = (
        (1, 2, 4, 3, 7, 8, 10, 9),
        (3, 4, 6, 5, 9, 10, 12, 11),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 4, 3, 7, 8, 10, 9),
            (3, 4, 6, 5, 9, 10, 12, 11),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 4, 3, 7, 8, 10, 9),
            (3, 4, 6, 5, 9, 10, 12, 11),
        ),
    )


class TripleX(Example):
    """A triple voxel lattice, coursed along the x-axis."""

    figure_title: str = COMMON_TITLE + "Triple"
    file_stem: str = "triple_x"
    segmentation = np.array(
        [
            [
                [
                    11,
                    11,
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = (
        (1, 2, 6, 5, 9, 10, 14, 13),
        (2, 3, 7, 6, 10, 11, 15, 14),
        (3, 4, 8, 7, 11, 12, 16, 15),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 6, 5, 9, 10, 14, 13),
            (2, 3, 7, 6, 10, 11, 15, 14),
            (3, 4, 8, 7, 11, 12, 16, 15),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 6, 5, 9, 10, 14, 13),
            (2, 3, 7, 6, 10, 11, 15, 14),
            (3, 4, 8, 7, 11, 12, 16, 15),
        ),
    )


class QuadrupleX(Example):
    """A quadruple voxel lattice, coursed along the x-axis."""

    figure_title: str = COMMON_TITLE + "Quadruple"
    file_stem: str = "quadruple_x"
    segmentation = np.array(
        [
            [
                [
                    11,
                    11,
                    11,
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = (
        (1, 2, 7, 6, 11, 12, 17, 16),
        (2, 3, 8, 7, 12, 13, 18, 17),
        (3, 4, 9, 8, 13, 14, 19, 18),
        (4, 5, 10, 9, 14, 15, 20, 19),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (2, 3, 8, 7, 12, 13, 18, 17),
            (3, 4, 9, 8, 13, 14, 19, 18),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (2, 3, 8, 7, 12, 13, 18, 17),
            (3, 4, 9, 8, 13, 14, 19, 18),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
    )


class Quadruple2VoidsX(Example):
    """A quadruple voxel lattice, coursed along the x-axis, with two
    intermediate voxels in the segmentation being void.
    """

    figure_title: str = COMMON_TITLE + "Quadruple2VoidsX"
    file_stem: str = "quadruple_2_voids_x"
    segmentation = np.array(
        [
            [
                [
                    99,
                    0,
                    0,
                    99,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (99,)
    gold_lattice = (
        (1, 2, 7, 6, 11, 12, 17, 16),
        (2, 3, 8, 7, 12, 13, 18, 17),
        (3, 4, 9, 8, 13, 14, 19, 18),
        (4, 5, 10, 9, 14, 15, 20, 19),
    )
    gold_mesh_lattice_connectivity = (
        (
            99,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            99,
            (1, 2, 6, 5, 9, 10, 14, 13),
            (3, 4, 8, 7, 11, 12, 16, 15),
        ),
    )


class Quadruple2Blocks(Example):
    """A quadruple voxel lattice, with the first intermediate voxel being
    the second block and the second intermediate voxel being void.
    """

    figure_title: str = COMMON_TITLE + "Quadruple2Blocks"
    file_stem: str = "quadruple_2_blocks"
    segmentation = np.array(
        [
            [
                [
                    100,
                    101,
                    101,
                    100,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (
        100,
        101,
    )
    gold_lattice = (
        (1, 2, 7, 6, 11, 12, 17, 16),
        (2, 3, 8, 7, 12, 13, 18, 17),
        (3, 4, 9, 8, 13, 14, 19, 18),
        (4, 5, 10, 9, 14, 15, 20, 19),
    )
    gold_mesh_lattice_connectivity = (
        (
            100,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
        (
            101,
            (2, 3, 8, 7, 12, 13, 18, 17),
            (3, 4, 9, 8, 13, 14, 19, 18),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            100,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
        (
            101,
            (2, 3, 8, 7, 12, 13, 18, 17),
            (3, 4, 9, 8, 13, 14, 19, 18),
        ),
    )


class Quadruple2BlocksVoid(Example):
    """A quadruple voxel lattice, with the first intermediate voxel being
    the second block and the second intermediate voxel being void.
    """

    figure_title: str = COMMON_TITLE + "Quadruple2BlocksVoid"
    file_stem: str = "quadruple_2_blocks_void"
    segmentation = np.array(
        [
            [
                [
                    102,
                    103,
                    0,
                    102,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (
        102,
        103,
    )
    gold_lattice = (
        (1, 2, 7, 6, 11, 12, 17, 16),
        (2, 3, 8, 7, 12, 13, 18, 17),
        (3, 4, 9, 8, 13, 14, 19, 18),
        (4, 5, 10, 9, 14, 15, 20, 19),
    )
    gold_mesh_lattice_connectivity = (
        (
            102,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
        (
            103,
            (2, 3, 8, 7, 12, 13, 18, 17),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            102,
            (1, 2, 7, 6, 11, 12, 17, 16),
            (4, 5, 10, 9, 14, 15, 20, 19),
        ),
        (
            103,
            (2, 3, 8, 7, 12, 13, 18, 17),
        ),
    )


class Cube(Example):
    """A (2 x 2 x 2) voxel cube."""

    figure_title: str = COMMON_TITLE + "Cube"
    file_stem: str = "cube"
    segmentation = np.array(
        [
            [
                [
                    11,
                    11,
                ],
                [
                    11,
                    11,
                ],
            ],
            [
                [
                    11,
                    11,
                ],
                [
                    11,
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = (
        (1, 2, 5, 4, 10, 11, 14, 13),
        (2, 3, 6, 5, 11, 12, 15, 14),
        (4, 5, 8, 7, 13, 14, 17, 16),
        (5, 6, 9, 8, 14, 15, 18, 17),
        (10, 11, 14, 13, 19, 20, 23, 22),
        (11, 12, 15, 14, 20, 21, 24, 23),
        (13, 14, 17, 16, 22, 23, 26, 25),
        (14, 15, 18, 17, 23, 24, 27, 26),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 5, 4, 10, 11, 14, 13),
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
            (10, 11, 14, 13, 19, 20, 23, 22),
            (11, 12, 15, 14, 20, 21, 24, 23),
            (13, 14, 17, 16, 22, 23, 26, 25),
            (14, 15, 18, 17, 23, 24, 27, 26),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 5, 4, 10, 11, 14, 13),
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
            (10, 11, 14, 13, 19, 20, 23, 22),
            (11, 12, 15, 14, 20, 21, 24, 23),
            (13, 14, 17, 16, 22, 23, 26, 25),
            (14, 15, 18, 17, 23, 24, 27, 26),
        ),
    )


class CubeMulti(Example):
    """A (2 x 2 x 2) voxel cube with two voids and six elements."""

    figure_title: str = COMMON_TITLE + "CubeMulti"
    file_stem: str = "cube_multi"
    segmentation = np.array(
        [
            [
                [
                    82,
                    2,
                ],
                [
                    2,
                    2,
                ],
            ],
            [
                [
                    0,
                    31,
                ],
                [
                    0,
                    44,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (
        82,
        2,
        31,
        44,
    )
    gold_lattice = (
        (1, 2, 5, 4, 10, 11, 14, 13),
        (2, 3, 6, 5, 11, 12, 15, 14),
        (4, 5, 8, 7, 13, 14, 17, 16),
        (5, 6, 9, 8, 14, 15, 18, 17),
        (10, 11, 14, 13, 19, 20, 23, 22),
        (11, 12, 15, 14, 20, 21, 24, 23),
        (13, 14, 17, 16, 22, 23, 26, 25),
        (14, 15, 18, 17, 23, 24, 27, 26),
    )
    gold_mesh_lattice_connectivity = (
        # (
        #   0,
        #   (10, 11, 14, 13, 19, 20, 23, 22),
        # ),
        # (
        #   0,
        #   (13, 14, 17, 16, 22, 23, 26, 25),
        (
            2,
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
        ),
        (
            31,
            (11, 12, 15, 14, 20, 21, 24, 23),
        ),
        (
            44,
            (14, 15, 18, 17, 23, 24, 27, 26),
        ),
        (
            82,
            (1, 2, 5, 4, 10, 11, 14, 13),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            2,
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
        ),
        (
            31,
            (11, 12, 15, 14, 19, 20, 22, 21),
        ),
        (
            44,
            (14, 15, 18, 17, 21, 22, 24, 23),
        ),
        (
            82,
            (1, 2, 5, 4, 10, 11, 14, 13),
        ),
    )


class CubeWithInclusion(Example):
    """A (3 x 3 x 3) voxel cube with a single voxel inclusion
    at the center.
    """

    figure_title: str = COMMON_TITLE + "CubeWithInclusion"
    file_stem: str = "cube_with_inclusion"
    segmentation = np.array(
        [
            [
                [
                    11,
                    11,
                    11,
                ],
                [
                    11,
                    11,
                    11,
                ],
                [
                    11,
                    11,
                    11,
                ],
            ],
            [
                [
                    11,
                    11,
                    11,
                ],
                [
                    11,
                    88,
                    11,
                ],
                [
                    11,
                    11,
                    11,
                ],
            ],
            [
                [
                    11,
                    11,
                    11,
                ],
                [
                    11,
                    11,
                    11,
                ],
                [
                    11,
                    11,
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (
        11,
        88,
    )
    gold_lattice = (
        (1, 2, 6, 5, 17, 18, 22, 21),
        (2, 3, 7, 6, 18, 19, 23, 22),
        (3, 4, 8, 7, 19, 20, 24, 23),
        (5, 6, 10, 9, 21, 22, 26, 25),
        (6, 7, 11, 10, 22, 23, 27, 26),
        (7, 8, 12, 11, 23, 24, 28, 27),
        (9, 10, 14, 13, 25, 26, 30, 29),
        (10, 11, 15, 14, 26, 27, 31, 30),
        (11, 12, 16, 15, 27, 28, 32, 31),
        (17, 18, 22, 21, 33, 34, 38, 37),
        (18, 19, 23, 22, 34, 35, 39, 38),
        (19, 20, 24, 23, 35, 36, 40, 39),
        (21, 22, 26, 25, 37, 38, 42, 41),
        (22, 23, 27, 26, 38, 39, 43, 42),
        (23, 24, 28, 27, 39, 40, 44, 43),
        (25, 26, 30, 29, 41, 42, 46, 45),
        (26, 27, 31, 30, 42, 43, 47, 46),
        (27, 28, 32, 31, 43, 44, 48, 47),
        (33, 34, 38, 37, 49, 50, 54, 53),
        (34, 35, 39, 38, 50, 51, 55, 54),
        (35, 36, 40, 39, 51, 52, 56, 55),
        (37, 38, 42, 41, 53, 54, 58, 57),
        (38, 39, 43, 42, 54, 55, 59, 58),
        (39, 40, 44, 43, 55, 56, 60, 59),
        (41, 42, 46, 45, 57, 58, 62, 61),
        (42, 43, 47, 46, 58, 59, 63, 62),
        (43, 44, 48, 47, 59, 60, 64, 63),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 6, 5, 17, 18, 22, 21),
            (2, 3, 7, 6, 18, 19, 23, 22),
            (3, 4, 8, 7, 19, 20, 24, 23),
            (5, 6, 10, 9, 21, 22, 26, 25),
            (6, 7, 11, 10, 22, 23, 27, 26),
            (7, 8, 12, 11, 23, 24, 28, 27),
            (9, 10, 14, 13, 25, 26, 30, 29),
            (10, 11, 15, 14, 26, 27, 31, 30),
            (11, 12, 16, 15, 27, 28, 32, 31),
            (17, 18, 22, 21, 33, 34, 38, 37),
            (18, 19, 23, 22, 34, 35, 39, 38),
            (19, 20, 24, 23, 35, 36, 40, 39),
            (21, 22, 26, 25, 37, 38, 42, 41),
            (23, 24, 28, 27, 39, 40, 44, 43),
            (25, 26, 30, 29, 41, 42, 46, 45),
            (26, 27, 31, 30, 42, 43, 47, 46),
            (27, 28, 32, 31, 43, 44, 48, 47),
            (33, 34, 38, 37, 49, 50, 54, 53),
            (34, 35, 39, 38, 50, 51, 55, 54),
            (35, 36, 40, 39, 51, 52, 56, 55),
            (37, 38, 42, 41, 53, 54, 58, 57),
            (38, 39, 43, 42, 54, 55, 59, 58),
            (39, 40, 44, 43, 55, 56, 60, 59),
            (41, 42, 46, 45, 57, 58, 62, 61),
            (42, 43, 47, 46, 58, 59, 63, 62),
            (43, 44, 48, 47, 59, 60, 64, 63),
        ),
        (
            88,
            (22, 23, 27, 26, 38, 39, 43, 42),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 6, 5, 17, 18, 22, 21),
            (2, 3, 7, 6, 18, 19, 23, 22),
            (3, 4, 8, 7, 19, 20, 24, 23),
            (5, 6, 10, 9, 21, 22, 26, 25),
            (6, 7, 11, 10, 22, 23, 27, 26),
            (7, 8, 12, 11, 23, 24, 28, 27),
            (9, 10, 14, 13, 25, 26, 30, 29),
            (10, 11, 15, 14, 26, 27, 31, 30),
            (11, 12, 16, 15, 27, 28, 32, 31),
            (17, 18, 22, 21, 33, 34, 38, 37),
            (18, 19, 23, 22, 34, 35, 39, 38),
            (19, 20, 24, 23, 35, 36, 40, 39),
            (21, 22, 26, 25, 37, 38, 42, 41),
            (23, 24, 28, 27, 39, 40, 44, 43),
            (25, 26, 30, 29, 41, 42, 46, 45),
            (26, 27, 31, 30, 42, 43, 47, 46),
            (27, 28, 32, 31, 43, 44, 48, 47),
            (33, 34, 38, 37, 49, 50, 54, 53),
            (34, 35, 39, 38, 50, 51, 55, 54),
            (35, 36, 40, 39, 51, 52, 56, 55),
            (37, 38, 42, 41, 53, 54, 58, 57),
            (38, 39, 43, 42, 54, 55, 59, 58),
            (39, 40, 44, 43, 55, 56, 60, 59),
            (41, 42, 46, 45, 57, 58, 62, 61),
            (42, 43, 47, 46, 58, 59, 63, 62),
            (43, 44, 48, 47, 59, 60, 64, 63),
        ),
        (
            88,
            (22, 23, 27, 26, 38, 39, 43, 42),
        ),
    )


class LetterF(Example):
    """A minimal letter F example."""

    figure_title: str = COMMON_TITLE + "LetterF"
    file_stem: str = "letter_f"
    segmentation = np.array(
        [
            [
                [
                    11,
                    0,
                    0,
                ],
                [
                    11,
                    0,
                    0,
                ],
                [
                    11,
                    11,
                    0,
                ],
                [
                    11,
                    0,
                    0,
                ],
                [
                    11,
                    11,
                    11,
                ],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (11,)
    gold_lattice = (
        (1, 2, 6, 5, 25, 26, 30, 29),
        (2, 3, 7, 6, 26, 27, 31, 30),
        (3, 4, 8, 7, 27, 28, 32, 31),
        (5, 6, 10, 9, 29, 30, 34, 33),
        (6, 7, 11, 10, 30, 31, 35, 34),
        (7, 8, 12, 11, 31, 32, 36, 35),
        (9, 10, 14, 13, 33, 34, 38, 37),
        (10, 11, 15, 14, 34, 35, 39, 38),
        (11, 12, 16, 15, 35, 36, 40, 39),
        (13, 14, 18, 17, 37, 38, 42, 41),
        (14, 15, 19, 18, 38, 39, 43, 42),
        (15, 16, 20, 19, 39, 40, 44, 43),
        (17, 18, 22, 21, 41, 42, 46, 45),
        (18, 19, 23, 22, 42, 43, 47, 46),
        (19, 20, 24, 23, 43, 44, 48, 47),
    )
    gold_mesh_lattice_connectivity = (
        (
            11,
            (1, 2, 6, 5, 25, 26, 30, 29),
            # (2, 3, 7, 6, 26, 27, 31, 30),
            # (3, 4, 8, 7, 27, 28, 32, 31),
            (5, 6, 10, 9, 29, 30, 34, 33),
            # (6, 7, 11, 10, 30, 31, 35, 34),
            # (7, 8, 12, 11, 31, 32, 36, 35),
            (9, 10, 14, 13, 33, 34, 38, 37),
            (10, 11, 15, 14, 34, 35, 39, 38),
            # (11, 12, 16, 15, 35, 36, 40, 39),
            (13, 14, 18, 17, 37, 38, 42, 41),
            # (14, 15, 19, 18, 38, 39, 43, 42),
            # (15, 16, 20, 19, 39, 40, 44, 43),
            (17, 18, 22, 21, 41, 42, 46, 45),
            (18, 19, 23, 22, 42, 43, 47, 46),
            (19, 20, 24, 23, 43, 44, 48, 47),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            11,
            (1, 2, 4, 3, 19, 20, 22, 21),
            #
            #
            (3, 4, 6, 5, 21, 22, 24, 23),
            #
            #
            (5, 6, 9, 8, 23, 24, 27, 26),
            (6, 7, 10, 9, 24, 25, 28, 27),
            #
            (8, 9, 12, 11, 26, 27, 30, 29),
            #
            #
            (11, 12, 16, 15, 29, 30, 34, 33),
            (12, 13, 17, 16, 30, 31, 35, 34),
            (13, 14, 18, 17, 31, 32, 36, 35),
        ),
    )


class LetterF3D(Example):
    """A three dimensional variation of the letter F, in a non-standard
    orientation.
    """

    figure_title: str = COMMON_TITLE + "LetterF3D"
    file_stem: str = "letter_f_3d"
    segmentation = np.array(
        [
            [
                [1, 1, 1, 1],
                [1, 1, 1, 1],
                [1, 1, 1, 1],
                [1, 1, 1, 1],
                [1, 1, 1, 1],
            ],
            [
                [1, 0, 0, 0],
                [1, 0, 0, 0],
                [1, 1, 1, 1],
                [1, 0, 0, 0],
                [1, 1, 1, 1],
            ],
            [
                [1, 0, 0, 0],
                [1, 0, 0, 0],
                [1, 0, 0, 0],
                [1, 0, 0, 0],
                [1, 1, 1, 1],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (1,)
    gold_lattice = (
        (1, 2, 7, 6, 31, 32, 37, 36),
        (2, 3, 8, 7, 32, 33, 38, 37),
        (3, 4, 9, 8, 33, 34, 39, 38),
        (4, 5, 10, 9, 34, 35, 40, 39),
        (6, 7, 12, 11, 36, 37, 42, 41),
        (7, 8, 13, 12, 37, 38, 43, 42),
        (8, 9, 14, 13, 38, 39, 44, 43),
        (9, 10, 15, 14, 39, 40, 45, 44),
        (11, 12, 17, 16, 41, 42, 47, 46),
        (12, 13, 18, 17, 42, 43, 48, 47),
        (13, 14, 19, 18, 43, 44, 49, 48),
        (14, 15, 20, 19, 44, 45, 50, 49),
        (16, 17, 22, 21, 46, 47, 52, 51),
        (17, 18, 23, 22, 47, 48, 53, 52),
        (18, 19, 24, 23, 48, 49, 54, 53),
        (19, 20, 25, 24, 49, 50, 55, 54),
        (21, 22, 27, 26, 51, 52, 57, 56),
        (22, 23, 28, 27, 52, 53, 58, 57),
        (23, 24, 29, 28, 53, 54, 59, 58),
        (24, 25, 30, 29, 54, 55, 60, 59),
        (31, 32, 37, 36, 61, 62, 67, 66),
        (32, 33, 38, 37, 62, 63, 68, 67),
        (33, 34, 39, 38, 63, 64, 69, 68),
        (34, 35, 40, 39, 64, 65, 70, 69),
        (36, 37, 42, 41, 66, 67, 72, 71),
        (37, 38, 43, 42, 67, 68, 73, 72),
        (38, 39, 44, 43, 68, 69, 74, 73),
        (39, 40, 45, 44, 69, 70, 75, 74),
        (41, 42, 47, 46, 71, 72, 77, 76),
        (42, 43, 48, 47, 72, 73, 78, 77),
        (43, 44, 49, 48, 73, 74, 79, 78),
        (44, 45, 50, 49, 74, 75, 80, 79),
        (46, 47, 52, 51, 76, 77, 82, 81),
        (47, 48, 53, 52, 77, 78, 83, 82),
        (48, 49, 54, 53, 78, 79, 84, 83),
        (49, 50, 55, 54, 79, 80, 85, 84),
        (51, 52, 57, 56, 81, 82, 87, 86),
        (52, 53, 58, 57, 82, 83, 88, 87),
        (53, 54, 59, 58, 83, 84, 89, 88),
        (54, 55, 60, 59, 84, 85, 90, 89),
        (61, 62, 67, 66, 91, 92, 97, 96),
        (62, 63, 68, 67, 92, 93, 98, 97),
        (63, 64, 69, 68, 93, 94, 99, 98),
        (64, 65, 70, 69, 94, 95, 100, 99),
        (66, 67, 72, 71, 96, 97, 102, 101),
        (67, 68, 73, 72, 97, 98, 103, 102),
        (68, 69, 74, 73, 98, 99, 104, 103),
        (69, 70, 75, 74, 99, 100, 105, 104),
        (71, 72, 77, 76, 101, 102, 107, 106),
        (72, 73, 78, 77, 102, 103, 108, 107),
        (73, 74, 79, 78, 103, 104, 109, 108),
        (74, 75, 80, 79, 104, 105, 110, 109),
        (76, 77, 82, 81, 106, 107, 112, 111),
        (77, 78, 83, 82, 107, 108, 113, 112),
        (78, 79, 84, 83, 108, 109, 114, 113),
        (79, 80, 85, 84, 109, 110, 115, 114),
        (81, 82, 87, 86, 111, 112, 117, 116),
        (82, 83, 88, 87, 112, 113, 118, 117),
        (83, 84, 89, 88, 113, 114, 119, 118),
        (84, 85, 90, 89, 114, 115, 120, 119),
    )
    gold_mesh_lattice_connectivity = (
        (
            1,
            (1, 2, 7, 6, 31, 32, 37, 36),
            (2, 3, 8, 7, 32, 33, 38, 37),
            (3, 4, 9, 8, 33, 34, 39, 38),
            (4, 5, 10, 9, 34, 35, 40, 39),
            (6, 7, 12, 11, 36, 37, 42, 41),
            (7, 8, 13, 12, 37, 38, 43, 42),
            (8, 9, 14, 13, 38, 39, 44, 43),
            (9, 10, 15, 14, 39, 40, 45, 44),
            (11, 12, 17, 16, 41, 42, 47, 46),
            (12, 13, 18, 17, 42, 43, 48, 47),
            (13, 14, 19, 18, 43, 44, 49, 48),
            (14, 15, 20, 19, 44, 45, 50, 49),
            (16, 17, 22, 21, 46, 47, 52, 51),
            (17, 18, 23, 22, 47, 48, 53, 52),
            (18, 19, 24, 23, 48, 49, 54, 53),
            (19, 20, 25, 24, 49, 50, 55, 54),
            (21, 22, 27, 26, 51, 52, 57, 56),
            (22, 23, 28, 27, 52, 53, 58, 57),
            (23, 24, 29, 28, 53, 54, 59, 58),
            (24, 25, 30, 29, 54, 55, 60, 59),
            (31, 32, 37, 36, 61, 62, 67, 66),
            (36, 37, 42, 41, 66, 67, 72, 71),
            (41, 42, 47, 46, 71, 72, 77, 76),
            (42, 43, 48, 47, 72, 73, 78, 77),
            (43, 44, 49, 48, 73, 74, 79, 78),
            (44, 45, 50, 49, 74, 75, 80, 79),
            (46, 47, 52, 51, 76, 77, 82, 81),
            (51, 52, 57, 56, 81, 82, 87, 86),
            (52, 53, 58, 57, 82, 83, 88, 87),
            (53, 54, 59, 58, 83, 84, 89, 88),
            (54, 55, 60, 59, 84, 85, 90, 89),
            (61, 62, 67, 66, 91, 92, 97, 96),
            (66, 67, 72, 71, 96, 97, 102, 101),
            (71, 72, 77, 76, 101, 102, 107, 106),
            (76, 77, 82, 81, 106, 107, 112, 111),
            (81, 82, 87, 86, 111, 112, 117, 116),
            (82, 83, 88, 87, 112, 113, 118, 117),
            (83, 84, 89, 88, 113, 114, 119, 118),
            (84, 85, 90, 89, 114, 115, 120, 119),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            1,
            (1, 2, 7, 6, 31, 32, 37, 36),
            (2, 3, 8, 7, 32, 33, 38, 37),
            (3, 4, 9, 8, 33, 34, 39, 38),
            (4, 5, 10, 9, 34, 35, 40, 39),
            (6, 7, 12, 11, 36, 37, 42, 41),
            (7, 8, 13, 12, 37, 38, 43, 42),
            (8, 9, 14, 13, 38, 39, 44, 43),
            (9, 10, 15, 14, 39, 40, 45, 44),
            (11, 12, 17, 16, 41, 42, 47, 46),
            (12, 13, 18, 17, 42, 43, 48, 47),
            (13, 14, 19, 18, 43, 44, 49, 48),
            (14, 15, 20, 19, 44, 45, 50, 49),
            (16, 17, 22, 21, 46, 47, 52, 51),
            (17, 18, 23, 22, 47, 48, 53, 52),
            (18, 19, 24, 23, 48, 49, 54, 53),
            (19, 20, 25, 24, 49, 50, 55, 54),
            (21, 22, 27, 26, 51, 52, 57, 56),
            (22, 23, 28, 27, 52, 53, 58, 57),
            (23, 24, 29, 28, 53, 54, 59, 58),
            (24, 25, 30, 29, 54, 55, 60, 59),
            (31, 32, 37, 36, 61, 62, 64, 63),
            (36, 37, 42, 41, 63, 64, 66, 65),
            (41, 42, 47, 46, 65, 66, 71, 70),
            (42, 43, 48, 47, 66, 67, 72, 71),
            (43, 44, 49, 48, 67, 68, 73, 72),
            (44, 45, 50, 49, 68, 69, 74, 73),
            (46, 47, 52, 51, 70, 71, 76, 75),
            (51, 52, 57, 56, 75, 76, 81, 80),
            (52, 53, 58, 57, 76, 77, 82, 81),
            (53, 54, 59, 58, 77, 78, 83, 82),
            (54, 55, 60, 59, 78, 79, 84, 83),
            (61, 62, 64, 63, 85, 86, 88, 87),
            (63, 64, 66, 65, 87, 88, 90, 89),
            (65, 66, 71, 70, 89, 90, 92, 91),
            (70, 71, 76, 75, 91, 92, 94, 93),
            (75, 76, 81, 80, 93, 94, 99, 98),
            (76, 77, 82, 81, 94, 95, 100, 99),
            (77, 78, 83, 82, 95, 96, 101, 100),
            (78, 79, 84, 83, 96, 97, 102, 101),
        ),
    )


class Sparse(Example):
    """A radomized 5x5x5 segmentation."""

    figure_title: str = COMMON_TITLE + "Sparse"
    file_stem: str = "sparse"
    segmentation = np.array(
        [
            [
                [0, 0, 0, 0, 2],
                [0, 1, 0, 0, 2],
                [1, 2, 0, 2, 0],
                [0, 1, 0, 2, 0],
                [1, 0, 0, 0, 1],
            ],
            [
                [2, 0, 2, 0, 0],
                [1, 1, 0, 2, 2],
                [2, 0, 0, 0, 0],
                [1, 0, 0, 2, 0],
                [2, 0, 2, 0, 2],
            ],
            [
                [0, 0, 1, 0, 2],
                [0, 0, 0, 1, 2],
                [0, 0, 2, 2, 2],
                [0, 0, 1, 0, 1],
                [0, 1, 0, 1, 0],
            ],
            [
                [0, 1, 2, 1, 2],
                [2, 0, 2, 0, 1],
                [1, 2, 2, 0, 0],
                [2, 1, 1, 1, 1],
                [0, 0, 1, 0, 0],
            ],
            [
                [0, 1, 0, 2, 0],
                [1, 0, 0, 0, 2],
                [0, 1, 0, 0, 0],
                [1, 0, 0, 0, 0],
                [0, 0, 1, 2, 1],
            ],
        ],
        dtype=np.uint8,
    )
    included_ids = (
        1,
        2,
    )
    gold_lattice = (
        (1, 2, 8, 7, 37, 38, 44, 43),
        (2, 3, 9, 8, 38, 39, 45, 44),
        (3, 4, 10, 9, 39, 40, 46, 45),
        (4, 5, 11, 10, 40, 41, 47, 46),
        (5, 6, 12, 11, 41, 42, 48, 47),
        (7, 8, 14, 13, 43, 44, 50, 49),
        (8, 9, 15, 14, 44, 45, 51, 50),
        (9, 10, 16, 15, 45, 46, 52, 51),
        (10, 11, 17, 16, 46, 47, 53, 52),
        (11, 12, 18, 17, 47, 48, 54, 53),
        (13, 14, 20, 19, 49, 50, 56, 55),
        (14, 15, 21, 20, 50, 51, 57, 56),
        (15, 16, 22, 21, 51, 52, 58, 57),
        (16, 17, 23, 22, 52, 53, 59, 58),
        (17, 18, 24, 23, 53, 54, 60, 59),
        (19, 20, 26, 25, 55, 56, 62, 61),
        (20, 21, 27, 26, 56, 57, 63, 62),
        (21, 22, 28, 27, 57, 58, 64, 63),
        (22, 23, 29, 28, 58, 59, 65, 64),
        (23, 24, 30, 29, 59, 60, 66, 65),
        (25, 26, 32, 31, 61, 62, 68, 67),
        (26, 27, 33, 32, 62, 63, 69, 68),
        (27, 28, 34, 33, 63, 64, 70, 69),
        (28, 29, 35, 34, 64, 65, 71, 70),
        (29, 30, 36, 35, 65, 66, 72, 71),
        (37, 38, 44, 43, 73, 74, 80, 79),
        (38, 39, 45, 44, 74, 75, 81, 80),
        (39, 40, 46, 45, 75, 76, 82, 81),
        (40, 41, 47, 46, 76, 77, 83, 82),
        (41, 42, 48, 47, 77, 78, 84, 83),
        (43, 44, 50, 49, 79, 80, 86, 85),
        (44, 45, 51, 50, 80, 81, 87, 86),
        (45, 46, 52, 51, 81, 82, 88, 87),
        (46, 47, 53, 52, 82, 83, 89, 88),
        (47, 48, 54, 53, 83, 84, 90, 89),
        (49, 50, 56, 55, 85, 86, 92, 91),
        (50, 51, 57, 56, 86, 87, 93, 92),
        (51, 52, 58, 57, 87, 88, 94, 93),
        (52, 53, 59, 58, 88, 89, 95, 94),
        (53, 54, 60, 59, 89, 90, 96, 95),
        (55, 56, 62, 61, 91, 92, 98, 97),
        (56, 57, 63, 62, 92, 93, 99, 98),
        (57, 58, 64, 63, 93, 94, 100, 99),
        (58, 59, 65, 64, 94, 95, 101, 100),
        (59, 60, 66, 65, 95, 96, 102, 101),
        (61, 62, 68, 67, 97, 98, 104, 103),
        (62, 63, 69, 68, 98, 99, 105, 104),
        (63, 64, 70, 69, 99, 100, 106, 105),
        (64, 65, 71, 70, 100, 101, 107, 106),
        (65, 66, 72, 71, 101, 102, 108, 107),
        (73, 74, 80, 79, 109, 110, 116, 115),
        (74, 75, 81, 80, 110, 111, 117, 116),
        (75, 76, 82, 81, 111, 112, 118, 117),
        (76, 77, 83, 82, 112, 113, 119, 118),
        (77, 78, 84, 83, 113, 114, 120, 119),
        (79, 80, 86, 85, 115, 116, 122, 121),
        (80, 81, 87, 86, 116, 117, 123, 122),
        (81, 82, 88, 87, 117, 118, 124, 123),
        (82, 83, 89, 88, 118, 119, 125, 124),
        (83, 84, 90, 89, 119, 120, 126, 125),
        (85, 86, 92, 91, 121, 122, 128, 127),
        (86, 87, 93, 92, 122, 123, 129, 128),
        (87, 88, 94, 93, 123, 124, 130, 129),
        (88, 89, 95, 94, 124, 125, 131, 130),
        (89, 90, 96, 95, 125, 126, 132, 131),
        (91, 92, 98, 97, 127, 128, 134, 133),
        (92, 93, 99, 98, 128, 129, 135, 134),
        (93, 94, 100, 99, 129, 130, 136, 135),
        (94, 95, 101, 100, 130, 131, 137, 136),
        (95, 96, 102, 101, 131, 132, 138, 137),
        (97, 98, 104, 103, 133, 134, 140, 139),
        (98, 99, 105, 104, 134, 135, 141, 140),
        (99, 100, 106, 105, 135, 136, 142, 141),
        (100, 101, 107, 106, 136, 137, 143, 142),
        (101, 102, 108, 107, 137, 138, 144, 143),
        (109, 110, 116, 115, 145, 146, 152, 151),
        (110, 111, 117, 116, 146, 147, 153, 152),
        (111, 112, 118, 117, 147, 148, 154, 153),
        (112, 113, 119, 118, 148, 149, 155, 154),
        (113, 114, 120, 119, 149, 150, 156, 155),
        (115, 116, 122, 121, 151, 152, 158, 157),
        (116, 117, 123, 122, 152, 153, 159, 158),
        (117, 118, 124, 123, 153, 154, 160, 159),
        (118, 119, 125, 124, 154, 155, 161, 160),
        (119, 120, 126, 125, 155, 156, 162, 161),
        (121, 122, 128, 127, 157, 158, 164, 163),
        (122, 123, 129, 128, 158, 159, 165, 164),
        (123, 124, 130, 129, 159, 160, 166, 165),
        (124, 125, 131, 130, 160, 161, 167, 166),
        (125, 126, 132, 131, 161, 162, 168, 167),
        (127, 128, 134, 133, 163, 164, 170, 169),
        (128, 129, 135, 134, 164, 165, 171, 170),
        (129, 130, 136, 135, 165, 166, 172, 171),
        (130, 131, 137, 136, 166, 167, 173, 172),
        (131, 132, 138, 137, 167, 168, 174, 173),
        (133, 134, 140, 139, 169, 170, 176, 175),
        (134, 135, 141, 140, 170, 171, 177, 176),
        (135, 136, 142, 141, 171, 172, 178, 177),
        (136, 137, 143, 142, 172, 173, 179, 178),
        (137, 138, 144, 143, 173, 174, 180, 179),
        (145, 146, 152, 151, 181, 182, 188, 187),
        (146, 147, 153, 152, 182, 183, 189, 188),
        (147, 148, 154, 153, 183, 184, 190, 189),
        (148, 149, 155, 154, 184, 185, 191, 190),
        (149, 150, 156, 155, 185, 186, 192, 191),
        (151, 152, 158, 157, 187, 188, 194, 193),
        (152, 153, 159, 158, 188, 189, 195, 194),
        (153, 154, 160, 159, 189, 190, 196, 195),
        (154, 155, 161, 160, 190, 191, 197, 196),
        (155, 156, 162, 161, 191, 192, 198, 197),
        (157, 158, 164, 163, 193, 194, 200, 199),
        (158, 159, 165, 164, 194, 195, 201, 200),
        (159, 160, 166, 165, 195, 196, 202, 201),
        (160, 161, 167, 166, 196, 197, 203, 202),
        (161, 162, 168, 167, 197, 198, 204, 203),
        (163, 164, 170, 169, 199, 200, 206, 205),
        (164, 165, 171, 170, 200, 201, 207, 206),
        (165, 166, 172, 171, 201, 202, 208, 207),
        (166, 167, 173, 172, 202, 203, 209, 208),
        (167, 168, 174, 173, 203, 204, 210, 209),
        (169, 170, 176, 175, 205, 206, 212, 211),
        (170, 171, 177, 176, 206, 207, 213, 212),
        (171, 172, 178, 177, 207, 208, 214, 213),
        (172, 173, 179, 178, 208, 209, 215, 214),
        (173, 174, 180, 179, 209, 210, 216, 215),
    )
    gold_mesh_lattice_connectivity = (
        (
            1,
            (8, 9, 15, 14, 44, 45, 51, 50),
            (13, 14, 20, 19, 49, 50, 56, 55),
            (20, 21, 27, 26, 56, 57, 63, 62),
            (25, 26, 32, 31, 61, 62, 68, 67),
            (29, 30, 36, 35, 65, 66, 72, 71),
            (43, 44, 50, 49, 79, 80, 86, 85),
            (44, 45, 51, 50, 80, 81, 87, 86),
            (55, 56, 62, 61, 91, 92, 98, 97),
            (75, 76, 82, 81, 111, 112, 118, 117),
            (82, 83, 89, 88, 118, 119, 125, 124),
            (93, 94, 100, 99, 129, 130, 136, 135),
            (95, 96, 102, 101, 131, 132, 138, 137),
            (98, 99, 105, 104, 134, 135, 141, 140),
            (100, 101, 107, 106, 136, 137, 143, 142),
            (110, 111, 117, 116, 146, 147, 153, 152),
            (112, 113, 119, 118, 148, 149, 155, 154),
            (119, 120, 126, 125, 155, 156, 162, 161),
            (121, 122, 128, 127, 157, 158, 164, 163),
            (128, 129, 135, 134, 164, 165, 171, 170),
            (129, 130, 136, 135, 165, 166, 172, 171),
            (130, 131, 137, 136, 166, 167, 173, 172),
            (131, 132, 138, 137, 167, 168, 174, 173),
            (135, 136, 142, 141, 171, 172, 178, 177),
            (146, 147, 153, 152, 182, 183, 189, 188),
            (151, 152, 158, 157, 187, 188, 194, 193),
            (158, 159, 165, 164, 194, 195, 201, 200),
            (163, 164, 170, 169, 199, 200, 206, 205),
            (171, 172, 178, 177, 207, 208, 214, 213),
            (173, 174, 180, 179, 209, 210, 216, 215),
        ),
        (
            2,
            (5, 6, 12, 11, 41, 42, 48, 47),
            (11, 12, 18, 17, 47, 48, 54, 53),
            (14, 15, 21, 20, 50, 51, 57, 56),
            (16, 17, 23, 22, 52, 53, 59, 58),
            (22, 23, 29, 28, 58, 59, 65, 64),
            (37, 38, 44, 43, 73, 74, 80, 79),
            (39, 40, 46, 45, 75, 76, 82, 81),
            (46, 47, 53, 52, 82, 83, 89, 88),
            (47, 48, 54, 53, 83, 84, 90, 89),
            (49, 50, 56, 55, 85, 86, 92, 91),
            (58, 59, 65, 64, 94, 95, 101, 100),
            (61, 62, 68, 67, 97, 98, 104, 103),
            (63, 64, 70, 69, 99, 100, 106, 105),
            (65, 66, 72, 71, 101, 102, 108, 107),
            (77, 78, 84, 83, 113, 114, 120, 119),
            (83, 84, 90, 89, 119, 120, 126, 125),
            (87, 88, 94, 93, 123, 124, 130, 129),
            (88, 89, 95, 94, 124, 125, 131, 130),
            (89, 90, 96, 95, 125, 126, 132, 131),
            (111, 112, 118, 117, 147, 148, 154, 153),
            (113, 114, 120, 119, 149, 150, 156, 155),
            (115, 116, 122, 121, 151, 152, 158, 157),
            (117, 118, 124, 123, 153, 154, 160, 159),
            (122, 123, 129, 128, 158, 159, 165, 164),
            (123, 124, 130, 129, 159, 160, 166, 165),
            (127, 128, 134, 133, 163, 164, 170, 169),
            (148, 149, 155, 154, 184, 185, 191, 190),
            (155, 156, 162, 161, 191, 192, 198, 197),
            (172, 173, 179, 178, 208, 209, 215, 214),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            1,
            (3, 4, 9, 8, 35, 36, 42, 41),
            (7, 8, 14, 13, 40, 41, 47, 46),
            (14, 15, 20, 19, 47, 48, 53, 52),
            (18, 19, 25, 24, 51, 52, 58, 57),
            (22, 23, 27, 26, 55, 56, 62, 61),
            (34, 35, 41, 40, 69, 70, 76, 75),
            (35, 36, 42, 41, 70, 71, 77, 76),
            (46, 47, 52, 51, 81, 82, 88, 87),
            (65, 66, 72, 71, 100, 101, 107, 106),
            (72, 73, 79, 78, 107, 108, 114, 113),
            (83, 84, 90, 89, 118, 119, 125, 124),
            (85, 86, 92, 91, 120, 121, 127, 126),
            (88, 89, 95, 94, 123, 124, 129, 128),
            (90, 91, 97, 96, 125, 126, 131, 130),
            (99, 100, 106, 105, 132, 133, 139, 138),
            (101, 102, 108, 107, 134, 135, 141, 140),
            (108, 109, 115, 114, 141, 142, 148, 147),
            (110, 111, 117, 116, 143, 144, 150, 149),
            (117, 118, 124, 123, 150, 151, 157, 156),
            (118, 119, 125, 124, 151, 152, 158, 157),
            (119, 120, 126, 125, 152, 153, 159, 158),
            (120, 121, 127, 126, 153, 154, 160, 159),
            (124, 125, 130, 129, 157, 158, 162, 161),
            (132, 133, 139, 138, 165, 166, 171, 170),
            (137, 138, 144, 143, 169, 170, 176, 175),
            (144, 145, 151, 150, 176, 177, 182, 181),
            (149, 150, 156, 155, 180, 181, 184, 183),
            (157, 158, 162, 161, 185, 186, 190, 189),
            (159, 160, 164, 163, 187, 188, 192, 191),
        ),
        (
            2,
            (1, 2, 6, 5, 32, 33, 39, 38),
            (5, 6, 12, 11, 38, 39, 45, 44),
            (8, 9, 15, 14, 41, 42, 48, 47),
            (10, 11, 17, 16, 43, 44, 50, 49),
            (16, 17, 22, 21, 49, 50, 55, 54),
            (28, 29, 35, 34, 63, 64, 70, 69),
            (30, 31, 37, 36, 65, 66, 72, 71),
            (37, 38, 44, 43, 72, 73, 79, 78),
            (38, 39, 45, 44, 73, 74, 80, 79),
            (40, 41, 47, 46, 75, 76, 82, 81),
            (49, 50, 55, 54, 84, 85, 91, 90),
            (51, 52, 58, 57, 87, 88, 94, 93),
            (53, 54, 60, 59, 89, 90, 96, 95),
            (55, 56, 62, 61, 91, 92, 98, 97),
            (67, 68, 74, 73, 102, 103, 109, 108),
            (73, 74, 80, 79, 108, 109, 115, 114),
            (77, 78, 84, 83, 112, 113, 119, 118),
            (78, 79, 85, 84, 113, 114, 120, 119),
            (79, 80, 86, 85, 114, 115, 121, 120),
            (100, 101, 107, 106, 133, 134, 140, 139),
            (102, 103, 109, 108, 135, 136, 142, 141),
            (104, 105, 111, 110, 137, 138, 144, 143),
            (106, 107, 113, 112, 139, 140, 146, 145),
            (111, 112, 118, 117, 144, 145, 151, 150),
            (112, 113, 119, 118, 145, 146, 152, 151),
            (116, 117, 123, 122, 149, 150, 156, 155),
            (134, 135, 141, 140, 167, 168, 173, 172),
            (141, 142, 148, 147, 173, 174, 179, 178),
            (158, 159, 163, 162, 186, 187, 191, 190),
        ),
    )


def lattice_connectivity(ex: Example) -> NDArray[np.uint8]:
    """Given an Example, prints the lattice connectivity."""
    offset = 0
    nz, ny, nx = ex.segmentation.shape
    nzp, nyp, nxp = nz + 1, ny + 1, nx + 1

    # Generate the lattice nodes
    lattice_nodes = []

    lattice_node = 0
    for k in range(nzp):
        for j in range(nyp):
            for i in range(nxp):
                lattice_node += 1
                lattice_nodes.append([lattice_node, i, j, k])

    # connectivity for each voxel
    cvs = []

    offset = 0

    # print("processing indices...")
    for iz in range(nz):
        for iy in range(ny):
            for ix in range(nx):
                # print(f"(ix, iy, iz) = ({ix}, {iy}, {iz})")
                cv = offset + np.array(
                    [
                        (iz + 0) * (nxp * nyp) + (iy + 0) * nxp + ix + 1,
                        (iz + 0) * (nxp * nyp) + (iy + 0) * nxp + ix + 2,
                        (iz + 0) * (nxp * nyp) + (iy + 1) * nxp + ix + 2,
                        (iz + 0) * (nxp * nyp) + (iy + 1) * nxp + ix + 1,
                        (iz + 1) * (nxp * nyp) + (iy + 0) * nxp + ix + 1,
                        (iz + 1) * (nxp * nyp) + (iy + 0) * nxp + ix + 2,
                        (iz + 1) * (nxp * nyp) + (iy + 1) * nxp + ix + 2,
                        (iz + 1) * (nxp * nyp) + (iy + 1) * nxp + ix + 1,
                    ]
                )
                cvs.append(cv)

    cs = np.vstack(cvs)

    # voxel by voxel comparison
    # breakpoint()
    vv = ex.gold_lattice == cs
    assert np.all(vv)
    return cs


def mesh_lattice_connectivity(
    ex: Example,
    lattice: np.ndarray,
) -> tuple:
    """Given an Example (in particular, the Example's voxel data structure,
    a segmentation) and the `lattice_connectivity`, create the connectivity
    for the mesh with lattice node numbers.  A voxel with a segmentation id not
    in the Example's included ids tuple is excluded from the mesh.
    """

    # segmentation = ex.segmentation.flatten().squeeze()
    segmentation = ex.segmentation.flatten()

    # breakpoint()

    # assert that the list of included ids is equal
    included_set_unordered = set(ex.included_ids)
    included_list_ordered = sorted(included_set_unordered)
    # breakpoint()
    seg_set = set(segmentation)
    for item in included_list_ordered:
        assert (
            item in seg_set
        ), f"Error: `included_ids` item {item} is not in the segmentation"

    # Create a list of finite elements from the lattice elements.  If the
    # lattice element has a segmentation id that is not in the included_ids,
    # exlude the voxel element from the collected list to create the finite
    # element list
    blocks = ()  # empty tuple
    # breakpoint()
    for bb in included_list_ordered:
        # included_elements = []
        elements = ()  # empty tuple
        elements = elements + (bb,)  # insert the block number
        for i, element in enumerate(lattice):
            if bb == segmentation[i]:
                # breakpoint()
                elements = elements + (tuple(element.tolist()),)  # overwrite

        blocks = blocks + (elements,)  # overwrite

    # breakpoint()

    # return np.array(blocks)
    return blocks


def renumber(source: tuple, old: tuple, new: tuple) -> tuple:
    """Given a source tuple, composed of a list of positive integers,
    a tuple of `old` numbers that maps into `new` numbers, return the
    source tuple with the `new` numbers."""

    # the old and the new tuples musts have the same length
    err = "Tuples `old` and `new` must have equal length."
    assert len(old) == len(new), err

    result = ()
    for item in source:
        idx = old.index(item)
        new_value = new[idx]
        result = result + (new_value,)

    return result


def mesh_element_connectivity(mesh_with_lattice_connectivity: tuple):
    """Given a mesh with lattice connectivity, return a mesh with finite
    element connectivity.
    """
    # create a list of unordered lattice node numbers
    ln = []
    for item in mesh_with_lattice_connectivity:
        # print(f"item is {item}")
        # The first item is the block number
        # block = item[0]
        # The second and onward items are the elements
        elements = item[1:]
        for element in elements:
            ln += list(element)

    ln_set = set(ln)  # sets are not necessarily ordered
    ln_ordered = tuple(sorted(ln_set))  # now these unique integers are ordered

    # and they will map into the new compressed unique interger list `mapsto`
    mapsto = tuple(range(1, len(ln_ordered) + 1))

    # now build a mesh_with_element_connectivity
    mesh = ()  # empty tuple
    # breakpoint()
    for item in mesh_with_lattice_connectivity:
        # The first item is the block number
        block_number = item[0]
        block_and_elements = ()  # empty tuple
        # insert the block number
        block_and_elements = block_and_elements + (block_number,)
        for element in item[1:]:
            new_element = renumber(source=element, old=ln_ordered, new=mapsto)
            # overwrite
            block_and_elements = block_and_elements + (new_element,)

        mesh = mesh + (block_and_elements,)  # overwrite

    return mesh


def flatten_tuple(t):
    """Uses recursion to convert nested tuples into a single-sevel tuple.

    Example:
        nested_tuple = (1, (2, 3), (4, (5, 6)), 7)
        flattened_tuple = flatten_tuple(nested_tuple)
        print(flattened_tuple)  # Output: (1, 2, 3, 4, 5, 6, 7)
    """
    flat_list = []
    for item in t:
        if isinstance(item, tuple):
            flat_list.extend(flatten_tuple(item))
        else:
            flat_list.append(item)
    # breakpoint()
    return tuple(flat_list)


def elements_without_block_ids(mesh: tuple) -> tuple:
    """Given a mesh, removes the block ids and returns only just the
    element connectivities.
    """

    aa = ()
    for item in mesh:
        bb = item[1:]
        aa = aa + bb

    return aa


def main():
    """The main program."""

    # Create an instance of a specific example
    # user input begin
    examples = [
        Single(),
        DoubleX(),
        # DoubleY(),
        # TripleX(),
        # QuadrupleX(),
        # Quadruple2VoidsX(),
        # Quadruple2Blocks(),
        # Quadruple2BlocksVoid(),
        # Cube(),
        # CubeMulti(),
        # CubeWithInclusion(),
        # LetterF(),
        # LetterF3D(),
        # Sparse(),
    ]

    # output_dir: Final[str] = "~/scratch"
    output_dir: Final[Path] = Path(__file__).parent
    dpi: Final[int] = 300  # resolution, dots per inch

    for ex in examples:

        # computation
        output_npy: Path = (
            Path(output_dir).expanduser().joinpath(ex.file_stem + ".npy")
        )

        # visualization
        visualize: bool = True  # True performs post-processing visualization
        output_png_short = ex.file_stem + ".png"
        output_png: Path = (
            Path(output_dir).expanduser().joinpath(output_png_short)
        )
        # el, az, roll = 25, -115, 0
        # el, az, roll = 28, -115, 0
        # el, az, roll = 63, -110, 0  # used for most visuals
        el, az, roll = 11, -111, 0  # used for CubeWithInclusion
        # el, az, roll = 60, -121, 0
        # el, az, roll = 42, -120, 0
        #
        # colors
        # cmap = cm.get_cmap("viridis")  # viridis colormap
        # cmap = plt.get_cmap(name="viridis")
        cmap = plt.get_cmap(name="tab10")
        # number of discrete colors
        num_colors = len(ex.included_ids)
        colors = cmap(np.linspace(0, 1, num_colors))
        # breakpoint()
        # azimuth (deg):
        #   0 is east  (from +y-axis looking back toward origin)
        #  90 is north (from +x-axis looking back toward origin)
        # 180 is west  (from -y-axis looking back toward origin)
        # 270 is south (from -x-axis looking back toward origin)
        # elevation (deg): 0 is horizontal, 90 is vertical (+z-axis up)
        lightsource = LightSource(azdeg=325, altdeg=45)  # azimuth, elevation
        nodes_shown: bool = True
        # nodes_shown: bool = False
        voxel_alpha: float = 0.1
        # voxel_alpha: float = 0.7

        # io: if the output directory does not already exist, create it
        output_path = Path(output_dir).expanduser()
        if not output_path.exists():
            print(f"Could not find existing output directory: {output_path}")
            Path.mkdir(output_path)
            print(f"Created: {output_path}")
            assert output_path.exists()

        nelz, nely, nelx = ex.segmentation.shape
        lc = lattice_connectivity(ex=ex)

        mesh_w_lattice_conn = mesh_lattice_connectivity(ex=ex, lattice=lc)
        # breakpoint()
        err = "Calculated lattice connectivity error."
        assert mesh_w_lattice_conn == ex.gold_mesh_lattice_connectivity, err

        mesh_w_element_conn = mesh_element_connectivity(mesh_w_lattice_conn)
        err = "Calcualted element connectivity error."  # overwrite
        assert mesh_w_element_conn == ex.gold_mesh_element_connectivity, err

        # save the numpy data as a .npy file
        np.save(output_npy, ex.segmentation)
        print(f"Saved: {output_npy}")

        # to load the array back from the .npy file,
        # use the numpy.load function:
        loaded_array = np.load(output_npy)

        # verify the loaded array
        print(f"segmentation loaded from saved file: {loaded_array}")

        assert np.all(loaded_array == ex.segmentation)

        # now that the .npy file has been created and verified,
        # move it to the repo at ~/autotwin/automesh/tests/input

        if not visualize:
            return

        # visualization

        # Define the dimensions of the lattice
        nxp, nyp, nzp = (nelx + 1, nely + 1, nelz + 1)

        # Create a figure and a 3D axis
        # fig = plt.figure()
        fig = plt.figure(figsize=(10, 5))  # Adjust the figure size
        # fig = plt.figure(figsize=(8, 4))  # Adjust the figure size
        # ax = fig.add_subplot(111, projection="3d")
        # figure with 1 row, 2 columns
        ax = fig.add_subplot(1, 2, 1, projection="3d")  # r1, c2, 1st subplot
        ax2 = fig.add_subplot(1, 2, 2, projection="3d")  # r1, c2, 2nd subplot

        # For 3D plotting of voxels in matplotlib, we must swap the 'x' and the
        # 'z' axes.  The original axes in the segmentation are (z, y, x) and
        # are numbered (0, 1, 2).  We want new exists as (x, y, z) and thus
        # with numbering (2, 1, 0).
        vox = np.transpose(ex.segmentation, (2, 1, 0))
        # add voxels for each of the included materials
        for i, block_id in enumerate(ex.included_ids):
            # breakpoint()
            solid = vox == block_id
            # ax.voxels(solid, facecolors=voxel_color, alpha=voxel_alpha)
            # ax.voxels(solid, facecolors=colors[i], alpha=voxel_alpha)
            ax.voxels(
                solid,
                facecolors=colors[i],
                edgecolor=colors[i],
                alpha=voxel_alpha,
                lightsource=lightsource,
            )
            # plot the same voxels on the 2nd axis
            ax2.voxels(
                solid,
                facecolors=colors[i],
                edgecolor=colors[i],
                alpha=voxel_alpha,
                lightsource=lightsource,
            )

        # breakpoint()

        # Generate the lattice points
        x = []
        y = []
        z = []
        labels = []

        # Generate the element points
        xel = []
        yel = []
        zel = []
        # generate a set from the element connectivity
        # breakpoint()
        # ec_set = set(flatten_tuple(mesh_w_lattice_conn))  # bug!
        # bug fix:
        ec_set = set(
            flatten_tuple(elements_without_block_ids(mesh_w_lattice_conn))
        )

        # breakpoint()

        lattice_ijk = 0
        # gnn = global node number
        gnn = 0
        gnn_labels = []

        for k in range(nzp):
            for j in range(nyp):
                for i in range(nxp):
                    x.append(i)
                    y.append(j)
                    z.append(k)
                    if lattice_ijk + 1 in ec_set:
                        gnn += 1
                        xel.append(i)
                        yel.append(j)
                        zel.append(k)
                        gnn_labels.append(f" {gnn}")
                    lattice_ijk += 1
                    labels.append(f" {lattice_ijk}: ({i},{j},{k})")

        if nodes_shown:
            # Plot the lattice coordinates
            ax.scatter(
                x,
                y,
                z,
                s=20,
                facecolors="red",
                edgecolors="none",
            )

            # Label the lattice coordinates
            for n, label in enumerate(labels):
                ax.text(x[n], y[n], z[n], label, color="darkgray", fontsize=8)

            # Plot the nodes included in the finite element connectivity
            ax2.scatter(
                xel,
                yel,
                zel,
                s=30,
                facecolors="blue",
                edgecolors="blue",
            )

            # Label the global node numbers
            for n, label in enumerate(gnn_labels):
                ax2.text(
                    xel[n], yel[n], zel[n], label, color="darkblue", fontsize=8
                )

        # Set labels for the axes
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.set_zlabel("z")
        # repeat for the 2nd axis
        ax2.set_xlabel("x")
        ax2.set_ylabel("y")
        ax2.set_zlabel("z")

        x_ticks = list(range(nxp))
        y_ticks = list(range(nyp))
        z_ticks = list(range(nzp))

        ax.set_xticks(x_ticks)
        ax.set_yticks(y_ticks)
        ax.set_zticks(z_ticks)
        # repeat for the 2nd axis
        ax2.set_xticks(x_ticks)
        ax2.set_yticks(y_ticks)
        ax2.set_zticks(z_ticks)

        ax.set_xlim(float(x_ticks[0]), float(x_ticks[-1]))
        ax.set_ylim(float(y_ticks[0]), float(y_ticks[-1]))
        ax.set_zlim(float(z_ticks[0]), float(z_ticks[-1]))
        # repeat for the 2nd axis
        ax2.set_xlim(float(x_ticks[0]), float(x_ticks[-1]))
        ax2.set_ylim(float(y_ticks[0]), float(y_ticks[-1]))
        ax2.set_zlim(float(z_ticks[0]), float(z_ticks[-1]))

        # Set the camera view
        ax.set_aspect("equal")
        ax.view_init(elev=el, azim=az, roll=roll)
        # repeat for the 2nd axis
        ax2.set_aspect("equal")
        ax2.view_init(elev=el, azim=az, roll=roll)

        # Adjust the distance of the camera.  The default value is 10.
        # Increasing/decreasing this value will zoom in/out, respectively.
        # ax.dist = 5  # Change the distance of the camera
        # Doesn't seem to work, and the title is clipping the uppermost node
        # and lattice numbers, so suppress the titles for now.

        # Set the title
        # ax.set_title(ex.figure_title)

        # Add a footnote
        # Get the current date and time in UTC
        now_utc = datetime.datetime.now(datetime.UTC)
        # Format the date and time as a string
        timestamp_utc = now_utc.strftime("%Y-%m-%d %H:%M:%S UTC")
        fn = f"Figure: {output_png_short} "
        fn += f"created with {__file__}\non {timestamp_utc}."
        fig.text(0.5, 0.01, fn, ha="center", fontsize=8)

        # Show the plot
        plt.show()

        # plt.show()
        fig.savefig(output_png, dpi=dpi)
        print(f"Saved: {output_png}")


if __name__ == "__main__":
    main()
r"""This module tests functionality of the included module.

Example:

    cd ~/autotwin/automesh

    Activate the venv with one of the following:
    source .venv/bin/activate       # for bash shell
    source .venv/bin/activate.csh   # for c shell
    source .venv/bin/activate.fish  # for fish shell
    .\.venv\Scripts\activate        # for powershell

    cd book/examples/unit_tests
    python -m pytest test_figures.py -v  # -v is for verbose

    to run a single test in this module, for example `test_hello` function:
    python -m pytest test_figures.py::test_hello -v
"""

import pytest

import figures as ff


def test_hello():
    """This is a minimum working example (MWE) test."""
    assert ff.hello_world() == "Hello world!"


def test_renumber():
    """Tests that the renumber function works as expected."""
    source = (300, 22, 1)
    old = (1, 22, 300, 40)
    new = (42, 2, 9, 1000)

    result = ff.renumber(source=source, old=old, new=new)
    assert result == (9, 2, 42)

    # Assure that tuples old and new of unequal length raise an AssertionError
    new = (42, 2)  # overwrite
    err = "Tuples `old` and `new` must have equal length."
    with pytest.raises(AssertionError, match=err):
        _ = ff.renumber(source=source, old=old, new=new)


def test_mesh_with_element_connectivity():
    """Test CubeMulti by hand."""
    gold_mesh_lattice_connectivity = (
        (
            2,
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
        ),
        (
            31,
            (11, 12, 15, 14, 20, 21, 24, 23),
        ),
        (
            44,
            (14, 15, 18, 17, 23, 24, 27, 26),
        ),
        (
            82,
            (1, 2, 5, 4, 10, 11, 14, 13),
        ),
    )
    gold_mesh_element_connectivity = (
        (
            2,
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
        ),
        (31, (11, 12, 15, 14, 19, 20, 22, 21)),
        (44, (14, 15, 18, 17, 21, 22, 24, 23)),
        (82, (1, 2, 5, 4, 10, 11, 14, 13)),
    )

    result = ff.mesh_element_connectivity(
        mesh_with_lattice_connectivity=gold_mesh_lattice_connectivity
    )

    assert result == gold_mesh_element_connectivity


def test_elements_no_block_ids():
    """Given a mesh, strips the block ids from the"""
    known_input = (
        (
            2,
            (2, 3, 6, 5, 11, 12, 15, 14),
            (4, 5, 8, 7, 13, 14, 17, 16),
            (5, 6, 9, 8, 14, 15, 18, 17),
        ),
        (31, (11, 12, 15, 14, 20, 21, 24, 23)),
        (44, (14, 15, 18, 17, 23, 24, 27, 26)),
        (82, (1, 2, 5, 4, 10, 11, 14, 13)),
    )

    gold_output = (
        (2, 3, 6, 5, 11, 12, 15, 14),
        (4, 5, 8, 7, 13, 14, 17, 16),
        (5, 6, 9, 8, 14, 15, 18, 17),
        (11, 12, 15, 14, 20, 21, 24, 23),
        (14, 15, 18, 17, 23, 24, 27, 26),
        (1, 2, 5, 4, 10, 11, 14, 13),
    )

    result = ff.elements_without_block_ids(mesh=known_input)

    assert result == gold_output