.. sectionauthor:: Eric Schmidt <eric.schmidt@swabianinstruments.com>

****************************************
Fluorescence Correlation Spectroscopy
****************************************

Fluorescence correlation spectroscopy (FCS) is a technique that analyzes fluorescence fluctuations at the single-molecule level
to infer fluorophore concentration, diffusion, and kinetic parameters underlying the signal.
A laser excites a small number of fluorophores within a tiny observation volume. As molecules diffuse in and out,
the intensity fluctuates primarily due to Brownian motion and, in some cases, photophysical processes such as triplet blinking.
In the single-photon regime, the detector produces individual photon events, and the Time Tagger
precisely time-stamps their arrival times.
Using these timestamps, the Time Tagger software and API enable computation of the correlation function
:math:`G(\tau)`, from which particle number, diffusion coefficients, and reaction or conformational rates can be estimated.

.. image:: imgs/FCS-sketch-intensity.svg
    :width: 80 %
    :align: center


This tutorial shows how to perform FCS and advanced variants like fluorescence cross-correlation spectroscopy (FCCS),
pulsed interleaved excitation (PIE), and raster image correlation spectroscopy (RICS), with the Swabian Instruments Time Tagger
for data acquisition and live analysis. After a brief recap, the tutorial shows how to configure channels and triggers, align delays,
acquire single-photon timestamps, and compute the correlation function :math:`G(\tau)` (auto-/cross-correlation)
using multiple-:math:`\tau` binning.

The tutorial covers:

- **FCS**: set up a single-detector, continuous-wave (CW) workflow, run autocorrelation on the detector channel,
  monitor :math:`G(\tau)`, and visualize traces.
- **FCCS**: acquire dual-color streams on two detectors/lasers, compute cross-correlation,
  and visualize traces for downstream interaction analysis.
- **PIE**: drive interleaved excitation pulses (via  `Pulse Streamer <https://www.swabianinstruments.com/pulse-streamer-8-2/>`_),
  define time-gating windows, and suppress spectral cross-talk while retaining lifetime information.
- **RICS**: integrate a scanning stage, map timestamps to pixels/lines, and compute spatially resolved correlation maps.


Microscope Setup
=================

FCS can be performed on microscope setups such as confocal or two-photon excitation, which create small,
well defined observation volumes (typically in the order of 1 :math:`\mu m^3`) to capture low fluorophore numbers (0.1-100).
The observation volume is typically modeled as a 3D Gaussian ellipsoid.
The simplest setup contains one laser, one single-photon detector, the optical elements and the Time Tagger for data acquisition,
as well as the fluorescent sample (A).
The system can be expanded to include multiple lasers and detectors (B), a `Pulse Streamer <https://www.swabianinstruments.com/pulse-streamer-8-2/>`_
as the laser driver for pulsed measurements, including PIE (C), and a scanning stage for RICS (D).

.. image:: imgs/FCS-Setup.svg
    :width: 90 %
    :align: center


Single Photon Correlation
===========================

In FCS, relevant dynamics, such as particle diffusion and reaction kinetics, span timescales from microseconds to seconds.
Building a correlation function from time-stamped single-photon data, therefore,
requires an algorithm that is both memory-wise and computationally efficient across several lag times decades.

A naive approach, using a :ref:`api.Histogram` with uniform time bins, becomes impractical when trying to cover
a broad range of lag times :math:`\tau` while preserving high temporal resolution.
This is where the multiple-tau approach comes in. Instead of using equally spaced bins, this algorithm uses logarithmic
binning: the bin width increases with :math:`\tau`, allowing coverage of large  timescales without excessive memory or CPU usage.

.. image:: imgs/FCS-sketch-correlation-computation.svg
    :width: 90 %
    :align: center

The sketch above illustrates a typical autocorrelation (CH 1 = CH 2) algorithm.
On the left is the intensity-trace view, where the signal is shifted by :math:`\tau` and multiplied by itself.
On the right is the event-based (single-photon) view.

In the event-based autocorrelation, each photon arrival time serves as a reference (starting with 1 in step 1 out of *N*).
From this reference point, the lag times :math:`\tau_0, \tau_1, \tau_2, ...` are discretized according to the logarithmic binning.
For each lag interval, the number of photons is counted and stored in the corresponding bin. By accumulating data from all *N* reference points
(Step 1, Step 2, …, Step *N*) and normalizing for bin width and the number of available photon pairs (not shown) at each 
:math:`\tau`, one obtains the final autocorrelation curve. For a detailed description of the algorithm, 
please refer to the publication `T. Laurence, S. Fore, and T. Huser, Opt. Lett.  31, 829-831 (2006) <https://doi.org/10.1364/OL.31.000829>`_.

.. plot::
    :align: center

    import FCS_plots 
    FCS_plots.FCS_Regions()    
         
        
The correlation curve :math:`G(\tau) = g^{(2)}(\tau) - 1` shows three characteristic regions.
Region I and II capture antibunching and triplet state dynamics at very short time scales
and are commonly omitted in FCS plots (gray region).
Region III reflects the diffusion of fluorophores through the observation volume and is the region
of interest for further FCS analyses.
The data from this region is typically fitted with a model that corresponds to the experimental expectation
(3D/2D/anomalous diffusion, triplet correction, etc.).
The model then reveals the sample's parameters.
In practice, the overall curve shape and the value of :math:`G(\tau)` at short-:math:`\tau` (e.g., :math:`\mu s`)
are informative about diffusion and particle number.

All regions can be computed simultaneously using the Time Tagger library, as will be shown next.


Time Tagger configuration
==============================

The Time Tagger library provides several measurement classes designed for microscopy.
We start by defining channel assignments and then add the necessary configuration.

.. code-block:: python

    DETECTOR_1_CH = 1  # Rising edge on input 1
    DETECTOR_2_CH = 2  # Optional: FCCS
    LASER_CH = 3       # Optional: Lifetime


Connect to the Time Tagger:

.. code-block:: python

    from Swabian import TimeTagger
    tt = TimeTagger.createTimeTagger()


The Time Tagger hardware allows you to specify an individual trigger level voltage for each input channel.
This trigger level applies to both rising and falling edges of an input pulse.
Whenever the signal crosses this threshold, the Time Tagger registers an event and stores its timestamp.
It is often convenient to set the trigger level to half the signal amplitude.
For example, if your laser sync output provides pulses of 0.2 V amplitude, set the trigger level to 0.1 V on this channel.
The default trigger level is 0.5 V.

.. code-block:: python

    tt.setTriggerLevel(DETECTOR_1_CH, 0.5)
    tt.setTriggerLevel(DETECTOR_2_CH, 0.4)
    tt.setTriggerLevel(LASER_CH, 0.1)

The basic hardware setup is complete; next we define the measurements.

Continuous Wave Laser
=====================

Building the autocorrelation function with the multiple-:math:`\tau` approach is straightforward in the API.
First, define your experiment parameters and create the :ref:`api.HistogramLogBins` measurement.

.. code-block:: python

    exp_start = -7 # 100 nanoseconds
    exp_stop = 0   # 1 second
    n_bins = 150
    hist_log_bins = TimeTagger.HistogramLogBins(tagger=tt,
                                                click_channel=DETECTOR_1_CH,
                                                start_channel= DETECTOR_1_CH,
                                                exp_start=exp_start,
                                                exp_stop=exp_stop,
                                                n_bins=n_bins)

By default, the measurement starts immediately after creation,
begins acquiring time tags and computing the autocorrelation on channel ``DETECTOR_1_CH``.
Acquisition can be controlled via the :ref:`common methods <common-methods>` shared by all measurement classes.
To run the measurement for a fixed duration, use :cpp:func:`~IteratorBase::startFor`:

.. code-block:: python

    DURATION = 60e12 # one minute
    hist_log_bins.startFor(capture_duration=DURATION)

While the measurement is running, you can poll :cpp:func:`~IteratorBase::isRunning`
and update a live plot of :math:`G(\tau)`:

.. code-block:: python

    import matplotlib.pyplot as plt

    while hist_log_bins.isRunning():
        g2 = hist_log_bins.getDataObject().getG2()
        tau = hist_log_bins.getBinEdges() # shape: (n_bins+1)
        plt.plot(tau[1:], g2)
        plt.pause(0.1)

You can fit the measured :math:`G(\tau)` on the fly with a model suited to your sample and optical parameters.

.. _FCCS:

Fluorescence Cross-Correlation Spectroscopy
===========================================


When studying molecular interactions between two or more molecules, fluorescence cross-correlation spectroscopy (FCCS)
becomes a powerful extension of FCS.
In practice, it is useful to acquire the two autocorrelations alongside the cross trace:
they provide per-channel quality checks and a reference when interpreting the cross-correlation.
With the Time Tagger API, create three :ref:`api.HistogramLogBins` measurements:
autocorrelation on detector 1 (``start_channel = click_channel = DETECTOR_1_CH``),
autocorrelation on detector 2 (``start_channel = click_channel = DETECTOR_2_CH``),
and the FCCS cross-correlation (e.g., ``start_channel = DETECTOR_2_CH``, ``click_channel = DETECTOR_1_CH``).
Start them together with the :ref:`api.SynchronizedMeasurements` helper class so they share the same acquisition window
and can be directly compared.


.. code-block:: python

    sm = TimeTagger.SynchronizedMeasurements(tt)
    sync_proxy = sm.getTagger()

    hist_log_bins_11 = TimeTagger.HistogramLogBins(tagger=sync_proxy,
                                                click_channel=DETECTOR_1_CH,
                                                start_channel= DETECTOR_1_CH,
                                                exp_start=exp_start,
                                                exp_stop=exp_stop,
                                                n_bins=n_bins)

    hist_log_bins_22 = TimeTagger.HistogramLogBins(tagger=sync_proxy,
                                                click_channel=DETECTOR_2_CH,
                                                start_channel= DETECTOR_2_CH,
                                                exp_start=exp_start,
                                                exp_stop=exp_stop,
                                                n_bins=n_bins)

    hist_log_bins_12 = TimeTagger.HistogramLogBins(tagger=sync_proxy,
                                                click_channel=DETECTOR_1_CH,
                                                start_channel= DETECTOR_2_CH,
                                                exp_start=exp_start,
                                                exp_stop=exp_stop,
                                                n_bins=n_bins)

    sm.startFor(DURATION)
    sm.waitUntilFinished()


CW excitation can photo-bleach fluorophores or drive triplet-state saturation, both of which degrade signal quality.
Background from scattered light also reduces the signal-to-noise ratio (SNR).
Pulsed excitation helps mitigate these effects and enables timing-based strategies (e.g., lifetime, gating, PIE).


Pulsed Laser
============

Pulsed lasers offer several important advantages for FCS.
The first one is that well-defined laser pulses provide access to fluorescence lifetime information
of the sample. More details on this topic can be found in the
`FLIM application page <https://www.swabianinstruments.com/applications/fluorescence-lifetime-imaging-flim/>`_
or in the :doc:`/tutorials/ConfocalMicroscope` tutorial.

A second advantage is control over excitation timing: the pulse sequence can be configured
to minimize bleaching and to allow recovery of fluorophores from non-radiative triplet states.
To achieve this, the pulse period is typically chosen longer than the characteristic relaxation times,
which in practice corresponds to repetition rates of about 80-100 MHz (12.5-10 ns) or lower.
The pulse duration is adjusted so that, on average, approximately one emission event per pulse is expected.
In this regime, the detected click rate is usually about 1% of the laser repetition rate,
(e.g., 1 MHz on the detector for a 100 MHz laser).
Under such conditions, the laser trigger generates roughly 80-100 MTags/s, while each detector
records 0.8-1 MTags/s; the overall stream can meet or exceed the sustained transfer limit
from the Time Tagger to the PC (|ttu-tagrate-usb|).

To reduce the bandwidth without losing physical information, the :doc:`/InDepthGuides/ConditionalFilter` can be used.
With this hardware setting enabled, the Time Tagger transmits only the next tag on a high-rate channel
after a tag on a low-rate channel. For pulsed excitation, it is useful to delay the laser channel by one laser period
so that the photon-originating laser event is forwarded.
This can be achieved with the :cpp:func:`~TimeTaggerSource::setDelayHardware` feature.
After applying the Conditional Filter, the laser can be
“pushed back” in software with :cpp:func:`~TimeTaggerSource::setDelaySoftware`.
See the in-depth guide :doc:`/InDepthGuides/ConditionalFilter` for details.


.. code-block:: python

    laser_frequency = 100e6 # 100 MHz
    laser_period = 1/laser_frequency * 1e12 # picoseconds

    tt.setDelayHardware(LASER_CH, int(laser_period))   # Delay is specified in picoseconds
    tt.setDelayHardware(DETECTOR_1_CH, 0) # Default value is 0

    tt.setConditionalFilter(trigger=[DETECTOR_1_CH], filtered=[LASER_CH])
    tt.setDelaySoftware(LASER_CH, -int(laser_period))

This approach extends to multiple lasers and detectors.

.. note::

    If fixed delays exist between laser and detector (e.g., cable length or device latency),
    compensate them first with per-channel hardware delays via :cpp:func:`~TimeTaggerSource::setDelayHardware`.


On pulsed-laser setups, autocorrelation often benefits from aligning bin edges to integer multiples of the laser period.
This is supported by :ref:`api.HistogramCustomBins` and not by :ref:`api.HistogramLogBins`.
In the following example, we run these two measurements in parallel by using :ref:`api.SynchronizedMeasurements` and
compare the results. The only difference between the two analyses is the time binning: the custom
bin edges are aligned precisely to integer multiples of the laser period,
while :ref:`api.HistogramLogBins` uses a purely logarithmic grid.

.. code-block:: python


    hist_log_bins = TimeTagger.HistogramLogBins(sync_proxy, DETECTOR_1_CH, DETECTOR_1_CH,
                                                exp_start, exp_stop, n_bins)

    # Example of custom bin edges starting from logarithmic grid (picosecond units)
    log_bin_edges = np.logspace(exp_start, exp_stop, num=n_bins + 1, base=10.0, dtype=np.float64) * 1e12

    custom_bin_edges = np.round(log_bin_edges / laser_period) * laser_period
    custom_bin_edges = np.unique(custom_bin_edges)  # remove duplicates

    hist_cust_bins = TimeTagger.HistogramCustomBins(tagger=sync_proxy,
                                                    click_channel=DETECTOR_1_CH,
                                                    start_channel=DETECTOR_1_CH,
                                                    binedges=custom_bin_edges)

The effect of aligning bin edges to the laser period is most evident at short lag times.
When a purely logarithmic grid is slightly misaligned with the laser pulse train, especially below 10 microseconds,
:ref:`api.HistogramLogBins` can produce a spiky correlation trace.
By contrast, :ref:`api.HistogramCustomBins` uses period-aligned edges, yielding a smoother correlation curve at short timescales.


.. plot::
    :align: center

    import FCS_plots 
    FCS_plots.LogBins_CustomBins_comparison()    
         
        
As a reminder, for extended runs, the laser stability can affect the analysis.
A practical mitigation is to lock the Time Tagger to the laser via :cpp:func:`~TimeTaggerSource::setReferenceClock`,
keeping detector timestamps phase-aligned to the pulse train.
When operated with the Reference Clock, the system also supports the Conditional Filter functionality,
as discussed in the in-depth guide :doc:`/InDepthGuides/ReferenceClock`.


Spectral Overlap and Pulsed Interleaved Excitation
==================================================

FCCS with two or more lasers and detectors can be set up as described in the section :ref:`FCCS`.
Additionally, you can measure fluorescence lifetime on the same time tags stream in parallel.
This can be achieved by using the :ref:`api.SynchronizedMeasurements` class together with the :ref:`api.Histogram` measurement.

.. code-block:: python

    lifetime_binwidth = 100 # ps
    n_bins = 250

    hist_cust_bins = TimeTagger.HistogramCustomBins(tagger=sync_proxy,
                                                    click_channel=DETECTOR_1_CH,
                                                    start_channel=DETECTOR_2_CH,
                                                    binedges=custom_bin_edges)

    hist_1 = TimeTagger.Histogram(tagger=sync_proxy,
                                  click_channel=DETECTOR_1_CH,
                                  start_channel=LASER_CH,
                                  binwidth=lifetime_binwidth,
                                  n_bins=n_bins)

    hist_2 = TimeTagger.Histogram(tagger=sync_proxy,
                                  click_channel=DETECTOR_2_CH,
                                  start_channel=LASER_CH,
                                  binwidth=lifetime_binwidth,
                                  n_bins=n_bins)

Dual-color FCCS setups may introduce spectral cross-talk between fluorophores.
This can be identified by exciting one laser at a time and inspecting the intensity trace
or the lifetime histogram of both detectors.
In Step 1 of the sketch below, the blue laser excites both the blue and the red fluorophore (bleed-through).
In the next step, the red laser excites the red fluorophore in the red detector channel as expected.

.. image:: imgs/FCS-bleedthrough.svg
    :width: 90 %
    :align: center

A practical way to suppress spectral overlap is pulsed interleaved excitation (PIE). PIE is typically performed in two steps:

1. Interleave the lasers in time: laser 1 is triggered, a defined delay (often comparable to the fluorescence lifetime) elapses,
   and laser 2 is triggered. This temporal separation allows each detected photon to be associated with its excitation laser.

2. Gate the detector channels: short time windows are applied so that each detector accepts photons only in the interval
   that follows its own laser pulse. For example, the red detector channel is open immediately after the red laser pulse;
   the blue detector channel is open immediately after the blue laser pulse.
   This gating ensures that each detected photon is attributed to the correct excitation source.

An example implementation, using the `Pulse Streamer <https://www.swabianinstruments.com/pulse-streamer-8-2/>`_ 
to drive the lasers and the Time Tagger to define the gates in software, is shown below.
The laser period and pulse duration are experiment-dependent. Detector channels are gated with
:cpp:class:`DelayedChannel` (to define the stop edge) and :cpp:class:`GatedChannel`.

.. code-block:: python

    from pulsestreamer import PulseStreamer
    ps = PulseStreamer('pulsestreamer')

    LASER_blue = 1
    LASER_red = 2
    DET_blue = 3
    DET_red = 4
    ... # configure trigger levels, hardware delays, conditional filters

    pulse_pattern_blue = [(2, 1),(24,0)] # 2 ns HIGH, 24 ns LOW
    pulse_pattern_red = [(13,0),(2, 1),(11,0)] # 13 ns low (pulse period), 2 ns HIGH, 11 ns LOW

    seq = ps.createSequence()
    seq.setDigital(0, pulse_pattern_blue) # connect PS channel 0 to blue laser
    seq.setDigital(1, pulse_pattern_red) # connect PS channel 1 to red laser
    ps.stream(seq) # start the pulse stream

    # define gates using delayed channels
    pulse_period = 13_000 # ps
    blueStop = TimeTagger.DelayedChannel(tt, LASER_blue, pulse_period)
    redStop = TimeTagger.DelayedChannel(tt, LASER_red, pulse_period)

    blueGate = TimeTagger.GatedChannel(tt, [DET_blue], LASER_blue, blueStop.getChannel())
    redGate = TimeTagger.GatedChannel(tt, [DET_red], LASER_red, redStop.getChannel())

    red_channel = redGate.getChannel()
    blue_channel = blueGate.getChannel()
    ... # use red_channel and blue_channel for FCS, FCCS, lifetime analysis, etc.
        # instead of the original DET_blue and DET_red channels

PIE separates the blue and red fluorescence signals with minimal spectral cross-talk,
producing per-excitation FCS/FCCS traces. Gating operates in real time, so acquisition and analysis remain live.
The same approach can be combined with scanning to obtain spatially resolved FCS maps across multiple pixels.