flow-plots
A library for creating visualizations of flow cytometry data.
Overview
This library provides a flexible, extensible API for creating different types of plots from flow cytometry data. The architecture is designed to be easily extended with new plot types while maintaining clean separation of concerns.
Features
- Extensible Architecture: Easy to add new plot types by implementing the
Plottrait - Builder Pattern: Type-safe configuration using the builder pattern
- Progress Reporting: Optional progress callbacks for streaming/progressive rendering
- Flexible Rendering: Applications can inject their own execution and progress logic
Basic Usage
Simple Density Plot
use ;
use RenderConfig;
let plot = new;
let options = new
.width
.height
.title
.build?;
let data: = vec!;
let mut render_config = default;
let bytes = plot.render?;
With FCS File Initialization
This example shows the complete workflow from opening an FCS file to generating a plot:
use ;
use RenderConfig;
use Fcs;
// Step 1: Open the FCS file from a file path
let fcs = open?;
// Step 2: Select parameters for the x and y axes
// You can find parameters by their channel name (e.g., "FSC-A", "SSC-A", "FL1-A")
let x_parameter = fcs.find_parameter?;
let y_parameter = fcs.find_parameter?;
// Step 3: Use the helper function to create plot options with sensible defaults
// This analyzes the FCS file and parameters to determine appropriate ranges and transforms
let mut builder = density_options_from_fcs?;
// Step 4: Customize the options further if needed
let options = builder
.width
.height
.build?;
// Step 5: Extract the data for plotting
// The helper function uses the parameter's transform to calculate appropriate ranges,
// so we should use the same data (raw or transformed) for plotting
let data: = fcs.get_xy_pairs?;
// Step 6: Create and render the plot
let plot = new;
let mut render_config = default;
let bytes = plot.render?;
// Step 7: Use the bytes (JPEG-encoded image) as needed
// e.g., save to file, send over network, display in UI, etc.
Key Points:
-
Opening FCS files:
Fcs::open(path)opens and parses an FCS file from a file path. The path must have a.fcsextension. This function:- Memory-maps the file for efficient access
- Parses the header, text segment, and data segment
- Loads event data into a Polars DataFrame
- Returns a fully parsed
Fcsstruct ready for use
-
Finding parameters:
fcs.find_parameter(channel_name)finds a parameter by its channel name (e.g., "FSC-A", "SSC-A", "FL1-A"). Returns aResult<&Parameter>- an error if the parameter doesn't exist. To list all available parameters, usefcs.get_parameter_names_from_dataframe()which returns aVec<String>of all channel names. -
Automatic option configuration:
helpers::density_options_from_fcs()analyzes the FCS file and parameters to automatically:- Determine plot ranges based on parameter type:
- FSC/SSC: Uses default range (0 to 200,000)
- Time: Uses the actual maximum time value from the data
- Fluorescence: Calculates percentile bounds (1st to 99th percentile) after applying the parameter's transform to the raw values
- Apply transformations: For fluorescence parameters, it transforms the raw values using the parameter's
TransformType(typically Arcsinh) before calculating percentile bounds, ensuring the plot range reflects the transformed data scale - Set axis transforms: Automatically sets Linear transform for FSC/SSC, and the parameter's default transform (usually Arcsinh) for fluorescence
- Extract metadata: Gets the file name from the
$FILkeyword for the plot title
- Determine plot ranges based on parameter type:
-
Extracting data:
fcs.get_xy_pairs(x_param, y_param)extracts (x, y) coordinate pairs for plotting. This returns raw (untransformed) values, which is appropriate since the plot options handle transformation during rendering and axis labeling.
Error Handling:
All operations return Result types. Here's a more complete example with error handling:
use Result;
With Application Executor and Progress
use ;
use cratewith_render_lock;
use cratePlotProgressEvent;
let options = new
.width
.height
// ... configure options
.build?;
// Configure rendering with app-specific concerns
let mut render_config = RenderConfig ;
// Wrap the render call with your executor's render lock
let bytes = with_render_lock?;
Batch Rendering
For processing multiple plots together, use the batch API:
use ;
use RenderConfig;
let plot = new;
let mut render_config = default;
// Prepare multiple plot requests
let requests: = vec!;
// Render all plots in batch
let plot_bytes: = plot.render_batch?;
// plot_bytes[0] contains the JPEG bytes for the first plot
// plot_bytes[1] contains the JPEG bytes for the second plot
Custom Batch Orchestration
For applications that want to orchestrate rendering themselves (e.g., custom progress reporting, parallel rendering, etc.), use the low-level batch density calculation:
use calculate_density_per_pixel_batch;
use ;
use ;
use Result;
// Calculate density for multiple plots
let requests: = vec!;
// Get raw pixel data for all plots (density calculation only)
let raw_pixels_batch = calculate_density_per_pixel_batch;
// Now you can orchestrate rendering yourself
let mut render_config = default;
// Example: Render plots in parallel using rayon
use *;
let plot_bytes: = raw_pixels_batch
.par_iter
.enumerate
.map
.collect;
let plot_bytes = plot_bytes?;
Note: While batch processing is available, sequential processing (calling render() in a loop) is typically faster for most use cases. See GPU_EVALUATION.md for performance analysis.
Architecture
The library is organized into several modules:
options: Plot configuration types using the builder patternBasePlotOptions: Layout and display settingsAxisOptions: Axis configuration (range, transform, label)DensityPlotOptions: Complete density plot configuration
plots: Plot implementationsDensityPlot: 2D density plot implementationPlottrait: Interface for all plot types
render: Rendering infrastructureRenderConfig: Configuration for rendering (progress callbacks)ProgressInfo: Progress information structureplotters_backend: Plotters-based rendering implementation
density: Density calculation algorithmscolormap: Color map implementationshelpers: Helper functions for common initialization patterns
Adding New Plot Types
To add a new plot type:
- Create a new options struct (e.g.,
DotPlotOptions) that implementsPlotOptions - Create a new plot struct (e.g.,
DotPlot) that implements thePlottrait - Implement the
rendermethod with your plot-specific logic
Example:
use Plot;
use PlotOptions;
use RenderConfig;
use PlotBytes;
use Result;
;
Migration Guide
From Old PlotOptions API
Old API:
let options = new?;
New API:
// Option 1: Use helper function
let mut builder = density_options_from_fcs?;
let options = builder
.width
.height
.build?;
// Option 2: Manual construction
let options = new
.width
.height
.x_axis
.y_axis
.build?;
From Old draw_plot Function
Old API:
let = draw_plot?;
New API:
let plot = new;
let mut render_config = default;
let bytes = plot.render?;
License
MIT