Run the indexing stages separately#

Indexer.index runs three stages for each frame: peak search, pixel-to-q conversion, and crystal indexing. You can also run each stage separately to compare settings or investigate an unexpected result without repeating the full pipeline. Once you have chosen the settings, use them with an Indexer to process a scan.

Stage

Call

Inputs

LaueGo program

Peak search

peak_search()

Image, optional mask

peaksearch

Pixel-to-q conversion

Geometry.pixels_to_q

Peak positions, geometry, frame region, depth

pix2qs

Crystal indexing

index_orientations()

Scattering vectors, crystal description

euler

Each call uses the same native code as Indexer.index. Given the same inputs, settings, and frame region, the separate calls return the same peaks, scattering vectors, and patterns as Indexer.index. They differ in how they handle peak positions outside the detector; see Constraints.

Run the three stages#

This example compares peak-search thresholds for the synthetic two-grain Ni frame, then runs pixel-to-q conversion and crystal indexing. The image is a full-detector numpy.uint16 array with shape (2048, 2048). The defaults for pixels_to_q therefore apply: origin (0, 0), no pixel grouping, and no depth correction.

import h5py

from lauelab.indexing import index_orientations, load_crystal, load_geometry, peak_search

geometry = load_geometry("tests/data/geo/geoN_2022-03-29_14-15-05.xml")
crystal = load_crystal("tests/config/Ni.xml")
with h5py.File("tests/data/synthetic/frames/synthetic_ni_two_grains.h5") as source:
    image = source["entry1/data/data"][...]

settings = dict(boxsize=18, max_rfactor=0.5, min_separation=20, threshold=None, max_peaks=200)
for ratio in (3.0, 4.0, 6.0):
    found = peak_search(image, threshold_ratio=ratio, **settings)
    print(ratio, found.threshold_used, found.n_peaks)

found = peak_search(image, threshold_ratio=4.0, **settings)
qhat = geometry.pixels_to_q(found.peaks)
patterns = index_orientations(qhat, crystal, kev_max_calc=17.2, angle_tolerance_deg=0.1)

peak_search and index_orientations accept keyword arguments with the same names, defaults, and units as the fields of PeakParams and IndexParams, respectively.

peak_search returns a PeakSearch object. Its structured array, found.peaks, contains the same fields as FrameResult.peaks except qhat. Peak positions are zero-based frame pixel coordinates (x, y), where image[y, x] accesses the pixel at (x, y).

pixels_to_q returns unit scattering vectors in the 34-ID-E laboratory frame as a numpy.float64 array with shape (n, 3). index_orientations returns a tuple of Pattern objects. Here, found.n_peaks is 48 and patterns contains two patterns.

Index a subset of peaks#

Pattern.pk_index contains zero-based row indices into the qhat array that is passed to index_orientations. To index only some peaks, select their rows:

import numpy as np

bright = found.peaks["intens"] > np.median(found.peaks["intens"])
bright_patterns = index_orientations(
    qhat[bright], crystal, kev_max_calc=17.2, angle_tolerance_deg=0.1
)

In this example, bright_patterns[0].pk_index contains row indices into qhat[bright], not into qhat.

Row order matters because index_orientations uses at most the first max_data rows (250 by default). peak_search sorts peaks by the maximum pixel value in the blob used for each fit, largest first. This usually matches decreasing intens, but the two orders can differ. Preserving the peak-search order when computing qhat gives priority to peaks from the brightest blobs.

If qhat has fewer than two rows, index_orientations returns an empty tuple.

Reuse the settings with an indexer#

PeakSearch.params contains the validated peak-search settings, including defaults. Use it to configure an Indexer for a scan:

from lauelab.indexing import Indexer, IndexParams

indexer = Indexer(
    geometry,
    crystal,
    peak_params=found.params,
    index_params=IndexParams(kev_max_calc=17.2, angle_tolerance_deg=0.1),
)

To run a stage with settings from an existing indexer, convert its parameter object to keyword arguments with dataclasses.asdict():

from dataclasses import asdict

again = peak_search(image, **asdict(indexer.peak_params))

Frame region and depth#

pixels_to_q uses start and group to transform frame pixel coordinates to full-detector pixel coordinates. For a cropped or binned frame, pass its origin and grouping; each coordinate maps to the center of the corresponding full-detector pixel group. For a reconstructed frame, also pass its depth in µm along the incident beam relative to the Si origin of the geometry file. You can obtain these values and the retained image from a FrameResult:

result = indexer.index("tests/data/synthetic/frames/synthetic_ni_two_grains.h5")
found = peak_search(result.image, **asdict(indexer.peak_params))
qhat = geometry.pixels_to_q(
    found.peaks,
    detector_index=indexer.detector_index,
    start=result.start,
    group=result.group,
    depth=result.depth,
)

pixels_to_q uses detector slot 0 unless you pass detector_index.

Warning

detector_index identifies a physical detector slot in the geometry file, not a position among the active detectors. Slots can be sparse. Selecting another detector’s slot can produce incorrect scattering vectors without raising an error. Use the slot of the detector that recorded the frame, such as indexer.detector_index. See Geometry for details.

Constraints#

  • The stage calls return arrays and patterns, not FrameResult objects. The tables, plots, and result writers accept only FrameResult objects. To store or plot a result, index the frame with an Indexer that uses the chosen settings.

  • Geometry.pixels_to_q raises ValueError if a fitted peak position falls outside the detector. Indexer.index does not check these bounds and can return a result for the same frame.

  • peak_search accepts arrays only. To search a frame that is stored in a file, use FrameResult.image or read the array with h5py.

See the Processing reference for complete signatures and field descriptions.