Skip to contents

When to use this workflow

Use this workflow when your data comes from standard 10x Chromium short reads processed with STARsolo or STAR. These tools can output a per-cell junction count table alongside the gene expression matrix. Matisse uses those junction counts to compute PSI (Percent Spliced In) for each splicing event in each cell.

If you have full-transcript counts from a long-read platform or a transcript quantifier (Bagpiper, FLAMES, LIQA), see the Long-read workflow instead.


What you need

Input Description
Seurat object Already processed: normalisation, UMAP, cluster labels.
Junction count matrix Cells × junctions sparse matrix of read counts. Row names are cell barcodes; column names are junction IDs. Produced by STARsolo with --soloFeatures SJ.
Event annotation A data.frame with one row per splicing event, or a path to a SUPPA2 .ioe file. Required columns when supplied as a data.frame: event_id, gene_id, chr, strand, event_type, inclusion_features, exclusion_features (the last two hold semicolon-separated junction IDs). Matisse does not ship a heuristic event caller — bring your own.

Step 1 – Build the Matisse object (PSI is computed in the same call)

library(Matisse)

obj <- CreateMatisseObject(
  seurat          = seu,
  junction_counts = jxn_counts,   # cells x junctions from STARsolo
                                   # (junction IDs encode coordinates, e.g.
                                   #  "chr1-12345-67890-+", auto-parsed for
                                   #  sashimi plots)
  events          = event_df,     # data.frame OR path(s) to SUPPA2 .ioe
  min_coverage    = 5L            # minimum reads per cell per event for PSI
)

CreateMatisseObject() calculates PSI as part of construction. After it returns, the object holds three things ready to use:

  • nCount_isoform – total junction read counts per cell (in cell metadata)
  • nFeature_isoform – number of junctions with ≥1 read per cell (in cell metadata)
  • nPercent_isoform – percentage of splice events with a non-NA PSI value per cell (in cell metadata)
  • A Assay5("psi") holding the PSI matrix and the per-event annotation as feature metadata

For each cell and event, Matisse sums the inclusion-supporting junctions and exclusion-supporting junctions, then computes:

PSIc,e=inclusion junction readsinclusion reads+exclusion readsPSI_{c,e} = \frac{\sum \text{inclusion junction reads}} {\sum \text{inclusion reads} + \sum \text{exclusion reads}}

Cells with fewer than min_coverage total reads for an event are left as NA. To re-compute PSI with different parameters later, call CalculatePSI(obj, min_coverage = ...). To skip the PSI step at construction (rare), pass defer_psi = TRUE and call CalculatePSI() manually.


Step 2 – Quality control

Visualise QC metrics

Call PlotViolin() with no feature argument to automatically plot all three isoform QC metrics (nCount_isoform, nFeature_isoform, nPercent_isoform) as a faceted panel.

Remove low-quality cells

obj <- FilterCells(
  obj,
  min_features_isoform = 5,    # at least 5 junctions detected
  min_pct_isoform      = 10    # PSI covered for >= 10% of splice events
)

Remove uninformative splice events

obj <- FilterEvents(obj, min_cells_covered = 20)

Step 3 – Visualise splicing patterns

Where does the splicing switch happen on the UMAP?

PlotUMAP(
  obj,
  feature = "SE:chr18:3433648-3434699:3434801-3436055:-",
  title   = "PTBP1 exon 9 -- PSI per cell"
)

Distributions by cell type

PlotViolin(
  obj,
  feature  = "SE:chr18:3433648-3434699:3434801-3436055:-",
  group_by = "cell_type"
)

Overview of all events

PlotHeatmap(obj, group_by = "cell_type", max_cells = 400)

Sashimi plot for a specific event

PlotSashimi() shows junction arcs scaled by read count, coloured by inclusion (blue) vs exclusion (red). Use group_by to compare cell types side by side.

PlotSashimi(
  obj,
  event_id  = "SE:chr18:3433648-3434699:3434801-3436055:-",
  group_by  = "cell_type",
  arc_scale = "sqrt",
  title     = "PTBP1 exon 9 -- junction coverage by cell type"
)

Accessing data at any point

# Extract the Seurat object for any standard Seurat step
seu <- GetSeurat(obj)

# Cell metadata (forwarded from the embedded Seurat object)
obj$seurat_clusters
obj$cell_type

# PSI matrix: cells x splice events
psi <- GetPSI(obj)

# Raw junction counts: cells x junctions (via the native Seurat layer API)
jxn <- Matrix::t(SeuratObject::GetAssayData(GetSeurat(obj)[["isoform"]],
                                            "counts"))

# Subset to a specific cell type
neurons <- obj[obj$cell_type == "Neuron", ]

Combining datasets

If you have multiple samples and want to analyse them together, merge the individual Matisse objects before running QC:

# Build each sample's MatisseObject (PSI is computed at construction), then
# merge. Both objects must share the same `input.mode` and event set.
combined <- MergeMatisse(
  obj_sample1, obj_sample2,
  add_cell_ids = c("S1", "S2")
)

Session info

sessionInfo()
#> R version 4.6.0 (2026-04-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 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.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.39     desc_1.4.3        R6_2.6.1          fastmap_1.2.0    
#>  [5] xfun_0.57         cachem_1.1.0      knitr_1.51        htmltools_0.5.9  
#>  [9] rmarkdown_2.31    lifecycle_1.0.5   cli_3.6.6         sass_0.4.10      
#> [13] pkgdown_2.2.0     textshaping_1.0.5 jquerylib_0.1.4   systemfonts_1.3.2
#> [17] compiler_4.6.0    tools_4.6.0       ragg_1.5.2        bslib_0.10.0     
#> [21] evaluate_1.0.5    yaml_2.3.12       otel_0.2.0        jsonlite_2.0.0   
#> [25] rlang_1.2.0       fs_2.1.0          htmlwidgets_1.6.4