Fluorescence Event Neural Network for Evaluating and Classifying bursts
FENNEC is a neural network that classifies single-molecule FRET bursts directly from raw photon timestamps. It performs three simultaneous binary classification tasks for each burst:
- Dynamics: did the FRET efficiency change during the burst (dynamic) or remain constant (static)?
- Bleaching: did the acceptor photobleach during the burst?
- Blinking: did the acceptor undergo triplet blinking during the burst?
The model is trained on simulated bursts generated with parameters calibrated from experimental data.
We're pleased to release FENNEC to the community, and we're excited to see how people put it to use!
- Bob and Joel
git clone https://github.com/jacrossley/FENNEC.git
cd FENNEC
conda env create -f environment.yml # creates the 'fennec' environment
conda activate fennec
pip install -e .# 1. Calibrate simulation parameters to your data
python scripts/calibrate.py data/myfile.hdf5
# 2. Train a model
python scripts/train.py
# 3. Classify experimental data
python scripts/infer.py --file data/myfile.hdf5Note: You don't need to train your own model to get started. A pre-trained model is provided, so you can skip straight to inference and run it on your own data file:
python scripts/infer.py --file data/myfile.hdf5Before running inference, edit configs/instrument_config.toml to match your own instrument excitation periods and the detector channels used for donor and acceptor photons.
FENNEC uses two TOML files and a calibration JSON:
- configs/train_config.toml contains simulation, training, calibration, and classification-threshold settings.
- configs/instrument_config.toml contains correction factors, excitation periods, and the detector channels used for donor and acceptor photons.
- The calibration JSON is generated from experimental data by
scripts/calibrate.py.
Photon rate and burst duration distributions, background rates, and rate
correlation in [simulation] are reference examples. These values, together
with the burst intensity profiles, come from the selected calibration JSON. By
default, [calibration].source = "latest" selects the latest JSON in
data/calibration/; source can also point to a specific file.
To use a different training configuration:
python scripts/train.py --config configs/my_experiment.tomlCalibration uses the instrument configuration together with the burst search settings in the training configuration. Alternative files can be supplied with:
python scripts/calibrate.py data/myfile.hdf5 \
--instrument-config configs/my_instrument.toml \
--train-config configs/my_experiment.tomlEach training run is saved under trained_models/ with its model weights,
metadata, training configuration, and calibration JSON. To select a run for
inference:
python scripts/infer.py --file data/myfile.hdf5 \
--model trained_models/fennec_YYYYMMDD_HHMMSS/ \
--instrument-config configs/my_instrument.tomlIf --model is omitted, the latest run is used. Inference reads the model and
burst search settings and classification thresholds saved with that run.
The model takes a sequence of up to 1000 photons per example. Each photon is represented by three features:
[log1p(time_us), excitation, emission]
Input shape:
(batch, max_photons=1000, 3)
The model uses a WaveNet-inspired gated dilated convolutional neural network to process each photon sequence. A first linear layer projects each photon from 3 features to 64 channels:
Linear(3 → 64)
The sequence then passes through 9 gated dilated convolution blocks with dilations:
1, 2, 4, 8, 16, 32, 64, 128, 256
Each block computes:
signal = tanh(Conv1d(x, 64 → 64, kernel_size=3, dilation=d))
gate = sigmoid(Conv1d(x, 64 → 64, kernel_size=3, dilation=d))
h = signal × gate # gated block output
skip += Conv1d(h, 64 → 128, kernel_size=1)
x = Conv1d(h, 64 → 64, kernel_size=1) + x
One convolution produces a candidate activation using tanh, while a second
produces gate values between 0 and 1 using sigmoid. Their element-wise
product, h, is the gated activation. The residual stream, x, remains at 64
channels throughout all nine blocks. Each block sends h through two separate
1×1 convolutional projections:
-
Residual path
A 1×1 convolution projectshto 64 channels. The result is added toxand passed to the next block. -
Skip path
A separate 1×1 convolution projectshto 128 channels and adds it to a running sum.
The nine 128-channel skip projections are summed and passed to the output head. This lets the final prediction use features from every dilation scale, from local photon patterns at small dilations to longer-range patterns at large dilations.
After the 9 blocks, the accumulated skip features are passed through a small convolutional output head:
ReLU
Conv1d(128 → 128, kernel_size=1)
ReLU
Conv1d(128 → 128, kernel_size=1)
This output head mixes the skip features collected from all dilation scales. The model then applies masked average pooling across the photon positions, excluding padding, to produce a single 128-dimensional representation for each burst.
The pooled 128-dimensional representation is projected into a shared 256-dimensional embedding:
Linear(128 → 256)
ReLU
Finally, three independent linear heads predict the target parameters:
shared embedding: 256
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
Linear(256 → 2) Linear(256 → 2) Linear(256 → 2)
Dynamics Bleaching Blinking
The model has approximately 400k parameters.
The receptive field is 1023 photons, covering the full input sequence of up to 1000 photons.
Here, B is the batch size (256 is the default). The number 1000 is the padded sequence length, set by the max_photons parameter in configs/train_config.toml. Each burst is represented as up to 1000 photon positions, with padding used for shorter bursts.
Input photons (B, 1000, 3)
Mask (B, 1000)
Linear projection (B, 1000, 3) → (B, 64, 1000)
9 gated dilated blocks:
hᵢ = tanh(conv_dᵢ(xᵢ)) × sigmoid(conv_dᵢ(xᵢ))
xᵢ₊₁ = xᵢ + residual_projection(hᵢ) (B, 64, 1000)
sᵢ = skip_projection(hᵢ) (B, 128, 1000)
Sum skip outputs across blocks (B, 128, 1000)
Output convolutions (B, 128, 1000)
Masked average over photons (B, 128)
Shared embedding (B, 256)
Three 2-output heads 3 × (B, 2)
If you use this code or data, please cite the associated publication. FENNEC uses FRETBursts for background estimation and dual-channel burst search (DCBS), so please also cite:
- FRETBursts software: Ingargiola, A., Lerner, E., Chung, S., Weiss, S. & Michalet, X. (2016). FRETBursts: An Open Source Toolkit for Analysis of Freely-Diffusing Single-Molecule FRET. PLOS ONE, 11(8), e0160716. doi:10.1371/journal.pone.0160716
- Original DCBS method: Nir, E. et al. (2006). Shot-Noise Limited Single-Molecule FRET Histograms: Comparison between Theory and Experiments. Journal of Physical Chemistry B, 110(44), 22103–22124. doi:10.1021/jp063483n
