Skip to content

Commit

Permalink
Improve phasor_from_lifetime tutorial (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgohlke authored Apr 15, 2024
1 parent 94f3ccd commit f4060ba
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/phasorpy/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def plot(
self._lines = ax.plot(re, im, fmt, label=lbl, **kwargs)
if label is not None:
ax.legend()
self._reset_limits()

def _histogram2d(
self,
Expand Down
110 changes: 76 additions & 34 deletions tutorials/phasorpy_phasor_from_lifetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@
from phasorpy.phasor import phasor_from_lifetime, phasor_to_polar
from phasorpy.plot import PhasorPlot, plot_phasor, plot_polar_frequency

rng = numpy.random.default_rng()

# %%
# Single-component lifetimes
# --------------------------
#
# The phasor coordinates of single-component lifetimes are located
# on the universal semicircle.
# For example, 3.9788735 ns and 0.9947183 ns at a frequency of 80 MHz:
# For example, 4.0 ns and 1.0 ns at a frequency of 80 MHz:

lifetime = numpy.array([3.9788735, 0.9947183])
frequency = 80.0
lifetimes = [4.0, 1.0]

plot_phasor(*phasor_from_lifetime(80.0, lifetime), frequency=80.0)
plot_phasor(
*phasor_from_lifetime(frequency, lifetimes),
frequency=frequency,
title='Single-component lifetimes',
)

# %%
# Multi-component lifetimes
Expand All @@ -39,12 +46,15 @@
# fractional intensities are linear combinations of the coordinates
# of the pure components:

fraction = numpy.array(
fractions = numpy.array(
[[1, 0], [0.25, 0.75], [0.5, 0.5], [0.75, 0.25], [0, 1]]
)

plot_phasor(
*phasor_from_lifetime(80.0, lifetime, fraction), fmt='o-', frequency=80.0
*phasor_from_lifetime(frequency, lifetimes, fractions),
fmt='o-',
frequency=frequency,
title='Multi-component lifetimes',
)

# %%
Expand All @@ -55,84 +65,116 @@
# pre-exponential amplitudes are also located on a line:

plot_phasor(
*phasor_from_lifetime(80.0, lifetime, fraction, preexponential=True),
*phasor_from_lifetime(
frequency, lifetimes, fractions, preexponential=True
),
fmt='o-',
frequency=80.0,
frequency=frequency,
title='Pre-exponential amplitudes',
)

# %%
# Average of lifetime distributions
# ---------------------------------
#
# The average phasor coordinates for wider distributions of lifetimes
# lie further inside the universal semicircle compared to the narrower
# distributions:

frequency = 80.0
lifetime = 4.0
standard_deviations = [0.1, 0.5, 1.0]
samples = 100000

plot = PhasorPlot(
frequency=frequency, title='Average of lifetime distributions'
)
for sigma in standard_deviations:
phi = numpy.sqrt(sigma * sigma / lifetime)
phasor_average = numpy.average(
phasor_from_lifetime(
frequency, rng.gamma(lifetime / phi, phi, samples)
),
axis=1,
)
plot.plot(*phasor_average, label=f'{sigma=:.1f}')
plot.show()

# %%
# Lifetime distributions at multiple frequencies
# ----------------------------------------------
#
# Phasor coordinates can be calculated at once for many frequencies,
# lifetime components, and their fractions. As an example, random distrinutions
# of lifetimes and their fractions are plotted at three frequencies.
# lifetime components, and their fractions. As an example, random
# distrinutions of lifetimes and their fractions are plotted at
# three frequencies.
# Lifetimes are passed in units of s and frequencies in Hz, requiring to
# specify a `unit_conversion` factor:

rng = numpy.random.default_rng()

samples = 100
lifetime_distribution = (
lifetimes = [4.0, 2.0, 1.0]

lifetime_distributions = (
numpy.column_stack(
(
rng.normal(3.9788735, 0.05, samples),
rng.normal(1.9894368, 0.05, samples),
rng.normal(0.9947183, 0.05, samples),
)
[rng.gamma(lifetime / 0.01, 0.01, samples) for lifetime in lifetimes]
)
* 1e-9
)
fraction_distribution = numpy.column_stack(
(rng.random(samples), rng.random(samples), rng.random(samples))
fraction_distributions = numpy.column_stack(
[rng.random(samples) for lifetime in lifetimes]
)

plot_phasor(
*phasor_from_lifetime(
frequency=[40e6, 80e6, 160e6],
lifetime=lifetime_distribution,
fraction=fraction_distribution,
lifetime=lifetime_distributions,
fraction=fraction_distributions,
unit_conversion=1.0,
),
fmt='.',
label=('40 MHz', '80 MHz', '160 MHz'),
title='Lifetime distributions at multiple frequencies',
)

# %%
# FRET efficiency
# ---------------
#
# The phasor coordinates of a fluorescence energy transfer donor
# with a single lifetime component of 4.2 ns as a function of FRET efficiency
# with a single lifetime component of 4.0 ns as a function of FRET efficiency
# at a frequency of 80 MHz, with some background signal and about 90 %
# of the donors participating in energy transfer, are on a curved trajectory.
# For comparison, when 100% donors participate in FRET and there is no
# background signal, the phasor coordinates lie on the universal semicircle:

frequency = 80.0
samples = 25
lifetime = 4.0
efficiency = numpy.linspace(0.0, 1.0, samples)

plot = PhasorPlot(frequency=80.0)
lifetime_quenched = lifetime * (1.0 - efficiency)

plot = PhasorPlot(frequency=frequency, title='FRET efficiency')
plot.plot(
*phasor_from_lifetime(80.0, 4.2 * (1.0 - efficiency)),
*phasor_from_lifetime(frequency, lifetime_quenched),
label='100% Donor in FRET',
fmt='k.',
)
plot.plot(
*phasor_from_lifetime(
80.0,
frequency,
lifetime=numpy.column_stack(
(
numpy.full(samples, 4.2), # donor-only lifetime
4.2 * (1.0 - efficiency), # donor lifetime with FRET
numpy.full(samples, lifetime), # donor-only lifetime
lifetime_quenched, # donor lifetime with FRET
numpy.full(samples, 1e9), # background with long lifetime
)
),
fraction=[0.1, 0.9, 0.1 / 1e9],
preexponential=True,
),
label='90% Donor in FRET',
fmt='o-',
label='90% Donor in FRET',
)
plot.show()

Expand All @@ -143,14 +185,14 @@
# Phase shift and demodulation of multi-component lifetimes can be calculated
# as a function of the excitation light frequency and fractional intensities:

frequency = numpy.logspace(-1, 4, 32)
fraction = numpy.array([[1, 0], [0.5, 0.5], [0, 1]])
frequencies = numpy.logspace(-1, 4, 32)
lifetimes = [4.0, 1.0]
fractions = numpy.array([[1, 0], [0.5, 0.5], [0, 1]])

plot_polar_frequency(
frequency,
*phasor_to_polar(
*phasor_from_lifetime(frequency, [3.9788735, 0.9947183], fraction)
),
frequencies,
*phasor_to_polar(*phasor_from_lifetime(frequencies, lifetimes, fractions)),
title='Multi-frequency plot',
)

# %%
Expand Down

0 comments on commit f4060ba

Please sign in to comment.