Working with a Hexagonal Grid

This example showcases one of the more esoteric features of scikit-bot’s transformation library: transformation to and from hexagonal coordinate systems.

Why would you want to use hexagonal coordinates? Various reasons: Firstly, they are useful in imaging applications, because they provide better sampling efficiency than their square-shaped alternative. Secondly, they are useful for occupancy grids because neighbouring elements have the same distance from each other. Finally, they look really cool and make for nice visualizations in a paper.

import numpy as np
import skbot.transform as tf
import matplotlib.pyplot as plt
import matplotlib

cmap = matplotlib.cm.get_cmap("tab10")

There is two parts to using them: Firstly there is tf.AxialHexagonTransform, which transforms 2D cartesian coordinates into hexagonal coordinates. Here are a few examples:

tf.AxialHexagonTransform()
tf.AxialHexagonTransform(size=4)  # a hex grid with larger hexagons
tf.AxialHexagonTransform(flat_top=False)  # a hex grid that is rotated by 90 degrees

When you use this transformation, the result will be a continous value, which is useful because it keeps all information and can be inverted to go back to cartesian coordinates. However, often you want to know which hexagon a coordinate falls into, which is where tf.HexagonAxisRound comes in. As the name suggests, it will round a vector in hexagon coordinates to the closest hexagon.

We can use this, for example, to perform rejection sampling on the hexagon grid:

to_hex = tf.AxialHexagonTransform()
from_hex = tf.InvertLink(to_hex)

# a 3x3 hex grid (because we can use a nice colormap in this case)
grid = np.stack(np.meshgrid(np.arange(3), np.arange(3)), axis=-1)
hex_centers = from_hex.transform(grid)

sampling_rectangle = [-1, -1, 7, 5]
x, y, w, h = [-1, -1, 7, 5]
candidate_points = np.random.rand(150, 2) * (h, w) + (y, x)
hex_points = to_hex.transform(candidate_points)
hex_points_rounded = tf.HexagonAxisRound().transform(hex_points)
is_in_hexagon = np.all(np.isin(hex_points_rounded, [0, 1, 2]), axis=1)
points = candidate_points[is_in_hexagon, :]  # points are rejected here

We can also use tf.HexagonAxisRound to figure out which hexagon a sampled point fell into, and then use that information to label the sampled point:

hex_points = to_hex.transform(points)
hex_points_rounded = tf.HexagonAxisRound().transform(hex_points)
col_idx = np.ravel_multi_index(tuple(hex_points_rounded[:, ::-1].T), (3, 3))

plt.scatter(points[..., 1], points[..., 0], marker="s", c=cmap(col_idx))
plt.scatter(hex_centers[..., 1], hex_centers[..., 0], c=cmap(np.arange(9)), s=150)
for pos in hex_centers.reshape(-1, 2):
    patch = matplotlib.patches.RegularPolygon(
        (pos[1], pos[0]), numVertices=6, radius=1, alpha=0.2, edgecolor="k"
    )
    plt.gca().add_patch(patch)
plot hexagonal grids

Total running time of the script: ( 0 minutes 0.150 seconds)

Gallery generated by Sphinx-Gallery