Source code for pyfli.simulator.sim_helper

"""
Provide sim helper tools for PyFLI synthetic FLI/FLIM data generation, hardware noise
modeling, calibration, and validation tools.

This module belongs to :mod:`pyfli.simulator` and is part of PyFLI synthetic FLI/FLIM
data generation, hardware noise modeling, calibration, and validation tools. Public API
includes functions :func:`irf_picker`.
"""

import numpy as np


[docs] def irf_picker( irf_full: np.ndarray, px: tuple[int, int] | None = None ) -> tuple[np.ndarray, tuple[int, int] | None]: # IRF Selection Logic """ Run the IRF picker routine. Parameters ---------- irf_full : np.ndarray Full instrument response function sampled over the decay window. px : tuple[int, int] | None Explicit ``(x, y)`` pixel to use when ``irf_full`` is 3-D, instead of picking one at random. Ignored when ``irf_full`` is 1-D. Returns ------- tuple[np.ndarray, tuple[int, int] | None] The selected IRF array, and the ``(x, y)`` pixel it came from — the given ``px`` if one was passed, the randomly chosen pixel otherwise, or ``None`` when ``irf_full`` is 1-D (there's no pixel to report). """ if irf_full.ndim == 3: H, W, T = irf_full.shape max_attempts = 1 if px is not None else 1000 for _ in range(max_attempts): if px is not None: x, y = px else: x = np.random.randint(H) y = np.random.randint(W) pixel_data = irf_full[x, y, :] peak = np.max(pixel_data) if peak <= 500: continue # Estimate noise from the pre-peak baseline (first quarter before the peak) # SNR criterion: noise <= 5% of peak ↔ SNR = peak/noise >= 20 peak_idx = int(np.argmax(pixel_data)) if peak_idx >= 3: n_baseline = max(3, peak_idx // 4) noise = np.std(pixel_data[:n_baseline]) else: # Peak is at the very start — fall back to the far tail noise = np.std(pixel_data[int(0.75 * T) :]) if noise <= 0.05 * peak: # SNR >= 20 irf = pixel_data break else: raise RuntimeError( f"Could not find a valid IRF pixel after {max_attempts} attempts." ) return irf, (x, y) elif irf_full.ndim == 1: return irf_full, None else: raise ValueError(f"IRF must be 1-D or 3-D, got shape {irf_full.shape}")