Skip to content

FLIM Analysis

Fluorescence-lifetime-imaging utilities: file summaries, per-pixel sorting into decay histograms, instrument-response and decay-model generators, and an intensity-weighted-lifetime colourisation helper.

import photonscore.flim as flim

# Quick file summary
info = flim.info("measurement.photons")
print(info)

# Sort into a 256×256 image with per-pixel decay histograms
result = flim.sort(x, 0, 4096, 256, y, dt)
intensity = result.intensity()
tau = result.mean()

Sort

flim.sort.sort(x, x_min, x_max, x_bins, y, *args)

Sort photons into a 2-D grid of decay histograms.

Two calling conventions are supported:

  • sort(x, x_min, x_max, x_bins, y, dt) — uses the same range and bin count for the y-axis as for the x-axis.
  • sort(x, x_min, x_max, x_bins, y, y_min, y_max, y_bins, dt) — explicit y-axis range and bin count.

Parameters:

Name Type Description Default
x ndarray

Per-photon x-coordinates.

required
x_min float

Lower bound of the x-axis.

required
x_max float

Upper bound of the x-axis.

required
x_bins int

Number of x-axis bins.

required
y ndarray

Per-photon y-coordinates.

required
*args object

(dt,) or (y_min, y_max, y_bins, dt).

()

Returns:

Type Description
SortResult

class:SortResult — wraps the per-pixel decay histograms.

Source code in flim/sort.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def sort(
  x: np.ndarray,
  x_min: float,
  x_max: float,
  x_bins: int,
  y: np.ndarray,
  *args: object,
) -> "SortResult":
  """Sort photons into a 2-D grid of decay histograms.

  Two calling conventions are supported:

  - ``sort(x, x_min, x_max, x_bins, y, dt)`` — uses the same range and
    bin count for the y-axis as for the x-axis.
  - ``sort(x, x_min, x_max, x_bins, y, y_min, y_max, y_bins, dt)`` —
    explicit y-axis range and bin count.

  Args:
      x: Per-photon x-coordinates.
      x_min: Lower bound of the x-axis.
      x_max: Upper bound of the x-axis.
      x_bins: Number of x-axis bins.
      y: Per-photon y-coordinates.
      *args: ``(dt,)`` or ``(y_min, y_max, y_bins, dt)``.

  Returns:
      :class:`SortResult` — wraps the per-pixel decay histograms.
  """
  if len(args) == 1:
    y_min = x_min
    y_max = x_max
    y_bins = x_bins
    dt = args[0]
  elif len(args) == 4:
    y_min = args[0]
    y_max = args[1]
    y_bins = args[2]
    dt = args[3]
  else:
    raise ValueError("Invalid number of arguments")

  (counts, t) = _sort(x, x_min, x_max, x_bins, y, y_min, y_max, y_bins, dt)
  return SortResult(counts, t)

flim.sort.SortResult

Per-pixel decay histograms returned by :func:sort.

Attributes:

Name Type Description
counts

3-D NumPy array of shape (x_bins, y_bins, dt_bins).

dt

Bin edges of the delta-t axis (NumPy 1-D array).

Source code in flim/sort.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class SortResult:
  """Per-pixel decay histograms returned by :func:`sort`.

  Attributes:
      counts: 3-D NumPy array of shape ``(x_bins, y_bins, dt_bins)``.
      dt: Bin edges of the delta-t axis (NumPy 1-D array).
  """

  def __init__(self, counts: np.ndarray, dt: np.ndarray) -> None:
    self.counts = counts
    self.dt = dt

  def _mean_or_median(self, dt_range, f):
    if dt_range is None:
      dt_range = (0, 4096)
    return f(self.counts, self.dt, dt_range[0], dt_range[1])

  def mean(self, dt_range: tuple[int, int] | None = None) -> np.ndarray:
    """Per-pixel mean arrival time within `dt_range` (default: full range)."""
    return self._mean_or_median(dt_range, _mean)

  def median(self, dt_range: tuple[int, int] | None = None) -> np.ndarray:
    """Per-pixel median arrival time within `dt_range`."""
    return self._mean_or_median(dt_range, _median)

  def intensity(self, dt_range: tuple[int, int] | None = None) -> np.ndarray:
    """Per-pixel photon count within `dt_range`."""
    return self._mean_or_median(dt_range, _intensity)

intensity(dt_range=None)

Per-pixel photon count within dt_range.

Source code in flim/sort.py
43
44
45
def intensity(self, dt_range: tuple[int, int] | None = None) -> np.ndarray:
  """Per-pixel photon count within `dt_range`."""
  return self._mean_or_median(dt_range, _intensity)

mean(dt_range=None)

Per-pixel mean arrival time within dt_range (default: full range).

Source code in flim/sort.py
35
36
37
def mean(self, dt_range: tuple[int, int] | None = None) -> np.ndarray:
  """Per-pixel mean arrival time within `dt_range` (default: full range)."""
  return self._mean_or_median(dt_range, _mean)

median(dt_range=None)

Per-pixel median arrival time within dt_range.

Source code in flim/sort.py
39
40
41
def median(self, dt_range: tuple[int, int] | None = None) -> np.ndarray:
  """Per-pixel median arrival time within `dt_range`."""
  return self._mean_or_median(dt_range, _median)

Convolution

flim.convolve.convolve(irf, irf_shift, tau, tau_ref=None, channels=None)

Convolve irf with one or more exponential decays.

Parameters:

Name Type Description Default
irf ndarray

Instrument-response function as a 1-D NumPy array.

required
irf_shift float

Sub-channel shift applied to the IRF before convolution.

required
tau float | ndarray

Exponential decay constant(s). Scalar or sequence — when a sequence is passed, the result is one decay per element.

required
tau_ref float | None

Optional reference / IRF lifetime to deconvolve from tau (set to None or 0 to skip).

None
channels int | None

Length of the output. Defaults to len(irf).

None

Returns:

Type Description
ndarray

NumPy array of the convolved decay(s).

Source code in flim/convolve.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def convolve(
  irf: "np.ndarray",
  irf_shift: float,
  tau: "float | np.ndarray",
  tau_ref: float | None = None,
  channels: int | None = None,
) -> "np.ndarray":
  """Convolve `irf` with one or more exponential decays.

  Args:
      irf: Instrument-response function as a 1-D NumPy array.
      irf_shift: Sub-channel shift applied to the IRF before
          convolution.
      tau: Exponential decay constant(s). Scalar or sequence — when a
          sequence is passed, the result is one decay per element.
      tau_ref: Optional reference / IRF lifetime to deconvolve from
          `tau` (set to ``None`` or ``0`` to skip).
      channels: Length of the output. Defaults to ``len(irf)``.

  Returns:
      NumPy array of the convolved decay(s).
  """
  tau_ref = 0.0 if tau_ref is None else math.fabs(tau_ref)
  channels = len(irf) if channels is None else channels
  return _convolve(irf, irf_shift, tau, tau_ref, channels)

Synthetic models

flim.gaussian_irf.gaussian_irf(mu, fwhm, channels=1000)

Generate a unit-area Gaussian IRF.

Parameters:

Name Type Description Default
mu float

Centre of the Gaussian, in channels.

required
fwhm float

Full-width at half-maximum, in channels.

required
channels int

Length of the output array.

1000

Returns:

Type Description
ndarray

NumPy array of length channels.

Source code in flim/gaussian_irf.py
 8
 9
10
11
12
13
14
15
16
17
18
19
def gaussian_irf(mu: float, fwhm: float, channels: int = 1000) -> "np.ndarray":
  """Generate a unit-area Gaussian IRF.

  Args:
      mu: Centre of the Gaussian, in channels.
      fwhm: Full-width at half-maximum, in channels.
      channels: Length of the output array.

  Returns:
      NumPy array of length `channels`.
  """
  return _gaussian_irf(channels, mu, fwhm, channels)

flim.gaussian_decay.gaussian_decay(mu, fwhm, tau, channels=1000)

Generate the decay produced by convolving a Gaussian IRF with a single exponential.

Parameters:

Name Type Description Default
mu float

Centre of the Gaussian instrument response, in channels.

required
fwhm float

Full-width at half-maximum of the IRF, in channels.

required
tau float

Exponential decay constant, in channels.

required
channels int

Length of the output array.

1000

Returns:

Type Description
ndarray

NumPy array of length channels — the unit-normalised decay.

Source code in flim/gaussian_decay.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def gaussian_decay(mu: float, fwhm: float, tau: float, channels: int = 1000) -> "np.ndarray":
  """Generate the decay produced by convolving a Gaussian IRF with a single exponential.

  Args:
      mu: Centre of the Gaussian instrument response, in channels.
      fwhm: Full-width at half-maximum of the IRF, in channels.
      tau: Exponential decay constant, in channels.
      channels: Length of the output array.

  Returns:
      NumPy array of length `channels` — the unit-normalised decay.
  """
  return _gaussian_decay(channels, mu, fwhm, tau)

Colourisation

flim.iwtau.iwtau(n, tau, pal, n_range=None, tau_range=None)

Render an intensity-weighted lifetime RGB image.

Parameters:

Name Type Description Default
n ndarray

Count image (2-D NumPy array).

required
tau ndarray

Lifetime image, same shape as n.

required
pal ndarray

Palette of shape (n_bins, tau_bins, 3) — RGB lookup indexed by clipped count and lifetime.

required
n_range list[float] | None

Clip range for n. Defaults to :func:iwtau_n_range(n).

None
tau_range list[float] | None

Clip range for tau. Defaults to :func:iwtau_tau_range(tau).

None

Returns:

Type Description
ndarray

RGB image of shape (*n.shape, 3).

Source code in flim/iwtau.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def iwtau(
  n: np.ndarray,
  tau: np.ndarray,
  pal: np.ndarray,
  n_range: list[float] | None = None,
  tau_range: list[float] | None = None,
) -> np.ndarray:
  """Render an intensity-weighted lifetime RGB image.

  Args:
      n: Count image (2-D NumPy array).
      tau: Lifetime image, same shape as `n`.
      pal: Palette of shape ``(n_bins, tau_bins, 3)`` — RGB lookup
          indexed by clipped count and lifetime.
      n_range: Clip range for `n`. Defaults to
          :func:`iwtau_n_range(n)`.
      tau_range: Clip range for `tau`. Defaults to
          :func:`iwtau_tau_range(tau)`.

  Returns:
      RGB image of shape ``(*n.shape, 3)``.
  """
  if n_range is None:
    n_range = iwtau_n_range(n)
  if tau_range is None:
    tau_range = iwtau_tau_range(tau)
  cn = np.clip(n, n_range[0], n_range[1])
  ct = np.clip(tau, tau_range[0], tau_range[1])
  a_n = pal.shape[0] / (n_range[1] - n_range[0])
  a_t = pal.shape[1] / (tau_range[1] - tau_range[0])
  i = np.int32((np.double(ct) - tau_range[0]) * a_t)
  j = np.int32((np.double(cn) - n_range[0]) * a_n)
  i = np.clip(i, 0, pal.shape[0] - 1)
  j = np.clip(j, 0, pal.shape[1] - 1)
  return pal[i, j, :]

flim.iwtau.iwtau_n_range(n)

Suggest an intensity range from a count image.

Lower bound is 0, upper bound is 0.9 * max(n).

Source code in flim/iwtau.py
25
26
27
28
29
30
def iwtau_n_range(n: np.ndarray) -> list[float]:
  """Suggest an intensity range from a count image.

  Lower bound is ``0``, upper bound is ``0.9 * max(n)``.
  """
  return [0, np.max(n) * 0.9]

flim.iwtau.iwtau_tau_range(tau)

Suggest a reasonable lifetime range for iwtau from a tau image.

Returns the 10th- and 90th-percentile of strictly positive entries in tau — these values usually correspond to the bulk of the lifetime distribution and produce a stable colour mapping.

Source code in flim/iwtau.py
11
12
13
14
15
16
17
18
19
20
21
22
def iwtau_tau_range(tau: np.ndarray) -> list[float]:
  """Suggest a reasonable lifetime range for `iwtau` from a tau image.

  Returns the 10th- and 90th-percentile of strictly positive entries
  in `tau` — these values usually correspond to the bulk of the
  lifetime distribution and produce a stable colour mapping.
  """
  t = tau[tau > 0]
  t = np.sort(t[:])
  n = len(t)
  r = np.int32([n * 0.1, n * 0.9])
  return [t[r[0]], t[r[1]]]

File summary

flim.info.info(path)

Open path and return an :class:Info summary.

Reads the photon coordinate streams to find the total count and the millisecond marker stream to derive the duration.

Source code in flim/info.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def info(path):
  """Open `path` and return an :class:`Info` summary.

  Reads the photon coordinate streams to find the total count and the
  millisecond marker stream to derive the duration.
  """
  v = Vault(path)
  return Info(
    filename = path,
    total_counts = min(
      v.data.photons.x.shape[0],
      v.data.photons.y.shape[0],
      v.data.photons.dt.shape[0],
    ),
    duration = v.data.photons.ms.shape[0] / 1000
  )

flim.info.Info

Lightweight description of a Photonscore data file.

Attributes:

Name Type Description
filename

Path to the file.

duration

Acquisition duration in seconds.

total_counts

Total number of photons recorded.

Source code in flim/info.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Info:
  """Lightweight description of a Photonscore data file.

  Attributes:
      filename: Path to the file.
      duration: Acquisition duration in seconds.
      total_counts: Total number of photons recorded.
  """

  def __init__(self, filename, duration, total_counts):
    self.filename = filename
    self.duration = duration
    self.total_counts = total_counts

  def __repr__(self):
    return "\n".join(
      [
        "filename = '{}'".format(self.filename),
        "duration = {} seconds".format(self.duration),
        "total_counts = {}".format(self.total_counts),
      ]
    )