pyextal.acom.quality¶
Judging whether a reflection carries signal inside the chosen ROI.
Indexing says where a reflection is; it says nothing about whether the data there is worth refining against. A reflection can be correctly indexed and still be nothing but noise over the sampled region – too weak, or clipped by the detector edge – and scoring it only adds noise to the goodness-of-fit while costing a template correlation every iteration.
The measure has to suit how LARBED rocking curves actually look. A weak reflection is not weak everywhere: it is dark over most of the rocking range and lights up where it is dynamically excited. Background subtraction in IntegrateLarbed then clips the dark part to zero, so a typical pixel of a weak-but-real reflection reads zero. On SrTiO3 [001] the (100) family comes out of the extraction 69-82% zeroed while still peaking near the mean of (200) – judging it on a median would throw away exactly the reflections worth refining. So the test is whether a reasonable fraction of the ROI rises above the noise, not whether the typical point does.
Attributes¶
Functions¶
|
Per-reflection signal statistics over the ROI. |
|
Splits the ROI's reflections into those worth refining and those that are not. |
|
Relabels an extraction in place by an integer operation on (h, k, l). |
|
Decides which symmetry-related labeling of the extracted reflections fits the dynamics. |
|
Decides whether the extracted labels or their negatives fit the dynamics. |
Module Contents¶
- pyextal.acom.quality.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.quality.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.quality.LABEL_OPS¶
- pyextal.acom.quality.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.quality.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.quality.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]