Does Tether fit my data?
Who this page is for. Anyone deciding whether to install Tether at all. It describes what an acquisition has to look like for Tether to read it, what Tether deliberately does not do, and roughly what a dataset costs in time and disk. Read it before you download an installer — if your experiment is in the "does not do" list below, nothing later on this site will change that.
The short version
Tether reads uncompressed multi-page TIFF movies from a single camera whose frame is
split side by side, down the vertical midline, into a left and a right half — one donor
channel, one acceptor channel — recorded with one excitation laser and two colours.
That is the dual-view TIRF geometry the whole pipeline is built around. A top/bottom
(stacked) dual-view export does not fit native extraction: the built-in splitter cuts on
width only, so it would slice each channel in two rather than separating them. (If you have
a Deep-LASI .tmap whose per-channel crops describe the stacked tiles, extracting with
--tmap splits at those crops instead — but that combination is untested, so treat it as
unsupported.)
If that describes your setup, Tether fits. If any of ALEX/PIE, stoichiometry,
three-colour, or confocal point detectors describes your setup, it does not — see
What Tether does not do. If your movies are in a vendor
container (.nd2, .czi, .sif), export them to uncompressed TIFF first and Tether will
read them.
Movie files
| Requirement | Detail |
|---|---|
| Container | multi-page TIFF |
| Compression | none — the movie must be uncompressed and contiguous |
| Dimensions | exactly 3-D: (frames, height, width) |
| Pixel type | not constrained by the reader; the reference acquisition is 16-bit unsigned big-endian, and byte order is preserved end to end |
| Geometry | one frame containing both channels side by side, split down the vertical midline |
The reader is tether.io.movie.MovieReader, and it refuses rather than guesses. It raises
ValueError when the TIFF series is not 3-D, when the file is not memory-mappable — which
is what a compressed or non-contiguous TIFF looks like from here — and when the memory-map
geometry disagrees with the geometry the TIFF directory reported. That last one is
deliberate belt-and-braces: serving frames from a mismatched map would return wrong data
quietly, which is worse than failing.
The extension is not what decides. On the native path (tether extract, tether
batch) the filename suffix is never checked; the file is handed to tifffile, so a real
.nd2 fails with a parse error ("not a TIFF file") rather than being politely classified.
Extension matching exists only in the Deep-LASI folder-intake wizard, where
tether.io.intake.classify_file maps .tif/.tiff to the movie role, .tdat, .tmap,
.mat, .txt and .hdf5/.h5 to their own legacy roles, and anything it does not
recognise to unknown — collected into the discovery result's ignored list rather than
silently dropped.
Frame time. Tether reads seconds-per-frame from the TIFF only when the file carries an
ImageJ finterval tag; a non-finite or non-positive value is rejected at that boundary
rather than propagated as a poisoned time axis. The reference big-endian acquisitions
carry no such tag, so a natively extracted project records the frame time as unknown
(0.0) and traces are plotted against a frame index rather than seconds. Nothing is
guessed.
The reference acquisition the performance envelope is written against is a
512 × 512 × 1700 big-endian uint16 movie — about 0.83 GiB per movie, uncompressed.
Acquisition geometry
Tether splits each frame into a left half and a right half and treats one as donor and the
other as acceptor. Which half is which is yours to declare, with --donor-side (default
left); the split itself is tether.project.extract._half_split_geometry, which crops
both halves from the frame width — there is no top/bottom split.
--donor-side is a tether extract flag only. The headless batch runner (tether
batch) does not expose it: _run_batch builds its MovieJobs without an ExtractOptions,
so every movie in a batch run uses the default left donor. If your acquisition puts the
donor on the right, run it through tether extract per movie, supply a .tmap (whose
reference channel then defines donor), or drive tether.project.batch.run_batch from Python
with extract_options=ExtractOptions(donor_side="right").
With --tmap, the split instead comes from an imported Deep-LASI map's own per-channel
crops, and --donor-side is ignored — donor is the map's reference channel. Crop is the
only geometry imported. Deep-LASI's processImage rotates and flips before cropping,
and the imported path does not apply either yet, so
tether.project.extract._imported_registration_map refuses a .tmap whose channels carry
a non-identity Rotation or Flip rather than splitting at the wrong frame — the
ExtractionError names the channels and says the imported path "does not yet apply (only
crop geometry is honored); re-run without --tmap to use a native fit"
(tests/test_extract_cli.py::test_extract_imported_tmap_nonidentity_geometry_refused
covers both axes). Empty or all-zero Rotation/Flip — what the reference UCKOPSB map
stores — is the supported case (RegistrationChannel.has_simple_geometry in
tether.imaging.register).
Getting
--donor-sidebackwards is silent, and relabelling afterwards will not fix itA wrong
--donor-sideraises no error during extraction. Both values are legal, so extraction succeeds and every trace looks plausible.It is worse than a mislabelling. Spot detection is donor-anchored: Tether finds spots in the half you called donor and colocalizes the other channel against them. Get it backwards and you have anchored on the acceptor half, so the molecule set itself is different. You cannot repair the project by swapping the column names afterwards, and you cannot repair the efficiencies by taking
1 − E— that is only a clean mirror for the uncorrected proximity ratio, and once leakage α and γ are applied it is not even that. Re-extract.How to tell. Find a molecule whose acceptor photobleaches partway through. When the acceptor dies, energy transfer stops and the donor de-quenches, so the donor trace steps up at that instant. If the trace you have labelled "donor" steps down while the other steps up, your halves are swapped.
This is a correctness risk, not a preference — which is why it is stated here, on a page you read before installing.
Legacy project files
Alongside native extraction, Tether imports existing Deep-LASI and tMAVEN projects —
.tdat, .tmap, .txt and .mat from Deep-LASI, and SMD .hdf5/.h5 from tMAVEN. See
the legacy import page for what each format carries.
Two things are worth knowing before you plan a round trip.
Not every legacy format carries pixel coordinates. The Deep-LASI .tdat and .mat
do; the .txt intensity export and the tMAVEN SMD do not — whatever the SMD was exported
from. Without coordinates there is nothing tying a trace back to the spot it came from.
Coordinates alone are not enough — the .mat is mandatory. Rebuilding a project from
a legacy import happens without re-extraction, so it needs the pre-integrated per-molecule
traces, and those live only in the .mat. The .tdat carries coordinates and correction
factors but no traces. A movie + .tdat set therefore has coordinates and still cannot
be reconstructed: the import wizard classifies it as skipped, naming the missing .mat
(_can_reconstruct in tether.gui.deeplasi_wizard, locked by
tests/test_deeplasi_wizard.py::test_movie_and_tdat_without_mat_cannot_reconstruct). If
you have movies and .tdat files but no .mat export, plan on native re-extraction rather
than reconstruction.
What Tether does not do
These are deliberate scope boundaries, not gaps waiting to be filled.
- No ALEX or PIE, no stoichiometry, no three-colour. Tether is two-colour, single-laser only. A direct consequence: the direct-excitation correction factor δ is structurally inert (it is zero), because estimating it needs the acceptor-under-acceptor-excitation channel that only ALEX provides.
- No confocal or point-detector data. This follows from the above rather than being a separate decision: the pipeline needs a camera frame it can split into two spatial halves and detect spots in, and a photon-arrival stream from a point detector has no such frame.
- No compressed or tiled TIFFs. The lazy reader memory-maps the file for O(1) frame access; a compressed TIFF cannot be mapped, and is refused rather than silently loaded whole. Vendor containers are not read either — convert to uncompressed TIFF first.
- Two Deep-LASI detection modes are not ported, so those
.tdatfiles cannot be decoded. A.tdatsaved with particle-detection mode 4 (local-variance) or mode 5 (ZMW intensity) is refused with aValueErrorfromtether.io.tdat— "modes 4 'local-variance' and 5 'ZMW intensity' are not ported" — rather than being silently re-detected with a different method. Tether implements modes 1–3 —wavelet,intensityandbandpass(tether.imaging.detect.ParticleDetectionMode). The refusal is on decoding that file:tether extract --tdatre-extraction and the.tdat's correction factors are unavailable. A bundle that also has the.matstill reconstructs — the import wizard's_read_tdat_best_effort(tether.gui.deeplasi_executor) catches the failure and rebuilds from the.matwithout corrections, degraded to apparent-E and carrying a warning that "the .tdat could not be decoded" (tests/test_deeplasi_executor.py::test_undecodable_tdat_degrades_to_mat_apparent_e). - Older
.datand vbFRET.matformats are out of scope. - No data simulator. Validation runs against real labelled traces and a published benchmark dataset, not synthesised data.
- No central server. Tether is a desktop application; each person runs it on their own machine against their own storage.
Scale and performance
Every figure below is a named constant in tether.project.perf, decided in
ADR-0032. Read them as engineering budgets
and derived envelopes, not measurements or a service guarantee:
tests/test_project_perf.py runs a real extraction and holds the projections under them,
which is what keeps them honest.
| Quantity | Value | Constant |
|---|---|---|
| Reference movie | 512 × 512, 1700 frames | REFERENCE_MOVIE_* |
| Movies per condition | 100 | REFERENCE_CONDITION_MOVIES |
| Molecules per movie | 250 | REFERENCE_MOLECULES_PER_MOVIE |
| Unattended batch window | 12 h ("overnight") | OVERNIGHT_WINDOW_HOURS |
| Per-trace UI latency budget | 100 ms | PER_TRACE_LATENCY_BUDGET_S |
| Trace bytes per molecule per frame | 24 B (six float32 layers) |
TRACE_BYTES_PER_MOLECULE_FRAME |
| Fixed bytes per molecule | 5 kB | FIXED_STORAGE_BYTES_PER_MOLECULE |
| Per-condition projection ceiling | 5 GiB | MAX_CONDITION_BYTES |
perf.estimate_condition_bytes() projects a full reference condition — 100 movies × 250
molecules × 1700 frames — at about 1.07 GiB of .tether store, against roughly 90 GB
of raw movies at the reference geometry. One molecule projects to about 46 kB.
Both are deliberately pessimistic: the model costs the six trace layers uncompressed, while the store writes them gzip-chunked, so real projects come in comfortably under it. The 5 GiB ceiling is a design self-check that the test suite holds the projection beneath — it is not enforced at runtime, and nothing stops or warns you if a real condition exceeds it.
The 100 ms per-trace budget is what keeps curation at a 1–2 second-per-trace cadence; it covers drawing one molecule's donor/acceptor and FRET curves and stepping to the next.
The store references your movies, it does not copy them
This is the most useful thing to know about disk planning. A .tether project records,
per movie, the path you gave it, a sha256, and a metadata-only signature — file size,
mtime and an offline-availability flag (see MOVIES_DTYPE in tether.io.schema). The
movie pixels stay where they are.
Reopening a project does not read your movie files, so it can never force a cloud-storage placeholder to hydrate just to check whether something changed.
The one thing a natively extracted store caches from the pixels is a window × window
temporal-mean patch per molecule per channel (/patches/{donor,acceptor}), so curation and
the static overlap view still work with the movies offline. At the default 21-pixel window
that is about 3.5 kB per molecule, already inside the 5 kB fixed-per-molecule figure above.
A project reconstructed from a Deep-LASI export is the exception: that path never reads the
movie pixels, so its patches are written zero-filled. Curation, the overlap distance and the
overlap flag still work, but the patch image itself renders blank rather than reporting a
missing patch.
Practical consequence: the traces, patches and all analysis survive moving or renaming your movies. What you lose is the trip back to the pixels — re-extraction, and inspecting a molecule against its frames.
Disk footprint of the application
The installer lays down two complete, isolated environments under the install root —
envs/tether for the application and envs/sidecar for the tMAVEN idealization backend,
which needs an older, incompatible numeric stack of its own. Two full environments plus a
bundled Python make the install considerably larger than a single-environment desktop
utility.
No figure is quoted here on purpose: the first signed release has not been cut, so any number would be a guess. The installers page is where the measured per-platform artifact sizes will live once they exist.
Still not sure?
If your setup is close to but not exactly the geometry above, the fastest check is to run
tether extract on a single movie and look at what comes out — tether extract --help
lists the options. Extraction either produces a project with a sensible molecule count or
fails with a message naming what it could not read.