codexfluorescence-decay-instrument-convolution.py
fluorescence-decay-instrument-convolution.py
"""Plot a stipulated fluorescence decay, timing response and background.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
No experimental data; writes the matching PNG to the current directory.
"""
from math import erfc
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def model(t, tau=4.0, sigma=0.6):
"""Exact convolution of exp(-t/tau)H(t) with a unit-area Gaussian."""
intrinsic = np.exp(-np.maximum(t, 0)/tau)*(t >= 0)
response = np.exp(-0.5*(t/sigma)**2)/(sigma*np.sqrt(2*np.pi))
complement = np.array([erfc(float((sigma*sigma/tau-x)/(np.sqrt(2)*sigma))) for x in t])
convolved = 0.5*np.exp(sigma*sigma/(2*tau*tau)-t/tau)*complement
return intrinsic, response, convolved
def main():
plt.rcParams.update({'font.size': 11, 'font.family': 'DejaVu Sans'})
t = np.linspace(-2.5, 24, 2651)
intrinsic, response, convolved = model(t)
background = 0.025
fig, axs = plt.subplots(1, 2, figsize=(10, 4.5), dpi=130, facecolor='white')
axs[0].plot(t, intrinsic, '--', color='#18845b', label='Intrinsic decay')
axs[0].plot(t, convolved, color='#2460a7', lw=2, label='Decay ∗ timing response')
axs[0].plot(t, response/response.max(), ':', color='#7753a6', label='Response (unit peak)')
axs[0].set(xlim=(-2.5, 15), ylim=(-0.02, 1.1), ylabel='Common signal units', title='Finite timing broadens the onset')
axs[0].legend(loc='upper right', fontsize=9)
axs[1].semilogy(t, convolved, color='#2460a7', lw=2, label='Background removed')
axs[1].semilogy(t, convolved+background, color='#b65e0a', lw=2, label='With background')
axs[1].axhline(background, color='#777777', ls=':', label='Background level')
axs[1].set(xlim=(0, 24), ylim=(0.001, 1.1), ylabel='Common signal units (log scale)', title='Background flattens the tail')
axs[1].legend(loc='upper right', fontsize=9)
for ax in axs:
ax.set_xlabel('Delay from excitation reference (ns)')
ax.grid(alpha=0.2)
ax.spines[['top', 'right']].set_visible(False)
fig.suptitle('Synthetic model: lifetime 4 ns; Gaussian timing width σ = 0.6 ns', fontsize=14)
fig.text(0.5, 0.025, 'No measured data. Background = 0.025; response centre = 0 ns. Only response display is peak-normalized.',
ha='center', fontsize=9)
fig.subplots_adjust(left=0.075, right=0.98, top=0.82, bottom=0.19, wspace=0.28)
fig.savefig(Path(__file__).with_suffix('.png').name, facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()