Data model and coordinates#

A 4D-STEM acquisition records one two-dimensional diffraction pattern at each two-dimensional probe position:

\[ I(\mathbf R,\mathbf k)=I[R_r,R_c,k_r,k_c]. \]

The public array convention is

\[ (\text{row},\text{column}) \equiv (r,c). \]

Therefore \(\mathbf R=(R_r,R_c)\) is the probe/scan coordinate in real space and \(\mathbf k=(k_r,k_c)\) is the detector scattering coordinate in diffraction space. Code uses row/column; equations use the matching \(r\)/\(c\) component subscripts. Both express the same ordered axes.

Shapes and indexing#

For a source with scan shape \((N_{R_r},N_{R_c})\) and detector shape \((N_{k_r},N_{k_c})\):

data.shape == (scan_rows, scan_columns, detector_rows, detector_columns)
           == (N_{R_r}, N_{R_c}, N_{k_r}, N_{k_c})

data[row, column] selects one diffraction pattern. A scan-shaped result such as BF or iDPC has shape (scan_rows, scan_columns). A detector-shaped result such as the mean diffraction pattern has shape (detector_rows, detector_columns).

Python acquisition and calibration#

io.load(path) returns Dataset4dstemGPU, an acquisition handle for encoded GPU storage. It is independent of QuantEM’s core dataset classes. Use ordinary indexing to decode the selected working region into a Torch tensor:

pattern = data[10, 12]
patch = data[8:12, 10:16, 64:128, 64:128]

Attribute

Meaning

data.shape, data.ndim, data.size

Logical dimensions and number of values

data.sampling

Per-axis spacing

data.units

Unit for each axis

data.origin

First-pixel coordinate for each axis

data.metadata

Complete calibration, provenance and storage record

Sampling, units and origin are read-only tuples in the same axis order as shape. Unknown entries are None. A selected Torch tensor does not retain these axis records automatically. When displaying a crop, use its matching sampling and account for the crop offset; do not label it with invented units. See I/O for selection and lifetime rules.

Regions are half-open#

Both scan and detector regions use:

(row_start, row_stop, column_start, column_stop)

This is a half-open interval: the start is included and the stop is excluded. Cropping is an explicit scientific choice, never an implicit memory or speed policy.

Binning preserves counts#

For detector bin factors \((b_r,b_c)\), the binned count at output detector coordinate \((Q_r,Q_c)\) is

\[ I_b[R_r,R_c,Q_r,Q_c] =\sum_{i=0}^{b_r-1}\sum_{j=0}^{b_c-1} I[R_r,R_c,b_rQ_r+i,b_cQ_c+j]. \]

The implementation widens the accumulator before summation. Incomplete edge bins are retained, so output dimensions use ceiling division. The output metadata records the original and output shapes, factors, source dtype, accumulation dtype, and output dtype.

Units#

Quantity

Coordinates

Typical units

Probe/scan position \(\mathbf R\)

\((R_r,R_c)\)

scan pixels, nm, or Å

Detector position \(\mathbf k\)

\((k_r,k_c)\)

detector pixels, mrad, or reciprocal length

Detector mask \(M(\mathbf k)\)

\((k_r,k_c)\)

dimensionless weight

Detector signal \(I(\mathbf R,\mathbf k)\)

all four axes

detector counts

Never label an uncalibrated detector pixel as mrad or a scan index as physical length. Calibration origin and units are part of provenance.

Implementation invariant#

CUDA thread indices, Metal grid coordinates, and WebGPU invocation IDs may be laid out for coalescing. The adapter must translate that private layout back to the public (row, column) ≡ (r, c) contract. Transposition, flattening, or detector-major storage is an implementation detail and requires parity tests that catch row/column swaps and rectangular-shape mistakes.