Skip to content

Hexagonal Grid Utilities

btorch provides a comprehensive hex grid toolkit for neuromorphic visual system modelling. All algorithms follow Red Blob Games conventions.

Primary coordinate system: axial (q, r) with s = -q - r implicit.

Coordinate Systems

btorch supports multiple hex coordinate systems. All convert to/from axial internally.

System Module Use Case
Axial (q, r) transform Primary. All internal math.
Cube (q, r, s) transform Distance, line drawing (s = -q-r).
Odd-r / Even-r offset Row-based array storage.
Odd-q / Even-q offset Column-based array storage.
Zigzag (x, y) offset FlyWire connectome data.
Double-width / Double-height doubled Rectangular array maps.
Pixel (x, y) transform Screen-space plotting.

Converting Between Systems

Use the functional API directly:

from btorch.utils.hex import (
    axial_to_odd_r, odd_r_to_axial,
    axial_to_zigzag, zigzag_to_axial,
    axial_to_doublewidth, doublewidth_to_axial,
    to_pixel, from_pixel,
)

# Axial -> odd-r offset -> back
q, r = np.array([1, -2]), np.array([0, 3])
col, row = axial_to_odd_r(q, r)
q_back, r_back = odd_r_to_axial(col, row)

# Axial -> pixel for plotting
x, y = to_pixel(q, r, size=1.0, orientation="pointy")

Or use the HexCoords OOP wrapper:

from btorch.utils.hex import HexCoords

c = HexCoords.from_disk(radius=5)
col, row = c.to_odd_r()
c_back = HexCoords.from_odd_r(col, row)
assert c == c_back

Using resolve_hex

The resolve_hex function is the single entry point for the coord → pixel pipeline used by all visualisation functions:

from btorch.utils.hex import resolve_hex

# Any coord format → (q, r, x, y)
q, r, x, y = resolve_hex(c1, c2, coord_format="axial", layout="pointy")

# FlyWire zigzag → screen coords
q, r, x, y = resolve_hex(zx, zy, coord_format="zigzag", layout="flat")

Supported coord_format values: "axial", "odd_r", "even_r", "odd_q", "even_q", "doublewidth", "doubleheight", "zigzag" (or "flywire", alias), "pixel".

Supported layout values: "pointy", "flat", "flywire", "pixel".

Data Structures

HexCoords

Struct-of-arrays for hex coordinates (q, r):

from btorch.utils.hex import HexCoords

# Create from various sources
c = HexCoords.from_disk(radius=5)
c = HexCoords.from_ring(radius=3)
c = HexCoords.from_spiral(radius=4)
c = HexCoords(q_array, r_array)

# Geometric operations
n = c.neighbors()       # 6 neighbors per hex
d = c.distance()        # distance from origin
rotated = c.rotate(1)   # rotate 60°
reflected = c.reflect("q")

HexData

Coordinates with associated values:

from btorch.utils.hex import HexData

coords = HexCoords.from_disk(radius=5)
values = np.random.randn(len(coords))
data = HexData(coords, values)

# Filtering, sorting, plotting
masked = data.mask(mask_array)
sorted_data = data.sort()
x, y, v = data.to_pixel()

HexGrid

Pre-built circular grid with convenience methods:

from btorch.utils.hex import HexGrid

grid = HexGrid(radius=10)
grid.values = np.random.randn(len(grid))

# Neighbor topology
vn = grid.valid_neighbors()  # list of neighbor index tuples
hull = grid.hull             # boundary ring

# Subset selection
circle = grid.circle(radius=3)
filled = grid.filled_circle(radius=3)

Generating Coordinate Sets

from btorch.utils.hex import disk, ring, spiral, rectangle

q, r = disk(radius=5)           # filled disk
q, r = ring(radius=3)           # boundary ring only
q, r = spiral(radius=4)         # spiral order (center, ring1, ...)
q, r = rectangle(8, 6)          # rectangular patch

Distance and Neighbors

from btorch.utils.hex import distance, radius, within_range, neighbors

d = distance(q1, r1, q2, r2)   # hex distance between point sets
r = radius(q, r)                # distance from origin
mask = within_range(q, r, 0, 0, radius=3)  # boolean mask

qn, rn = neighbors(q, r)       # 6 neighbors per hex, shape (6, n)

Storage (Array Indexing)

Convert between axial coords and flat array indices for rectangular storage:

from btorch.utils.hex import (
    axial_to_rect_index, rect_index_to_axial,
    axial_to_hex_index, hex_index_to_axial,
    align, permute, reflect_index,
)

# Axial -> rectangular array index
row, col = axial_to_rect_index(q, r, orientation="pointy")
q_back, r_back = rect_index_to_axial(row, col, orientation="pointy")

# Align data between grids of different sizes
aligned = align(q_tgt, r_tgt, q_src, r_src, values_src, fill=np.nan)

# Rotation/reflection permutation for data augmentation
perm = permute(radius=5, rotation=1)
rotated_values = values[perm]

Visualization

Static (Matplotlib)

from btorch.visualisation.hex import scatter, flow, grid

scatter(q, r, values, coord_format="axial", cmap="viridis")
flow(q, r, dq, dr, coord_format="axial")
grid(radius=3, annotate=True)

Interactive (Plotly)

from btorch.visualisation.hex import heatmap, heatmap_from_index

heatmap(df, dataset, coord_format="axial", orientation="pointy")
heatmap_from_index(df, title="Connectome Activity")

Animation

from btorch.visualisation.hex.animate import HexScatter, HexFlow

anim = HexScatter(values, q, r, coord_format="axial")
anim.save("activity.mp4", writer="ffmpeg", fps=5)

Hex Convolution

from btorch.models.hex import Conv2dHex

conv = Conv2dHex(
    in_channels=16, out_channels=32,
    kernel_size=7, storage="rect_pointy",
)
out = conv(x)  # hexagonal receptive field mask

References