Getting Started with CyFj11

library(CyFj11)
library(flowWorkspace)
#> As part of improvements to flowWorkspace, some behavior of
#> GatingSet objects has changed. For details, please read the section
#> titled "The cytoframe and cytoset classes" in the package vignette:
#> 
#>   vignette("flowWorkspace-Introduction", "flowWorkspace")
library(flowCore)
library(CytoML)
library(ggcyto)
#> Loading required package: ggplot2
#> Loading required package: ncdfFlow
#> Loading required package: BH

Introduction

CyFj11 is an R package designed to bridge the gap between FlowJo v11 workspaces and the R flowWorkspace ecosystem. It provides asymmetric format conversion capabilities:

  • Import: Full support for parsing FlowJo v11 workspaces (.flowjo files) into R GatingSet objects
  • Export: Generation of FlowJo v10 workspace files (.wsp) from GatingSet objects with broad compatibility

This asymmetric design reflects the technical realities of developing against proprietary formats without vendor documentation. While FlowJo v11 uses a complex JSON schema with extensive UUID cross-referencing that makes direct export impractical, the v10 XML format follows the documented Gating-ML 2.0 standard, making it feasible to generate valid workspaces.

Comparison with Other FlowJo Import Packages

Several Bioconductor packages can import FlowJo workspaces. Here’s how CyFj11 compares:

Package FlowJo Version Input Format Output Key Features
CyFj11 v11 .flowjo (ZIP) GatingSet v11 native, comp
CytoML v10 .wsp (XML) GatingSet Gating-ML 2.0 standard
flowWorkspace v10 .wsp (XML) GatingSet Basic v10 import
flowWorkspaceData v10 .wsp (XML) GatingSet Example datasets

Key advantages of CyFj11:

  1. Native FlowJo v11 support: CyFj11 is the only Bioconductor package that directly parses FlowJo v11’s .flowjo format (ZIP archive with JSON)
  2. No intermediate conversion needed: Unlike CytoML which requires FlowJo v10 XML, CyFj11 reads v11 workspaces directly
  3. Preserves v11-specific features: Handles compensation matrices, transformations, and population definitions from v11
  4. Export capability: Can export GatingSet objects back to FlowJo v10 .wsp format for round-trip workflows

When to use CyFj11: - You have FlowJo v11 workspace files (.flowjo extension) - You want to import workspaces without manually exporting to v10 format in FlowJo - You need to preserve v11-specific compensation and transformation information

When to use CytoML: - You have FlowJo v10 workspace files (.wsp XML format) - You need Gating-ML 2.0 standard compliance - You’re working with legacy v10 workflows

Installation

To install CyFj11, ensure you have the required dependencies:

# Install from GitHub (or CRAN if available)
# devtools::install_github("baj12/CyFj11")

# Load the package
library(CyFj11)

Basic Workflow

The typical workflow with CyFj11 involves three steps:

  1. Import a FlowJo v11 workspace
  2. Analyze the data in R
  3. Export results back to FlowJo (v10 format)

Let’s walk through this process with a simple example.

Importing a FlowJo v11 Workspace

First, we’ll read a FlowJo v11 workspace file:

# Set the path to your FlowJo workspace file
workspace_path <- system.file("extdata", "test.data.flowjo", package = "CyFj11")

# Read the FlowJo v11 workspace
ws <- read_flowjo11_workspace(workspace_path)

# Examine the workspace structure
print(names(ws))
#>  [1] "path"                  "manifest"              "json"                 
#>  [4] "groups"                "dataSources"           "populationDefinitions"
#>  [7] "populations"           "schemaVersion"         "analysisUUID"         
#> [10] "uri"                   "reports"               "compoundParameterSets"
#> [13] "compoundPopulations"   "paramsetDefinitions"   "platforms"            
#> [16] "cytometers"            "analysisRoot"          "timestamp"

The read_flowjo11_workspace() function parses the ZIP-based FlowJo v11 format and extracts the JSON data structures. The returned object contains all the information from the workspace, including groups, samples, gates, and populations.

Converting to GatingSet

Next, we convert the parsed workspace to a GatingSet object, which is the standard format for flow cytometry data in R:

workspace_fcs <- system.file("extdata", "", package = "CyFj11")
# fj11_to_gatingset() returns a named list of GatingSet objects, one per sample
gsList <- fj11_to_gatingset(
    fj11_workspace = ws,
    group_name = 1,
    path = workspace_fcs
)
#> ===========================================
#>   Resolving FCS File Paths
#> ===========================================
#> 
#> Resolving1sample paths...
#> OK sample04.fcs
#> 
#> ===========================================
#>   Resolution Summary
#> ===========================================
#> Total samples:  1
#>   Found:        1 (100.0%)
#>   Missing:      0 (0.0%)
#>   Multiple:     0 (0.0%)
#> ===========================================
#> Created mapping for1samples
#> Warning in comp_platform_or_legacy(sample_uuid, custom_comp, sample_comp_map, :
#> No compensation found for sample: 9d31a6f4-342d-4b66-a508-1f7d470e6bd9
#> Warning in pop_warn_failed_add(e, node_name, parent, gate_obj_adjusted, : Failed to add population 'ch8High': Expected string vector of length 1
#>   Parent: cells/Singlets/Single_cells/cd8Pos/Q4:PE-A :: CD4-,PerCP-Cy5-5-A :: CD14- : cells/Singlets/Single_cells/cd8Pos/Q3:PE-A :: CD4+,PerCP-Cy5-5-A :: CD14- : cells/Singlets/Single_cells/cd8Pos/Q1:PE-A :: CD4-,PerCP-Cy5-5-A :: CD14+ : cells/Singlets/Single_cells/cd8Pos/Q2:PE-A :: CD4+,PerCP-Cy5-5-A :: CD14+
#>   Gate type: rectangleGate
#> Warning in pop_warn_failed_add(e, node_name, parent, gate_obj_adjusted, : Failed to add population 'cd8HighLow': Expected string vector of length 1
#>   Parent: cells/Singlets/Single_cells/cd8Pos/Q4:PE-A :: CD4-,PerCP-Cy5-5-A :: CD14- : cells/Singlets/Single_cells/cd8Pos/Q3:PE-A :: CD4+,PerCP-Cy5-5-A :: CD14- : cells/Singlets/Single_cells/cd8Pos/Q1:PE-A :: CD4-,PerCP-Cy5-5-A :: CD14+ : cells/Singlets/Single_cells/cd8Pos/Q2:PE-A :: CD4+,PerCP-Cy5-5-A :: CD14+
#>   Gate type: rectangleGate
#> Warning in pop_warn_unresolved(deferred, gh): Could not resolve all component paths for logical gate: Not gate of ch8High
#>   Missing components: ch8High
#> Warning in pop_warn_unresolved(deferred, gh): Could not resolve all component paths for logical gate: And gate of ch8High, cd8HighLow
#>   Missing components: ch8High, cd8HighLow
#> Warning in pop_warn_unresolved(deferred, gh): Could not resolve all component paths for logical gate: Or gate of ch8High, cd8HighLow
#>   Missing components: cd8HighLow, ch8High
#> Warning in pop_warn_unresolved(deferred, gh): Could not resolve all component paths for logical gate: Not gate of cd8HighLow
#>   Missing components: cd8HighLow
#> done!

pretty_print_flowjo(workspace_path)
#> Pretty printed JSON saved to:test.data_pretty.json
#> [1] "test.data_pretty.json"
# Check the number of samples
length(gsList)
#> [1] 1

# View sample names for the first sample
flowCore::sampleNames(gsList[[1]])
#> [1] "sample04.fcs"

# Work with a single sample directly
gs <- gsList[[1]]

# Or merge all samples into one GatingSet
# (may overwrite per-sample transform settings)
# gs_merged <- flowWorkspace::merge_list_to_gs(gsList)

Note: If you don’t know the group name you can leave it empty and you will be prompted to choose:

1: Acquired Data
2: Compensation Data
3: Experiment Data

Note: fj11_to_gatingset() returns a list of GatingSet objects, one per sample. This preserves per-sample gate variations (tailored gates) from FlowJo v11. To work with all samples in a single GatingSet, use flowWorkspace::merge_list_to_gs(gsList), but be aware this may discard sample-specific transformation settings.

Note that you’ll need to provide the path to your FCS files. The fj11_to_gatingset() function will attempt to locate the FCS files referenced in the workspace.

Examining the GatingSet

Once we have a GatingSet, we can examine its structure:

# View the population hierarchy
gs_get_pop_paths(gs)
#>  [1] "root"                                                                      
#>  [2] "/Lymphocytes-test"                                                         
#>  [3] "/cells"                                                                    
#>  [4] "/cells/Singlets"                                                           
#>  [5] "/cells/Singlets/Single_cells"                                              
#>  [6] "/cells/Singlets/Single_cells/cd8Pos"                                       
#>  [7] "/cells/Singlets/Single_cells/cd8Pos/Q1:PE-A :: CD4-,PerCP-Cy5-5-A :: CD14+"
#>  [8] "/cells/Singlets/Single_cells/cd8Pos/Q2:PE-A :: CD4+,PerCP-Cy5-5-A :: CD14+"
#>  [9] "/cells/Singlets/Single_cells/cd8Pos/Q3:PE-A :: CD4+,PerCP-Cy5-5-A :: CD14-"
#> [10] "/cells/Singlets/Single_cells/cd8Pos/Q4:PE-A :: CD4-,PerCP-Cy5-5-A :: CD14-"

# Get population statistics
stats <- gs_pop_get_stats(gs)
head(stats)
#>          sample                                 pop count
#>          <char>                              <char> <num>
#> 1: sample04.fcs                                root  8000
#> 2: sample04.fcs                   /Lymphocytes-test  4816
#> 3: sample04.fcs                              /cells  7417
#> 4: sample04.fcs                     /cells/Singlets  7344
#> 5: sample04.fcs        /cells/Singlets/Single_cells  7296
#> 6: sample04.fcs /cells/Singlets/Single_cells/cd8Pos     0

plot(gs)

autoplot(gs, "cells", bins = 80)

autoplot(gs, "cd8Pos", bins = 80, axis_inverse_trans = TRUE)


gate <- gh_pop_get_gate(gs, "cd8Pos")
gh = gs[[1]][[1]]
gh_get_transformations(gh,channel = "APC-A")
#> function (x, deriv = 0) 
#> {
#>     deriv <- as.integer(deriv)
#>     if (deriv < 0 || deriv > 3) 
#>         stop("'deriv' must be between 0 and 3")
#>     if (deriv > 0) {
#>         z0 <- double(z$n)
#>         z[c("y", "b", "c")] <- switch(deriv, list(y = z$b, b = 2 * 
#>             z$c, c = 3 * z$d), list(y = 2 * z$c, b = 6 * z$d, 
#>             c = z0), list(y = 6 * z$d, b = z0, c = z0))
#>         z[["d"]] <- z0
#>     }
#>     res <- stats:::.splinefun(x, z)
#>     if (deriv > 0 && z$method == 2 && any(ind <- x <= z$x[1L])) 
#>         res[ind] <- ifelse(deriv == 1, z$y[1L], 0)
#>     res
#> }
#> <bytecode: 0x60fbbbf2e228>
#> <environment: 0x60fbe54c0228>
#> attr(,"type")
#> [1] "biexp"
#> attr(,"parameters")
#> attr(,"parameters")$channelRange
#> [1] 256
#> 
#> attr(,"parameters")$maxValue
#> [1] 262144
#> 
#> attr(,"parameters")$neg
#> [1] 0
#> 
#> attr(,"parameters")$pos
#> [1] 4
#> 
#> attr(,"parameters")$widthBasis
#> [1] -10

Exporting Back to FlowJo

After performing analysis in R, you can export your results back to FlowJo v10 format:

# Export to FlowJo v10 format
export_path <- tempfile(fileext = ".wsp")
export_flowjo10_workspace(gs[[1]], export_path)
#> Successfully exported FlowJo v10 workspace to: /tmp/RtmpL7KFxg/file1c6c4461176b.wsp
#> [1] TRUE

# Verify the export was successful
file.exists(export_path)
#> [1] TRUE

Note:

warnings about invalid transformations are normal, as we don’t have any transformations set in FlowJo 11 and the gatingset.

The exported workspace can be opened in FlowJo v11, which maintains backward compatibility with v10 workspaces.

Next Steps

For more detailed information about compensation handling, see vignette("compensation-workflow").

For exporting GatingSet objects back to FlowJo v10 format, see ?export_flowjo10_workspace.

For function-specific documentation, use the help system:

?read_flowjo11_workspace
?fj11_to_gatingset
?export_flowjo10_workspace
library(CytoML)
ws <- CytoML::open_flowjo_xml(export_path)
gs_back <- CytoML::flowjo_to_gatingset(ws, name = "All Samples",
                                            path = workspace_fcs)

# Get population statistics
stats <- gs_pop_get_stats(gs_back)
head(stats)

plot(gs_back)
ggcyto::autoplot(gs_back, "cells", bins = 80)
autoplot(gs_back, "cd8Pos", bins = 80)
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] ggcyto_1.41.1        ncdfFlow_2.59.1      BH_1.90.0-1         
#> [4] ggplot2_4.0.3        CytoML_2.25.0        flowCore_2.25.1     
#> [7] flowWorkspace_4.25.1 CyFj11_0.99.113      BiocStyle_2.41.0    
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6          xfun_0.61             bslib_0.12.0         
#>  [4] Biobase_2.73.2        lattice_0.23-1        tzdb_0.5.0           
#>  [7] vctrs_0.7.3           tools_4.6.1           generics_0.1.4       
#> [10] stats4_4.6.1          tibble_3.3.1          pkgconfig_2.0.3      
#> [13] Matrix_1.7-6          data.table_1.18.6.1   RColorBrewer_1.1-3   
#> [16] S7_0.2.2              S4Vectors_0.51.10     graph_1.91.0         
#> [19] lifecycle_1.0.5       compiler_4.6.1        farver_2.1.2         
#> [22] htmltools_0.5.9       sys_3.4.3             buildtools_1.0.0     
#> [25] sass_0.4.10           yaml_2.3.12           pillar_1.11.1        
#> [28] hexbin_1.28.6         jquerylib_0.1.4       tidyr_1.3.2          
#> [31] DelayedArray_0.39.8   cachem_1.1.0          abind_1.4-8          
#> [34] RProtoBufLib_2.25.0   tidyselect_1.2.1      digest_0.6.39        
#> [37] dplyr_1.2.1           purrr_1.2.2           labeling_0.4.3       
#> [40] maketools_1.3.2       fastmap_1.2.0         grid_4.6.1           
#> [43] SparseArray_1.13.4    cli_3.6.6             magrittr_2.0.5       
#> [46] S4Arrays_1.13.2       RBGL_1.89.0           XML_3.99-0.25        
#> [49] readr_2.2.0           withr_3.0.3           scales_1.4.0         
#> [52] XVector_0.53.0        rmarkdown_2.32        matrixStats_1.5.0    
#> [55] otel_0.2.0            gridExtra_2.3.1       cytolib_2.25.0       
#> [58] hms_1.1.4             evaluate_1.0.5        knitr_1.52           
#> [61] IRanges_2.47.5        rlang_1.3.0           Rcpp_1.1.2           
#> [64] glue_1.8.1            Rgraphviz_2.57.2      BiocManager_1.30.27  
#> [67] xml2_1.6.0            BiocGenerics_0.59.12  jsonlite_2.0.0       
#> [70] R6_2.6.1              plyr_1.8.9            MatrixGenerics_1.25.0