LARBED Extraction (pyextal.larbed)¶
pyextal.larbed is the stage that runs before refinement. It turns a raw
EMPAD 4D-STEM acquisition into one 2D rocking curve per reflection — the arrays
that LARBEDDiffractionInfo and the rest of the pipeline consume.
It was merged in from the standalone PyLARBED package, so the class and method names are unchanged; only the import path is new:
from pyextal.larbed import LARBEDAnalysis, LARBEDCalibration
Where it sits in the pipeline¶
scan_x256_y256.raw (EMPAD, 4D: scan_x × scan_y × det_x × det_y)
│
▼
┌──────────────────────────────────────────────┐
│ Stage 0 — Extraction (pyextal.larbed) │
│ │
│ LARBEDAnalysis │
│ → load_data / align_data │
│ → assign_gvector + calculate_grid_vectors │
│ → calculate_g_vectors │
│ → IntegrateLarbed │
│ → to_dinfo(...) or save_h5(...) │
└───────────────┬──────────────────────────────┘
│ LARBEDDiffractionInfo / {sample}.h5 ──▶ /raw
▼
Stage 1 — Pre-refinement (LARBEDROI, CoarseOptimize, FineOptimize)
▼
Stage 2 — JAX refinement (jaxEngine)
Typical workflow¶
from pyextal.larbed import LARBEDAnalysis
larbed = LARBEDAnalysis("scan_x256_y256.raw")
larbed.load_data(type=0)
larbed.average_data()
# larbed.align_data() # optional; only if the direct beam drifts
# 1. Index the reflections. Two basis vectors for a zone-axis pattern …
larbed.assign_gvector((46, 48), (64, 36)) # detector pixel positions
larbed.calculate_grid_vectors(crop=2, order=3, Adjust=True)
larbed.calculate_g_vectors((1, 3, -2), (4, 2, 0)) # matching Miller indices
larbed.plot_grid_vectors(ratio=0.005) # check the indexing
# 2. Integrate each reflection into a rocking curve
larbed.IntegrateLarbed(ri=3, ro=(6, 8), g=580, m=0.47, Variance=True)
larbed.plot_larbed(ratio=0.5)
assign_gvector takes detector pixel positions; calculate_g_vectors takes
the Miller indices of those same reflections, in the same order. Supply two
of each for a zone-axis grid, or four to include HOLZ reflections:
larbed.assign_gvector((59.7, 52.3), (52.1, 63.2), (53, 32.2), (62, 62))
larbed.calculate_grid_vectors(order=(8, 8, 0, 0), Adjust=False)
larbed.calculate_g_vectors((-1, 0, 0), (0, 1, 0), (0, 0, 1), (0, 0, 0))
calculate_grid_vectors dispatches on how many basis vectors it was given: four
selects the HOLZ grid (and order must then be a 4-tuple), two selects either
the peak-snapped grid (Adjust=True) or the plain lattice grid.
Aperture and variance¶
IntegrateLarbed(ri, ro, g, m, DQE, varB, Variance) sums each reflection over a
square aperture of half-width ri. When ro=(inner, outer) is given, the mean
of that annulus is subtracted as background.
With Variance=True the counting variance is propagated alongside. The per-pixel
model matches pyextal.LucyRichardson.sgnoise(): a signal-dependent term
g*m*I/DQE plus a constant readout term g*m*varB, summed over the aperture.
varB defaults to 0.
Handing off to refinement¶
Two routes, both replacing the older .npy round-trip:
# (a) straight into the refinement objects
dinfo = larbed.to_dinfo("si110.dat", thickness=1000.0, gl=37.0)
roi = LARBEDROI(dinfo=dinfo, rotation=214, gx=..., gInclude=[...])
# (b) into the same HDF5 file the later stages append to
larbed.save_h5("sample.h5", prefix="YIG_tilt12", crystal_dat="yig.dat")
save_h5 writes /raw/dp, /raw/variance, and /raw/g_vectors — exactly what
pyextal.hdf5io.save_raw() defines — so extraction, pre-refinement, and
every JAX refinement run share one file per dataset.
gl is the tilt step size, expressed as the number of scan steps spanning the
x-axis reflection. Deriving it is covered in
Understand how to define/index LARBED dataset;
calculate_average_step_length() measures
the pixels-per-step half of it.
stack() returns the same three arrays
directly if you would rather handle them yourself, and
save_larbed() still writes the loose .npy
files.
MTF calibration and deconvolution¶
Note
Legacy. Because IntegrateLarbed already integrates over an aperture,
deconvolving against the MTF mainly adds noise, and deconv_Larbedstack
discards the propagated variance. It is normally skipped.
LARBEDCalibration measures the detector point-spread function from a reference
scan taken with no sample and descan off:
calib = LARBEDCalibration("probe_scan.raw")
calib.load_data(type=0)
calib.find_peaks(start=100, end=115)
calib.calculate_average_step_length()
calib.crop_peaks()
calib.interpolate()
calib.calculate_mtf()
larbed.deconv_Larbedstack(mtf_2d=calib.mtf_2d, varB=787, g=580, niter=10)
Two convolution conventions¶
calculate_mtf returns a Fourier-domain MTF, and
pyextal.LucyRichardson.lucy_Richardson() accepts a mode argument
selecting how it is applied:
|
Operator |
Use |
|---|---|---|
|
|
Consistent with a Fourier-domain MTF, and what the CBED path has always used. |
|
|
What the standalone PyLARBED used. Kept so earlier results reproduce exactly. |
The LARBEDAnalysis deconvolution methods pass mode='real', so they reproduce
pre-merge PyLARBED output bit for bit. The two operators are genuinely
different — a Fourier-domain array fed to the real-space convolution is not the
same deconvolution — so switching an existing analysis to 'fourier' will change
its numbers.
Worked example¶
example/LARBED/STO_001/STO_001_LARBED.ipynb runs this whole page against a real
SrTiO₃ [001] dataset and then continues through pre-refinement, using the
indexing parameters validated in the original PyLARBED notebook. It ships with
its crystal file, STO_001.dat.
Note the step it takes between extraction and refinement. STO_001.dat leaves
both of its beam lists implicit: sele picks the matrix beams geometrically at
parse time and aper picks the output reflections by g-length, so both move with
the voltage, the tilt, and xaxis. The notebook rewrites it as
STO_001_explicit.dat with an explicit hkl block (all 185 beams) and an
explicit out block set to exactly gInclude, and refines against that — so the
calculation is reproducible beam for beam.
The output beams are written as a prefix of the hkl list, so
hkl[:nout] == hklout and the engine’s idptr mapping is the identity. That is
worth preserving: bloch_parse resolves each out entry to its index in hkl,
and any entry it cannot find is appended to hkl — past the array already
allocated from the declared nbeams.
Warning
That notebook’s gl (tilt step size) is a placeholder and its Debye-Waller
factors are literature starting values. Set them for your data before trusting a
refined number.
Module layout¶
Module |
Contents |
|---|---|
|
|
|
|
|
Raw I/O, virtual apertures, reciprocal-grid construction |
|
Least-squares peak models and fitting |
Throughout pyextal.larbed.empad, x is the first (slow) detector axis and
y the second — not the screen-space x/y of a plotted image. The grid builders
take a shape argument used to reject off-detector nodes; it defaults to
(124, 124), the cropped EMPAD frame.