An OpenScan device module that adapts a Swabian Instruments Time Tagger as a detector for laser-scanning microscopy TCSPC (time-correlated single-photon counting) acquisitions, usable from Micro-Manager via OpenScan's LSM device.
The module reads raw event tags from the Time Tagger (sync, photon, and line clock channels), correlates photons against sync pulses, bins them per pixel using a libtcspc processing pipeline, and delivers each frame to OpenScanLib as a conventional 16-bit image.
src/TimeTagger.cpp,src/TimeTaggerSettings.cpp,src/TimeTaggerPrivate.h— theOScDev_DeviceImpldevice module itself: device lifecycle (Open/Close/Arm/Start/Stop/IsRunning/Wait) and the settings described below.src/EventPipeline.cpp/.h— the libtcspc processing graph: decodes raw tags, pairs sync/photon detections, derives per-pixel timing windows from the line clock, bins photons into per-pixel histograms, and delivers frames.src/fake_timetagger/— a from-scratch, header-only fake of the vendor Time Tagger C++ SDK, used forsimulate=truebuilds (see below). Its goal is to be a faithful stand-in: code written against it should build and run unmodified against the real SDK headers. It also synthesizes plausible sync/photon/line-clock activity so the pipeline has something to process without real hardware attached.tools/dump_tags.py— decodes a raw Time Tagger tag dump (the format written by the SDK'sDumpmeasurement, and by this module's own "Save Raw Data" setting) into human-readable text.
Building this device module requires Meson (>=1.9.0) and Ninja, and an MSVC or
clang-cl toolchain (this project is Windows-only, matching the Time Tagger
SDK). Building should be done on a Visual Studio developer command prompt (or after running
vcvars64.bat).
By default the module builds against a real Time Tagger and the vendor SDK.
If the vendor SDK isn't installed yet, download and run the Windows installer from Swabian Instruments' installation guide before building — this installs the headers and libraries the build links against, along with the USB drivers and license/runtime components needed to actually run against hardware.
The build locates the installed SDK via the TIMETAGGER_INSTALL_PATH
environment variable (set by Swabian's installer to the SDK's install
root). If that variable isn't set, the build falls back to the default
install location (C:/Program Files/Swabian Instruments/Time Tagger) and
logs a warning — set TIMETAGGER_INSTALL_PATH yourself if the SDK is
installed somewhere else.
meson setup builddir --buildtype=release
meson compile -C builddir
Note
--buildtype=release is important, as libtcspc
is optimization-sensitive, showing up directly in this module's live-acquisition
throughput.
To build against the fake SDK in src/fake_timetagger/ instead — no
hardware or vendor SDK installation needed, useful for developing and
testing the pipeline itself:
meson setup builddir -Dsimulate=true --buildtype=release
meson compile -C builddir
No source changes are needed to switch between the two — #include <TimeTagger.h> resolves to one or the other purely via the include path
meson selects, based on the simulate option.
Copy the built OpenScanSwabian.osdev into your Micro-Manager installation's
device adapter directory alongside the other OpenScan device modules
(OpenScan.dll, OpenScan-NIDAQ.osdev, etc.), then add the device
(Swabian Time Tagger) as a detector in your OpenScan .cfg configuration —
see the OpenScan/Micro-Manager documentation for how detector/scanner/clock
roles are assembled into an LSM device.
These are exposed as standard MicroManager device properties (device
Swabian Time Tagger, prefixed with the model name in the property browser).
The Time Tagger's raw channel numbers (positive = rising edge, negative = falling edge) that this module expects each signal to be wired to:
- Sync Channel (default
2) — the laser sync / reference clock input. - Photon Channel (default
3) — the detector (e.g. PMT) pulse input. Both edges of this channel are used: the module measures pulse width by pairing each rising detection with its matching falling detection. - Line Clock Channel (default
1) — a per-scan-line marker from the scanner. Used to derive per-pixel timing windows: each line-clock tick starts a run ofwidthpixel windows, each1e12 / pixelRatepicoseconds wide, wherewidthandpixelRatecome from the current OpenScan acquisition (ROI width, pixel rate), not from a setting here.
- Sync Delay (ps, default
0) — a fixed offset applied to sync detections before correlating them with photons (delay()ahead of the sync/photon pairing stage). Compensates for a known, fixed timing offset between the sync and photon paths (e.g. cable length differences). - Max Photon Pulse Width (ps, default
100000) — the maximum allowed separation between a photon channel's rising and falling edges for them to be treated as one pulse. Should comfortably exceed the detector's real pulse width, but stay short enough to avoid accidentally pairing across two separate pulses. - Max Diff Time (ps, default
15000) — the maximum allowed time between a sync tick and a photon detection for them to be correlated as "this photon resulted from this sync pulse." This is the parameter that actually bounds the histogram's meaningful time range (see "Histogram binning" below) — it should be set based on the laser's sync period and the fluorophore's expected decay time, not based on the pixel dwell time. Those are unrelated: pixel dwell time is about how long the scanner stays at one spatial position, while Max Diff Time is about how long after a single laser pulse a photon can plausibly arrive and still belong to that pulse. In a typical setup many sync pulses occur within one pixel's dwell time, each independently contributing photons to that pixel's histogram.
-
Histogram Bins (default
256) — the per-pixel TCSPC histogram has this many bins, covering difftimes[0, Max Diff Time).[!NOTE] The bin width of the TCSPC histogram is derived as
ceil(Max Diff Time / Histogram Bins), so the histogram's range fully covers Max Diff Time and avoids photon loss. -
Cumulative (default
false) — if enabled, the per-pixel histogram accumulates across the whole acquisition instead of resetting every frame. -
Save Histograms (default
false) — when enabled, the full per-pixel histogram (Histogram Bins bins/pixel) is additionally computed and written tohistogram_debug.bin(next to the executable) once per frame, for offline inspection. This is off by default because computing and dumping it roughly doubles per-event processing cost and writes a large file (width * height * Histogram Bins * 2bytes) every frame — leave it off for normal/live acquisitions and enable it only when you need to inspect the underlying histogram data.
Only the total photon count per pixel (summed across all histogram bins, independent of Histogram Bins) is sent to OpenScanLib/Micro-Manager as the live/displayed image — the frame callback contract only supports one 16-bit sample per pixel.
- Save Raw Data (default
false) — when enabled, writes every raw tag received during the acquisition to a file in the vendor SDK's raw dump format (16-byte records; decode withtools/dump_tags.py). - File Name Prefix (default
OpenScan-Swabian) — path prefix for saved files; may include a directory (e.g.C:/Data/myexperiment) or be a bare name (relative to Micro-Manager's working directory). Each acquisition's actual filename is<prefix>_NNNN.raw, whereNNNN(0000-9999) is the smallest index not already in use.
When built with -Dsimulate=true, src/fake_timetagger/ synthesizes
activity on three fixed channels so the pipeline has realistic-looking data
to process without hardware: a line clock, a sync channel, and gated Poisson
photon noise correlated to the sync channel. These are fixed, hardcoded
constants (LineClockChannel, SyncChannel, PhotonChannel, and their
rates, in
src/fake_timetagger/include/TimeTagger.h) rather than derived from the
current OpenScan acquisition's settings — the fake has no access to
OpenScan-specific concepts like pixel rate or ROI, by design (see that
file's comments for the full rationale). In practice this means the
simulated channel numbers and rates need to be kept roughly in step with the
Sync/Photon/Line Clock Channel settings and the acquisition's pixel rate for
the simulated data to look meaningful; see the comments alongside those
constants for the current assumptions and how to recompute them if you
change the acquisition's ROI or pixel rate.