| Crates.io | flow-gates |
| lib.rs | flow-gates |
| version | 0.2.0 |
| created_at | 2026-01-14 15:39:12.911188+00 |
| updated_at | 2026-01-21 19:38:47.175868+00 |
| description | Package for drawing and interacting with gates in flow cytometry data |
| homepage | |
| repository | https://github.com/jrmoynihan/flow/flow-gates |
| max_upload_size | |
| id | 2043060 |
| size | 15,580,069 |
A comprehensive Rust library for working with gates in flow cytometry data analysis. This library provides tools for creating, managing, and applying gates to flow cytometry data, supporting the GatingML 2.0 standard for gate definitions and hierarchies.
Add this to your Cargo.toml:
[dependencies]
flow-gates = { path = "../flow-gates" }
flow-fcs = { path = "../flow-fcs" } # Required for FCS file support
use flow_gates::*;
use flow_gates::geometry::*;
// Create a polygon gate from coordinates
let coords = vec![
(100.0, 200.0),
(300.0, 200.0),
(300.0, 400.0),
(100.0, 400.0),
];
let geometry = create_polygon_geometry(coords, "FSC-A", "SSC-A")?;
let gate = Gate::new(
"lymphocytes",
"Lymphocytes",
geometry,
"FSC-A",
"SSC-A",
);
use flow_gates::{filter_events_by_gate, Gate};
use flow_fcs::Fcs;
// Load FCS file
let fcs = Fcs::from_file("data.fcs")?;
// Filter events by gate
let event_indices = filter_events_by_gate(&fcs, &gate, None)?;
println!("Found {} events in gate", event_indices.len());
use flow_gates::GateStatistics;
let stats = GateStatistics::calculate(&fcs, &gate)?;
println!("Event count: {}", stats.event_count);
println!("Percentage: {:.2}%", stats.percentage);
println!("X parameter mean: {:.2}", stats.x_stats.mean);
println!("Y parameter median: {:.2}", stats.y_stats.median);
A Gate represents a region of interest in 2D parameter space. Each gate has:
Polygon gates are defined by a series of vertices forming a closed or open polygon:
use flow_gates::{Gate, GateGeometry, GateNode, geometry::*};
let coords = vec![
(100.0, 200.0),
(300.0, 200.0),
(300.0, 400.0),
(100.0, 400.0),
];
let geometry = create_polygon_geometry(coords, "FSC-A", "SSC-A")?;
let gate = Gate::new("polygon-gate", "Polygon", geometry, "FSC-A", "SSC-A");
Rectangle gates are axis-aligned rectangular regions:
let coords = vec![(100.0, 200.0), (500.0, 600.0)];
let geometry = create_rectangle_geometry(coords, "FSC-A", "SSC-A")?;
let gate = Gate::new("rect-gate", "Rectangle", geometry, "FSC-A", "SSC-A");
Ellipse gates are elliptical regions with optional rotation:
let coords = vec![
(300.0, 400.0), // Center
(500.0, 400.0), // Right point (defines radius_x and angle)
(300.0, 600.0), // Top point (defines radius_y)
];
let geometry = create_ellipse_geometry(coords, "FSC-A", "SSC-A")?;
let gate = Gate::new("ellipse-gate", "Ellipse", geometry, "FSC-A", "SSC-A");
Gates can be scoped to apply globally or to specific files:
use flow_gates::GateMode;
// Global gate (applies to all files)
let global_gate = Gate::new(/* ... */);
// Gate mode defaults to Global
// File-specific gate
let mut file_gate = Gate::new(/* ... */);
file_gate.mode = GateMode::FileSpecific { guid: "file-123".into() };
// File group gate
let mut group_gate = Gate::new(/* ... */);
group_gate.mode = GateMode::FileGroup {
guids: vec!["file-1".into(), "file-2".into()],
};
Gate hierarchies allow sequential gating where child gates are applied to events that pass parent gates:
use flow_gates::GateHierarchy;
let mut hierarchy = GateHierarchy::new();
// Build hierarchy: root -> parent -> child
hierarchy.add_child("root-gate", "parent-gate");
hierarchy.add_child("parent-gate", "child-gate");
// Get chain from root to a specific gate
let chain = hierarchy.get_chain_to_root("child-gate");
// Returns: ["root-gate", "parent-gate", "child-gate"]
// Get ancestors
let ancestors = hierarchy.get_ancestors("child-gate");
// Returns: ["parent-gate", "root-gate"]
// Get descendants
let descendants = hierarchy.get_descendants("root-gate");
// Returns: ["parent-gate", "child-gate"]
Filter events through a chain of gates:
use flow_gates::{filter_events_by_hierarchy, GateHierarchy};
// Build gate chain from hierarchy
let gate_chain: Vec<&Gate> = hierarchy
.get_chain_to_root("child-gate")
.iter()
.filter_map(|id| storage.get(id.as_ref()))
.collect();
// Filter through hierarchy
let indices = filter_events_by_hierarchy(&fcs, &gate_chain, None, None)?;
For repeated filtering operations, use a spatial index:
use flow_gates::{EventIndex, filter_events_by_gate};
// Build index once
let x_slice = fcs.get_parameter_events_slice("FSC-A")?;
let y_slice = fcs.get_parameter_events_slice("SSC-A")?;
let index = EventIndex::build(x_slice, y_slice)?;
// Reuse index for multiple gates (much faster!)
let indices1 = filter_events_by_gate(&fcs, &gate1, Some(&index))?;
let indices2 = filter_events_by_gate(&fcs, &gate2, Some(&index))?;
let indices3 = filter_events_by_gate(&fcs, &gate3, Some(&index))?;
Thread-safe gate storage with optional persistence:
use flow_gates::gate_storage::GateStorage;
use std::path::PathBuf;
// Create storage with auto-save
let storage = GateStorage::with_save_path(PathBuf::from("gates.json"));
// Load existing gates
storage.load()?;
// Insert gates
storage.insert(gate1);
storage.insert(gate2);
// Query gates
let file_gates = storage.gates_for_file("file-guid");
let param_gates = storage.gates_for_parameters("FSC-A", "SSC-A");
let specific_gates = storage.gates_for_file_and_parameters(
"file-guid",
"FSC-A",
"SSC-A",
);
// Manual save (auto-save is enabled by default)
storage.save()?;
Export gates to GatingML 2.0 format:
use flow_gates::gates_to_gatingml;
let gates = vec![gate1, gate2, gate3];
let xml = gates_to_gatingml(&gates)?;
// Save to file
std::fs::write("gates.xml", xml)?;
Import gates from GatingML format:
use flow_gates::gatingml_to_gates;
let xml = std::fs::read_to_string("gates.xml")?;
let gates = gatingml_to_gates(&xml)?;
use flow_gates::*;
use flow_fcs::Fcs;
fn apply_gate_to_file(fcs_path: &str, gate: &Gate) -> Result<Vec<usize>> {
// Load FCS file
let fcs = Fcs::from_file(fcs_path)?;
// Filter events
let indices = filter_events_by_gate(&fcs, gate, None)?;
Ok(indices)
}
use flow_gates::*;
use flow_fcs::Fcs;
fn hierarchical_gating(
fcs: &Fcs,
hierarchy: &GateHierarchy,
storage: &GateStorage,
target_gate_id: &str,
) -> Result<Vec<usize>> {
// Get gate chain from hierarchy
let chain_ids = hierarchy.get_chain_to_root(target_gate_id);
// Resolve gates from storage
let gate_chain: Vec<&Gate> = chain_ids
.iter()
.filter_map(|id| storage.get(id.as_ref()))
.collect();
// Filter through hierarchy
filter_events_by_hierarchy(fcs, &gate_chain, None, None)
}
use flow_gates::*;
use flow_fcs::Fcs;
use std::sync::Arc;
use std::collections::HashMap;
struct SimpleFilterCache {
cache: Arc<dashmap::DashMap<FilterCacheKey, Arc<Vec<usize>>>>,
}
impl FilterCache for SimpleFilterCache {
fn get(&self, key: &FilterCacheKey) -> Option<Arc<Vec<usize>>> {
self.cache.get(key).map(|entry| entry.value().clone())
}
fn insert(&self, key: FilterCacheKey, value: Arc<Vec<usize>>) {
self.cache.insert(key, value);
}
}
fn batch_process_with_cache(
fcs: &Fcs,
gates: &[Gate],
file_guid: &str,
) -> Result<HashMap<String, Vec<usize>>> {
let cache = SimpleFilterCache {
cache: Arc::new(dashmap::DashMap::new()),
};
let mut results = HashMap::new();
for gate in gates {
let chain = vec![gate];
let indices = filter_events_by_hierarchy(
fcs,
&chain,
Some(&cache),
Some(file_guid),
)?;
results.insert(gate.id.to_string(), indices);
}
Ok(results)
}
use flow_gates::*;
use flow_fcs::Fcs;
fn generate_statistics_report(
fcs: &Fcs,
gates: &[Gate],
) -> Result<Vec<(String, GateStatistics)>> {
let mut report = Vec::new();
for gate in gates {
let stats = GateStatistics::calculate(fcs, gate)?;
report.push((gate.name.clone(), stats));
}
Ok(report)
}
fn print_statistics_report(report: &[(String, GateStatistics)]) {
for (name, stats) in report {
println!("Gate: {}", name);
println!(" Events: {}", stats.event_count);
println!(" Percentage: {:.2}%", stats.percentage);
println!(" Centroid: ({:.2}, {:.2})", stats.centroid.0, stats.centroid.1);
println!(" X Parameter:");
println!(" Mean: {:.2}", stats.x_stats.mean);
println!(" Median: {:.2}", stats.x_stats.median);
println!(" Std Dev: {:.2}", stats.x_stats.std_dev);
println!(" Y Parameter:");
println!(" Mean: {:.2}", stats.y_stats.mean);
println!(" Median: {:.2}", stats.y_stats.median);
println!(" Std Dev: {:.2}", stats.y_stats.std_dev);
println!();
}
}
use flow_gates::*;
use flow_gates::geometry::*;
// User draws polygon on plot
fn create_gate_from_user_drawing(
points: Vec<(f32, f32)>,
x_param: &str,
y_param: &str,
gate_id: &str,
gate_name: &str,
) -> Result<Gate> {
// Create geometry from user-drawn points
let geometry = create_polygon_geometry(points, x_param, y_param)?;
// Create gate
let gate = Gate::new(gate_id, gate_name, geometry, x_param, y_param);
// Validate
if !gate.geometry.is_valid(x_param, y_param)? {
return Err(GateError::invalid_geometry("Invalid gate geometry"));
}
Ok(gate)
}
// Update gate after user edits
fn update_gate_geometry(
gate: &mut Gate,
new_points: Vec<(f32, f32)>,
) -> Result<()> {
let geometry = create_polygon_geometry(
new_points,
gate.x_parameter_channel_name(),
gate.y_parameter_channel_name(),
)?;
gate.geometry = geometry;
Ok(())
}
For repeated filtering operations on the same dataset, use EventIndex:
Implement the FilterCache trait for your application to cache filter results:
use flow_gates::{FilterCache, FilterCacheKey};
use std::sync::Arc;
struct MyFilterCache {
// Your cache implementation
}
impl FilterCache for MyFilterCache {
fn get(&self, key: &FilterCacheKey) -> Option<Arc<Vec<usize>>> {
// Retrieve from cache
}
fn insert(&self, key: FilterCacheKey, value: Arc<Vec<usize>>) {
// Store in cache
}
}
The library uses GateError for all error conditions. Most operations return Result<T, GateError>:
use flow_gates::{GateError, Result};
match create_polygon_geometry(coords, "FSC-A", "SSC-A") {
Ok(geometry) => {
// Use geometry
}
Err(GateError::InvalidGeometry { message }) => {
eprintln!("Invalid geometry: {}", message);
}
Err(e) => {
eprintln!("Error: {}", e);
}
}
Most types in this library are thread-safe:
GateStorage: Thread-safe concurrent accessEventIndex: Immutable after construction, safe to shareGate, GateGeometry, GateNode: Clone to share between threadsGateHierarchy: Use synchronization primitives for concurrent accessMIT
Contributions are welcome! Please feel free to submit a Pull Request.