Claude Skill

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

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_bioinformatics_alterlab-flowio-e4836c0.zip · 14 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-flowio
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git 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

  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.

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.

No comments yet.

Reviews (0)

No reviews yet.

Related