Skip to main content

OpenFlo

A Python pipeline + Tk GUI for high-content flow cytometry analysis. Reads FCS files, applies FlowJo-compatible compensation and gates, clusters populations (PhenoGraph / Leiden / FlowSOM), projects with UMAP and other embeddings, and exports per-sample / per-condition statistics, plots, and a FlowJo .wsp that round-trips back to the desktop tool. A themed (light/dark) editor with resizable, pop-out panels drives it all interactively.

Panel-agnostic — point it at any FCS set and pass --channels. The examples throughout use a generic 3-colour panel (BV421=CD11b, APC=CD34, PE-Cy7=CD45); substitute your own markers and controls.

📣 Free to use — the one ask is a citation

OpenFlo is free (MIT). If it contributes to research you publish or present, please acknowledge it: cite OpenFlo — Skyler Niedzielski (ORCID 0009-0004-4727-4639). Details in Citing; GitHub's "Cite this repository" button generates the entry for you.


Features

  • FCS I/O via FlowIO; auto-detect channels, scatter, time
  • Compensation matrix — read from .wsp, .fcs spillover, or CSV/TSV; manual editor; single-stain auto-optimizer
  • Gating — interactive editor with rectangle, polygon, ellipsoid, quadrant, threshold/FMO, lasso and boolean (AND/OR/NOT) gates, nested population trees, and undo/redo; FlowJo Gating-ML v2 reader/writer for .wsp round-trip
  • Auto-gate — reviewable, scored proposals (singlet ratio-band, BIC-selected GMM ellipses, 1-D valley/Otsu threshold) added as ordinary gates you accept, tweak, or delete
  • Transforms — logicle, hyperlog, arcsinh, log (per-channel editor)
  • Clustering — PhenoGraph (Louvain, optional cuGraph GPU), Leiden, and FlowSOM (SOM + metaclustering)
  • Dimensionality reduction — UMAP (seeded/reproducible), t-SNE, PHATE, TriMap, PaCMAP (optional backends via pip install openflo[embed])
  • Differential analysis — abundance/expression between sample groups (Mann-Whitney + log2 FC + BH-FDR)
  • Spectral unmixing — reference-spectra least-squares for full-spectrum cytometers
  • Quality control — time-based signal-drift detection plus flow-rate anomalies (clogs/bubbles) and margin/saturation event removal
  • Cell cycle — DNA-content G1/S/G2M modelling (PI/DAPI/FxCycle/7-AAD/ Hoechst/DRAQ5…), in the pipeline and the editor (phases as populations)
  • Voltage titration — Stain Index voltage walk over a titration series (openflo-voltage), with a recommended plateau voltage
  • Outputs — per-sample heatmaps, scatter, FSC/SSC, UMAP, concatenated condition comparison plots, FlowJo Table-style CSV, and a saved .wsp
  • Tk GUI with drag-and-drop, per-sample gate trees, compensation editor, and an interactive post-analysis viewer
  • compare_workspace.py — diff OpenFlo vs FlowJo population counts cell by cell, HTML + CSV report

Install

Python 3.11+ required.

One-step setup (recommended). Clone, then run the bundled setup script — it creates a local .venv and installs OpenFlo plus every dependency:

git clone https://github.com/ChironTheCentaur/openflo-public.git
cd openflo-public
# Windows:        double-click setup.bat   (or run it in a terminal)
# Linux / macOS:  ./setup.sh               (chmod +x setup.sh once)

Then launch with openflo-gui.bat / ./openflo-gui.sh (these also run setup automatically the first time if the .venv is missing).

Manual install, if you prefer to manage the environment yourself:

python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate

pip install -e ".[gui]"        # OpenFlo + all deps (incl. drag-and-drop)

Either path installs the openflo-run, openflo-compare, openflo-gui, and openflo-voltage console scripts on your PATH.

From PyPI, for the CLI and the library API:

pip install openflo            # CLI + library
pip install "openflo[gui]"     # adds the drag-and-drop extra for the GUI

Worth knowing what this does and does not give you. OpenFlo is a Tk desktop application first; the repo plus the launcher above is the path that is tested end to end on every commit, and it is what to use if you want the GUI. The PyPI package is the right route for openflo-run and openflo-compare in a script or a container, and for importing openflo.pipeline directly. The GUI works from a pip install too — it is the same code — but you are then responsible for the environment it needs, starting with Tk.

If Tk is missing. tkinter cannot be installed from PyPI: it ships with CPython on Windows and macOS, but on Debian and Ubuntu it is a separate system package. A pip install openflo on a bare Linux box therefore succeeds and then fails at openflo-gui with an ImportError that does not name the fix:

sudo apt install python3-tk      # Debian / Ubuntu
sudo dnf install python3-tkinter # Fedora / RHEL
brew install python-tk           # macOS, if you installed Python via Homebrew

Run openflo-doctor and it will tell you which of these you need. A Tk-less install is not reported as unhealthy — a headless server running openflo-run is a perfectly good install, and neither the CLI nor the library API needs Tk.

Optional GPU acceleration (RAPIDS — conda only, not on PyPI):

conda install -c rapidsai -c conda-forge rapids=24.10

The pipeline auto-detects GPU at import time and falls back to CPU.


Quickstart

GUI

openflo-gui

Or use the quick-launch scripts at the repo root (they pick up the project .venv automatically, and work from a fresh checkout without pip install):

  • Windows — double-click openflo-gui.bat (or run it in a terminal)
  • Linux / macOS — ./openflo-gui.sh (chmod +x openflo-gui.sh once)

Add FCS files (or drag them in), pick X / Y channels — the plot updates live as you gate. Batch clustering / embedding over groups of samples runs from the Pipeline Workspace (its Run button).

CLI

openflo-run \
  --trials /path/to/fcs/ \
  --out outputs/ \
  --seed 42 \
  --export-wsp outputs/analysis.wsp \
  -v                                # -v for INFO, -vv for DEBUG, -q for warnings only

Compare OpenFlo vs FlowJo

openflo-compare my_flowjo.wsp --html compare_report.html

Programmatic

import openflo

s = openflo.FlowSample("sample.fcs")
s.run_qc()
s.auto_compensate()
s.apply_transform()
s.cluster(k=30)
s.export_stats("sample_stats.csv")

Common workflows

Single-sample exploration in the GUI

Load one FCS, gate interactively, save the gate set as a reusable template:

openflo-gui
  1. Add FCS → pick your sample. It auto-compensates from $SPILL and logicle-transforms on load.
  2. Pick X / Y channels from the combos; use Mode → pseudocolor for a density view.
  3. Pick a Tool: Quadrant for a click-to-drop four-way split, Polygon for a freehand region, Edit to tweak vertices on an existing gate (see the hint line under the tool selector for gestures).
  4. Save Template… writes a .json you can load later or hand to the CLI via --gates.

Multi-trial batch run with FMO gating

Process every FCS under trials/ against a single FMO control set, producing per-sample stats CSVs, per-condition comparison plots, and a FlowJo-compatible workspace:

openflo-run \
  --trials trials/day1,trials/day2 \
  --groups '[{"name":"Day1","samples":["m1","m2"],"fmo_set":"standard"},
             {"name":"Day2","samples":["m1","m2"],"fmo_set":"standard"}]' \
  --fmo-sets '{"standard":{"BV421-A":"bv421-fmo","APC-A":"apc-fmo","PE-Cy7-A":"pecy7-fmo"}}' \
  --k 30 \
  --workers 4 \
  --seed 42 \
  --export-wsp outputs/analysis.wsp \
  --out outputs/ \
  -v

The result: outputs/Day1/, outputs/Day2/, each with cluster heatmaps + UMAP + per-sample stats; plus a single analysis.wsp you can open in FlowJo to validate the gates.

Compare OpenFlo's gating against FlowJo

openflo-compare reads a FlowJo workspace, re-applies every Population gate via OpenFlo's evaluator, and emits a side-by-side diff of event counts per population per sample:

openflo-compare my_flowjo.wsp \
  --fcs-dir test_fcs/ \
  --html compare_report.html \
  --csv compare_report.csv

The HTML report colour-codes |Δ| > 5% rows for review. Use it to validate a new OpenFlo version against an established FlowJo analysis before switching the analysis pipeline over.

Where FlowJo and OpenFlo count differently

Measured against FlowJo 10.10.2, which opened, computed and saved OpenFlo-exported workspaces and synthetic probe workspaces:

  • Rectangle gates agree exactly. That covers 1-D ranges, 2-D rectangles and quadrant sectors.
  • Polygon gates do not. FlowJo decides polygon membership on a display grid whose size gateResolution sets (default 256). It is not an exact point-in-polygon test, and its exact rule is not known. On real data FlowJo's polygon populations, and everything gated below them, usually come out higher than OpenFlo's: by about 2% for scatter polygons, and by 3-7% for exported ellipses. The display axis matters too. With no axis settings in the workspace, FlowJo uses each FCS file's P<n>DISPLAY keyword (LIN or LOG), so the same polygon can count differently on two acquisitions. OpenFlo keeps the exact test.
  • The bottom of an axis. FlowJo treats a rectangle's lower edge at or near the bottom of an axis as open, and also counts every event below the axis. On linear scatter and Time axes, "near" means within about 20 units of the axis minimum: a Time gate that starts at 10 s starts at 0 s in FlowJo. A polygon whose bottom reaches the axis minimum is opened the same way. On import (openflo-compare, the gate editor, templates) OpenFlo applies the rule to rectangles. On export it cannot change FlowJo, so it lists each affected gate as a warning.
  • Ellipse gates are exported as 64-vertex polygons, with a warning. FlowJo silently drops a whole sample that carries a Gating-ML ellipse.
  • Time gates cross the .wsp boundary in seconds (ticks × $TIMESTEP), the unit FlowJo uses.

Synthetic data & self-test

OpenFlo ships a seeded synthetic dataset generator and a self-test so you can try features and confirm a change hasn't altered other behavior — without needing your own data:

openflo-synth --out synthetic_data   # write the full example dataset (PBMC,
                                      # FMO, comp, calibration, size beads, …)
openflo-selftest                      # run seeded data through the feature
                                      # paths and check against the baseline

openflo-selftest regenerates the seeded data, runs auto-clean, clustering, calibration and compensation, and compares each metric to a committed golden baseline (src/openflo/_golden.json):

✓ Auto-clean debris removed (bead 4 µm)            7.05%   (exp 7.05% ±0.25)
✓ Auto-clean dead cells removed (viability)       7.965%   (exp 7.97% ±0.6)
✓ Leiden clusters (PBMC, res 0.5)                     18    (exp 18 ±2)
✓ MESF calibration slope                          1.9978   (exp 2 ±0.05)
...
7/7 passed — behavior matches baseline.

A red row tells you exactly which feature drifted. After an intended change, refresh the baseline with openflo-selftest --update. The same golden file backs the pytest continuity tests, so the CLI and CI agree on one source of truth. (The generated .fcs files are regenerable and stay out of git — only the seeded generator is shipped.)


Repo layout

src/openflo/
  __init__.py        public API surface (lazy re-exports)
  pipeline.py        core: FCS, compensation, gating, clustering, UMAP, WSP IO
  cli.py             CLI runner (entry point: openflo-run)
  gui.py             Tk GUI (entry point: openflo-gui)
  compare.py         OpenFlo vs FlowJo diff tool (entry point: openflo-compare)
  voltage.py         Voltage titration / Stain Index (entry point: openflo-voltage)
  synthetic.py       seeded example-dataset generator (entry point: openflo-synth)
  selftest.py        golden behavior self-test (entry point: openflo-selftest)
  _golden.json       locked baseline metrics (selftest + continuity tests)
  preview.py         ad-hoc gate-preview utility
  inspect_fcs.py     FCS header dump
  template_library/  shipped gating-template library (cleanup recipes + example panel)
tests/               pytest suite (runs on synthetic data in CI)
templates/           dev test fixture (testtemplate.json)
scripts/             dev utilities (manual smoke test, helper scripts)
docs/                algorithms reference + notes

Algorithms

Curious what OpenFlo actually does to your data — defaults, parameter choices, citations? See docs/algorithms.md. Covers logicle defaults, FMO threshold derivation, Phenograph k choice, the spillover optimizer's heuristics, and what the pipeline is not good at.


Known limitations

  • UMAP / t-SNE / TriMap / PaCMAP / PHATE have a slow first run each session (numba/JIT compilation, cached afterwards) — expected, not a hang.
  • With downsampling set to Off, very large samples draw every event and can be slow; use a Display downsample (the default) or a Max-points cap.

Please file bugs on the GitHub issue tracker.

Roadmap

  • More automatic comparative tooling — cross-group / cross-condition comparison with less manual setup.
  • A library of generic gating templates covering common panels and use cases (today ships a single example template).
  • Broad ease-of-use improvements across the gate editor and pipeline workspace.

Contributions welcome — see CONTRIBUTING.md.


Citing

OpenFlo is free under the MIT license — the only thing asked in return is a citation. If it contributes to research you publish or present, please cite it (citing the software you use is standard academic practice). Use the CITATION.cff shown in GitHub's "Cite this repository" sidebar (Niedzielski, S., OpenFlo, with ORCID).

Please also cite the upstream methods OpenFlo builds on, where you use them:

  • Levine et al. 2015 — PhenoGraph
  • McInnes et al. 2018 — UMAP
  • Parks et al. 2006 — logicle transform
  • FlowJo, LLC — .wsp format

Acknowledgements

OpenFlo was developed with substantial assistance from Anthropic's Claude (Claude Code), which contributed to implementation, testing, and analysis as a development tool. Claude is gratefully acknowledged here as a contributor — not as an author of the software.


License

OpenFlo's own code is MIT — see LICENSE.txt.

Third-party licenses

OpenFlo depends on third-party packages under their own licenses; they are installed via pip and are not redistributed in this repository. Most are permissive (BSD / Apache-2.0 / MIT / PSF). Note that graph-based clustering pulls in copyleft libraries through PhenoGraph:

Dependency License
igraph GPL-2.0-or-later
leidenalg GPL-3.0

Your use of OpenFlo together with these libraries is subject to their terms. In particular, if you redistribute a bundled build that includes them (e.g. a packaged executable), the GPL obligations apply to that distribution. OpenFlo itself does not import or vendor these libraries directly — they are runtime dependencies of PhenoGraph. See each package's own license for details.


Disclaimers

Research use only. OpenFlo is provided for research and educational use. It is not a medical device and is not intended for diagnostic, therapeutic, or other clinical decision-making. Independently validate any result before relying on it. The software is provided "as is", without warranty of any kind (see LICENSE.txt).

Trademarks. "FlowJo" is a trademark of Becton, Dickinson and Company / FlowJo, LLC. OpenFlo is an independent, unaffiliated project and is not sponsored, endorsed by, or associated with them. The name is used only descriptively, to indicate interoperability with the FlowJo .wsp file format. All other trademarks are the property of their respective owners.

Metadata

Release files for openflo 2.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for openflo 2.6.1
File Interpreter ABI Platform
openflo-2.6.1-py3-none-any.whl Python 3 none any Details

Release files / openflo-2.6.1-py3-none-any.whl

Download URL openflo-2.6.1-py3-none-any.whl
Size 630.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
44127f48948b9f11a62b87723c379bbbab4e429cb4dc3b0aae3c0e77a310d1eb
BLAKE2b-256 checksum
How to use checksums
b410392f6502413271490c2eeb4a87c8be7d81d4c91355ab0a40a7bbb5c25253
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

2.6.2

2 release files

This release

2.6.1 This release

1 release file

2.6.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page