Peak finding

COSEDA detects Bragg peaks from single-shot frames using peakfinder_8 or peakfinder_9, parallelized with Dask. Results are written back into the dataset HDF5 and summarized for QA. This stage must run before file stripping, center finding/refinement and indexing.

Algorithms

peakfinder_8 (pf8)

A connected-component finder from Diffractem. For each frame it:

  1. Subtracts a local background estimated over an annular region (local_bg_radius).

  2. Finds connected pixel groups that exceed an absolute threshold and a minimum SNR.

  3. Accepts a group as a peak if its pixel count is within [min_pix_count, max_pix_count].

Good default choice. Relies on a compiled C extension.

peakfinder_9 (pf9)

A pure-Python implementation compiled with Numba (@njit, parallel=True). For each candidate pixel it checks four conditions against the square ring of pixels at Chebyshev distance exactly window_radius:

  1. The pixel must exceed every ring pixel by at least min_peak_over_neighbors ADU (strict local maximum).

  2. At least max(1, 2·r−1) ring pixels must be valid (not masked).

  3. The pixel SNR must exceed min_snr_max_pixel sigma above the ring mean.

  4. The integrated peak mass (sum of above-threshold pixel excesses within the window) must exceed min_snr_whole_peak sigma.

Peaks are refined to sub-pixel accuracy with intensity-weighted centre-of-mass. No compiled C dependency; the Numba kernel is cached to disk after the first run.

Inputs

  • COSEDA working file (<dataset>.h5) with:

    • entry/data/images (required)

    • /mask (2D pixel mask; same size as one frame) — required when running from the GUI. Generate one in the Preflight Check step before launching peak finding.

    • optional entry/data/center_x, center_y (used if you select x0=y0=h5)

  • Dataset .ini ([Parameters]) with the algorithm-specific keys below

Processing steps (both algorithms)

For each frame (processed in batches):

  1. Choose center

    • If x0 = h5 and y0 = h5: read per-frame centers from entry/data/center_x,y.

    • Otherwise: use the fixed (x0, y0) from the INI for all frames.

  2. Build a radial mask With pixel coordinates ((X,Y)) and center ((x_0, y_0)), [ R = \sqrt{(X-x_0)^2 + (Y-y_0)^2}. ] Keep pixels with min_res R max_res. If /mask exists and is 2D with matching shape, multiply it in.

  3. Run the selected finder (see above for details)

  4. Store fixed-length outputs For each frame, write:

    • entry/data/nPeaks (scalar)

    • entry/data/peakXPosRaw, peakYPosRaw, peakTotalIntensity (each length 500 per frame; zero-padded if fewer than 500 peaks)

Batches are scheduled via Dask; progress is reported either to the console (ProgressBar) or to the GUI via a callback.

Outputs

Both algorithms write identical datasets:

  • HDF5:

    • entry/data/nPeaks — shape (N,)

    • entry/data/peakXPosRaw, peakYPosRaw, peakTotalIntensity — shape (N, 500)

  • Run folder (created by the GUI):

    • findpeaks_*/peak_counts.npy — array [[frame_idx, nPeaks], ...] (pf8)

    • findpeaks9_*/peak_counts.npy — same format (pf9)

    • */paramdump.txt — parameters used (provenance)

  • Logs:

    • Peak count histogram buckets + an empty-frame warning into the dataset log

Statistics & QA

After writing results, COSEDA logs peak count statistics over four relative bins (0–25–50–75–100% of the observed max per file) and warns if >10% of frames are empty (suggesting to use Strip HDF5 to save disk and speed up downstream steps).

Parameters (INI [Parameters])

peakfinder_8

Key

Description

peakfinding_threshold

Absolute intensity threshold (ADU)

peakfinding_min_snr

Minimum signal-to-noise ratio

peakfinding_min_pix_count

Minimum connected pixels per peak

peakfinding_max_pix_count

Maximum connected pixels per peak

peakfinding_local_bg_radius

Local background annulus radius (pixels)

peakfinding_min_res

Inner radius of the search ring (pixels)

peakfinding_max_res

Outer radius of the search ring (pixels)

peakfinding_x0, peakfinding_y0

Beam centre in pixels, or h5 for per-frame

Required keys check: the batch runner hard-fails if any of min_snr, min_pix_count, max_pix_count, local_bg_radius, min_res is missing.

peakfinder_9

Key

Description

pf9_window_radius

Half-side of the square background ring (r)

pf9_min_sigma

Floor on background std before SNR comparisons

pf9_min_peak_over_neighbors

Required ADU excess over every ring pixel

pf9_min_snr_max_pixel

Required SNR of the peak pixel

pf9_min_snr_peak_pixels

SNR threshold for pixels counted toward peak mass

pf9_min_snr_whole_peak

Required integrated peak-mass SNR

pf9_min_res

Inner radius of the search ring (pixels)

pf9_max_res

Outer radius of the search ring (pixels)

pf9_x0, pf9_y0

Beam centre in pixels, or h5 for per-frame

Both parameter sets can coexist in the same INI file; only the keys for the selected algorithm are read at run time.

Performance

  • Batching: frames are chunked (default batch size provided by GUI/CLI); each batch is a Dask delayed task.

  • Masking: combining /mask and radial bounds reduces false positives.

  • I/O: results are written once after all futures complete to minimize HDF5 overhead. Workers open the file with locking=False for safe concurrent read access.

  • pf9 JIT compilation: the Numba kernel compiles on the first call (~3 s) and is cached to disk. Subsequent runs start immediately.

Using the GUI

  1. Open the Peakfinding window.

  2. Select the algorithm (peakfinder8 or peakfinder9) from the drop-down at the top. The parameter panel updates to show the relevant settings.

  3. Set the parameters. Hover over any field for a tooltip explanation.

  4. Optionally enable Live Preview to see peaks overlaid on the current frame as you adjust settings.

  5. Click Current (or All Files) under Save Settings to persist the settings into the .ini.

  6. Click Current (one dataset) or All Files (all loaded datasets) under Find Peaks to run.

  7. After completion, review the logged peak statistics and peak_counts.npy in the run folder.

The Find Peaks buttons are only enabled once all required parameters have been saved to the .ini and a pixel mask exists in the HDF5. If a mask is missing, a dialog will prompt you to run the Preflight Check first.

CLI

# peakfinder_8
from coseda.peakfinding.findpeaks_dask import findpeaks_dask_batch
findpeaks_dask_batch("/path/to/dataset.ini", batch_size=1000)

# peakfinder_9
from coseda.peakfinding.peakfinder9 import findpeaks9_dask_batch
findpeaks9_dask_batch("/path/to/dataset.ini", batch_size=1000)

The GUI normally writes all parameters to the .ini before running; replicate that if calling programmatically.