Synthesize signals from lifetimes#

An introduction to the lifetime_to_signal function.

The phasorpy.phasor.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.phasor import (
    lifetime_to_signal,
    phasor_calibrate,
    phasor_from_lifetime,
    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]  # lifetime 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, obtained with the same instrument and sampling parameters, is required to correct/calibrate the phasor coordinates of signals. 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."""
    numpy.testing.assert_allclose(
        phasor_calibrate(
            *phasor_from_signal(signal)[1:],
            *phasor_from_signal(reference_signal),
            frequency,
            reference_lifetime,
        ),
        phasor_from_lifetime(frequency, lifetimes, fractions),
    )


verify_signal(fractions)

Plot the synthesized signal, the instrument response, and reference signal:

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 signal:

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 de-modulation.

TODO: generate digitized image from lifetime distributions with background and noise.

sphinx_gallery_thumbnail_number = -1 mypy: allow-untyped-defs, allow-untyped-calls mypy: disable-error-code=”arg-type”

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

Gallery generated by Sphinx-Gallery