Synthesize signals from lifetimes#

An introduction to the lifetime_to_signal function.

The phasorpy.lifetime.lifetime_to_signal() function is used to synthesize time- and frequency-domain signals as a function of fundamental frequency, single or multiple lifetime components, lifetime fractions, mean and background intensity, and instrument response function (IRF) peak location and width.

Import required modules and functions:

import numpy
from matplotlib import pyplot

from phasorpy.lifetime import (
    lifetime_to_signal,
    phasor_calibrate,
    phasor_from_lifetime,
)
from phasorpy.phasor import phasor_from_signal

Define common parameters used throughout the tutorial:

frequency = 80.0  # fundamental frequency in MHz
reference_lifetime = 4.2  # lifetime of reference signal in ns

lifetimes = [0.5, 1.0, 2.0, 4.0]  # lifetimes in ns
fractions = [0.25, 0.25, 0.25, 0.25]  # fractional intensities

settings = {
    'samples': 256,  # number of samples to synthesize
    'mean': 1.0,  # average intensity
    'background': 0.0,  # no signal from background
    'zero_phase': None,  # location of IRF peak in the phase
    'zero_stdev': None,  # standard deviation of IRF in radians
}

Time domain, multi exponential#

Synthesize a time-domain signal of a multi-component lifetime system with given fractional intensities, convolved with an instrument response function:

signal, instrument_response, times = lifetime_to_signal(
    frequency, lifetimes, fractions, **settings
)

A reference signal of known lifetime is required to calibrate the phasor coordinates. The reference signal must be obtained with the same instrument and sampling parameters. The calibrated phasor coordinates match the theoretical phasor coordinates expected for the lifetimes:

reference_signal, _, _ = lifetime_to_signal(
    frequency, reference_lifetime, **settings
)


def verify_signal(fractions):
    """Verify calibrated phasor coordinates match expected results."""
    assert numpy.allclose(
        phasor_calibrate(
            *phasor_from_signal(signal)[1:],
            *phasor_from_signal(reference_signal),
            frequency,
            reference_lifetime,
        ),
        phasor_from_lifetime(frequency, lifetimes, fractions),
        atol=1e-3,
        equal_nan=True,
    )


verify_signal(fractions)

Plot the synthesized signals (multi-exponential, reference, and instrument response):

fig, ax = pyplot.subplots()
ax.set(
    title=f'Time-domain signals ({frequency} MHz)',
    xlabel='Times [ns]',
    ylabel='Intensity [au]',
)
ax.plot(times, signal, label='Multi-exponential')
ax.plot(times, reference_signal, label='Reference')
ax.plot(times, instrument_response, label='Instrument response')
ax.legend()
pyplot.show()
Time-domain signals (80.0 MHz)

Time domain, single exponential#

To synthesize separate signals for each lifetime component at once, omit the lifetime fractions:

signal, _, times = lifetime_to_signal(frequency, lifetimes, **settings)

verify_signal(None)

Plot the synthesized signals:

fig, ax = pyplot.subplots()
ax.set(
    title=f'Time-domain signals ({frequency} MHz)',
    xlabel='Times [ns]',
    ylabel='Intensity [au]',
)
ax.plot(times, signal.T, label=[f'{t} ns' for t in lifetimes])
ax.legend()
pyplot.show()
Time-domain signals (80.0 MHz)

As expected, the shorter the lifetime, the faster the decay.

Frequency domain, multi exponential#

To synthesize a frequency-domain homodyne signal, limit the synthesis to the fundamental frequency (harmonic=1):

signal, instrument_response, _ = lifetime_to_signal(
    frequency, lifetimes, fractions, harmonic=1, **settings
)

reference_signal, _, _ = lifetime_to_signal(
    frequency, reference_lifetime, harmonic=1, **settings
)

verify_signal(fractions)

Plot the synthesized signals:

phase = numpy.linspace(0.0, 360.0, signal.size)

fig, ax = pyplot.subplots()
ax.set(
    title=f'Frequency-domain signals ({frequency} MHz)',
    xlabel='Phase [°]',
    ylabel='Intensity [au]',
    xticks=[0, 90, 180, 270, 360],
)
ax.plot(phase, signal, label='Multi-exponential')
ax.plot(phase, reference_signal, label='Reference')
ax.plot(phase, instrument_response, label='Instrument response')
ax.legend()
pyplot.show()
Frequency-domain signals (80.0 MHz)

Frequency domain, single exponential#

To synthesize separate signals for each lifetime component at once, omit the lifetime fractions:

signal, _, _ = lifetime_to_signal(frequency, lifetimes, harmonic=1, **settings)

verify_signal(None)

Plot the synthesized signals:

fig, ax = pyplot.subplots()
ax.set(
    title=f'Frequency-domain signals ({frequency} MHz)',
    xlabel='Phase [°]',
    ylabel='Intensity [au]',
    xticks=[0, 90, 180, 270, 360],
)
ax.plot(phase, signal.T, label=[f'{t} ns' for t in lifetimes])
ax.legend()
pyplot.show()
Frequency-domain signals (80.0 MHz)

As expected, the shorter the lifetime, the smaller the phase shift and demodulation.

Total running time of the script: (0 minutes 0.318 seconds)

Gallery generated by Sphinx-Gallery