# 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](https://github.com/diffractem/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 (`.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 ```python # 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.