pyextal.acom¶
Automated crystal orientation mapping for indexed diffraction patterns.
Determines the orientation of an averaged diffraction pattern from its Bragg peak positions, predicts every reflection the detector should see – including weak ones no peak finder would pick up – and writes a .dat ready for structure-factor refinement.
The search is kinematic: peak positions follow from the metric tensor alone, so a candidate orientation costs microseconds rather than the ~10 s a bloch_run takes. The Bloch engine is used once, on the winner, to confirm it.
Example
>>> from pyextal.callBloch import bloch_parse
>>> from pyextal.acom import auto_index
>>> param = bloch_parse("STO_001.dat")
>>> res = auto_index(larbed.average_aligned, param, center=(61.35, 61.5))
>>> res.zone, round(res.mapping.rms, 3)
((0, 0, 1), 0.18)
>>> res.write_dat("STO_001.dat", "STO_001_acom.dat")
Note
Peak positions fix the orientation only up to the Laue symmetry of the zone. On a cubic [001] pattern the solutions at theta, theta+90 and their mirrors are all exact, and every reflection they assign is symmetry-equivalent, so they are interchangeable for structure-factor refinement. Resolving the absolute setting needs intensities – that is what confirm is for.
Submodules¶
Attributes¶
Classes¶
Reflections of one zone axis, projected onto the detector plane. |
|
Where the scan sits in K_t space, relative to the zone axis. |
|
Linear map from in-plane reciprocal coordinates to detector pixels. |
|
Outcome of an orientation solve. |
Functions¶
|
Laue zones a zone axis needs before its library forms a 2D net. |
|
Rational zone axes closest to a beam direction. |
|
Builds the reflection library for one zone axis. |
|
Enumerates primitive low-index zone axes. |
|
Finds higher-order-Laue-zone reflections that land on the detector. |
|
Largest |g| the detector can reach, in inverse Angstroms. |
|
Miller search box big enough to reach gmax. |
|
Finds Bragg peaks and refines them to sub-pixel positions. |
|
Picks the direct beam from a set of detected peaks. |
|
Clones an explicit-beam .dat with a wide convergence for this alignment. |
|
Coarse-refines the direct beam alone to find where the scan sits in K_t. |
|
The incident direction a tilt corresponds to, in crystal coordinates. |
|
Names the beam direction as a rational zone axis. |
|
Re-solves the pattern with the beam along a higher-order zone axis. |
|
Writes the refinement .dat for a tilted orientation. |
|
Reflections on the frame, with how close the scan brings each to Bragg. |
|
Fits the detector map in the beam-normal frame from labelled positions. |
|
The low-index zone axis whose ZOLZ the indexed reflections lie in. |
|
Refines the beam direction from where the HOLZ spots actually are. |
|
Relabels an extraction for the orientation found, picking up FOLZ and HOLZ. |
|
Finds the detector mapping that best explains a set of detected peaks. |
|
Assigns each detected peak the nearest predicted reflection. |
|
Writes a .dat for one orientation, with explicit beam and output blocks. |
|
Copies a .dat, changing only the orientation. |
|
Reads the explicit hkl and out blocks back out of a .dat. |
|
Plots candidate zone axes on a symmetry-reduced stereographic map. |
|
Overlays the indexed reflections on the diffraction pattern. |
|
Per-reflection signal statistics over the ROI. |
|
Splits the ROI's reflections into those worth refining and those that are not. |
|
Decides whether the extracted labels or their negatives fit the dynamics. |
|
Decides which symmetry-related labeling of the extracted reflections fits the dynamics. |
|
Relabels an extraction in place by an integer operation on (h, k, l). |
|
Determines the orientation of a diffraction pattern automatically. |
|
Confirms an orientation dynamically, with the Bloch engine. |
Package Contents¶
- pyextal.acom.min_laue_max(param, zone, gmax, max_index=6, cover=0.8, limit=40)¶
Laue zones a zone axis needs before its library forms a 2D net.
A reflection’s Laue index is
n = g . [uvw]. For a low-index axis the reflections inside a frame nearly all haven = 0, so the ZOLZ alone is a 2D net andlaue_max=0is right. For a high-index axis they are spread over many zones and the ZOLZ collapses: the [1,1,15] ZOLZ of SrTiO3 inside 1.2 A^-1 holds seven reflections, every one on the single row(n,-n,0). Fitting a 2D net to a 1D row is what produced a scale 1.4x too small and indexed (200) as (1,-1,0).This returns the smallest
laue_maxwhose library spans two dimensions out to cover of the requested gmax. Counting instead togmax * |[uvw]|– the largest Laue index any reflection inside gmax can carry – gives 71 here rather than 6, because it counts reflections lying nearly along the zone axis, which project onto the pattern centre and form no part of the net.- Parameters:
param (SimParams) – Parsed simulation parameters.
zone (array-like) – (3,) zone axis, integer Miller indices.
gmax (float) – Reflection library extent, A^-1.
max_index (int, optional) – Miller index bound. Defaults to 6.
cover (float, optional) – Fraction of gmax both in-plane directions must reach. Defaults to 0.8.
limit (int, optional) – Give up above this. Defaults to 40.
- Returns:
Smallest workable
laue_max, or limit if none was found.- Return type:
int
Note
Raising laue_max weakens the zone-axis search in exchange. The ZOLZ restriction is what makes different axes predict different nets; a library carrying every Laue zone projects to something dense enough to match almost anything. Raise it to test a specific high-index axis against a solution you already have, not to widen a blind search.
- pyextal.acom.nearest_zone_axis(param, direction, max_index=20, tol_deg=None)¶
Rational zone axes closest to a beam direction.
A pattern taken off zone has a beam direction that is not, in general, along any lattice row. Naming a high-index axis for it means finding the rational directions that come closest – and there are usually several at nearly the same angle, so this returns a ranked list rather than an answer.
- Parameters:
param (SimParams) – Parsed simulation parameters.
direction (array-like) – (3,) beam direction in crystal coordinates, as
zonetilted by the refinedtiltX/tiltY. Need not be normalised or rational.max_index (int, optional) – Largest index to enumerate. Defaults to 20.
tol_deg (float, optional) – Keep only axes within this angle. All are ranked when None.
- Returns:
(uvw, angle_deg), closest first.- Return type:
list[tuple]
Note
This is bookkeeping, not a measurement. The projected net of a tilted pattern is the net of the low-index axis it is near, and every rational direction close to that axis reproduces the same reflections with the same Miller indices – verified on the SrTiO3 scan taken 4 degrees off [001], where [1,1,15] with enough Laue zones labelled all 23 shared peaks exactly as [001] did. What the geometry cannot do is say which of them the beam was along. That comes from excitation – a visible HOLZ ring, or the tilt that
pyextal.optimize.CoarseOptimizerefines against the dynamical simulation.
- class pyextal.acom.ReflectionLibrary¶
Reflections of one zone axis, projected onto the detector plane.
- Variables:
zone (tuple[int, int, int]) – The zone axis, direct-space indices.
xaxis (tuple[int, int, int]) – Reflection defining the in-plane x axis.
hkl (np.ndarray) – (n, 3) int Miller indices.
xy (np.ndarray) – (n, 2) in-plane coordinates, inverse Angstroms.
gmag (np.ndarray) – (n,) |g| in inverse Angstroms.
laue (np.ndarray) – (n,) Laue zone index,
g . zone; 0 is ZOLZ.weight (np.ndarray) – (n,) kinematic strength |U_g|, 0 for the direct beam.
e1 (np.ndarray) – In-plane x unit vector, Cartesian.
e2 (np.ndarray) – In-plane y unit vector, Cartesian.
n (np.ndarray) – Zone-axis unit vector, Cartesian.
- zone: tuple¶
- xaxis: tuple¶
- hkl: numpy.ndarray¶
- xy: numpy.ndarray¶
- gmag: numpy.ndarray¶
- laue: numpy.ndarray¶
- weight: numpy.ndarray¶
- e1: numpy.ndarray¶
- e2: numpy.ndarray¶
- n: numpy.ndarray¶
- __len__()¶
- select(mask)¶
Returns a new library keeping only the reflections mask selects.
- strong(rel=1e-06)¶
The subset whose kinematic weight is worth matching against.
Extinct and near-extinct reflections are dropped: they may or may not be visible depending on the multiple scattering, so requiring a peak at one, or allowing one to explain a peak, both mislead the orientation search. The direct beam is always kept.
- Parameters:
rel (float, optional) – Keep |U_g| above this fraction of the strongest. Defaults to 1e-6.
- Returns:
The filtered view.
- Return type:
- property zolz¶
Only the zero-order Laue zone.
- Type:
- pyextal.acom.build_library(param, zone, gmax=2.0, max_index=8, laue_max=0, xaxis=None, weights=True, min_weight_rel=0.0)¶
Builds the reflection library for one zone axis.
- Parameters:
param (SimParams) – Parsed simulation parameters; supplies the metric tensors and |K|.
zone (tuple[int, int, int]) – Zone axis, direct-space indices.
gmax (float, optional) – Largest |g| to include, inverse Angstroms. Defaults to 2.0.
max_index (int, optional) – Search box for Miller indices. Defaults to 8.
laue_max (int, optional) – Highest Laue zone to include. 0 keeps the ZOLZ only; 1 adds FOLZ, and so on. Defaults to 0.
xaxis (tuple[int, int, int], optional) – Reflection defining the in-plane x axis. The shortest in-plane reflection is chosen when None; the choice only sets where the fitted rotation angle is measured from.
weights (bool, optional) – Look up kinematic |U_g| for each reflection. Defaults to True.
min_weight_rel (float, optional) –
Drop reflections whose |U_g| falls below this fraction of the strongest. Defaults to 0.0, i.e. keep everything.
In electron diffraction a kinematically forbidden reflection still carries intensity, because multiple scattering routes electrons into it – SrTiO3 (100) is nearly extinct kinematically yet plainly visible. Such reflections therefore have to be predicted and integrated. They are useless for finding the orientation, though, since whether they appear is not something the kinematic weight can say; use
ReflectionLibrary.strong()for the matching subset.
- Returns:
Reflections with |g| <= gmax in the kept Laue zones.
- Return type:
- Raises:
ValueError – If the zone axis admits no in-plane reflection within gmax.
- pyextal.acom.candidate_zone_axes(max_index=3)¶
Enumerates primitive low-index zone axes.
Sign-equivalent axes ([uvw] and [-u-v-w]) give the same pattern, so only one of each pair is returned. Symmetry-equivalent axes are not removed: they score identically, and that degeneracy is a real property of the measurement worth reporting rather than hiding.
- Parameters:
max_index (int, optional) – Largest absolute index considered. Defaults to 3.
- Returns:
Candidate zone axes, shortest first.
- Return type:
list[tuple[int, int, int]]
- pyextal.acom.visible_holz(param, lib, mapping, shape, smax=0.05, laue_max=3, gmax=None, max_index=8)¶
Finds higher-order-Laue-zone reflections that land on the detector.
Run this once the ZOLZ orientation is settled. A HOLZ reflection is kept when its predicted pixel falls inside the frame and, if smax is given, when its excitation error is small enough for it to be excited at all.
- Parameters:
param (SimParams) – Parsed simulation parameters.
lib (ReflectionLibrary) – The solved ZOLZ library; supplies zone and axes.
mapping (DetectorMap) – The fitted detector mapping.
shape (tuple[int, int]) – Detector shape (n_x, n_y).
smax (float, optional) – Largest |s_g| accepted, inverse Angstroms. Defaults to 0.05. Passing None drops the filter, which is rarely what you want: a reflection can project onto the frame while sitting far off the Ewald sphere, so the unfiltered count is dominated by beams that are never excited. For a zone axis the first Laue ring sits at roughly sqrt(2 |K| H), H being the reciprocal layer spacing – if that exceeds the detector’s reach, no HOLZ is excited at all.
laue_max (int, optional) – Highest Laue zone to consider. Defaults to 3.
gmax (float, optional) – Largest |g|; defaults to what the frame can hold.
max_index (int, optional) – Search box for Miller indices. Defaults to 8.
- Returns:
The HOLZ reflections predicted to be on the frame.
- Return type:
- pyextal.acom.frame_gmax(mapping, shape)¶
Largest |g| the detector can reach, in inverse Angstroms.
- Parameters:
mapping (DetectorMap) – The fitted detector mapping.
shape (tuple[int, int]) – Detector shape.
- Returns:
Distance from the direct beam to the furthest frame corner.
- Return type:
float
- pyextal.acom.auto_max_index(gmx, gmax)¶
Miller search box big enough to reach gmax.
A reflection (h00) has |g| = h / a, so the box has to grow with the longest cell edge. Too small a box silently truncates the outer reflections.
- Parameters:
gmx (np.ndarray) – (3, 3) direct-space metric tensor.
gmax (float) – Largest |g| wanted, inverse Angstroms.
- Returns:
Half-width of the index box.
- Return type:
int
- pyextal.acom.detect_peaks(image, min_distance=4, threshold_rel=0.02, num_peaks=200, refine=True, crop=5)¶
Finds Bragg peaks and refines them to sub-pixel positions.
- Parameters:
image (np.ndarray) – 2D averaged diffraction pattern.
min_distance (int, optional) – Smallest separation between peaks, in pixels. Defaults to 4.
threshold_rel (float, optional) – Peak height floor as a fraction of the brightest pixel. Defaults to 0.02.
num_peaks (int, optional) – Most peaks to return. Defaults to 200.
refine (bool, optional) – Fit a 2D Gaussian to each peak for sub-pixel accuracy. Defaults to True.
crop (int, optional) – Half-width of the patch fitted per peak. Defaults to 5.
- Returns:
(positions, intensities)– (n, 2) float pixel coordinates in (x, y) = (row, col) detector convention, and the peak height at each.- Return type:
tuple[np.ndarray, np.ndarray]
- pyextal.acom.direct_beam(positions, intensities, hint=None, search=8.0)¶
Picks the direct beam from a set of detected peaks.
- Parameters:
positions (np.ndarray) – (n, 2) peak positions.
intensities (np.ndarray) – (n,) peak heights.
hint (tuple[float, float], optional) – Approximate position; the nearest peak within search pixels is taken. The brightest peak is used when None.
search (float, optional) – Radius around hint, in pixels. Defaults to 8.
- Returns:
Index into positions, and the position itself.
- Return type:
tuple[int, np.ndarray]
- Raises:
ValueError – If no peak was found, or none lies near hint.
- class pyextal.acom.TiltResult¶
Where the scan sits in K_t space, relative to the zone axis.
- Variables:
offset (np.ndarray) – (2,) scan-step offset of the scan centre from the zone axis. This is the measurement; it does not depend on gl.
tilt (np.ndarray) – (2,) the same offset in degrees, which does.
rotation (float) – Scan-to-simulation rotation, degrees.
thickness (float) – Thickness the match was found at, Angstroms.
correlation (float) – Peak cross-correlation.
significance (float) – Peak over
mean + 3 sigmaof the correlation.on_edge (bool) – The peak reached the edge of the simulated range, so the true optimum lies outside it and the answer is a lower bound. Raise conv and re-run.
gl (float) – The gl used for the degree conversion.
half_angle (float) – Half-width of the simulated K_t range, degrees.
start_tilt (np.ndarray) – (2,) tilt the search started from, degrees.
rotation_scan (dict) – Best correlation at each rotation tried.
- offset: numpy.ndarray¶
- tilt: numpy.ndarray¶
- rotation: float¶
- thickness: float¶
- correlation: float¶
- significance: float¶
- on_edge: bool¶
- gl: float¶
- half_angle: float¶
- start_tilt: numpy.ndarray¶
- rotation_scan: dict¶
- property magnitude¶
Tilt magnitude, degrees.
- Type:
float
- property steps¶
Offset magnitude, scan steps.
- Type:
float
- property correction¶
(2,) how far the search moved from start_tilt, degrees.
A correction much larger than the field is a sign the estimate was not close enough to help; one near zero means the estimate was already right, not that the search did nothing.
- Type:
np.ndarray
- gl_for(angle)¶
The gl that would make this offset a given angle.
- Parameters:
angle (float) – Known tilt magnitude, degrees – from the goniometer.
- Returns:
The implied gl.
- Return type:
float
Note
The inverse of the gl dependence, and the more useful direction when the specimen tilt is known but the tilt step size is not.
- __str__()¶
- pyextal.acom.wide_dat(template, dst, conv, gl, samp=None, zoom=TARGET_ZOOM, out_beam=(0, 0, 0), verbose_cost=True)¶
Clones an explicit-beam .dat with a wide convergence for this alignment.
- Parameters:
template (str | Path) – A .dat with explicit hkl and out blocks – what
pyextal.acom.write_dat()produces. The beam list is carried over unchanged, which is the point: it must not grow with conv.dst (str | Path) – Where to write.
conv (float) – Convergence semi-angle, in units of
|g_xaxis| / |K|.gl (float) – Tilt step size, needed to choose samp.
samp (int, optional) – Sampling. Derived from zoom when None.
zoom (float, optional) – Simulation-to-experiment tilt step ratio to aim for. Defaults to
TARGET_ZOOM.out_beam (tuple, optional) – The single output reflection. Defaults to the direct beam, which is all this alignment uses and which keeps the stored eigenvectors small.
verbose_cost (bool, optional) – Print the tilt count and memory estimate. Defaults to True.
- Returns:
dst.
- Return type:
Path
- Raises:
ValueError – If the template has no explicit beam block.
- pyextal.acom.tilt_from_zero_beam(dp, gindex, dat, thickness, gl, rotation, region, gx=None, variance=None, start_tilt=(0.0, 0.0), ncores=1, verbose=True)¶
Coarse-refines the direct beam alone to find where the scan sits in K_t.
Runs the ordinary coarse refinement –
pyextal.optimize.CoarseOptimizewithgInclude = [(0, 0, 0)]– against a wide-field .dat, so rotation, gl, thickness and the tilt bookkeeping all come from the code path the rest of the workflow uses. What makes it work on an off-zone scan is the .dat: the simulated K_t range has to be wider than the scan plus the offset, or the correlation peak has nowhere to land. Seewide_dat().- Parameters:
dp (np.ndarray) – (nbeam, ny, nx) integrated rocking curves, from
LARBEDAnalysis.stack().gindex (array-like) – (nbeam, 3) Miller indices matching dp.
dat (str | Path) – Wide-field .dat from
wide_dat().thickness (float) – Starting thickness, Angstroms.
gl (float) – Tilt step size, in scan steps per
|g_xaxis|.rotation (float) – Scan-to-simulation rotation, degrees.
region (np.ndarray) – selectROI region covering the rocking curve.
gx (array-like, optional) – Horizontal reference reflection.
variance (np.ndarray, optional) – Matching variance maps.
start_tilt (tuple, optional) – (2,) estimate of the tilt off the zone axis, degrees, in the same convention the result reports. The simulation is centred there instead of on the zone axis, so the correlation only has to find what is left. Defaults to (0, 0).
ncores (int, optional) – Cores for the simulation. Defaults to 1.
verbose (bool, optional) – Report progress. Defaults to True.
- Returns:
The scan centre relative to the zone axis.
- Return type:
Note
The tilt is read from
dinfo.tiltX/dinfo.tiltY, whichoptimizeOrientationThickness()accumulates – so it carries the engine’s sign convention already and needs no reinterpretation here.Check
TiltResult.on_edge. A correlation peak against the edge of the simulated range means the true optimum lies outside it, and the answer is a lower bound; raise conv inwide_dat()and re-run.start_tilt is the cheaper way out of that. The simulated range has to cover the scan plus the offset, and its cost grows as the square of the range at fixed sampling – covering 4 degrees on this instrument needs
conv >= 11and tens of thousands of tilts. Seeding the search with an estimate leaves only the residual to find, so a narrower conv suffices and the peak lands near the middle of the field rather than against its edge. A goniometer reading is usually good enough; so is the answer from a previous scan of the same specimen.The estimate is applied to the simulation itself, through the same
bloch_parse(dat, tiltX, tiltY)the engine uses, soTiltResult.tiltis always the total tilt off the zone axis – seed included – and never something the caller has to add the seed back onto.TiltResult.correctionreports what the search contributed.
- pyextal.acom.beam_direction(param, tilt, zone=None, handed=1)¶
The incident direction a tilt corresponds to, in crystal coordinates.
- Parameters:
param (SimParams) – Parsed simulation parameters.
tilt (array-like) – (2,) tilt along the pattern x and y axes, degrees –
TiltResult.tilt.zone (array-like, optional) – Zone axis. Read from the parsed geometry (
blochwave.difpar.zone) when None.handed (int, optional) –
-1if the scan’s second axis runs opposite togh, which negates that component of the tilt. Defaults to +1.
- Returns:
(3,) direction in direct-space (uvw) components, normalised so the largest component is 1.
- Return type:
np.ndarray
Note
param.tilt0is the in-plane deviation from the zone axis, not the incident wavevector – it is[0, 0, 0]forkt 0 0 0. The wavevector has to be built from the zone axis and the deviation added to it; starting fromtilt0alone yields a purely in-plane direction with no component along the beam at all.The wavevector lives in reciprocal space, so converting it to a lattice direction goes through the metric:
uvw = gmxr @ K. Feed the result topyextal.acom.library.nearest_zone_axis()to name it, bearing in mind that many high-index axes sit within a fraction of a degree of one another, so the ranking is only ever as sharp as the tilt is.handed exists because the tilt that best aligns the simulation to the experiment is not automatically the crystal’s tilt: if the scan’s second axis runs opposite to
gh, the two differ by a mirror. Round-tripping a known tilt through the simulation recovers it exactly (applied(0, +2.00)came back as(-0.005, +1.992)), so the arithmetic is not at fault – the mirror sits between the scan andghand has to be supplied. On the SrTiO3 scan taken off zone it is-1: taken at face value the tilt names(0,-1,15), which refines to a thickness of 122 Angstrom against 578 for the same crystal on zone, while the mirrored reading names(0,1,15)and refines to 517.
- pyextal.acom.higher_order_zone(param, tilt, max_index=20, tol_deg=None, handed=1, verbose=True)¶
Names the beam direction as a rational zone axis.
- Parameters:
param (SimParams) – Parsed simulation parameters.
tilt (array-like) – (2,) tilt in degrees, from
TiltResult.tilt.max_index (int, optional) – Largest index to consider. Defaults to 20.
tol_deg (float, optional) – Only accept an axis within this angle.
handed (int, optional) – Passed to
beam_direction();-1when the scan’s second axis runs opposite togh. Defaults to +1.verbose (bool, optional) – Print the ranked candidates. Defaults to True.
- Returns:
(uvw, angle_deg, ranked)– the closest rational axis, how far the beam is from it, and the ranked list behind the choice.- Return type:
tuple
- Raises:
ValueError – If nothing lies within tol_deg.
Note
The ranking is dense: a beam 4 degrees off [001] toward [110] has (1,1,20) at 0.045 degrees, (1,1,19) at 0.257 and (1,1,18) at 0.492, and they do not all select the same beams. The choice is only as good as the tilt, which is why max_index is worth keeping modest – a very high index buys an axis closer in angle without buying more physics.
- pyextal.acom.reindex_at_zone(image, param, zone, center, gmax=1.2, max_index=6, handed=None, laue_max=None, verbose=True, **kw)¶
Re-solves the pattern with the beam along a higher-order zone axis.
The point is the beam list. A zone axis selects which reflections the engine treats as excited, and that selection follows the zone, not
kt: on SrTiO3, movingktto0.05 0.05 0leaves the 661-beam list identical, while changing the zone from [001] to [1,1,20] returns 544 beams of which 152 appear in neither. Re-solving here is what brings the ZOLZ and HOLZ of the real tilted condition into the refinement.- Parameters:
image (np.ndarray) – The averaged pattern, as passed to
pyextal.acom.auto_index().param (SimParams) – Parsed simulation parameters.
zone (array-like) – The higher-order zone axis.
center (tuple) – Direct-beam pixel hint.
gmax (float, optional) – Library extent. Defaults to 1.2.
max_index (int, optional) – Miller index bound. Defaults to 6.
handed (int, optional) – Pin the handedness, as for auto_index.
laue_max (int, optional) – Laue zones in the library. Sized with
pyextal.acom.library.min_laue_max()when None, which is essential here: a high-index ZOLZ alone is a single row of reflections and cannot index a 2D net.verbose (bool, optional) – Report. Defaults to True.
**kw – Passed to
pyextal.acom.auto_index().
- Returns:
Indexed at the new orientation, ready for
to_gridandwrite_dat.- Return type:
- pyextal.acom.write_tilted_dat(template, dst, zone, out_beams, xaxis=None, kt=(0, 0, 0), conv=None, samp=None, verbose=True)¶
Writes the refinement .dat for a tilted orientation.
The two blocks come from different places, and that is the point:
outis what the experiment measured – the reflections that fall on the detector and have integrated rocking curves. It cannot be widened without integrating more.hklis the matrix, and should hold every beam the tilted condition excites, on the detector or not. That list comes from the engine’s own selection at the new zone axis, which is the only thing that responds to the orientation: movingktleaves the selection untouched, while changing the zone from [001] to [0,-1,15] on SrTiO3 swaps 240 beams out and 187 in, 119 of them withl != 0. Those are the HOLZ reflections that come into excitation when the beam tilts off the low-order axis, and a library built from the detector image can never contain them – it only holds what lands on the frame.
- Parameters:
template (str | Path) – A .dat for this crystal whose beam block is still implicit (
sele), so the engine re-selects at the new zone.dst (str | Path) – Where to write.
zone (array-like) – The tilted zone axis.
out_beams (list) – Reflections with integrated rocking curves.
xaxis (tuple, optional) – Horizontal reference. Kept from template when None – worth keeping, since gl is expressed in units of it and changing it rescales gl proportionally.
kt (tuple, optional) – Residual tilt. Defaults to (0, 0, 0).
conv (float, optional) – Convergence. Kept from template when None.
samp (int, optional) – Sampling. Kept from template when None.
verbose (bool, optional) – Report the beam counts. Defaults to True.
- Returns:
dst.
- Return type:
Path
- pyextal.acom.predict_excited(param, beam, zone, xaxis, mapping, shape, laue_max=3, smax=0.02, scan_half=0.0, gmax=None, max_index=None)¶
Reflections on the frame, with how close the scan brings each to Bragg.
- Parameters:
param (SimParams) – Parsed simulation parameters.
beam (array-like) – (3,) beam direction in uvw, as from
beam_direction().zone (array-like) – Zone axis the Laue index is counted against.
xaxis (array-like) – In-plane reference reflection of zone.
mapping (DetectorMap) – Detector map in the beam-normal frame, as fitted by
fit_beam_map().shape (tuple[int, int]) – Detector shape.
laue_max (int, optional) – Highest |Laue index| considered. Defaults to 3.
smax (float, optional) – Excitation error, A^-1, below which a reflection counts as excited. Defaults to 0.02.
scan_half (float, optional) – Half-range of the rocking scan, radians. A tilt d changes s_g by about |g_t| d, so a reflection is excited somewhere in the scan if
|s_g| - |g_t| scan_half <= smax. Defaults to 0, the scan centre only.gmax (float, optional) – Largest |g|; the frame’s reach when None.
max_index (int, optional) – Miller search box; sized from gmax when None.
- Returns:
hkl(n, 3),pixels(n, 2),laue(n,),s0(n,) the excitation error at the scan centre,s_reach(n,) the smallest |s_g| the scan reaches, andexcited(n,) bool.- Return type:
dict
- pyextal.acom.fit_beam_map(hkl, pixels, param, beam, zone, xaxis)¶
Fits the detector map in the beam-normal frame from labelled positions.
Both handednesses are fitted and the better kept, so labels that went through a mirror in
pyextal.acom.resolve_label_symmetry()still fit.- Parameters:
hkl (array-like) – (n, 3) labels, ZOLZ of zone.
pixels (array-like) – (n, 2) their detector positions.
param (SimParams) – Parsed simulation parameters.
beam (array-like) – (3,) beam direction in uvw.
zone (array-like) – Zone axis of the labels.
xaxis (array-like) – In-plane reference reflection of zone.
- Returns:
The fitted map,
rmsin pixels.- Return type:
- pyextal.acom.net_zone(labels, param, beam, max_index=6, max_angle=20.0, verbose=True)¶
The low-index zone axis whose ZOLZ the indexed reflections lie in.
fit_beam_to_holz()andrelabel_holz()fit the detector map from the ZOLZ labels of zone, and count Laue zones from it. That has to be the net the pattern actually shows, which far enough off the nominal axis is a different one. SrTiO3 10 deg off [001] still shows the [001] net plus the l = +-1 bands; 15 deg off, the beam is 0.6 deg from [014], and every label the (0,5,19) re-index gives lies in the [014] ZOLZ while only the (h00) row lies in the [001] one. Fitting the map to that row is degenerate: it comes out at rms 0 with the wrong handedness, and every (hk0) node is then predicted on the frame.Candidates are the axes up to max_index within max_angle of the beam whose ZOLZ holds a 2D set of the labels; the one holding the most wins, the closer to the beam on a tie. The sign facing the beam is returned, so HOLZ layers count positive along it.
- Parameters:
labels (array-like) – (n, 3) current labels.
param (SimParams) – Parsed simulation parameters.
beam (array-like) – (3,) beam direction, uvw.
max_index (int, optional) – Largest index tried. Defaults to 6.
max_angle (float, optional) – Largest beam-to-axis angle, degrees. Defaults to 20.
verbose (bool, optional) – Report. Defaults to True.
- Returns:
(uvw, angle_deg, n_zolz).- Return type:
tuple
- pyextal.acom.fit_beam_to_holz(peaks, hkl, pixels, param, beam, zone=(0, 0, 1), xaxis=None, shape=(124, 124), search_deg=4.0, step_deg=0.25, laue_max=3, smax=0.01, scan_half=0.0, node_tol=0.9, match_px=1.0, verbose=True)¶
Refines the beam direction from where the HOLZ spots actually are.
The (000) tilt search measures the offset in scan steps and turns it into an angle through gl, so it is only as good as gl. The HOLZ rings are not: where the Ewald sphere cuts the l = +-1 layers depends on the beam direction and |K| alone. On the SrTiO3 scan taken off [001] the (000) search gave 7.0 degrees while the two HOLZ bands sit where a 10.3 degree tilt puts them, matching 18 of 25 off-node peaks to within a pixel.
Peaks further than node_tol from every ZOLZ node are taken as HOLZ spots. Beam directions on a grid around beam are scored by how many of them fall within match_px of an excited HOLZ prediction, ties going to fewer predictions with no peak nearby; the best is then refined on a finer grid.
- Parameters:
peaks (array-like) – (n, 2) detected peak positions.
hkl (array-like) – (m, 3) ZOLZ labels the map is fitted from.
pixels (array-like) – (m, 2) their positions.
param (SimParams) – Parsed simulation parameters.
beam (array-like) – (3,) starting beam direction, uvw.
zone (array-like, optional) – Zone axis of the net. Defaults to [001].
xaxis (array-like, optional) – In-plane reference reflection; the shortest label in hkl when None.
shape (tuple, optional) – Detector shape. Defaults to (124, 124).
search_deg (float, optional) – Half-width of the search, degrees, in both directions normal to beam. Defaults to 4.
step_deg (float, optional) – Coarse grid step, degrees. Defaults to 0.25.
laue_max – As for
predict_excited().smax – As for
predict_excited().scan_half – As for
predict_excited().node_tol (float, optional) – Peaks closer than this to a ZOLZ node, pixels, are ZOLZ spots. Defaults to 0.9.
match_px (float, optional) – A HOLZ spot counts as explained within this many pixels. Defaults to 1.0.
verbose (bool, optional) – Report. Defaults to True.
- Returns:
(beam, info)– the refined direction in uvw, scaled so its largest component is 1, and a dict withhits,n_holz_peaks,unexplained(excited predictions with no peak),offset_deg(shift from beam along the two beam-normal axes) andangle_deg. beam is returned unchanged when fewer than four HOLZ spots are found.- Return type:
tuple
- pyextal.acom.relabel_holz(larbed, param, beam, zone=(0, 0, 1), xaxis=None, laue_max=3, smax=0.02, scan_half=0.0, min_sep=3.0, verbose=True)¶
Relabels an extraction for the orientation found, picking up FOLZ and HOLZ.
Off zone, the Ewald sphere cuts the upper layers close to the direct beam: 7-10 degrees off SrTiO3 [001] puts the l = +-1 rings 1.3-1.8 A^-1 out, inside the frame. Their reflections then sit on the detector as spots 1-2.5 px from ZOLZ nodes, and indexing on the projected net alone labels them as the ZOLZ reflection nearby. Which of the two a spot is cannot be decided by position; it is decided by which one the beam excites.
So, with the beam direction known:
the detector map is refitted from the ZOLZ entries already in larbed, in the plane normal to the beam;
every reflection with
|laue| <= laue_maxon the frame is predicted, with the excitation error the scan brings it closest to;ZOLZ reflections are all kept, as before, and HOLZ reflections are kept when excited somewhere in the scan;
where two predictions fall within min_sep pixels – one spot on the detector – the more strongly excited one keeps it. That is the relabel: a ZOLZ node that sits under an excited HOLZ reflection is renamed.
Labels only; the positions are the predicted ones, as to_grid gives them, so the extraction has to be re-integrated afterwards.
- Parameters:
larbed (LARBEDAnalysis) – Extraction whose grid and g_vectors to replace. Its ZOLZ entries define the map, so run this after the label symmetry is settled.
param (SimParams) – Parsed simulation parameters.
beam (array-like) – (3,) beam direction in uvw, in the frame of the current labels.
zone (array-like, optional) – Zone axis of the net. Defaults to [001].
xaxis (array-like, optional) – In-plane reference reflection; the shortest ZOLZ label present when None.
laue_max (int, optional) – Highest |Laue index| considered. Defaults to 3.
smax (float, optional) – Excitation error, A^-1, counting as excited. Defaults to 0.02.
scan_half (float, optional) – Half-range of the rocking scan, radians; see
predict_excited(). Defaults to 0.min_sep (float, optional) – Predictions closer than this, pixels, are one spot. Defaults to 3.0, the integration aperture.
verbose (bool, optional) – Report. Defaults to True.
- Returns:
mapping(DetectorMap),added/removedlabel lists,renamedas(zolz, holz)pairs, andpredictedfrompredict_excited().- Return type:
dict
- class pyextal.acom.DetectorMap¶
Linear map from in-plane reciprocal coordinates to detector pixels.
- Variables:
center (np.ndarray) – (2,) direct-beam pixel position.
pixel_size (float) – Inverse Angstroms per pixel.
theta (float) – Rotation from the library’s x axis to the detector, radians.
handed (int) – +1, or -1 if the pattern is mirrored relative to the library frame.
rms (float) – Residual of the fit, in pixels.
n_used (int) – Reflections the fit was based on.
- center: numpy.ndarray¶
- pixel_size: float¶
- theta: float¶
- handed: int = 1¶
- rms: float¶
- n_used: int = 0¶
- to_pixels(xy)¶
Maps in-plane coordinates (inverse Angstroms) to pixels.
- Parameters:
xy (np.ndarray) – (n, 2) in-plane coordinates.
- Returns:
(n, 2) pixel positions.
- Return type:
np.ndarray
- to_reciprocal(pixels)¶
Maps pixels back to in-plane coordinates (inverse Angstroms).
- Parameters:
pixels (np.ndarray) – (n, 2) pixel positions.
- Returns:
(n, 2) in-plane coordinates.
- Return type:
np.ndarray
- pyextal.acom.solve_orientation(peaks, center, lib, shape, tol_px=3.0, n_candidates=12, intensities=None, pixel_size=None, pixel_tol=0.02, **match_kw)¶
Finds the detector mapping that best explains a set of detected peaks.
- Parameters:
peaks (np.ndarray) – (n, 2) absolute peak positions, in pixels.
center (array-like) – (2,) direct-beam pixel position.
lib (ReflectionLibrary) – Candidate reflections for one zone axis.
shape (tuple[int, int]) – Detector shape.
tol_px (float, optional) – Match radius, pixels. Defaults to 3.0.
n_candidates (int, optional) – Pair-matching proposals to refine and score. Defaults to 12.
intensities (np.ndarray, optional) – (n,) peak heights, used to check that bright peaks land on reflections that can be bright.
pixel_size (float, optional) – Known detector scale, inverse Angstroms per pixel, from an earlier solve on the same camera length. Mappings further than pixel_tol from it are discarded, both as proposals and after the fit. Free when None, the default.
pixel_tol (float, optional) – Fractional tolerance on pixel_size. Defaults to 0.02.
**match_kw – Passed to
match_pairs()–handed=among them.
- Returns:
Best mapping and its score, or
(None, None)if nothing could be fitted. The score carrieshanded_marginandhanded_degenerate: when the mirror is degenerate the choice of handedness came from candidate ordering, not from the pattern, and two scans of the same crystal can disagree. Pin it withhanded=once it is known.pixel_size exists for the same reason. Peak positions alone admit nets related by a scale of sqrt(2): on the SrTiO3 scan taken 7 degrees off [001] a blind search found (2,-3,-3) and (1,-6,-1) at 1.41x the true scale, scoring above [001]. The camera length does not change between scans, so a scale fitted once on an on-zone pattern removes that branch.
- Return type:
tuple[DetectorMap, dict] | tuple[None, None]
- pyextal.acom.assign(peak_vectors, lib, mapping, tol_px=3.0)¶
Assigns each detected peak the nearest predicted reflection.
- Parameters:
peak_vectors (np.ndarray) – (n, 2) peak positions relative to the direct beam, in pixels.
lib (ReflectionLibrary) – Candidate reflections.
mapping (DetectorMap) – Mapping to test.
tol_px (float, optional) – Largest accepted mismatch, in pixels. Defaults to 3.0.
- Returns:
Indices into peak_vectors and into lib, for peaks that found a reflection within tol_px.
- Return type:
tuple[np.ndarray, np.ndarray]
- pyextal.acom.write_dat(template, dst, zone, xaxis, beams, out_beams, kt=(0, 0, 0), norm=None, conv=None, samp=None, sgmax=None, sgmin=None, omeg=None, abs_=None)¶
Writes a .dat for one orientation, with explicit beam and output blocks.
The output beams are written as a prefix of the beam list, so
hkl[:nout] == hkloutand the engine’s idptr mapping is the identity. That is not cosmetic: bloch_parse resolves each out entry against hkl and appends any it cannot find, writing past the array it already allocated from the declared beam count.- Parameters:
template (str | Path) – Existing .dat supplying the crystal block.
dst (str | Path) – File to write.
zone (tuple[int, int, int]) – Zone axis.
xaxis (tuple[int, int, int]) – Reflection defining the horizontal axis.
beams (array-like) – (n, 3) every beam in the calculation.
out_beams (array-like) – (m, 3) reflections to output.
kt (tuple, optional) – Tangential incident wavevector. Defaults to (0,0,0).
norm (tuple, optional) – Surface normal; zone is used when None.
conv (float, optional) – Convergence angle; the template’s value is kept when None.
samp (int, optional) – Sampling; the template’s value is kept when None.
sgmax (float, optional) – Maximum excitation error. Defaults to 3.5 when the template used sele and gave no standalone value.
sgmin (float, optional) – Minimum excitation error; template value kept when None.
omeg (float, optional) – Bethe parameter; template value kept when None.
abs (float, optional) – Mean absorption; template value kept when None.
- Returns:
The file written.
- Return type:
Path
- Raises:
ValueError – If an output beam is missing from beams, or the template lacks a block that has to be replaced.
- pyextal.acom.write_oriented_dat(template, dst, zone, xaxis, kt=(0, 0, 0), norm=None)¶
Copies a .dat, changing only the orientation.
The beam-selection blocks are left exactly as the template had them, so a template using sele/aper still selects its beams geometrically – at the new orientation. That makes it the natural reference to compare an explicit ACOM beam list against: same crystal, same cutoffs, same engine, only the zone axis replaced.
- Parameters:
template (str | Path) – Existing .dat.
dst (str | Path) – File to write.
zone (tuple[int, int, int]) – Zone axis.
xaxis (tuple[int, int, int]) – Reflection defining the horizontal axis.
kt (tuple, optional) – Tangential incident wavevector. Defaults to (0,0,0).
norm (tuple, optional) – Surface normal; zone is used when None.
- Returns:
The file written.
- Return type:
Path
- pyextal.acom.read_dat_beams(path)¶
Reads the explicit hkl and out blocks back out of a .dat.
Lets downstream code take its reflection list from the file the engine will actually parse, rather than from a variable that may have drifted from it.
- Parameters:
path (str | Path) – A .dat with explicit blocks, as written by
write_dat().- Returns:
(beams, out_beams)as (h, k, l) tuples.- Return type:
tuple[list, list]
- Raises:
ValueError – If either block is missing, or a declared count does not match the rows that follow it.
- pyextal.acom.plot_zone_map(result, param, ax=None, annotate=6, cmap='viridis', show_all=False)¶
Plots candidate zone axes on a symmetry-reduced stereographic map.
Every zone axis in result.ranking is folded onto its symmetry representative and the best score of each group is kept, so a cubic crystal shows one point for <100> rather than three. Zones in result.failed, tried but left without a mapping, are drawn as grey crosses so the map shows every axis the search tested.
A wide search packs its axes closely: to index 15 a cubic crystal gives 624 distinct orientations inside the fundamental triangle, and neighbouring high-index axes sit a fraction of a degree apart. The view is zoomed to the region the points occupy, and each marker is sized to its nearest neighbour, so close axes stay separate instead of hiding under one another.
- Parameters:
result (AcomResult) – A solved orientation carrying ranking.
param (SimParams) – Parsed parameters, for the direct-space frame.
ax (matplotlib.axes.Axes, optional) – Axes to draw on; created if None.
annotate (int, optional) – Label this many top groups. Defaults to 6.
cmap (str, optional) – Colormap for the score. Defaults to ‘viridis’.
show_all (bool, optional) – Draw every equivalent instead of reducing. Defaults to False.
- Returns:
(ax, groups)where groups maps representative zone to score.- Return type:
tuple
- pyextal.acom.plot_indexed(image, result, ax=None, max_label=12, vmax_pct=99.5, include_holz=True)¶
Overlays the indexed reflections on the diffraction pattern.
Predicted reflections are drawn whether or not a peak was detected there, which is the point: the weak ones are exactly what a peak finder misses and what the integration still needs.
- Parameters:
image (np.ndarray) – The averaged pattern that was indexed.
result (AcomResult) – The solved orientation.
ax (matplotlib.axes.Axes, optional) – Axes to draw on.
max_label (int, optional) – Label this many of the strongest predicted reflections. Defaults to 12.
vmax_pct (float, optional) – Display ceiling percentile. Defaults to 99.5.
include_holz (bool, optional) – Include HOLZ. Defaults to True.
- Returns:
The axes drawn on.
- Return type:
matplotlib.axes.Axes
- pyextal.acom.reflection_snr(roi, min_snr=3.0)¶
Per-reflection signal statistics over the ROI.
- Parameters:
roi (LARBEDROI) – An ROI with selectROI already called.
min_snr (float, optional) – The per-point threshold that counts as signal. Defaults to 3.0.
- Returns:
(snr_high, fraction, valid)– the 90th-percentile ofI / sqrt(sigma^2), the fraction of points at or above min_snr, and the fraction of the ROI that landed on measured data, one entry per reflection.- Return type:
tuple[np.ndarray, np.ndarray, np.ndarray]
- Raises:
AttributeError – If the ROI carries no variance, i.e. it is not a LARBEDROI.
- pyextal.acom.prune_noisy(roi, min_snr=3.0, min_fraction=0.1, min_valid_fraction=0.9, always_keep=((0, 0, 0),))¶
Splits the ROI’s reflections into those worth refining and those that are not.
- Parameters:
roi (LARBEDROI) – An ROI with selectROI already called.
min_snr (float, optional) – Per-point signal-to-noise counting as signal. Defaults to 3.0.
min_fraction (float, optional) – Fraction of the ROI that must reach min_snr for the reflection to be kept. Defaults to 0.1 – low on purpose, because a weak reflection is only excited over part of the rocking range.
min_valid_fraction (float, optional) – Fraction of the ROI that must land on measured data. Defaults to 0.9.
always_keep (tuple, optional) – Reflections kept whatever their score. The direct beam is kept by default: it anchors the intensity scaling and the engine’s output list has to start with it.
- Returns:
(keep, drop, stats). stats maps each reflection to(snr_high, fraction, valid_fraction).- Return type:
tuple[list, list, dict]
- pyextal.acom.resolve_label_sign(larbed, dat_path, thickness, gl, rotation, region, n_beams=6, gx=None, ncores=1, verbose=True)¶
Decides whether the extracted labels or their negatives fit the dynamics.
The g/-g special case of
resolve_label_symmetry(): for a centrosymmetric crystal Friedel’s law gives |U_g| = |U_-g|, so peak positions cannot separate the two, but their rocking curves differ away from exact zone-axis incidence.:param As for
resolve_label_symmetry(): :param with n_beams defaulting to 6.:- Returns:
(sign, scores)–+1to keep the labels as they are,-1to negate every one of them; scores is keyed by sign.- Return type:
tuple[int, dict]
- pyextal.acom.resolve_label_symmetry(larbed, dat_path, thickness, gl, rotation, region, n_beams=5, ops=None, gx=None, thickness_scan=None, ncores=1, plot=True, verbose=True)¶
Decides which symmetry-related labeling of the extracted reflections fits the dynamics.
Peak positions fix the reflection net only up to its own point group. On a square (hk0) net that is 4mm: the labels can come out rotated by 90 degrees, mirrored (h,k) -> (h,-k), or inverted, and every candidate puts reflections on exactly the same spots with the same kinematic strengths.
Dynamical diffraction does tell them apart. With the .dat orientation and the ROI rotation held fixed, each candidate pairs every measured rocking curve with a different simulated reflection, and only the right pairing reproduces the structure inside each disk.
How it is scored matters, and is why this does not simply run CoarseOptimize per candidate. CoarseOptimize correlates the whole (beam, y, x) stack as one normalised block, so its score is dominated by the brightness differences between beams – (000) bright, {200} medium, {100} dark – and a relabeling within a family leaves those untouched: on SrTiO3 every candidate scored 0.88-0.91 at the same tilt. Here instead:
the simulation is run once. It depends on the .dat, not on the labels, so one Bloch run serves every candidate and a thickness scan;
the tilt offset is fixed by the (000) disk alone, which no relabeling touches, so a wrong labeling cannot move the ROI to suit itself;
each of the other disks is correlated on its own, normalised on its own, at that offset, and a candidate’s score is the mean over those disks.
Hold rotation at the instrument value. A 90-degree label rotation fits just as well as a 90-degree rotation of the ROI with the labels left as they are, so letting both vary makes the answer arbitrary.
- Parameters:
larbed (LARBEDAnalysis) – Extraction holding Store_Larbed, g_vectors and the propagated variance.
dat_path (str | Path) – The .dat describing this orientation.
thickness (float) – Starting thickness estimate, Angstroms.
gl (float) – Tilt step size.
rotation (float) – ROI rotation, degrees.
region (np.ndarray) – The selectROI region to score over. It must be rectangular, as for CoarseOptimize; a large one carrying plenty of in-disk contrast separates the candidates best.
n_beams (int, optional) – Strongest reflections to test, besides the direct beam. Defaults to 5.
ops (dict, optional) – Name -> 3x3 integer matrix to try. Defaults to
LABEL_OPS. An op that sends more than half the measured reflections outside the measured set is dropped: it is not a positional ambiguity of this net (off zone, the 90-degree turns about [001] are not).gx (array-like, optional) – Horizontal reference reflection for the ROI.
thickness_scan (array-like, optional) – Thicknesses to try, Angstroms. Defaults to 0.7-1.3 x thickness in 13 steps; each candidate keeps its best.
ncores (int, optional) – Unused; kept for call compatibility.
plot (bool, optional) – Show measured disks beside the simulated disk each of the best candidates pairs them with. Defaults to True.
verbose (bool, optional) – Report. Defaults to True.
- Returns:
(name, op, scores)– the best operation’s name and matrix, and the score each candidate reached. Apply the winner withapply_label_op().- Return type:
tuple[str, np.ndarray, dict]
- Raises:
ValueError – If no op maps the measured reflections onto themselves, or no reflection’s images under every remaining op are in the .dat output block.
Note
This does not call terminate(): it deallocates the engine’s arrays, and a later CoarseOptimize then simulates as all zeros.
- pyextal.acom.apply_label_op(larbed, op)¶
Relabels an extraction in place by an integer operation on (h, k, l).
Only the labels move: the rocking curves and their detector positions stay where they are, so nothing has to be re-integrated.
- Parameters:
larbed (LARBEDAnalysis) – Extraction whose g_vectors and grid to relabel.
op (array-like) – 3x3 integer matrix, applied as
g -> op @ g.
- Returns:
larbed, relabelled.
- Return type:
- pyextal.acom.LABEL_OPS¶
- class pyextal.acom.AcomResult¶
Outcome of an orientation solve.
- Variables:
zone (tuple[int, int, int]) – The zone axis found.
mapping (DetectorMap) – Reflection-to-pixel mapping.
lib (ReflectionLibrary) – Reflections of the winning zone.
score (dict) – Diagnostics from
pyextal.acom.solve.score_mapping().peaks (np.ndarray) – (n, 2) detected peak positions.
peak_idx (np.ndarray) – Indices of peaks that were assigned.
lib_idx (np.ndarray) – Matching indices into lib.
shape (tuple[int, int]) – Detector shape.
ranking (list) –
(zone, score)for every zone tried, best first.failed (list) – Zones tried that gave no mapping, so carry no score.
holz (ReflectionLibrary | None) – HOLZ reflections predicted on the frame.
- zone: tuple¶
- mapping: solve.DetectorMap¶
- score: dict¶
- peaks: numpy.ndarray¶
- peak_idx: numpy.ndarray¶
- lib_idx: numpy.ndarray¶
- shape: tuple¶
- ranking: list = []¶
- holz: library.ReflectionLibrary = None¶
- failed: list = []¶
- property assigned¶
(hkl, pixel)for every assigned peak.- Type:
list[tuple]
- predicted(include_holz=True, margin=0.0)¶
Every reflection predicted to fall on the detector.
This is the point of the exercise: reflections too weak to be detected are still predicted, so they can be integrated rather than missed.
- Parameters:
include_holz (bool, optional) – Add the HOLZ reflections found by
add_holz(). Defaults to True.margin (float, optional) – Shrink the accepted frame by this many pixels on each side. Defaults to 0.
- Returns:
(hkl, pixels).- Return type:
tuple[np.ndarray, np.ndarray]
- invert()¶
Flips the indexing through the direct beam, so g becomes -g.
Applied when
pyextal.acom.quality.resolve_label_sign()finds the labels inverted. It is done by turning the detector mapping through 180 degrees rather than by negating Miller indices: that keeps every library entry consistent with its own projection, and lands each reflection on the position its opposite used to hold. Positions are unchanged as a set; what changes is which reflection sits at each one.- Returns:
self, re-assigned in place.
- Return type:
- add_holz(param, smax=0.05, laue_max=3, **kw)¶
Finds HOLZ reflections predicted to land on the frame.
Run once the ZOLZ orientation is settled; it does not change the mapping.
- Parameters:
param (SimParams) – Parsed simulation parameters.
smax (float, optional) – Largest accepted |s_g|, inverse Angstroms. Defaults to 0.05; None drops the excitation check and returns every reflection that merely projects onto the frame.
laue_max (int, optional) – Highest Laue zone. Defaults to 3.
**kw – Passed to
pyextal.acom.library.visible_holz().
- Returns:
The HOLZ reflections found.
- Return type:
- to_grid(include_holz=True)¶
Returns the indexing in the form LARBEDAnalysis consumes.
Assigning these to grid and g_vectors lets IntegrateLarbed run without any manual assign_gvector step.
- Parameters:
include_holz (bool, optional) – Include HOLZ reflections. Defaults to True.
- Returns:
(grid, g_vectors)– grid entries are((i, j), (x, y))and g_vectors are(h, k, l).- Return type:
tuple[list, list]
- write_dat(template, dst, out_beams=None, include_holz=True, out_gmax=None, zone=None, xaxis=None, **kw)¶
Writes a .dat for this orientation, ready for refinement.
- Parameters:
template (str | Path) – Existing .dat supplying the crystal block.
dst (str | Path) – File to write.
out_beams (array-like, optional) – Reflections to output, i.e. the ones a refinement can touch. When None every predicted reflection out to out_gmax is used.
out_gmax (float, optional) –
Radius of the output set, inverse Angstroms. Defaults to the largest |g| that was actually assigned, so the output covers the indexed part of the pattern.
Weak and near-extinct reflections inside that radius are included deliberately: they carry intensity through multiple scattering, and they are often the most informative to refine, since a near-cancelling structure factor is sensitive to exactly the bonding charge one is trying to measure.
include_holz (bool, optional) – Include HOLZ in the beam list. Defaults to True.
zone (array-like, optional) – Orientation written to the file, when it should differ from the one solved at. Off zone the net can be solved at the low-index axis the pattern shows ([014] for SrTiO3 15 deg off [001]) while the tilt is still measured from the nominal axis: the beams are this solution’s, the frame is zone. xaxis must be normal to zone.
xaxis (array-like, optional) – Orientation written to the file, when it should differ from the one solved at. Off zone the net can be solved at the low-index axis the pattern shows ([014] for SrTiO3 15 deg off [001]) while the tilt is still measured from the nominal axis: the beams are this solution’s, the frame is zone. xaxis must be normal to zone.
**kw – Passed to
pyextal.acom.datfile.write_dat().
- Returns:
The file written.
- Return type:
Path
- write_reference_dat(template, dst, zone=None, xaxis=None, **kw)¶
Writes the template’s own beam selection at the solved orientation.
Same crystal and cutoffs as template, only the zone axis and x axis replaced, so sele/aper reselect geometrically. Comparing this against the explicit file from
write_dat()checks the ACOM beam list against the engine’s own choice at the same orientation.- Parameters:
template (str | Path) – Existing .dat.
dst (str | Path) – File to write.
zone (array-like, optional) – Orientation to write instead of the solved one, as for
write_dat().xaxis (array-like, optional) – Orientation to write instead of the solved one, as for
write_dat().**kw – Passed to
pyextal.acom.datfile.write_oriented_dat().
- Returns:
The file written.
- Return type:
Path
- pyextal.acom.auto_index(image, param, center=None, zones=None, max_zone_index=2, gmax=1.2, max_index=6, tol_px=3.0, detect_kw=None, match_min_weight_rel=1e-06, extend_to_frame=True, laue_max=0, pixel_size=None, pixel_tol=0.02, theta_ref=None, **solve_kw)¶
Determines the orientation of a diffraction pattern automatically.
- Parameters:
image (np.ndarray) – 2D averaged diffraction pattern.
param (SimParams) – Parsed simulation parameters for the crystal.
center (tuple[float, float], optional) – Approximate direct-beam pixel. The brightest peak is used when None.
zones (list, optional) – Zone axes to try. Low-index axes up to max_zone_index are used when None.
max_zone_index (int, optional) – Largest zone index enumerated. Defaults to 2.
laue_max (int, optional) – Highest Laue zone in the search library. Defaults to 0, the ZOLZ, which is what makes different zone axes predict different nets. A high-index axis cannot be indexed at 0 – its ZOLZ is nearly empty, and the [1,1,15] ZOLZ of SrTiO3 inside 1.2 A^-1 is seven reflections on one row, which is what made it return a scale 1.4x too small and call (200) a (1,-1,0). Use
pyextal.acom.library.min_laue_max()to size it, and pass a single axis in zones when you do: raising it for a blind search makes every candidate library dense enough to match anything.gmax (float, optional) – Largest |g| in the library. Defaults to 1.2.
max_index (int, optional) – Miller search box. Defaults to 6.
tol_px (float, optional) – Match radius, pixels. Defaults to 3.0.
detect_kw (dict, optional) – Passed to
detect_peaks().match_min_weight_rel (float, optional) – Reflections below this fraction of the strongest |U_g| are excluded from the search only. Defaults to 1e-6.
extend_to_frame (bool, optional) – Once the mapping is known, rebuild the library large enough to cover the whole detector, so the outer orders are predicted too. Defaults to True.
pixel_size (float, optional) – Known detector scale, inverse Angstroms per pixel –
result.mapping.pixel_sizefrom a solve of an on-zone pattern taken at the same camera length. Only mappings within pixel_tol of it are considered, which removes the sqrt(2)-scaled nets a tilted pattern otherwise admits. Free when None, the default.pixel_tol (float, optional) – Fractional tolerance on pixel_size. Defaults to 0.02.
theta_ref (float, optional) – Detector rotation to settle the net’s own rotational symmetry against, degrees. The [001] net of a cubic crystal fits equally well turned by any multiple of 90 degrees, so which of those labelings comes out depends on the detected peaks – on the SrTiO3 scans R1 gave +79.0 and R2 +169.7. The labeling fixes the frame the tilt is measured in, so START_TILT and ROTATION only carry over between scans if it does too. When given, the solved mapping is turned by the zone’s rotation step onto the branch closest to theta_ref. Like handed and pixel_size, it belongs to the instrument: R4 and R1 agree to 0.3 degrees. Free when None, the default.
**solve_kw – Passed to
solve_orientation().
- Returns:
The best orientation found.
- Return type:
Note
Two stages, for two different jobs. The search matches against the strong reflections only: whether an extinct one shows up depends on multiple scattering, so it can neither be required nor used as evidence. Prediction then uses the complete library out to the frame corners, because those same extinct reflections do carry intensity in electron diffraction and still have to be integrated.
- Raises:
ValueError – If no peaks are found, or no zone axis explains them.
- pyextal.acom.confirm(result, dat_path, thickness, ncores=1)¶
Confirms an orientation dynamically, with the Bloch engine.
Runs the full simulation for the solved orientation and returns the per-reflection intensities, so the kinematic solution can be checked against what the dynamical theory predicts. This is the expensive step, which is why it runs once rather than inside the search.
- Parameters:
result (AcomResult) – The orientation to confirm.
dat_path (str | Path) – .dat written for that orientation.
thickness (float) – Sample thickness, Angstroms.
ncores (int, optional) – Cores for the spawned workers. Defaults to 1.
- Returns:
(param, exitwave)– the parsed parameters and the simulated intensity of each output reflection.- Return type:
tuple