Skip to content
david-priestPublic

About

Interactive flow and mass cytometry gating in R for SingleCellExperiment and GatingSet objects. Browser version: https://gate-lab.github.io/

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Repository files navigation

GateLabR

Gate a SingleCellExperiment or a GatingSet by hand, from your R session.

Open the GateLab app on the object, draw gates on its samples, and read the populations back in R. The gating is stored in the object.

DOI License: MIT Works on SingleCellExperiment and GatingSet Exchanges Gating-ML 2.0

Documentation and getting started

Documentation · Installation · GateLab in your browser

GateLabR opens the GateLab gating application on a SingleCellExperiment or a flowWorkspace GatingSet from an R session, for flow cytometry and mass cytometry (CyTOF) data. It does three things.

The object is gated where it is. launchGatingApp(sce) or launchGatingApp(gs) opens a browser tab on its samples, one at a time or pooled, drawing the assay chosen in the header. Rectangle, polygon, ellipse and quadrant gates build one population tree for the object. The interface is GateLab's, with its Strategy, Illustration, Layout, Plotting and Statistics tabs, and with shiny 1.14 or later the R prompt stays free while the tab is open.

The gating is stored in the object. Gates, populations, scales, compensation records and figure settings are written to metadata(sce), or to a keyword on every sample of a GatingSet, as the work is done and restored when the object is opened again, so saving the object saves the gating. A GatingSet also brings its own hierarchy: the gates it holds open in the app, each in the transformed space the object evaluates it in, with the same counts.

Populations come back to R as data. "Save to SCE" stores which events each population holds, read with gatelabPopulations(), gatelabHierarchy() and gatelabLeafPopulation(). "Export populations to colData" writes a column per population for diffcyt, CATALYST or any other tool that reads the object. On a GatingSet, "Save to GatingSet" writes the populations to its hierarchy as flowWorkspace gates, so gs_get_pop_paths(), gh_pop_get_count(), gs_pop_get_data() and ggcyto read them as they read any node, and save_gs() keeps them. gatelabGatingSet(sce) builds a GatingSet from a gated SCE, its populations written as the same nodes, for flowWorkspace, openCyto, ggcyto and CytoML.

It exists because a Bioconductor cytometry analysis holds its events in a SingleCellExperiment, while manual gating is usually done in a separate program on FCS files, and a gate drawn there has to be exported and matched back to the object's events. GateLabR gates the object itself, so a gated population is a column of the data the next step already uses.

remotes::install_github("david-priest/GateLabR")
library(GateLabR)
launchGatingApp(sce)

GateLabR starts from a SingleCellExperiment or a GatingSet and does not read FCS files itself; flowWorkspace::GatingSet(read.flowSet(...)) or CATALYST's prepData() assembles them (the vignette shows both). To gate FCS files without R, use GateLab in the browser, which is the same application.

For flowWorkspace and openCyto users, nothing changes in the object's conventions: a GatingSet parsed from FlowJo, Cytobank or Diva with CytoML, or gated by openCyto, opens with its hierarchy, transforms and compensation as they are; a gate drawn in the app returns as a rectangleGate, polygonGate or ellipsoidGate under its parent's node, named as the population; a population the object's gate types cannot hold (a quadrant, a gate excluded from a population, a polygon drawn in another transform space) returns as its exact per-sample membership, a booleanFilter-class node that gh_pop_get_indices() reads like any other. The object is modified in place, as flowWorkspace always modifies it: a matrix applied in the app goes through compensate() on a writable, uncompensated, untransformed object, and a GatingSet loaded with load_gs() is read-only on its data, so for one of those compensate and transform before launching.

Features

  • Draw and edit gates interactively. Polygon, rectangle, ellipse and quadrant gates on any pair of channels, with click-and-drag vertex editing, undo / redo, and per-gate colour and label. A gate can be fitted to the events it holds from its right-click menu.
  • Population trees. Build hierarchies of populations from gate references — each population is the intersection (AND) of its gates within its parent, and a population may exclude a gate (NOT); counts and percentages update live.
  • Flow and CyTOF modes. Auto-detects the instrument type from channel names. Flow uses per-channel logicle for fluorescence and arcsinh for FSC / SSC scatter, with editable W and cofactor; CyTOF channels use arcsinh (cofactor 5).
  • Flow and CyTOF compensation. Inspect, edit, sweep and apply spill matrices in the shared Compensation tab. GateLabR performs the durable calculation in R, installs a revisioned full-size SCE assay atomically, preserves the original assay, and restores saved compensated assays without recomputation. Existing compensated SCE assays can be adopted directly.
  • Cytobank-compatible Gating-ML 2.0 import / export. Exchange gates and population trees with Cytobank and other ISAC Gating-ML 2.0 tools. Import is done by the embedded GateLab core in the browser, not in R: it reads rectangle, range, polygon and ellipse gates, AND populations, and the Boolean NOT of a gate. A population's excluded gate is read from the Gating-ML 2.0 schema's gating:use-as-complement="true" on the gate's reference, which GateLab now writes in its Cytobank format, and from gating:complement="true", an attribute outside the schema that GateLab and GateLabR wrote before. An OR population is left out, with everything beneath it, and named in a warning, and the rest of the file is imported; so is a population that uses a gate placed beneath a population that is not its ancestor, which GateLab cannot represent within one parent. Files containing other gate types, transformations GateLab does not read, or gates whose channels cannot be matched to the loaded data are refused before import. FlowJo workspaces are read separately (next item).
  • FlowJo workspace import. The gating strategy of a FlowJo workspace (.wsp) can be imported onto the object's samples from the Import menu, through the same core as GateLab. After an import, a report sets the event count FlowJo recorded for each population beside GateLabR's own, and says whether a difference arises at a population's own gate or is inherited from one above it.
  • Agent access. An AI agent (Claude Code, or anything that speaks MCP) can read the gating in the open session and propose gates, which appear in the tab at once as ordinary gates with a badge saying who made them and why. launchGatingApp(sce, agent = TRUE) opens the tab connected to the agent's relay; setting the relay up is described in GateLab's README. Saving stays yours.
  • Workspace persistence. Gates, populations, scales, active assay, compensation provenance and illustration settings are saved inside the SCE and re-loaded automatically.
  • FCS export. Export gated populations as FCS files, optionally split by sample_id.
  • Sample filter and multi-sample overlay. Filter by any colData column and overlay multiple samples with distinct colours.
  • Colour by a colData column. Any factor, character or logical column with up to 254 levels (a FlowSOM or CATALYST merge level, say) appears under "Colour by", so you can watch cluster composition change while you drag a gate. Values are fetched only when a column is chosen. Set metadata(sce)$gatelab_palettes$<column> to a named colour vector to make the app use the same colours as your figures.
  • Statistics tab. Per-population, per-channel summary stats (count, % parent / total, median, mean, geometric mean, SD, CV) exportable to CSV.
  • Cell-division profiler. A CFSE / CellTrace dye-dilution tab: draggable per-sample division gates on a 1-D histogram (Div0…DivN), a marker-vs-dye biplot with density contours, and per-cell division calls written back to colData.
  • Composition preview. A Proportions tab for quick stacked-bar and boxplot previews of any colData composition (e.g. cluster or division proportions by condition), with per-sample averaging and faceting.
  • Figure export. Strategy, Illustration and Layout tabs render publication-style panels. Strategy traces the gating path to one population or to several, with an arrow from each gate to the plot of the population it makes. Illustration includes biplot and histogram grids, stacked ridgelines, and population-by-channel median / mean expression heatmaps with per-channel, per-population or z-score scaling and three colour palettes. Layout places plots and text on a page. Figures export as SVG, PDF or PNG.

How GateLabR compares with other tools

GateLabR fills a specific gap: an interactive GUI for gating that lives natively on a SingleCellExperiment, so it slots into a Bioconductor pipeline instead of replacing it — while keeping full R access to the same object.

GateLabR FlowJo Cytobank CytoExploreR / flowGate
Interface GUI (the GateLab app in a browser tab, served from R) + full R access GUI (desktop, proprietary) GUI (cloud, proprietary) R, with interactive gating helpers
Data object SingleCellExperiment (Bioconductor-native) or GatingSet (flowWorkspace) .wsp workspace cloud workspace GatingSet (flowWorkspace)
Cost / licence Free, MIT, open source Commercial Commercial Free, open source
Flow / CyTOF Both (auto-detected) Flow-focused Both Flow-focused
Gates persist in the object Yes — in metadata(), or in the GatingSet's hierarchy and keywords; reload restores everything Workspace files Cloud workspace GatingSet on disk
Gate exchange Gating-ML 2.0 import and export (AND populations); FlowJo .wsp import .wsp / .wspt workspace formats Gating-ML 2.0 import and export flowWorkspace / CytoML ecosystem
Access for AI agents An MCP server: an agent reads the gating and proposes gates in the open session No MCP server documented No MCP server documented Scripted in R
Downstream hand-off Populations → colData for diffcyt / CATALYST / any SCE tool, or → GatingSet nodes for flowWorkspace / openCyto / ggcyto Export gated FCS Export gated FCS GatingSet → downstream

On gate exchange: GateLabR reads and writes Cytobank-compatible Gating-ML, so supported gate geometry and AND population trees can move between GateLabR and Cytobank, and it reads the gating strategy of a FlowJo workspace (.wsp). A population that excludes a gate is written with the schema's gating:use-as-complement in the Cytobank format and through a NOT gate in the standard format, and both are read back, as is the gating:complement attribute GateLab wrote before; such a population has not been shown to cross to Cytobank. OR populations are not imported.

On agents: an agent reaches the open tab through a relay on your computer, an MCP server kept in GateLab's tools/agent-mcp, and launchGatingApp(sce, agent = TRUE) opens the tab connected to it. Its gates go through the same session as yours, and nothing is saved to the object until you save. What the agent reads (counts, histograms, medians, pictures of the plot) goes to the agent's model provider; the object stays in your R session. In October 2026 we found no MCP server documented for FlowJo or Cytobank.

FlowJo is a trademark of Becton, Dickinson and Company. GateLabR is an independent project and is not affiliated with or endorsed by BD or FlowJo.

Installation

GateLabR can be used either as an installed R package or straight from a clone.

Option A — install as a package (recommended)

# install.packages("remotes")            # if needed
# Bioconductor dependencies are resolved automatically; if you don't already
# have BiocManager, install it first: install.packages("BiocManager")
remotes::install_github("david-priest/GateLabR")

library(GateLabR)
launchGatingApp()        # or launchGatingApp(my_sce)

Option B — clone and run from source

# 1. git clone https://github.com/david-priest/GateLabR.git
# 2. From an R session, install dependencies once (CRAN + Bioconductor):
source("path/to/GateLabR/install_dependencies.R")
# 3. Source the launcher and run (see Quick start):
source("path/to/GateLabR/launch.R")
launchGatingApp()

Dependencies (installed automatically by Option A, or by install_dependencies.R for Option B):

  • CRAN: shiny, DT, jsonlite, base64enc, uuid, sp, gridSVG, png, ggplot2
  • Bioconductor: SingleCellExperiment, SummarizedExperiment, S4Vectors, flowCore, xml2

R ≥ 4.2 and Bioconductor ≥ 3.16 are recommended.

Quick start

# Installed package:           Or from a clone:
library(GateLabR)              # source("path/to/GateLabR/launch.R")

# Option A: launch and pass an existing SCE
launchGatingApp(my_sce)

# Option B: launch with no SCE — the app scans the global environment
#           and lets you pick from any SingleCellExperiment objects present
launchGatingApp()

The app opens in your default browser. launchGatingApp() is the single entry point and starts the shared React interface. With shiny 1.14 or later the call returns at once and the prompt is free while the tab is open (the app is serviced while R is idle); gatelabStop() stops it. A change to the object at the console while the app runs is kept: the app refuses its next save until gatelabSync("my_sce") hands it the console's object. With an older shiny the call blocks until the app is stopped. The three-column layout is:

Panel Content
Left SCE samples and workspace / Gating-ML / FCS actions
Centre Interactive plot (tabs: Gating | Strategy | Illustration | Layout | Plotting | Division | Statistics | Metadata | Panel | Scales | Compensation)
Right Gates list, population tree, bulk-rename controls

Typical workflow

  1. Get your events into the app, either way:

    • Load an existing SingleCellExperiment — e.g. from CATALYST prepData() (CyTOF) or one you've built from flowCore / your own pipeline.
  2. Call launchGatingApp(sce), or call launchGatingApp() to use the first SCE already loaded in your global environment.

  3. Draw gates on the central plot; the gate list and population tree update live.

  4. Build populations by referencing one or more gates; multiple references use AND logic.

  5. Export results for downstream work — populations as new colData columns on the SCE (Export Population, e.g. for diffcyt, CATALYST, or any SCE-aware analysis), or gated events back out as .fcs.

  6. Press Save to SCE. Besides the workspace this stores which events every population holds, for every hierarchy, so the whole tree can be read in R:

    gatelabHierarchy(sce)                    # one row per population: parent, depth, path, gates, count
    members <- gatelabPopulations(sce)       # logical matrix, events × populations
    sce$population <- gatelabLeafPopulation(sce)   # each event's deepest population, or "ungated"

    These refuse to read memberships that are behind the workspace (gates changed after the last explicit save) unless allow_stale = TRUE; press Save to SCE again to refresh them. The save also gives every event an id in colData(sce)$gatelab_event_id, and the memberships are read back through it, so they follow the events through a reorder or a subset. Events the saved object did not hold, such as those added by cbind(), have no membership, and reading is refused rather than guessed. A population the tree a file is gated under has no counterpart for is not evaluated for that file: its events are NA in it, not FALSE, in the saved memberships and in an exported colData column alike. The save, the export and each read that returns such an NA warn, naming the file and the population.

  7. Save the SCE (e.g. saveRDS(sce, "gated.rds")) — the workspace is embedded in metadata() and reloaded next time.

Data persistence

GateLabR stores its canonical workspace and host provenance inside the SCE:

metadata(sce)$gatelab_workspace
#> revisioned GateLab workspace JSON (gates, populations, scales and settings)

metadata(sce)$gatelab_workspace$memberships
#> every population's membership as a packed bitset, with the hierarchy tables,
#> written by "Save to SCE"; read with gatelabPopulations() and friends

colData(sce)$gatelab_event_id
#> each event's id, written by "Save to SCE"; it ties the stored memberships to their
#> events. cbind() needs it on both objects: give the other object the column as NA
#> (other$gatelab_event_id <- NA_real_) rather than dropping it from the saved one,
#> and the saved events read again once the combined object is subset back to them.

metadata(sce)$gatelabr_compensation
#> compensation profiles, assay bindings, revisions and provenance

rowData(sce)$gatelabr_label
#> optional Panel-tab display-name overrides

metadata(sce)$gatelab_palettes
#> optional: a named colour vector per colData column, used by "Colour by"

The established metadata(sce)$gating_workspace mirror remains available for compatibility with the previous GateLabR interface. It carries each gate's coordinate space and transforms, and GateLabR reads it only when the canonical record above is absent.

File formats supported

  • Input: SingleCellExperiment assays, Cytobank Gating-ML 2.0 XML and FlowJo workspaces (.wsp).
  • Output: FCS, Gating-ML 2.0 (Cytobank-compatible or standard re-importable), SCE .rds / .qs2 (with embedded workspace and assays), per-population colData columns, CSV statistics, SVG / PDF / PNG figures.

Related

figtracer — a plain-text, git-tracked electronic lab notebook (ELN) for reproducible bioinformatics: it threads the figures your R / Python analysis produces into living, provenance-tracked lab notes. GateLabR gates your cells; figtracer keeps the resulting figures and analysis as a reproducible written record. Together they cover the single-cell / cytometry workflow from gating to lab notebook.

Citation

If you use GateLabR in published work, please cite the Zenodo archive:

Priest, D. G. (2026). GateLabR: Interactive manual gating for SingleCellExperiment objects. Version 1.0.0. Zenodo. https://doi.org/10.5281/zenodo.20404387

BibTeX:

@software{priest_gatelabr_2026,
  author    = {Priest, David G.},
  title     = {GateLabR: Interactive manual gating for SingleCellExperiment objects},
  year      = {2026},
  version   = {1.0.0},
  doi       = {10.5281/zenodo.20404387},
  url       = {https://github.com/david-priest/GateLabR},
  publisher = {Zenodo}
}

GitHub's "Cite this repository" button (powered by CITATION.cff) renders formatted citations in several styles.

License

MIT — see LICENSE.md.

Acknowledgements

GateLabR is developed in the Wing Lab at the Immunology Frontier Research Center (IFReC), Osaka University.

Built on top of the Bioconductor stack (SingleCellExperiment, SummarizedExperiment, flowCore), the Shiny web framework, and D3.js for the interactive plot.

Issues and contributions

If you run into a bug, or there's a feature you'd find useful, please open an issue or get in touch — I'm happy to take a look and would be glad to implement it.

A note on compensation: embedded-$SPILLOVER compensation has been validated on conventional fluorescence data from a BD FACSAria III / FACSymphony S6, with compensated values matching flowCore::compensate. It has not yet been exercised on embedded matrices from other vendors' acquisition software; the matrix is read vendor-agnostically (flowCore::spillover), so if compensation looks wrong for a given file please send me its channel names and spillover keyword and I'll take a look.

About

Interactive flow and mass cytometry gating in R for SingleCellExperiment and GatingSet objects. Browser version: https://gate-lab.github.io/

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages