This MR is a rework of !516 (closed) with a few changes that will hopefully make it more convenient and clear for the user how the grid is constructed. Also, the implementation is faster and requires significantly less branching to to coordinate transformations. Before describing the approach taken, there are a few preconditions:

Requires !527 (merged) to be merged 
Requires !538 (merged) to be merged  (postponed) Would profit from !531 (closed) landing before the merge  otherwise this will require rework upstream
...and two frameworkwide changes required:
 The
Pixel:Index
has altered from type<unsigned int, unsigned int>
to<int, int>
to also allow for negative pixel indices. You will see below why.  The
Pixel
object has a new property calledPixel:Type
to also store the shape of the pixel in output data. Only with this, a correct interpretation is ensured. We could rename this toPixel:Shape
of preferred.
Alright, let's dive into the changes. First of all I can really recommend reading this excellent overview of hexagonal grids which also already includes much of the math required to handle our hexagonal detector and the coordinate transformations to and from Cartesian local coordinates. Not only are the explanations very clear, there are also very nice interactive animations which help in getting a better feeling for how these grids work mathematically.
For the APSQ implementation we chose axial coordinates here because they
 make many calculations quite simple and fast since they derive from a projection of cubic Cartesian coordinates
 require only two coordinates and therefore fit well into the framework and the indices in Cartesian coordinates already available.
 minimize required memory footprint by storing only two and not the third (redundant) cubic coordinate. The latter can always be reconstructed from the first two as
z =  x  y
.
With this, we now have two options of orienting out hexagons on our pixel plane. The manual already has a basic description of the coordinate system and the relevant parameters, so let's take a figure from there:
The two orientations of hexagons are subsequently referred to as pointy (with sides parallel to the y axis of the Cartesian coordinate system and corners at the top and bottom) and flat (with sides parallel to the Cartesian x axis and corners to the left and right). The pitches p_x
and p_y
of the hexagon align with the axial coordinate system and are rotated differently with respect to the Cartesian system between the two variants. Accordingly, the resulting corner positions in Cartesian coordinates are different for the two variants.
The pitches have been chosen differently than in the linked reference. While in the original description, the size of the hexagons are defined by the radii from the center to two adjacent corners, this implementation has chosen the diameter of the hexagon at two adjacent corners instead. This makes the definition more compatible with rectangular pixel grids, where the pitch also refers to the full side length of the pixel, corner to corner, instead of the halflength to the pixel center. This should also simplify things for plotting adjustments later on.
From these basic building blocks, we can now construct our pixel grid. For now, the implementation expects the lowerleft hexagon pixel with indices (0,0) to be among the leftmost and bottommost pixels. Since there is no special treatment fro sensor borders in APSQ, this is not really a restriction. How does a 8x4 pixel grid look like now? It looks like this (also from the manual):
Note that with two pitches defined, irregular (or stretched) hexagons can be used as well, simply by providing different sizes for pitch p_x
and p_y
. Here a demonstration taken from the hexagon compendium linked above:
The negative pixel indices mentioned above appear on the top left portion of the matrix as a result of the axes running at an angle of 60° with respect to each other instead of the Cartesian 90°. The same also results in some pixel indices being cut off at the top right corner of the grid where the y
coordinate would be running out of the rectangular hexagon grid:
The implemented detector model currently supports:
 Transformation from local Cartesian coordinates of the sensor to hexagon indices (
getPixelIndex
)  Transformation from hexagon indices to the local Cartesian center of that hexagon (
getPixelCenter
)  Calculation of the total grid size, including protruding sides or edges for the two variants (
getGridSize
)  Calculation of the geometric center of the sensor, taking into account the offset of hexagon corners (
getCenter
)  The check if a given pair of hexagon indices are part of the defined sensor grid (
isWithinPixelGrid
)  The nearest neighbor search for single hexagons and pairs of hexagons, required for induction calculation (
getNeighbors
)  The calculation of hexagon distances required for clustering (
areNeighbors
)
The following things are currently missing:
 (separate) A correct wrapping and lookup of fields (electric, weighting potential etc)  followed up in !560 (merged)

The calculation of inpixel coordinates (also required for rectangular pixel grids, low priority as only used for plotting)  (postponed) A better structured documentation of the different detector models, likely happening only after !531 (closed) is merged.