Standalone light-sheet tile stitching pipeline and GUI for Flamingo T-SPIM data.
It turns a folder of raw microscope tiles into a single stitched 3D volume you can open in Fiji, napari, QuPath, or Imaris. You can run it on any machine — no microscope and no full control software required. The same code also powers the "Tile Stitching" menu inside Py2Flamingo, so results match (single source of truth, no drift).
If you just want to stitch a dataset, this is all you need.
- Go to the Releases page.
- Download the newest
FlamingoStitcher-Setup-vX.Y.Z.exe. - Run it. (It's unsigned for now, so Windows SmartScreen may warn — click More info → Run anyway.)
- Launch Flamingo Stitcher from the Start menu.
The app updates itself: the Updates tab checks for newer versions on launch and offers a one-click install when one exists.
You'll see five tabs: Single Workflow, Multi-Acquisition, Multi-View, Options, and Updates. The app opens on Single Workflow the first time and on the tab you last used after that. For a first run you want one of the first two — Which tab? tells you which, by looking at your folder. Then:
- Add your data. Click Add… and select your acquisition folder. (You can add several and stitch them back-to-back.)
- Click "Discover Tiles". The app scans the folder and figures out the details for you. A line appears at the top of "Tell me about your image" showing what it detected — frame/ROI size, pixel size, Z step, channels, and tile count. Glance at it to confirm it looks right.
- Pick an output folder. Click Browse… next to Output Directory. Each acquisition is saved into its own subfolder there.
- Click "Run All". Progress shows at the bottom. When it finishes, open the
result (an
..._stitchedfolder) in Fiji or napari.
That's it. The default settings are chosen to work for typical Flamingo data — you usually don't need to change anything in steps 2–3.
The tab names describe how the microscope software wrote your files, not anything you chose at the scope. So open your acquisition folder and look at it:
| What you see in the folder | Use this tab | Why |
|---|---|---|
Image files sitting loose in one folder, named …_X000_Y000_C03_I0_… |
Single Workflow | The microscope's own control software writes every tile into one folder. This is the usual case, and where most people are. |
One subfolder per tile, named for its stage position — X4.00_Y12.00 |
Multi-Acquisition | The Py2Flamingo layout. Also use this tab to queue several acquisitions and stitch them back to back. |
| Several acquisitions of one sample at different rotation angles | Multi-View | Fuses the angles into one volume. The rotation conventions are not yet confirmed on an instrument — check the result before trusting it. |
Tile files may be .raw, .tif, .tiff or .btf — the extension does not decide the
tab; the folder shape does.
The two layouts look more alike than you'd expect: a tile file is X000_Y000
(zero-padded counts) and a tile folder is X4.00_Y12.00 (stage millimetres). If in
doubt, the test that settles it is whether the images are loose in the folder you
selected, or one level down inside per-tile subfolders.
Still unsure? Add the folder and click Discover Tiles. 0 tiles means the layout doesn't match that tab — try the other one.
Options holds the registration settings, saved per microscope and objective, so a value you tune once is reused by every future run from that instrument. You don't need it for a first run. Go there when the registration report says seams were rejected, or when you want to set which illumination side lights the left of the frame.
Updates checks for a newer version on launch and installs it in one click.
The basic controls are organized into three plainly-named boxes. For a first run you can leave all of them alone. Everything more specialized lives in the collapsed Processing Options panel below them.
- Tell me about your image — facts about your data: pixel size, Z step, frame
(camera ROI), and which channels. These are filled in automatically by Discover
Tiles; change one only if you know a detected value is wrong. Unless you set the
XY pixel size by hand, each queued acquisition uses the pixel size implied by its
own objective (from its
ScopeSettings.txt), so a batch mixing objectives stitches every item at the right scale. Type a value to override it for all.- The pixel size is always shown in the log. Discovery prints
Effective XY pixel size: … µm/px (the value stitching will use). If the tiles come out spaced apart like dice, this value is almost always wrong — check that line first. - Pick the right folder. The objective is read from
ScopeSettings.txt. If you select a parent folder whose acquisitions live in a dated subfolder, the app now searches down into it — but if it still can't find the objective it logs a ⚠ warning and falls back to the current (possibly wrong) pixel size. - Per-microscope fallback. For systems that don't record the objective in
ScopeSettings.txt, a per-microscopeobjective_magnificationinmicroscope_hardware.yaml(microscopes:block) supplies it — e.g.liara(25.48×, measured on a calibration standard) → ~0.255 µm/px.
- The pixel size is always shown in the log. Discovery prints
- What kind of processing should we do? — the choices that affect the output: Downsample (make the result smaller/faster), how the two light-sheet sides are combined, and how overlapping tiles are blended. The defaults are a good start.
- How should we save it? — output format, compression, and memory mode. OME-Zarr (Fiji compatible) is a safe default. The size / time / memory estimates underneath update live as you change settings.
Tip: hover over any control to get a tooltip explaining it. Open the Log panel (collapsed at the bottom) to see full detail of what was detected and done.
If you hit trouble (out-of-memory, a run that seems stuck, slow stitching), click
Help / Troubleshooting in the app, or see
stitching_hardware_troubleshooting.md.
Every microscope must have its tile orientation chosen once. Different Flamingos mount the camera differently, so there is no safe default — the first time you stitch data from a microscope the app has not seen, it stops and asks you to pick the orientation (rather than guess and silently mis-place tiles). Once chosen, it's remembered for that microscope and applied automatically. If a dataset has no image data to preview from (no per-tile MIPs and no readable raw stacks), the app tells you it can't determine the orientation and that you need a dataset that includes MIPs for that microscope.
To pick it — fix it visually, no config editing:
- Add your acquisition and click Discover Tiles.
- Click Orientation Preview…. It shows the mosaic under all 8 tile orientations (built quickly from the per-tile MIP files) — each panel re-orients every tile and re-tiles at the stage grid.
- If scattered beads hide the real structure, narrow the Z projection (e.g. Bottom 25%) — structure often lives in part of the stack.
- If the tiles are laid out backwards (X3 X2 X1 X0 instead of X0 X1 X2 X3), tick Reverse X order / Reverse Y order — this is separate from the panel choice (a system can need a flip in X but a reversed order in Y).
- Select the panel where the tissue is continuous across the seams, then click Use for stitching.
Your choice is applied to the run and remembered for that microscope (by its
name in the acquisition metadata), so future data from the same system picks it
up automatically. Orientation is resolved per acquisition at run time from
each item's own microscope name, so a batch mixing systems orients each one
correctly (a choice made for one scope never touches another). The effective
orientation is printed in the log at the start of every run. Command line:
--tile-orientation NAME [--reverse-x-tiles] [--reverse-y-tiles].
Re-stitching the same data with the same settings would overwrite the previous
result. The app now asks first — Overwrite, New folder (writes a
numbered copy, keeping the old one), or Skip — with an "apply to all remaining"
option for batches. Runs with different settings already get distinct
filenames and coexist. Command line: --if-exists {overwrite,skip,unique}.
If a tile's metadata or image file can't be read (a flaky USB drive, a truncated
.raw), discovery no longer throws away the whole acquisition — it estimates
that tile's position from the acquisition grid and keeps going, then shows a
"Data-quality warnings" summary so you know which tiles may be off before you
trust the result.
Each acquisition gets its own ..._stitched folder containing:
| File | What it is |
|---|---|
<name>.ome.tif / .ome.zarr / .ims |
the stitched volume |
stitch_metadata.json |
the record worth keeping. Software version and git hash, voxel size, channels, tile count, every per-tile position, the world frame, and every processing setting the run used |
registration_report.txt |
the human summary — read this first |
registration_report.csv |
one row per tile: the correction applied, in µm |
registration_seams.csv |
one row per expected neighbour pair, and what became of it |
Stitching the same acquisition twice with different settings also writes a
second copy of each report under a name carrying those settings
(..._flatfield_registration_report.csv), so a later run cannot overwrite an
earlier run's evidence. The plain names always hold the most recent run.
stitch_metadata.json carries a world_frame block. Read it before converting
stitched coordinates back to stage coordinates — some acquisitions negate world
X, so an origin can legitimately be a large negative number, and treating it as
a stage coordinate places the volume mirrored and displaced.
Tune a stitch once, then apply exactly that treatment to more datasets — on the microscope computer, or on your own laptop.
- Save Configuration… writes the current settings to a file.
- Load Configuration… reads one back, or reads the
stitch_metadata.jsonfrom any finished run.
It carries everything that shapes the output: processing options, destripe tuning, deconvolution parameters, registration thresholds, border QC.
It deliberately does not carry pixel size, Z spacing or frame size — Discover measures those from your own data — nor the machine's memory ceiling, worker counts or scratch path. A PSF file is used only if that file exists on your machine, and the app says so plainly when it doesn't.
Stitching can succeed on part of a mosaic and fail on the rest. Several gates exist to stop a half-registered result being presented as a good one. When one fires, tiles are placed by their recorded stage positions instead, and the registration report names which gate fired and for which tiles.
| Gate | Default | What it means when it fires |
|---|---|---|
| Minimum tile overlap to attempt registration | 5% of a frame | Too little shared content to measure a shift |
| Minimum share of seams that must register | 50% | Not enough of the mosaic agreed; the whole result falls back to stage positions |
| Minimum tile structure | 0.15 | A tile has nothing to align on — empty medium, or bright but featureless gel |
| Seam quality threshold | 0.4 | That seam's correlation was too weak to believe |
| Maximum lateral / axial correction | auto | A proposed shift was larger than the geometry allows |
Two further limits:
- Multi-view rotation is not instrument-validated. The rotation sign and centre conventions are checked on synthetic data only. Verify with a two-angle test acquisition, and flip the sign if the views come out mirrored.
- There is no per-tile intensity equalisation. Tiles that were genuinely brighter stay brighter; flat-field correction addresses the illumination profile within a tile, not tile-to-tile differences.
Registration and fusion are performed by multiview-stitcher. Two of its algorithms are the ones a methods section should name:
- Global optimisation with iterative edge pruning, and cosine-weighted blending across tile overlaps — after BigStitcher: Hörl et al., BigStitcher: reconstructing high-resolution image datasets of cleared and expanded samples, Nature Methods 16, 870–874 (2019).
- Content-based fusion weighting — Preibisch et al., local-variance weighting of each tile's contribution in overlap regions.
Flat-field correction uses BaSiCPy; destriping uses a vendored copy of the pystripe wavelet filter.
To cite the software itself, see CITATION.cff in the repository root.
pip install "flamingo-stitcher[gui]" # CLI + GUI (PyQt5, no napari)
pip install "flamingo-stitcher[gui,preview]" # + napari background-zero previewOptional backends:
pip install "flamingo-stitcher[imaris]" # direct .ims output (Windows only)
pip install "flamingo-stitcher[destripe]" # destriping (vendored filter + PyWavelets)
pip install "flamingo-stitcher[deconv]" # RedLionfish GPU deconvolution (OpenCL)
conda install -c conda-forge pycudadecon # NVIDIA GPU deconvolutionGUI:
flamingo-stitch-guiCLI:
flamingo-stitch /path/to/acquisition -o /path/to/output \
--pixel-size-um 0.406 --output-format ome-zarr-sharded
flamingo-stitch /path/to/acquisition --dry-run # list discovered tiles
flamingo-stitch --helpIt converts raw acquisition folders into stitched volumes using
multiview-stitcher for
registration and fusion, and writes OME-Zarr, OME-TIFF, or Imaris .ims.
Core (always installed): numpy, scipy, dask[array], zarr, numcodecs,
multiview-stitcher, ngff-zarr, tifffile, psutil, PyYAML.
The dask version range excludes 2025.12.0–2026.3.0 (they break ngff-zarr's
OME-Zarr v0.4 writes; see forum.image.sc topic 120480).
GNU General Public License v3.0 or later (GPL-3.0-or-later) — see LICENSE.
The GUI uses PyQt5 (GPL-3.0), which the Windows installer bundles, making the
distributed binary GPL-3.0. Every other dependency is permissive (BSD-3-Clause /
MIT / Apache-2.0) and GPL-compatible. This project is derived from
Py2Flamingo (MIT); see
NOTICE for full third-party attribution and
LICENSE_ANALYSIS_TODO.md for the analysis (incl. the
PySide6/MIT alternative).