FLIMParams classes

The FLIMParams class, and their variants, stores parameters for fitting FLIM data.

Initialization of the FLIMParams class

The flim params class can be initialized either using the from_tuple class method or by passing a list of flim parameter instances. The flim parameter classes are UNITFUL – for some purposes it’s easiest to work with indices of the arrival time bins and sometimes it’s easiest to work with real SI units.

from siffpy.core.flim import FLIMParams, Exp, Irf

fp = FLIMParams.from_tuple(
    (
        0.5, 0.3, # tau, fraction in state 1
        2.2, 0.5, # tau, fraction in state 2
        6.2, 0.2, # tau, fraction in state 3
        4.1, 0.03 # IRF mean offset and breadth
    ),
    units = 'nanoseconds'
)

# Equivalent
fp = FLIMParams(
    # any of these params could be defined
    # with their own units if desired
    Exp(0.5, 0.3, units = 'nanoseconds'),
    Exp(2.2, 0.5, units = 'nanoseconds'),
    Exp(6.2, 0.2, units = 'nanoseconds'),
    Irf(4.1, 0.03, units = 'nanoseconds'),
)

FlimUnits and the as_units context manager

The flim params class provides a context manager for its units internally so that most methods (other than convert_units) do not cryptically change the units (and thus the numerical values used for the various parameters). To use the context manager, use with fp.as_units('unit'): where fp is a flim params instance and unit is the desired unit. unit may be a string or a flim units unit.

from siffpy.core.flim import FLIMParams, Exp, Irf

fp = FLIMParams(
    Exp(0.5, 0.3, units = 'nanoseconds'),
    Exp(2.6, 0.7, units = 'nanoseconds'),
    Irf(4.1, 0.03, units = 'nanoseconds'),
)

print(fp.units) # nanoseconds

with fp.as_units('countbins'):
    # do some processing
    analyses_with_fp(fp, ...)

print(fp.units) # still nanoseconds

Saving FLIMParams

The flim params class can be saved to a file using the save method. The file format is a simple JSON file, but uses the suffix .flimparams. These data can be loaded as a dict and used to instantiate a new flim params instance with the from_dict class method, or the load class method with a path to file.

from siffpy.core.flim import FLIMParams, Exp, Irf

fp = FLIMParams(
    Exp(0.5, 0.3, units = 'nanoseconds'),
    Exp(2.6, 0.7, units = 'nanoseconds'),
    Irf(4.1, 0.03, units = 'nanoseconds'),
)

fp.save('my_params.flimparams')

# later
fp = FLIMParams.load('my_params.flimparams')

# OR:
import json
with open('my_params.flimparams', 'r') as f:
    data = json.load(f)
fp = FLIMParams.from_dict(data)
class siffpy.core.flim.flimunits.FlimUnits(value)

An enumeration.

conversion_factor(other: FlimUnits) float

self*conversion_factor = other

static convert_flimunits(in_array: ndarray | float, from_units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'], to_units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless']) ndarray | float

Converts an array or float in_array from one type of FLIMUnit to another.

UNITLESS is a special case, where the input is returned unchanged.

class siffpy.core.flim.flimparams.FLIMParams(*args: List[FLIMParameter] | Tuple[float], color_channel: int | None = None, noise: float = 0.0, name: str | None = None)

A class for storing parameters related to fitting distributions of fluorescence lifetime or photon arrival data.

Currently only implements combinations of exponentials.

The FLIMParams object uses units, specifically the FlimUnits enum, to keep track of the units of the parameters.

property T_O: float

Back-compatibility

property allow_noise: bool

Whether or not the noise parameter is allowed

as_units(to_units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'])

Context manager that temporarily converts the units of this FLIMParams object to the units of the first parameter, and then converts them back to the original units when the context is exited.

Example

```python

import numpy as np from siffpy.core.flim import FLIMParams, Exp, Irf

flim_params = FLIMParams(

Exp(tau=0.5, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.1, units=’nanoseconds’)

)

with flim_params.as_units(‘picoseconds’):

# do some stuff while this is in picoseconds print(flim_params)

print(flim_params) >>> FLIMParams object:

Parameters:

Exp

UNITS: FlimUnits.PICOSECONDS tau : 500.0 frac : 0.2

Exp

UNITS: FlimUnits.PICOSECONDS tau : 3000.0 frac : 0.8

Irf

UNITS: FlimUnits.PICOSECONDS tau_offset : 3000.0 tau_g : 100.0

Noise: 0.0 Color channel: None

FLIMParams object:

Parameters:

Exp

UNITS: FlimUnits.NANOSECONDS tau : 0.5 frac : 0.2

Exp

UNITS: FlimUnits.NANOSECONDS tau : 3.0 frac : 0.8

Irf

UNITS: FlimUnits.NANOSECONDS tau_offset : 3.0 tau_g : 0.1

Noise: 0.0 Color channel: None

```

property bounds: Bounds

Returns a bounds object from the scipy optimization library that can be used to constrain the parameters of this FLIMParams

property constraints: List[LinearConstraint]

Exponential fractions sum to one, taus in increasing order

convert_units(to_units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'], flim_info=None)

Converts the units of all the parameters of this FLIMParams to the requested unit type of to_units.

Arguments

to_units : FlimUnits

A FlimUnits object specifying the units into which the FLIMParams will be transformed.

flim_info : FlimInfo

A FlimInfo object that is necessary to determine how to interchange between arrival_bins and real time units like picoseconds and nanoseconds. If converting between real time units, this parameter can be ignored. NOT YET IMPLEMENTED ACTUALLY. TODO!!

Returns

None

Example

```python import numpy as np from siffpy.core.flim import FLIMParams, Exp, Irf

flim_params = FLIMParams(

Exp(tau=0.5, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.1, units=’nanoseconds’)

)

flim_params.convert_units(‘picoseconds’) flim_params

>>> FLIMParams object: 
Parameters:

Exp

UNITS: FlimUnits.PICOSECONDS tau : 500.0 frac : 0.5

Exp

UNITS: FlimUnits.PICOSECONDS tau : 3000.0 frac : 0.5

Irf

UNITS: FlimUnits.PICOSECONDS tau_offset : 1000.0 tau_g : 100.0

Noise: 0.0 Color channel: None

```

fit_params_to_data(data: ~numpy.ndarray, initial_guess: ~numpy.ndarray | None = None, loss_function: LossFunction = <class 'siffpy.core.flim.loss_functions.MSE'>, solver: ~typing.Callable | None = None, x_range: ~numpy.ndarray | None = None, optimization_units: FlimUnits | ~typing.Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'] = FlimUnits.NANOSECONDS, **kwargs) OptimizeResult

Takes in the data and adjusts the internal parameters of this FLIMParams object to minimize the metric input. Default is CHI-SQUARED.

Stores new parameter values IN PLACE, but will return the scipy OptimizeResult object.

ACTUALLY NO LONGER USING LOSS_FUNCTION AND SOLVER — FUNCTION CALL OVERHEAD WAS MAKING IT VERY SLOW. TO DO: FIGURE OUT A WAY TO PRESERVE THAT FLEXIBILITY!!

Inputs

  • data : np.ndarray

    A numpy array of the arrival time histogram. Data[n] = number of photons arriving in bin n

  • initial_guess : tuple

    Guess for initial params in FLIMParams.param_tuple format. Presumed to be in the same units as the optimization_units (if not None).

  • loss_function : LossFunction

    Defines the cost function for curve fitting. Defaults to chi-squared

    Argument 1: DATA (1d-ndarray) as above

    Argument 2: PARAMS (tuple)

    All other arguments must be KWARGS.

  • solver : Callable

    A function that takes the metric and an initial guess and returns some object that has an attribute called ‘x’ that is a tuple with the same format as the FIT result of the param_tuple. This is the format of the default scipy.optimize.minimize functions.

  • x_range : np.ndarray

    The range of the x-axis in the same units as the FLIMParams. If not provided, assumes the data is in countbins and uses np.arange(len(data)) as the x_range.

  • **kwargs

    Passed to the scipy.optimize.minimize opts kwarg.

Returns

  • fit : scipy.optimize.OptimizeResult

    The OptimizeResult object for diagnostics on the fit.

Examples

### Create a FLIMParams object that generates the data, ### sample from it, then fit a new FLIMParams object to ### those data.

```python from siffpy.core.flim import FLIMParams, Exp, Irf import numpy as np

flim_params = FLIMParams(

Exp(tau=0.4, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.05, units=’nanoseconds’)

)

# 12.5 nanoseconds in 10 ps increments arrival_time_axis = np.arange(0.01,12.5, 0.01) # Sample from the distribution, using a seed to make sure your result matches the example data = flim_params.sample(arrival_time_axis, 1000000, seed = 120, as_histogram = True)

# A particularly bad guess fit_params = FLIMParams(

Exp(tau=0.1, frac=0.5, units=’nanoseconds’), Exp(tau=0.2, frac=0.5, units=’nanoseconds’), Irf(tau_offset=5, tau_g=0.1, units=’nanoseconds’)

)

fit_params.fit_params_to_data(data.astype(float), x_range = arrival_time_axis[1:])

# A not-so-bad result fit_params

>>> FLIMParams object: 
Parameters:

Exp

UNITS: FlimUnits.NANOSECONDS tau : 0.39989360953557634 frac : 0.5006910298045519

Exp

UNITS: FlimUnits.NANOSECONDS tau : 2.9891959985672134 frac : 0.49930897019544795

Irf

UNITS: FlimUnits.NANOSECONDS tau_offset : 1.0102523399410983 tau_g : 0.05007446535532903

Noise: 0.0 Color channel: None

property fraction_bounds: Bounds

Fraction-only bounds

property fraction_constraints: List[LinearConstraint]

For when the taus and IRF are fixed

classmethod from_dict(data_dict: Dict[str, Any]) FLIMParams

Converts a JSON-compatible dictionary to a FLIMParams

Example code:

```python flim_params = FLIMParams.from_dict(

dict(
exps = [

dict(tau=2, frac=0.5, units=”NANOSECONDS”), dict(tau=3, frac=0.5, units=”NANOSECONDS”),

], irf = dict(tau_offset=1, tau_g=2), color_channel = 0, noise = 0.1, name = “Test FLIMParams”,

)

classmethod from_tuple(param_tuple: Tuple, units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'] = FlimUnits.COUNTBINS, noise: float = 0.0)

Instantiate a FLIMParams from the parameter tuple. Order is:

(tau, frac, tau, frac, … , mean, sigma).

Example code:

```python flim_params = FLIMParams.from_tuple(

(2, 0.5, 3, 0.5, 5, 1, 2), units = FlimUnits.PICOSECONDS,

)

classmethod load(path: str | Path) FLIMParams

Load from a json file with different extension

property n_exp: int

Number of exponentials in this FLIMParams

property ncomponents: int

Number of exponentials in this FLIMParams

property noise: float

The noise parameter

property param_tuple: Tuple

Returns a tuple for all of the parameters together so they can be passed into numerical solvers.

property params: List[FLIMParameter]

Returns a list of all FLIMParameter objects contained by this FLIMParams

pdf(x_range: ndarray) ndarray

Return the fit value’s probability distribution. To plot against a data set, rescale this by the total number of photons in the data set. Assumes x_range is in the same units as the FLIMParams.

Alias for probability_dist

INPUTS

x_range : np.ndarray (1-dimensional)

The x values you want the output probabilities of. Usually this will be something like np.arange(MAX_BIN_VALUE), e.g. np.arange(1024)

RETURN VALUES

p_out : np.ndarray(1-dimensional)

The probability of observing a photon in each corresponding bin of x_range.

Example

Create a FLIMParams object and get the probability distribution of a photon arriving in any of the time bins provided

```python import numpy as np from siffpy.core.flim import FLIMParams, Exp, Irf

flim_params = FLIMParams(

Exp(tau=2, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.1, units=’nanoseconds’)

)

flim_params.pdf(np.arange(0,12.5, 0.01))

>>> array([4.42152784e-05, 4.40548485e-05, 4.38950189e-05, ...,
4.51206365e-05, 4.49570342e-05, 4.47940435e-05])
```
probability_dist(x_range: ndarray) ndarray

Return the fit value’s probability distribution. To plot against a data set, rescale this by the total number of photons in the data set. Assumes x_range is in the same units as the FLIMParams.

INPUTS

x_range : np.ndarray (1-dimensional)

The x values you want the output probabilities of. Usually this will be something like np.arange(MAX_BIN_VALUE), e.g. np.arange(1024)

RETURN VALUES

p_out : np.ndarray(1-dimensional)

The probability of observing a photon in each corresponding bin of x_range.

Example

Create a FLIMParams object and get the probability distribution of a photon arriving in any of the time bins provided

```python import numpy as np from siffpy.core.flim import FLIMParams, Exp, Irf

flim_params = FLIMParams(

Exp(tau=2, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.1, units=’nanoseconds’)

)

flim_params.pdf(np.arange(0,12.5, 0.01))

>>> array([4.42152784e-05, 4.40548485e-05, 4.38950189e-05, ...,
4.51206365e-05, 4.49570342e-05, 4.47940435e-05])
```
sample(x_range: ndarray, n: int = 10000, seed: int | None = None, as_histogram: bool = True) ndarray

Sample the arrival time distribution of this FLIMParams

Arguments

x_range : np.ndarray

The range of the x-axis in the same units as the FLIMParams. If using as_histogram, this will return a histogram of the samples binned as in x_range (so the returned array will have shape (len(x_range)-1,) if as_histogram is True).

n : int

The number of samples to take.

seed : int

The seed for the numpy random number generator.

as_histogram : bool

If True, returns a histogram of the samples, binned as in x_range. If False, returns the samples themselves.

Returns

samples : np.ndarray

An array of n samples from the arrival time distribution if as_histogram is False. If as_histogram is True, returns a histogram of the samples binned as in x_range of length len(x_range) - 1.

Example

### Return a sample of 3 arrival times from the FLIMParams object.

```python import numpy as np from siffpy.core.flim import FLIMParams, Exp, Irf

flim_params = FLIMParams(

Exp(tau=2, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.1, units=’nanoseconds’)

)

flim_params.sample(np.arange(0,12.5,1000), 3, seed = 15, as_histogram = False)

>>> array([5.73, 6.9 , 3.95])

samps = flim_params.sample(np.arange(0,12.5,1), 100000, seed = 15, as_histogram = True)

print(samps.shape, np.arange(0,12.5,1).shape)

>>> (12,) (13,)
```

### Return a histogram of 100000 samples from the FLIMParams object, ### binned into 1 nanosecond bins

```python import numpy as np from siffpy.core.flim import FLIMParams, Exp, Irf

flim_params = FLIMParams(

Exp(tau=0.5, frac=0.5, units=’nanoseconds’), Exp(tau=3, frac=0.5, units=’nanoseconds’), Irf(tau_offset=1, tau_g=0.1, units=’nanoseconds’)

)

flim_params.sample(np.arange(0,12.5,1), 100000, seed = 15, as_histogram = True)

>>> array([  367, 46941, 23666,  9627,  5890,  4053,  2939,  2114,  1536,
1098,   789,   980])
```
save(path: str | Path)

Save to a json file with different extension

to_dict() Dict[str, Any]

Converts the FLIMParams to a JSON-compatible dictionary

class siffpy.core.flim.multi_pulse.MultiPulseFLIMParams(*args: List[FLIMParameter] | Tuple[float], **kwargs)

Specialized FLIMParams that permits more than one IRF. Much of the functionality is re-written, which makes inheritance seem… silly. I’ll have to think about a better way at some point.

property bounds: Bounds

Returns the bounds for the parameters

property constraints: List[LinearConstraint]

Returns the constraints for the parameters

fit_params_to_data(data: ~numpy.ndarray, initial_guess: ~numpy.ndarray | None = None, loss_function: LossFunction = <class 'siffpy.core.flim.loss_functions.MSE'>, solver: ~typing.Callable | None = None, x_range: ~numpy.ndarray | None = None, optimization_units: FlimUnits | ~typing.Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'] = FlimUnits.NANOSECONDS, **kwargs) OptimizeResult

Takes in the data and adjusts the internal parameters of this FLIMParams object to minimize the metric input. Default is CHI-SQUARED.

Stores new parameter values IN PLACE, but will return the scipy OptimizeResult object.

ACTUALLY NO LONGER USING LOSS_FUNCTION AND SOLVER — FUNCTION CALL OVERHEAD WAS MAKING IT VERY SLOW. TO DO: FIGURE OUT A WAY TO PRESERVE THAT FLEXIBILITY!!

Inputs

data : np.ndarray

A numpy array of the arrival time histogram. Data[n] = number of photons arriving in bin n

initial_guess : tuple

Guess for initial params in FLIMParams.param_tuple format. Presumed to be in the same units as the FLIMParams when the function is called (if not None).

loss_function : LossFunction

Defines the cost function for curve fitting. Defaults to chi-squared

Argument 1: DATA (1d-ndarray) as above

Argument 2: PARAMS (tuple)

All other arguments must be KWARGS.

solver : Callable

A function that takes the metric and an initial guess and returns some object that has an attribute called ‘x’ that is a tuple with the same format as the FIT result of the param_tuple. This is the format of the default scipy.optimize.minimize functions.

x_range : np.ndarray

The range of the x-axis in the same units as the FLIMParams. If not provided, assumes the data is in countbins and uses np.arange(len(data)) as the x_range.

**kwargs

Passed to the metric function.

Returns

fit : scipy.optimize.OptimizeResult

The OptimizeResult object for diagnostics on the fit.

classmethod from_dict(data: Dict[str, Any]) MultiPulseFLIMParams

Reconstructs the MultiPulseFLIMParams from a dictionary

classmethod from_tuple(param_tuple: Tuple, n_pulses: int, units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'] = FlimUnits.COUNTBINS, noise: float = 0.0)

Instantiate a MultiPulseFLIMParams object from a tuple of parameters. Because this could have any number of pulses, the values are not uniquely determined simply by the length of the param_tuple itself, and so the number of pulses must be given.

Tuple form required: (tau, frac, tau, frac, …, mean, sigma, irf_frac, mean, sigma, irf_frac…)

Example code:

```python # Instantiate a MultiPulseFLIMParams object # with 2 pulses, 2 exps, and 2 irfs params = MultiPulseFLIMParams.from_tuple(

(1.0, 0.5, 2.0, 0.5, 100.0, 10.0, 0.5, 100.0, 10.0, 0.5), 2, units = ‘nanoseconds’, noise = 0.1

)

property irfs: MultiIrf

Returns the MultiIrf object

property n_params: int

Number of parameters in the model

property n_pulses: int

Number of laser pulses in the model

property param_tuple: Tuple

Returns a tuple for all of the parameters together so they can be passed into numerical solvers.

pdf(x_range: ndarray) ndarray

Returns the PDF at the given x value

probability_dist(x_range: ndarray) ndarray

Returns the probability distribution at the given x value

class siffpy.core.flim.flimparams.FLIMParameter(units: FlimUnits = FlimUnits.COUNTBINS, **params)

Base class for the various types of parameters.

Doesn’t do anything special, just a useful organizer for shared behavior.

FLIMParameters can be converted between units, and can be serialized to and from JSON.

The units attribute is used to keep track of the units of the parameters. The unitful_params attribute is used to keep track of which parameters are unitful and need to be converted when the units are changed.

The aliases attribute is used to keep track of aliases for the parameters, so that they can be accessed with different names (each of which is more intuitive to certain users)

convert_units(to_units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless']) None

Converts unitful params

classmethod from_dict(data_dict: Dict) FLIMParameter

Converts a JSON-compatible dictionary to a FLIMParameter

nondimensionalize() None

Converts unitful params to nondimensional

Alias for convert_units(FlimUnits.UNITLESS)

redimensionalize(to_units: FlimUnits | Literal['picoseconds', 'nanoseconds', 'countbins', 'unknown', 'unitless'])

Converts UNITLESS params to to_units

Alias for convert_units

to_dict() Dict

JSON-parsable string