Interactive visualization of single-cell RNA-seq data, built on top of Shiny.
cerebroAppLite supports loading pre-processed single-cell data, exploring projections and gene expression, browsing marker genes and enriched pathways, and inspecting group compositions—all through an interactive web interface. The sections below cover the key features.
For the original feature set and data preparation workflows, refer to the upstream cerebroApp documentation at https://romanhaa.github.io/cerebroApp/—everything described there works the same way here.
A community fork of cerebroApp by Roman Hillje, developed and maintained by mihem.
remotes::install_github('mihem/cerebroAppLite')convertSeuratToCerebro() handles the entire export process in a single call: reading the Seurat object (.rds on disk, or one already loaded in memory), renaming grouping variables, loading marker gene tables, calculating most-expressed genes, and saving a .crb file.
library(cerebroAppLite)
convertSeuratToCerebro(
seurat_file = "my_seurat.rds", # or an in-memory Seurat object
result_dir = "output/",
assay = "RNA",
slot = "data",
experiment_name = "My Experiment",
organism = "Human",
groups = c("sample_id", "condition", "cell_type"),
groups_naming = list(
"sample_id" = "sample",
"cell_type" = "cluster"
),
marker_file = "markers.csv", # optional: .csv/.tsv/.txt/.tab
expression_matrix_mode = "h5" # "embedded" | "bpcells" | "h5", see §3
)
# → saves output/cerebro_my_seurat.crb (+ sibling .h5 / .bpcells/ when applicable)Instead of running launchCerebro() interactively, you can generate a self-contained Shiny app directory with all data and source files bundled. This is useful for deploying to a Shiny server or sharing with collaborators.
createShinyApp(
cerebro_data = c(
`snRNAseq` = "output/cerebro_snrnaseq.crb",
`Sample2` = "output/cerebro_sample2.crb"
),
result_dir = "my_app/",
welcome_message = "<h2>My Single-Cell Atlas</h2>", # rendered via HTML()
port = 8080,
host = "127.0.0.1",
max_request_size = 8000, # MB
overwrite = TRUE
)
# → run with shiny::runApp("my_app/") or deploy to Shiny Servercerebro_data is required and must be a named vector / list of .crb (or .rds) paths — names become the dataset labels users switch between in the app. result_dir is optional. Sibling <stem>.bpcells/ and <stem>.h5 artefacts produced by the external backends are detected and copied into the bundle automatically (see §3). Other knobs available: colors, cerebro_options, crb_pick_smallest_file, show_upload_ui, point_size, variable_to_compare — run ?createShinyApp for the full list.
exportFromSeurat() (and convertSeuratToCerebro()) accept expression_matrix_mode = c("embedded", "bpcells", "h5") for how the count matrix is persisted alongside the .crb:
| Backend | Where the matrix lives | .crb size |
Load behaviour | Extra packages | Portability |
|---|---|---|---|---|---|
embedded |
inside the .crb itself |
full matrix included | always in memory after readRDS |
— | single-file; works with any reader |
bpcells |
sibling <stem>.bpcells/ directory |
tiny (handle only) | lazy — IterableMatrix reads on slice access |
BPCells |
.crb + sibling dir must travel together |
h5 |
sibling <stem>.h5 file (TENx CSC) |
tiny (tag only) | lazy — HDF5Array::TENxMatrix seed; queries stream from disk |
HDF5Array |
.crb + sibling .h5 must travel together |
Benchmark trade-offs on a PBMC fixture (38,606 genes × 147,756 cells):
| metric | embedded | bpcells | h5 |
|---|---|---|---|
| total disk | 681 MB | 592 MB | 391 MB |
| open URL → dataset visible (browser) | 14.3 s | 9.2 s | 8.7 s |
| RAM (RSS) on the server after attach | 4.5 GB | 1.2 GB | 1.1 GB |
| single-gene query, once loaded (cold) | 0.51 s | 0.74 s | 0.01 s |
Browser-side TTFB / DOM-ready / load are within ~10 ms of each other across backends — all the divergence in the second row is server-side R work (readRDS + .attachExternalExpression()) plus a constant ~5 s Shiny session handshake.
Disk-size caveat — it depends on fixture size. The 391 MB h5 total above is for a large fixture where HDF5's default gzip filter on the TENx CSC layout compresses sparse counts well (~6× vs uncompressed). On small/dense fixtures the trade-off can flip — for example on Roman Hillje's inst/extdata/v1.4/example.h5 (1000 cells × 500 genes) the sibling .h5 is actually larger than the equivalent embedded .crb would have been, because metadata + chunk overhead dominates over compressed payload. The h5 win on RAM and load time is consistent across fixture sizes; the win on disk only emerges at scale. Since 1.7.0 the bpcells exporter automatically calls BPCells::convert_matrix_type("uint32_t") whenever the input values are losslessly representable as non-negative integers (the typical scRNA-seq counts case), which triggers BPCells's bit-packed integer storage and shrinks the sibling by ~5× vs raw double; for normalised float values (slot = "data" / "scale.data") the exporter falls back to raw storage to avoid silent precision loss.
Picking one:
h5(recommended default) — fastest startup, lowest RAM, fastest queries; smallest disk on large fixtures (subject to the caveat above). The TENx CSC layout aligns with how Cerebro reads expression (per-gene = single column slice), and HDF5 page-caching makes repeated reads memory-fast without committing the whole matrix to RAM. Requires theHDF5ArrayBioconductor package on the host.bpcells— RAM-constrained host with very large matrices, or workloads dominated by chunk-level batched operations rather than per-gene reads. Disk size is similar to h5 on integer counts (bit-packed since 1.7.0); per-gene query is ~0.7 s, so chunk-level batched ops benefit more than per-gene streaming.embedded— single-file convenience (no sibling to manage), or compatibility with very old.crbreaders. ~14 s end-to-end and pins the full matrix into RAM per loaded copy. Best for small datasets or one-shot scripts.
For reference, before the 1.7.0 lazy h5 refactor, h5 attach was eager (rhdf5::h5read + full dgCMatrix reconstruction), giving ~33 s open-URL time, ~11 GB RSS, and ~0.45 s queries — i.e. lazy-h5 is the same backend with attach ~263× faster, RAM ~10× smaller, queries ~45× faster, web load ~4× faster.
createShinyApp() already knows about both <stem>.bpcells/ and <stem>.h5 and copies them next to the bundled .crb. The Shiny runtime re-resolves the sibling location on load via getExpressionBackend()$location relative to the .crb's parent directory, so the bundle stays portable.
- Seurat v5 support throughout (
GetAssayData()-based slot access) - Loading spinners on all plot outputs
The package ships with a testthat + shinytest2 suite under tests/testthat/. CI (.github/workflows/R-cmd-check.yaml, .github/workflows/R-tests.yaml) runs it on every PR; locally:
# whole suite (loads dev source via pkgload::load_all)
devtools::test()
# one file at a time
devtools::test(filter = "app-inst") # shinytest2 end-to-end smoke
devtools::test(filter = "exportFromSeurat") # exporter-only
devtools::test(filter = "r-functions") # plain unit teststest-app-inst.R drives a real headless Chrome via chromote and relies on NOT_CRAN=true (already set in tests/testthat/setup.R). Install the extras once:
install.packages(c("testthat", "shinytest2", "chromote"))
# or pull the whole Suggests block:
devtools::install_dev_deps()From the shell (CI / scripting):
# what R CMD check effectively runs (uses installed package, not dev source)
Rscript -e 'testthat::test_dir("tests/testthat")'
# only the shinytest2 suite, with a verbose reporter
NOT_CRAN=true Rscript -e 'devtools::test(filter="app-inst", reporter="summary")'Snapshot diffs from expect_snapshot() land under tests/testthat/_snaps/; review them with testthat::snapshot_review() and accept with testthat::snapshot_accept() only after confirming the new output is correct.
See tests/README.md for the full layout, the inst_dir resolution rule, and the gotcha about regenerating inst/extdata/v1.4/example.crb after R6 method changes (a stale fixture surfaces as a misleading Shiny app did not become stable in 15000ms from shinytest2).
MIT — see LICENSE.md. Original cerebroApp © Roman Hillje; cerebroAppLite fork by mihem.