Source code for pyfli.simulator.irf_sim.irf_generator

# simulator/irf_sim/irf_generator.py

"""
Generate synthetic instrument response function (IRF) traces for simulator and
alignment testing.

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

import numpy as np


[docs] class IRFGenerator: """ Generate synthetic IRF traces for testing alignment, fitting, and simulation code without needing measured instrument data. Each method returns a 1D `(num_bins,)` trace, or a `(H, W, num_bins)` cube with the same trace broadcast to every pixel when `H` and `W` are both given. """ @staticmethod def _broadcast(irf_1d: np.ndarray, H: int | None, W: int | None) -> np.ndarray: """ Broadcasts a 1D IRF trace to a (H, W, num_bins) cube when H and W are both given, otherwise returns the 1D trace unchanged. """ if H is None and W is None: return irf_1d if H is None or W is None: raise ValueError( "H and W must both be given for 3D output, or both omitted for 1D." ) return np.broadcast_to(irf_1d, (H, W, len(irf_1d))).copy()
[docs] @staticmethod def gaussianIRF( mu: float, T: float = 12.5, num_bins: int = 256, sigma: float = 0.1, H: int | None = None, W: int | None = None, ) -> np.ndarray: """ Generates a Gaussian-shaped IRF: a narrow pulse centered at bin `mu`, peak normalized to 1. Parameters ---------- mu : float Bin index of the pulse peak (fractional values are supported). T : float Laser period in nanoseconds. num_bins : int Number of time bins spanning one laser period. sigma : float Pulse spread, in nanoseconds. Converted to bins internally via `gate_delay = T / num_bins`. H, W : int | None If both given, the trace is broadcast to a (H, W, num_bins) cube. If both omitted, a 1D (num_bins,) trace is returned. """ gate_delay = T / num_bins sigma_bins = sigma / gate_delay t = np.arange(num_bins) irf_1d = np.exp(-0.5 * ((t - mu) / sigma_bins) ** 2) return IRFGenerator._broadcast(irf_1d, H, W)
[docs] @staticmethod def expdecay( mu: float, T: float = 12.5, num_bins: int = 256, tau: float = 0.1, H: int | None = None, W: int | None = None, ) -> np.ndarray: """ Generates a single-exponential-decay IRF: a Dirac delta at bin `mu` convolved with a very fast causal exponential decay, peak normalized to 1. Parameters ---------- mu : float Bin index of the delta impulse (rounded to the nearest bin). T : float Laser period in nanoseconds. num_bins : int Number of time bins spanning one laser period. tau : float Exponential decay time constant, in nanoseconds (0.1 ns or less for a "very fast" IRF-like decay). H, W : int | None If both given, the trace is broadcast to a (H, W, num_bins) cube. If both omitted, a 1D (num_bins,) trace is returned. """ gate_delay = T / num_bins delta = np.zeros(num_bins) delta[round(mu)] = 1.0 kernel = np.exp(-np.arange(num_bins) * gate_delay / tau) irf_1d = np.convolve(delta, kernel)[:num_bins] irf_1d = irf_1d / irf_1d.max() return IRFGenerator._broadcast(irf_1d, H, W)
[docs] @staticmethod def gaussianExpIRF( mu: float, T: float = 12.5, num_bins: int = 256, sigma: float = 0.1, tau: float = 0.1, H: int | None = None, W: int | None = None, ) -> np.ndarray: """ Generates an exponentially modified Gaussian (EMG) IRF: a Gaussian pulse (as in :meth:`gaussianIRF`) convolved with a fast causal exponential decay (as in :meth:`expdecay`), peak normalized to 1. This models a common real-detector IRF shape: a Gaussian core from optical and timing jitter, with an exponential tail from the detector's electronic response. Parameters ---------- mu : float Bin index of the Gaussian core's peak. T : float Laser period in nanoseconds. num_bins : int Number of time bins spanning one laser period. sigma : float Gaussian core spread, in nanoseconds. tau : float Exponential tail time constant, in nanoseconds. H, W : int | None If both given, the trace is broadcast to a (H, W, num_bins) cube. If both omitted, a 1D (num_bins,) trace is returned. """ gate_delay = T / num_bins sigma_bins = sigma / gate_delay t = np.arange(num_bins) gaussian = np.exp(-0.5 * ((t - mu) / sigma_bins) ** 2) kernel = np.exp(-np.arange(num_bins) * gate_delay / tau) irf_1d = np.convolve(gaussian, kernel)[:num_bins] irf_1d = irf_1d / irf_1d.max() return IRFGenerator._broadcast(irf_1d, H, W)