alterlab-flowio
Parse and write FCS (Flow Cytometry Standard) files v2.0-3.1 with FlowIO — extract event data as NumPy arrays, read $-keyword metadata and channel/parameter definitions, and convert events to CSV or pandas DataFrame. Use when loading raw .fcs flow-cytometry files, inspecting chan
Install
npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-flowio
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
FlowIO: Flow Cytometry Standard File Handler
Overview
FlowIO is a lightweight Python library for reading and writing Flow Cytometry Standard (FCS) files. Parse FCS metadata, extract event data, and create new FCS files with minimal dependencies. Supports FCS versions 2.0, 3.0, and 3.1 — ideal for backend services, data pipelines, and basic cytometry file operations.
When to Use This Skill
Use this skill when:
- FCS files require parsing or metadata extraction
- Flow cytometry data needs conversion to NumPy arrays
- Event data requires export to FCS format
- Multi-dataset FCS files need separation
- Channel information (scatter, fluorescence, time) must be extracted
- Cytometry files need validation or inspection
- Pre-processing is needed before advanced analysis
Related tool: For advanced analysis (compensation, transformation, gating, FlowJo 10 workspace import), recommend the FlowKit library (1.3.2 as of 2026-09), which is built on FlowIO by the same author.
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| Compensation, transforms, gating, or FlowJo/GatingML workspaces | FlowKit (companion library, not a skill in this suite) |
| Clustering / dimensionality reduction of cytometry events | alterlab-scanpy, alterlab-umap |
| Statistical comparison of populations across samples | alterlab-statistical-analysis |
| Mass-cytometry or imaging-based spatial single-cell data | alterlab-squidpy-spatial |
| Registering and versioning the FCS files themselves | alterlab-lamindb |
Installation
uv pip install flowio # 1.4.0 as of 2026-09
Requires Python 3.9 or later; NumPy is the only runtime dependency.
Quick Start
from flowio import FlowData
# Read FCS file and inspect
flow = FlowData('experiment.fcs')
print(f"FCS Version: {flow.version}")
print(f"Events: {flow.event_count}")
print(f"Channels: {flow.pnn_labels}")
# Get event data as NumPy array, shape (events, channels)
events = flow.as_array()
import numpy as np
from flowio import create_fcs
# Write a new FCS file from a NumPy array.
# Gotcha: create_fcs takes a WRITABLE BINARY FILE HANDLE (not a path) and a
# FLATTENED 1-D event array — pass data.flatten(), not the 2-D matrix.
data = np.array([[100, 200, 50], [150, 180, 60]], dtype='float32') # 2 events, 3 channels
with open('output.fcs', 'wb') as fh:
create_fcs(fh, data.flatten(), ['FSC-A', 'SSC-A', 'FL1-A'])
Core Workflow
- Read — Construct a
FlowData('file.fcs')instance. Useonly_text=Truefor metadata-only (memory-efficient) reads; pass offset/null-channel flags for problematic files. - Inspect — Read
flow.version,flow.event_count,flow.pnn_labels,flow.pns_labels, channel-type indices, and theflow.textmetadata dict. - Extract — Get a NumPy array via
flow.as_array()(preprocessed) orflow.as_array(preprocess=False)(raw). Slice by channel type as needed. - Transform / export — Convert to a pandas DataFrame or CSV; or write a new
FCS file with
flow.write_fcs(path, ...)(takes a path) orcreate_fcs(fh, data.flatten(), ...)(takes a binary file handle + flattened events). Output is always FCS 3.1, single-precision float. - Multi-dataset — If a file holds multiple datasets, use
read_multiple_data_sets()instead of the constructor.
Routing Guidance
- Need exact signatures, attributes, exceptions, or FCS keyword definitions?
Read
references/api_reference.md. - Doing one of the core operations (read/parse, metadata, create, export,
multi-dataset, preprocessing)? Read
references/workflows.mdfor full code. - Need a task recipe (inspect a file, batch a directory, FCS→CSV, filter
events, extract channels)? Read
references/recipes.md. - Hitting an error, or want best practices / file-structure / troubleshooting?
Read
references/error-handling-and-troubleshooting.md.
References
references/api_reference.md— CompleteFlowDataclass, utility functions (read_multiple_data_sets,create_fcs), exception classes, FCS file structure, common TEXT-segment keywords, channel types, and example workflows.references/workflows.md— Full code for the core operations: reading/parsing, metadata & channel extraction, creating files, exporting/modifying, multi-dataset handling, and data preprocessing.references/recipes.md— Worked examples: inspecting contents, batch processing a directory, FCS→CSV conversion, event filtering & re-export, and channel extraction with statistics.references/error-handling-and-troubleshooting.md— Exception-handling patterns, best practices, FCS file-structure notes, a troubleshooting table, and integration notes (NumPy, pandas, FlowKit, web apps).
Summary
FlowIO provides essential FCS file handling for flow cytometry workflows — use it for parsing, metadata extraction, and file creation. For simple file operations and data extraction, FlowIO alone is sufficient; for complex analysis (compensation, gating), integrate with FlowKit or other specialized tools.
Part of the AlterLab Academic Skills suite.
Files (alterlab-academic-skills)
-
evals
-
evals.json 5 KB
{ "skill": "alterlab-flowio", "evals": [ { "id": "parse-fcs-metadata", "prompt": "I have a bunch of raw .fcs files from our flow cytometer. I want to read one, see the FCS version, the number of events, and the channel names before doing anything else.", "expected_output": "Invokes alterlab-flowio. Uses FlowData('sample.fcs') and reports flow.version, flow.event_count, and flow.pnn_labels (and optionally pns_labels). May suggest only_text=True when only metadata is needed.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "FlowData" }, { "type": "behavior", "value": "Reads version, event_count, and channel labels from the FCS file." } ] }, { "id": "events-to-numpy-csv", "prompt": "I need to pull the event matrix out of an FCS file as a NumPy array and then export it to a CSV with the channel names as column headers.", "expected_output": "Invokes alterlab-flowio. Reads the file with FlowData, calls flow.as_array() to get the (events, channels) NumPy array, builds a pandas DataFrame with columns=flow.pnn_labels, and writes it to CSV. May mention preprocess=True/False control.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "as_array" }, { "type": "behavior", "value": "Extracts event data as a NumPy array and converts it to CSV/DataFrame using pnn_labels." } ] }, { "id": "metadata-only-batch-summary", "prompt": "I have a directory of hundreds of FCS files and just want a summary table of acquisition date, event count, and channel count per file without loading all the event data into memory.", "expected_output": "Invokes alterlab-flowio. Iterates the directory, opens each file with FlowData(path, only_text=True) to skip the DATA segment, and collects flow.version, flow.event_count, flow.channel_count and flow.text.get('$DATE') into a summary DataFrame, wrapped in try/except.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "only_text" }, { "type": "behavior", "value": "Uses only_text=True for memory-efficient metadata-only batch reading." } ] }, { "id": "offset-discrepancy-error", "prompt": "FlowIO throws a DataOffsetDiscrepancyError when I try to open one of my .fcs files. How do I get it to read anyway?", "expected_output": "Invokes alterlab-flowio. Recommends re-opening with FlowData(path, ignore_offset_discrepancy=True) (or use_header_offsets=True / ignore_offset_error=True), framing these as the relaxed-parsing options for problematic offset mismatches between HEADER and TEXT segments.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "ignore_offset_discrepancy" }, { "type": "behavior", "value": "Resolves offset errors with ignore_offset_discrepancy / use_header_offsets parameters." } ] }, { "id": "create-fcs-from-array", "prompt": "I have a NumPy array of synthetic cytometry events and channel names; I want to write it out as a proper FCS file with descriptive stain names and some custom metadata.", "expected_output": "Invokes alterlab-flowio. Uses create_fcs(fh, events.flatten(), channel_names, opt_channel_names=..., metadata_dict=...) where fh is a writable binary file handle (open(path,'wb')) and the event array is flattened to 1-D, noting the FCS 3.1 single-precision float export.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "create_fcs" }, { "type": "behavior", "value": "Passes a binary file handle (not a path string) and a flattened 1-D event array to create_fcs, using opt_channel_names and metadata_dict." } ] }, { "id": "near-miss-flowkit-gating", "prompt": "I have a compensated FCS sample and want to apply a polygon gate from my FlowJo workspace / GatingML and get the gated subpopulation frequencies.", "expected_output": "Does NOT invoke this skill; FlowIO only parses/writes FCS and does not do compensation or gating. Defers to FlowKit, which handles compensation, gating, and FlowJo/GatingML support (FlowIO is the recommended companion parser, not the gating tool).", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "FlowKit" } ] }, { "id": "near-miss-lamindb", "prompt": "I want to register all my flow cytometry datasets in a queryable, versioned data store and track which analysis run produced each one.", "expected_output": "Does NOT invoke this skill; defers to alterlab-lamindb. The user wants FAIR data management, versioning, and lineage tracking of cytometry datasets, not low-level FCS file parsing.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "alterlab-lamindb" } ] } ] }
-
-
references
-
api_reference.md 11.3 KB
# FlowIO API Reference ## Overview FlowIO is a Python library for reading and writing Flow Cytometry Standard (FCS) files. It supports FCS versions 2.0, 3.0, and 3.1 with minimal dependencies. ## Installation ```bash pip install flowio ``` Supports Python 3.9 and later. ## Core Classes ### FlowData The primary class for working with FCS files. #### Constructor ```python FlowData(fcs_file, ignore_offset_error=False, ignore_offset_discrepancy=False, use_header_offsets=False, only_text=False, nextdata_offset=None, null_channel_list=None) ``` **Parameters:** - `fcs_file`: File path (str), Path object, or file handle - `ignore_offset_error` (bool): Ignore offset errors (default: False) - `ignore_offset_discrepancy` (bool): Ignore offset discrepancies between HEADER and TEXT sections (default: False) - `use_header_offsets` (bool): Use HEADER section offsets instead of TEXT section (default: False) - `only_text` (bool): Only parse the TEXT segment, skip DATA and ANALYSIS (default: False) - `nextdata_offset` (int): Byte offset for reading multi-dataset files - `null_channel_list` (list): List of PnN labels for null channels to exclude #### Attributes **File Information:** - `name`: Name of the FCS file - `file_size`: Size of the file in bytes - `version`: FCS version (e.g., '3.0', '3.1') - `header`: Dictionary containing HEADER segment information - `data_type`: Type of data format ('I', 'F', 'D', 'A') **Channel Information:** - `channel_count`: Number of channels in the dataset - `channels`: Dictionary mapping channel numbers to channel info - `pnn_labels`: List of PnN (short channel name) labels - `pns_labels`: List of PnS (descriptive stain name) labels - `pnr_values`: List of PnR (range) values for each channel - `fluoro_indices`: List of indices for fluorescence channels - `scatter_indices`: List of indices for scatter channels - `time_index`: Index of the time channel (or None) - `null_channels`: List of null channel indices **Event Data:** - `event_count`: Number of events (rows) in the dataset - `events`: Raw event data as bytes **Metadata:** - `text`: Dictionary of TEXT segment key-value pairs - `analysis`: Dictionary of ANALYSIS segment key-value pairs (if present) #### Methods ##### as_array() ```python as_array(preprocess=True) ``` Return event data as a 2-D NumPy array. **Parameters:** - `preprocess` (bool): Apply gain, logarithmic, and time scaling transformations (default: True) **Returns:** - NumPy ndarray with shape (event_count, channel_count) **Example:** ```python flow_data = FlowData('sample.fcs') events_array = flow_data.as_array() # Preprocessed data raw_array = flow_data.as_array(preprocess=False) # Raw data ``` ##### write_fcs() ```python write_fcs(filename, metadata=None) ``` Export the FlowData instance as a new FCS file. **Parameters:** - `filename` (str): Output file path - `metadata` (dict): Optional dictionary of TEXT segment keywords to add/update **Example:** ```python flow_data = FlowData('sample.fcs') flow_data.write_fcs('output.fcs', metadata={'$SRC': 'Modified data'}) ``` **Note:** Exports as FCS 3.1 with single-precision floating-point data. ## Utility Functions ### read_multiple_data_sets() ```python read_multiple_data_sets(filename_or_handle, ignore_offset_error=False, ignore_offset_discrepancy=False, use_header_offsets=False, only_text=False) ``` Read all datasets from an FCS file containing multiple datasets. **Parameters:** - Same as the FlowData constructor except `nextdata_offset` and `null_channel_list` (accepts a path or file handle plus the offset/`only_text` flags). **Returns:** - List of FlowData instances, one for each dataset **Example:** ```python from flowio import read_multiple_data_sets datasets = read_multiple_data_sets('multi_dataset.fcs') print(f"Found {len(datasets)} datasets") for i, dataset in enumerate(datasets): print(f"Dataset {i}: {dataset.event_count} events") ``` ### create_fcs() ```python create_fcs(file_handle, event_data, channel_names, opt_channel_names=None, metadata_dict=None) ``` Create a new FCS file from event data. **Parameters:** - `file_handle`: A **writable binary file handle** (e.g. `open(path, 'wb')`), **not** a path string. (`create_fcs` calls `file_handle.seek(0)`; passing a `str` raises `AttributeError: 'str' object has no attribute 'seek'`.) - `event_data`: **Flattened 1-D** sequence of event values, channel-interleaved (event0_ch0, event0_ch1, …, event1_ch0, …). Pass `array.flatten()` for a 2-D `(events, channels)` matrix. (Internally `n_points = len(event_data)`; a 2-D array gives `len == n_events`, raising `ValueError: Number of data points is not a multiple of the number of channels`.) - `channel_names` (list): List of PnN (short) channel names - `opt_channel_names` (list): Optional list of PnS (descriptive) channel names - `metadata_dict` (dict): Optional dictionary of TEXT segment keywords. **Note the name: `metadata_dict`, not `metadata`** (the latter is used by `write_fcs`); passing `metadata=` raises `TypeError`. **Example:** ```python import numpy as np from flowio import create_fcs # Create synthetic data (n_events * n_channels values) events = (np.random.rand(10000, 5) * 1000).astype('float32') channels = ['FSC-A', 'SSC-A', 'FL1-A', 'FL2-A', 'Time'] opt_channels = ['Forward Scatter', 'Side Scatter', 'FITC', 'PE', 'Time'] with open('synthetic.fcs', 'wb') as fh: create_fcs(fh, events.flatten(), channels, opt_channel_names=opt_channels, metadata_dict={'$SRC': 'Synthetic data'}) ``` ## Exception Classes ### FlowIOWarning Generic warning class for non-critical issues. ### PnEWarning Warning raised when PnE values are invalid during FCS file creation. ### FlowIOException Base exception class for FlowIO errors. ### FCSParsingError Raised when there are issues parsing an FCS file. ### DataOffsetDiscrepancyError Raised when the HEADER and TEXT sections provide different byte offsets for data segments. **Workaround:** Use `ignore_offset_discrepancy=True` parameter when creating FlowData instance. ### MultipleDataSetsError Raised when attempting to read a file with multiple datasets using the standard FlowData constructor. **Solution:** Use `read_multiple_data_sets()` function instead. ## FCS File Structure Reference FCS files consist of four segments: 1. **HEADER**: Contains FCS version and byte locations of other segments 2. **TEXT**: Key-value metadata pairs (delimited format) 3. **DATA**: Raw event data (binary, floating-point, or ASCII) 4. **ANALYSIS** (optional): Results from data processing ### Common TEXT Segment Keywords - `$BEGINDATA`, `$ENDDATA`: Byte offsets for DATA segment - `$BEGINANALYSIS`, `$ENDANALYSIS`: Byte offsets for ANALYSIS segment - `$BYTEORD`: Byte order (1,2,3,4 for little-endian; 4,3,2,1 for big-endian) - `$DATATYPE`: Data type ('I'=integer, 'F'=float, 'D'=double, 'A'=ASCII) - `$MODE`: Data mode ('L'=list mode, most common) - `$NEXTDATA`: Offset to next dataset (0 if single dataset) - `$PAR`: Number of parameters (channels) - `$TOT`: Total number of events - `PnN`: Short name for parameter n - `PnS`: Descriptive stain name for parameter n - `PnR`: Range (max value) for parameter n - `PnE`: Amplification exponent for parameter n (format: "a,b" where value = a * 10^(b*x)) - `PnG`: Amplification gain for parameter n ## Channel Types FlowIO automatically categorizes channels: - **Scatter channels**: FSC (forward scatter), SSC (side scatter) - **Fluorescence channels**: FL1, FL2, FITC, PE, etc. - **Time channel**: Usually labeled "Time" Access indices via: - `flow_data.scatter_indices` - `flow_data.fluoro_indices` - `flow_data.time_index` ## Data Preprocessing When calling `as_array(preprocess=True)`, FlowIO applies: 1. **Gain scaling**: Multiply by PnG value 2. **Logarithmic transformation**: Apply PnE exponential transformation if present 3. **Time scaling**: Convert time values to appropriate units To access raw, unprocessed data: `as_array(preprocess=False)` ## Best Practices 1. **Memory efficiency**: Use `only_text=True` when only metadata is needed 2. **Error handling**: Wrap file operations in try-except blocks for FCSParsingError 3. **Multi-dataset files**: Always use `read_multiple_data_sets()` if unsure about dataset count 4. **Offset issues**: If encountering offset errors, try `ignore_offset_discrepancy=True` 5. **Channel selection**: Use null_channel_list to exclude unwanted channels during parsing ## Integration with FlowKit For advanced flow cytometry analysis including compensation, gating, and GatingML support, consider using FlowKit library alongside FlowIO. FlowKit provides higher-level abstractions built on top of FlowIO's file parsing capabilities. ## Example Workflows ### Basic File Reading ```python from flowio import FlowData # Read FCS file flow = FlowData('experiment.fcs') # Print basic info print(f"Version: {flow.version}") print(f"Events: {flow.event_count}") print(f"Channels: {flow.channel_count}") print(f"Channel names: {flow.pnn_labels}") # Get event data events = flow.as_array() print(f"Data shape: {events.shape}") ``` ### Metadata Extraction ```python from flowio import FlowData flow = FlowData('sample.fcs', only_text=True) # Access metadata print(f"Acquisition date: {flow.text.get('$DATE', 'N/A')}") print(f"Instrument: {flow.text.get('$CYT', 'N/A')}") # Channel information for i, (pnn, pns) in enumerate(zip(flow.pnn_labels, flow.pns_labels)): print(f"Channel {i}: {pnn} ({pns})") ``` ### Creating New FCS Files ```python import numpy as np from flowio import create_fcs # Generate or process data data = (np.random.rand(5000, 3) * 1000).astype('float32') # Define channels channels = ['FSC-A', 'SSC-A', 'FL1-A'] stains = ['Forward Scatter', 'Side Scatter', 'GFP'] # Create FCS file (binary file handle + flattened events + metadata_dict=) with open('output.fcs', 'wb') as fh: create_fcs(fh, data.flatten(), channels, opt_channel_names=stains, metadata_dict={ '$SRC': 'Python script', '$DATE': '19-OCT-2025' }) ``` ### Processing Multi-Dataset Files ```python from flowio import read_multiple_data_sets # Read all datasets datasets = read_multiple_data_sets('multi.fcs') # Process each dataset for i, dataset in enumerate(datasets): print(f"\nDataset {i}:") print(f" Events: {dataset.event_count}") print(f" Channels: {dataset.pnn_labels}") # Get data array events = dataset.as_array() mean_values = events.mean(axis=0) print(f" Mean values: {mean_values}") ``` ### Modifying and Re-exporting ```python from flowio import FlowData # Read original file flow = FlowData('original.fcs') # Get event data events = flow.as_array(preprocess=False) # Modify data (example: apply custom transformation) events[:, 0] = events[:, 0] * 1.5 # Scale first channel # FlowIO doesn't modify event data in place; write a new file with create_fcs() # (binary handle + flattened events + metadata_dict=). from flowio import create_fcs with open('modified.fcs', 'wb') as fh: create_fcs(fh, events.flatten(), flow.pnn_labels, opt_channel_names=flow.pns_labels, metadata_dict=flow.text) ``` -
error-handling-and-troubleshooting.md 3.8 KB
# FlowIO Error Handling and Troubleshooting Exception handling patterns, best practices, FCS file structure notes, and a troubleshooting table. ## Error Handling Handle common FlowIO exceptions appropriately: ```python from flowio import ( FlowData, FCSParsingError, DataOffsetDiscrepancyError, MultipleDataSetsError ) try: flow = FlowData('sample.fcs') events = flow.as_array() except FCSParsingError as e: print(f"Failed to parse FCS file: {e}") # Try with relaxed parsing flow = FlowData('sample.fcs', ignore_offset_error=True) except DataOffsetDiscrepancyError as e: print(f"Offset discrepancy detected: {e}") # Use ignore_offset_discrepancy parameter flow = FlowData('sample.fcs', ignore_offset_discrepancy=True) except MultipleDataSetsError as e: print(f"Multiple datasets detected: {e}") # Use read_multiple_data_sets instead from flowio import read_multiple_data_sets datasets = read_multiple_data_sets('sample.fcs') except Exception as e: print(f"Unexpected error: {e}") ``` ## Best Practices 1. **Memory efficiency:** Use `only_text=True` when event data is not needed. 2. **Error handling:** Wrap file operations in try-except blocks for robust code. 3. **Multi-dataset detection:** Check for `MultipleDataSetsError` and use the appropriate function. 4. **Preprocessing control:** Explicitly set the `preprocess` parameter based on analysis needs. 5. **Offset issues:** If parsing fails, try `ignore_offset_discrepancy=True`. 6. **Channel validation:** Verify channel counts and names match expectations before processing. 7. **Metadata preservation:** When modifying files, preserve original TEXT segment keywords. ## FCS File Structure FCS files consist of four segments: 1. **HEADER:** FCS version and byte offsets for other segments 2. **TEXT:** Key-value metadata pairs (delimiter-separated) 3. **DATA:** Raw event data (binary/float/ASCII format) 4. **ANALYSIS** (optional): Results from data processing Access these segments via `FlowData` attributes: - `flow.header` — HEADER segment - `flow.text` — TEXT segment keywords - `flow.events` — DATA segment (as bytes) - `flow.analysis` — ANALYSIS segment keywords (if present) ## Troubleshooting | Problem | Solution | |---------|----------| | "Offset discrepancy error" | Use `ignore_offset_discrepancy=True` parameter | | "Multiple datasets error" | Use `read_multiple_data_sets()` instead of the `FlowData` constructor | | Out of memory with large files | Use `only_text=True` for metadata-only operations, or process events in chunks | | Unexpected channel counts | Check for null channels; use `null_channel_list` to exclude them | | Cannot modify event data in place | FlowIO doesn't support direct modification; extract data, modify, then use `create_fcs()` to save (see below for its call contract) | | `create_fcs` → `AttributeError: 'str' object has no attribute 'seek'` | First arg must be a writable binary file handle (`open(path, 'wb')`), not a path string | | `create_fcs` → `ValueError: Number of data points is not a multiple of the number of channels` | `event_data` must be a flattened 1-D array; pass `array.flatten()`, not the 2-D `(events, channels)` matrix | | `create_fcs` → `TypeError: unexpected keyword argument 'metadata'` | Use `metadata_dict=` for `create_fcs` (only `write_fcs` uses `metadata=`) | ## Integration Notes - **NumPy arrays:** All event data is returned as NumPy ndarrays with shape `(events, channels)`. - **Pandas DataFrames:** Convert easily with `pd.DataFrame(flow.as_array(), columns=flow.pnn_labels)`. - **FlowKit integration:** For advanced analysis (compensation, gating, FlowJo support), use FlowKit, which builds on FlowIO's parsing capabilities. - **Web applications:** FlowIO's minimal dependencies make it ideal for web backend services processing FCS uploads. -
recipes.md 3.9 KB
# FlowIO Common Recipes Worked examples for frequent FCS tasks: inspecting contents, batch processing, CSV export, event filtering, and channel extraction. ## Inspecting FCS File Contents Quick exploration of FCS file structure: ```python from flowio import FlowData flow = FlowData('unknown.fcs') print("=" * 50) print(f"File: {flow.name}") print(f"Version: {flow.version}") print(f"Size: {flow.file_size:,} bytes") print("=" * 50) print(f"\nEvents: {flow.event_count:,}") print(f"Channels: {flow.channel_count}") print("\nChannel Information:") for i, (pnn, pns) in enumerate(zip(flow.pnn_labels, flow.pns_labels)): ch_type = "scatter" if i in flow.scatter_indices else \ "fluoro" if i in flow.fluoro_indices else \ "time" if i == flow.time_index else "other" print(f" [{i}] {pnn:10s} | {pns:30s} | {ch_type}") print("\nKey Metadata:") for key in ['$DATE', '$BTIM', '$ETIM', '$CYT', '$INST', '$SRC']: value = flow.text.get(key, 'N/A') print(f" {key:15s}: {value}") ``` ## Batch Processing Multiple Files Process a directory of FCS files (metadata-only, memory efficient): ```python from pathlib import Path from flowio import FlowData import pandas as pd # Find all FCS files fcs_files = list(Path('data/').glob('*.fcs')) # Extract summary information summaries = [] for fcs_path in fcs_files: try: flow = FlowData(str(fcs_path), only_text=True) summaries.append({ 'filename': fcs_path.name, 'version': flow.version, 'events': flow.event_count, 'channels': flow.channel_count, 'date': flow.text.get('$DATE', 'N/A') }) except Exception as e: print(f"Error processing {fcs_path.name}: {e}") # Create summary DataFrame df = pd.DataFrame(summaries) print(df) ``` ## Converting FCS to CSV Export event data to CSV format: ```python from flowio import FlowData import pandas as pd # Read FCS file flow = FlowData('sample.fcs') # Convert to DataFrame df = pd.DataFrame( flow.as_array(), columns=flow.pnn_labels ) # Add metadata as attributes df.attrs['fcs_version'] = flow.version df.attrs['instrument'] = flow.text.get('$CYT', 'Unknown') # Export to CSV df.to_csv('output.csv', index=False) print(f"Exported {len(df)} events to CSV") ``` ## Filtering Events and Re-exporting Apply filters and save filtered data: ```python from flowio import FlowData, create_fcs import numpy as np # Read original file flow = FlowData('sample.fcs') events = flow.as_array(preprocess=False) # Apply filtering (example: threshold on first channel) fsc_idx = 0 threshold = 500 mask = events[:, fsc_idx] > threshold filtered_events = events[mask] print(f"Original events: {len(events)}") print(f"Filtered events: {len(filtered_events)}") # Create new FCS file with filtered data. # create_fcs wants a binary file handle + a FLATTENED 1-D event array, and the # metadata keyword is metadata_dict= (not metadata=). with open('filtered.fcs', 'wb') as fh: create_fcs(fh, filtered_events.flatten(), flow.pnn_labels, opt_channel_names=flow.pns_labels, metadata_dict={**flow.text, '$SRC': 'Filtered data'}) ``` ## Extracting Specific Channels Extract and process specific channels: ```python from flowio import FlowData import numpy as np flow = FlowData('sample.fcs') events = flow.as_array() # Extract fluorescence channels only fluoro_indices = flow.fluoro_indices fluoro_data = events[:, fluoro_indices] fluoro_names = [flow.pnn_labels[i] for i in fluoro_indices] print(f"Fluorescence channels: {fluoro_names}") print(f"Shape: {fluoro_data.shape}") # Calculate statistics per channel for i, name in enumerate(fluoro_names): channel_data = fluoro_data[:, i] print(f"\n{name}:") print(f" Mean: {channel_data.mean():.2f}") print(f" Median: {np.median(channel_data):.2f}") print(f" Std Dev: {channel_data.std():.2f}") ``` -
workflows.md 7.5 KB
# FlowIO Core Workflows Detailed code for the four primary FlowIO operations: reading/parsing, metadata extraction, creating files, and exporting/modifying. See `api_reference.md` for the full class/function signatures. ## Reading and Parsing FCS Files The `FlowData` class is the primary interface for reading FCS files. **Standard reading:** ```python from flowio import FlowData # Basic reading flow = FlowData('sample.fcs') # Access attributes version = flow.version # '3.0', '3.1', etc. event_count = flow.event_count # Number of events channel_count = flow.channel_count # Number of channels pnn_labels = flow.pnn_labels # Short channel names pns_labels = flow.pns_labels # Descriptive stain names # Get event data events = flow.as_array() # Preprocessed (gain, log scaling applied) raw_events = flow.as_array(preprocess=False) # Raw data ``` **Memory-efficient metadata reading** (no event data): ```python # Only parse TEXT segment, skip DATA and ANALYSIS flow = FlowData('sample.fcs', only_text=True) # Access metadata metadata = flow.text # Dictionary of TEXT segment keywords print(metadata.get('$DATE')) # Acquisition date print(metadata.get('$CYT')) # Instrument name ``` **Handling problematic files** (offset discrepancies or errors): ```python # Ignore offset discrepancies between HEADER and TEXT sections flow = FlowData('problematic.fcs', ignore_offset_discrepancy=True) # Use HEADER offsets instead of TEXT offsets flow = FlowData('problematic.fcs', use_header_offsets=True) # Ignore offset errors entirely flow = FlowData('problematic.fcs', ignore_offset_error=True) ``` **Excluding null channels:** ```python # Exclude specific channels during parsing flow = FlowData('sample.fcs', null_channel_list=['Time', 'Null']) ``` ## Extracting Metadata and Channel Information FCS files contain rich metadata in the TEXT segment. **Common metadata keywords:** ```python flow = FlowData('sample.fcs') # File-level metadata text_dict = flow.text acquisition_date = text_dict.get('$DATE', 'Unknown') instrument = text_dict.get('$CYT', 'Unknown') data_type = flow.data_type # 'I', 'F', 'D', 'A' # Channel metadata for i in range(flow.channel_count): pnn = flow.pnn_labels[i] # Short name (e.g., 'FSC-A') pns = flow.pns_labels[i] # Descriptive name (e.g., 'Forward Scatter') pnr = flow.pnr_values[i] # Range/max value print(f"Channel {i}: {pnn} ({pns}), Range: {pnr}") ``` **Channel type identification** (FlowIO auto-categorizes channels): ```python # Get indices by channel type scatter_idx = flow.scatter_indices # [0, 1] for FSC, SSC fluoro_idx = flow.fluoro_indices # [2, 3, 4] for FL channels time_idx = flow.time_index # Index of time channel (or None) # Access specific channel types events = flow.as_array() scatter_data = events[:, scatter_idx] fluorescence_data = events[:, fluoro_idx] ``` **ANALYSIS segment** (if present, processed results): ```python if flow.analysis: analysis_keywords = flow.analysis # Dictionary of ANALYSIS keywords print(analysis_keywords) ``` ## Creating New FCS Files Generate FCS files from NumPy arrays or other data sources. **Basic creation:** > **`create_fcs` call contract (two easy mistakes):** > 1. First arg is a **writable binary file handle** (`open(path, 'wb')`), not a > path string — it calls `file_handle.seek(0)`. > 2. `event_data` must be a **flattened 1-D** sequence (channel-interleaved); pass > `array.flatten()`, not the 2-D `(events, channels)` matrix. > 3. The metadata keyword is `metadata_dict=` (not `metadata=`, which belongs to > `write_fcs`). ```python import numpy as np from flowio import create_fcs # Create event data (rows=events, columns=channels) events = (np.random.rand(10000, 5) * 1000).astype('float32') # Define channel names channel_names = ['FSC-A', 'SSC-A', 'FL1-A', 'FL2-A', 'Time'] # Create FCS file with open('output.fcs', 'wb') as fh: create_fcs(fh, events.flatten(), channel_names) ``` **With descriptive channel names:** ```python # Add optional descriptive names (PnS) channel_names = ['FSC-A', 'SSC-A', 'FL1-A', 'FL2-A', 'Time'] descriptive_names = ['Forward Scatter', 'Side Scatter', 'FITC', 'PE', 'Time'] with open('output.fcs', 'wb') as fh: create_fcs(fh, events.flatten(), channel_names, opt_channel_names=descriptive_names) ``` **With custom metadata:** ```python # Add TEXT segment metadata metadata = { '$SRC': 'Python script', '$DATE': '19-OCT-2025', '$CYT': 'Synthetic Instrument', '$INST': 'Laboratory A' } with open('output.fcs', 'wb') as fh: create_fcs(fh, events.flatten(), channel_names, opt_channel_names=descriptive_names, metadata_dict=metadata) ``` **Note:** FlowIO exports as FCS 3.1 with single-precision floating-point data. ## Exporting Modified Data **Approach 1 — `write_fcs()` method:** ```python from flowio import FlowData # Read original file flow = FlowData('original.fcs') # Write with updated metadata flow.write_fcs('modified.fcs', metadata={'$SRC': 'Modified data'}) ``` **Approach 2 — extract, modify, and recreate** (for modifying event data): ```python from flowio import FlowData, create_fcs # Read and extract data flow = FlowData('original.fcs') events = flow.as_array(preprocess=False) # Modify event data events[:, 0] = events[:, 0] * 1.5 # Scale first channel # Create new FCS file with modified data (handle + flattened + metadata_dict=) with open('modified.fcs', 'wb') as fh: create_fcs(fh, events.flatten(), flow.pnn_labels, opt_channel_names=flow.pns_labels, metadata_dict=flow.text) ``` ## Handling Multi-Dataset FCS Files Some FCS files contain multiple datasets in a single file. **Detecting multi-dataset files:** ```python from flowio import FlowData, MultipleDataSetsError try: flow = FlowData('sample.fcs') except MultipleDataSetsError: print("File contains multiple datasets") # Use read_multiple_data_sets() instead ``` **Reading all datasets:** ```python from flowio import read_multiple_data_sets # Read all datasets from file datasets = read_multiple_data_sets('multi_dataset.fcs') print(f"Found {len(datasets)} datasets") # Process each dataset for i, dataset in enumerate(datasets): print(f"\nDataset {i}:") print(f" Events: {dataset.event_count}") print(f" Channels: {dataset.pnn_labels}") # Get event data for this dataset events = dataset.as_array() print(f" Shape: {events.shape}") print(f" Mean values: {events.mean(axis=0)}") ``` **Reading a specific dataset:** ```python from flowio import FlowData # Read first dataset (nextdata_offset=0) first_dataset = FlowData('multi.fcs', nextdata_offset=0) # Read second dataset using NEXTDATA offset from first next_offset = int(first_dataset.text['$NEXTDATA']) if next_offset > 0: second_dataset = FlowData('multi.fcs', nextdata_offset=next_offset) ``` ## Data Preprocessing FlowIO applies standard FCS preprocessing transformations when `preprocess=True`: 1. **Gain scaling:** Multiply values by PnG (gain) keyword 2. **Logarithmic transformation:** Apply PnE exponential transformation if present - Formula: `value = a * 10^(b * raw_value)` where PnE = "a,b" 3. **Time scaling:** Convert time values to appropriate units ```python # Preprocessed data (default) preprocessed = flow.as_array(preprocess=True) # Raw data (no transformations) raw = flow.as_array(preprocess=False) ```
-
-
SKILL.md 5.9 KB
--- name: alterlab-flowio description: Parse and write FCS (Flow Cytometry Standard) files v2.0-3.1 with FlowIO — extract event data as NumPy arrays, read $-keyword metadata and channel/parameter definitions, and convert events to CSV or pandas DataFrame. Use when loading raw .fcs flow-cytometry files, inspecting channels and metadata, or preprocessing cytometry data for downstream gating and analysis. Part of the AlterLab Academic Skills suite. license: MIT allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*) compatibility: "Self-contained — runs under `uv run python` with `flowio` installed (1.4.0 as of 2026-09; Python 3.9+, NumPy is the only dependency). No API key or account required." metadata: skill-author: AlterLab version: "1.0.1" last_updated: "2026-09-23" --- # FlowIO: Flow Cytometry Standard File Handler ## Overview FlowIO is a lightweight Python library for reading and writing Flow Cytometry Standard (FCS) files. Parse FCS metadata, extract event data, and create new FCS files with minimal dependencies. Supports FCS versions 2.0, 3.0, and 3.1 — ideal for backend services, data pipelines, and basic cytometry file operations. ## When to Use This Skill Use this skill when: - FCS files require parsing or metadata extraction - Flow cytometry data needs conversion to NumPy arrays - Event data requires export to FCS format - Multi-dataset FCS files need separation - Channel information (scatter, fluorescence, time) must be extracted - Cytometry files need validation or inspection - Pre-processing is needed before advanced analysis **Related tool:** For advanced analysis (compensation, transformation, gating, FlowJo 10 workspace import), recommend the **FlowKit** library (1.3.2 as of 2026-09), which is built on FlowIO by the same author. ### Does NOT Trigger | Scenario | Use Instead | |----------|-------------| | Compensation, transforms, gating, or FlowJo/GatingML workspaces | FlowKit (companion library, not a skill in this suite) | | Clustering / dimensionality reduction of cytometry events | `alterlab-scanpy`, `alterlab-umap` | | Statistical comparison of populations across samples | `alterlab-statistical-analysis` | | Mass-cytometry or imaging-based spatial single-cell data | `alterlab-squidpy-spatial` | | Registering and versioning the FCS files themselves | `alterlab-lamindb` | ## Installation ```bash uv pip install flowio # 1.4.0 as of 2026-09 ``` Requires Python 3.9 or later; NumPy is the only runtime dependency. ## Quick Start ```python from flowio import FlowData # Read FCS file and inspect flow = FlowData('experiment.fcs') print(f"FCS Version: {flow.version}") print(f"Events: {flow.event_count}") print(f"Channels: {flow.pnn_labels}") # Get event data as NumPy array, shape (events, channels) events = flow.as_array() ``` ```python import numpy as np from flowio import create_fcs # Write a new FCS file from a NumPy array. # Gotcha: create_fcs takes a WRITABLE BINARY FILE HANDLE (not a path) and a # FLATTENED 1-D event array — pass data.flatten(), not the 2-D matrix. data = np.array([[100, 200, 50], [150, 180, 60]], dtype='float32') # 2 events, 3 channels with open('output.fcs', 'wb') as fh: create_fcs(fh, data.flatten(), ['FSC-A', 'SSC-A', 'FL1-A']) ``` ## Core Workflow 1. **Read** — Construct a `FlowData('file.fcs')` instance. Use `only_text=True` for metadata-only (memory-efficient) reads; pass offset/null-channel flags for problematic files. 2. **Inspect** — Read `flow.version`, `flow.event_count`, `flow.pnn_labels`, `flow.pns_labels`, channel-type indices, and the `flow.text` metadata dict. 3. **Extract** — Get a NumPy array via `flow.as_array()` (preprocessed) or `flow.as_array(preprocess=False)` (raw). Slice by channel type as needed. 4. **Transform / export** — Convert to a pandas DataFrame or CSV; or write a new FCS file with `flow.write_fcs(path, ...)` (takes a path) or `create_fcs(fh, data.flatten(), ...)` (takes a binary file handle + flattened events). Output is always FCS 3.1, single-precision float. 5. **Multi-dataset** — If a file holds multiple datasets, use `read_multiple_data_sets()` instead of the constructor. ## Routing Guidance - **Need exact signatures, attributes, exceptions, or FCS keyword definitions?** Read `references/api_reference.md`. - **Doing one of the core operations (read/parse, metadata, create, export, multi-dataset, preprocessing)?** Read `references/workflows.md` for full code. - **Need a task recipe (inspect a file, batch a directory, FCS→CSV, filter events, extract channels)?** Read `references/recipes.md`. - **Hitting an error, or want best practices / file-structure / troubleshooting?** Read `references/error-handling-and-troubleshooting.md`. ## References - `references/api_reference.md` — Complete `FlowData` class, utility functions (`read_multiple_data_sets`, `create_fcs`), exception classes, FCS file structure, common TEXT-segment keywords, channel types, and example workflows. - `references/workflows.md` — Full code for the core operations: reading/parsing, metadata & channel extraction, creating files, exporting/modifying, multi-dataset handling, and data preprocessing. - `references/recipes.md` — Worked examples: inspecting contents, batch processing a directory, FCS→CSV conversion, event filtering & re-export, and channel extraction with statistics. - `references/error-handling-and-troubleshooting.md` — Exception-handling patterns, best practices, FCS file-structure notes, a troubleshooting table, and integration notes (NumPy, pandas, FlowKit, web apps). ## Summary FlowIO provides essential FCS file handling for flow cytometry workflows — use it for parsing, metadata extraction, and file creation. For simple file operations and data extraction, FlowIO alone is sufficient; for complex analysis (compensation, gating), integrate with FlowKit or other specialized tools. Part of the AlterLab Academic Skills suite.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.