Date: 2026-08-13

# SpaceTrooper utilities overview

Benedetta Banzi and Dario Righelli

#### 13 August 2026

## 0.1 Introduction

`SpaceTrooper` is an `R/Bioconductor` package for Quality Control (QC) of imaging-based spatial transcriptomics and proteomics data. It provides multi-platform data harmonization, cell-level QC, and visualization utilities. The package leverages SpatialExperiment objects to support data from CosMx, Xenium, and MERFISH technologies.

This vignette describes additional SpaceTrooper utilities that extend the standard workflow and are intended to further improve the user experience. Please, be aware that this is an appendix vignette and does not present an exhaustive analytical workflow. Therefore, users should consult RNA and Protein tutorials before proceeding.

In addition, this vignette provides practical tips for making the most effective use of the package. It is organized into three main sections, corresponding to the typical analysis pipeline, with relevant insights for each step:

- Data loading
- Quality Control (QC)
- Visualization

## 0.2 Data loading

### 0.2.1 SpatialExperiment object creation

This section explains how to specify the inputs required by each data-reading function. Both `readCosmxSPE` and `readXeniumSPE` are wrapper functions built on the SpatialExperimentIO infrastructure. Further details on their usage are available in the corresponding function documentation, e.g. `help(readCosmxSPE)`.

COSMX

    library(SpaceTrooper)
    library(ggplot2)

    cosmxFolder <- system.file(file.path("extdata", "CosMx_DBKero_Tiny"),
                            package="SpaceTrooper")

    spe <- readCosmxSPE(dirName=cosmxFolder,
                        sampleName="DBKero_CosMx",
                        coordNames=c("CenterX_global_px", "CenterY_global_px"),
                        countMatFPattern="exprMat_file.csv",
                        metadataFPattern="metadata_file.csv",
                        polygonsFPattern="polygons.csv",
                        fovPosFPattern="fov_positions_file.csv",
                        fovdims=c(xdim=4256, ydim=4256)
    )

Here are summarized the default values of the input parameters. The `dirName` parameter specifies the path to the directory containing the flat files produced by the CosMx platform:

- expression matrix, (e.g. `exprMat_file.csv`)
- cell metadata, (e.g. `metadata_file.csv`)
- Fields of View positions, (e.g. `fov_positions_file.csv`)
- cell polygons, (e.g. `polygons.csv`)

These files represent the minimum set of inputs required to run the data-reading function.

The `sampleName` parameter defines the dataset label used as the title in plotting functions. The `coordNames` parameter specifies the column names in the cell metadata corresponding to centroid coordinates. These are stored in the `spatialCoords` slot of the `SpatialExperiment` and can be assessed via `spatialCoords(spe)`. They are also the columns used by `plotCentroid` function. Additional details on working with `SpatialExperiment` objects are available in the SpatialExperiment documentation.

The `countMatFPattern`, `metadataFPattern`, `polygonsFPattern` and `fovPosFPattern` define file name patterns used to identify the expression matrix, cell metadata, Fields of view (FoV) positions, and polygon files, respectively. As file naming conventions may change over time with evolving technologies, these parameters allow users to adapt to different file names or extensions. Currently, CSV files are supported, including compressed `.csv.gz.` formats.

IMPORTANT: the cell polygon file is the only input that is not imported directly into the `SpatialExperiment` object by the reading function. However, because polygons can be added later using the `readAndAddPolygonsToSPE` function (see the Load polygons subsection), the path to the cell polygon file is stored in the `metadata` slot and accessed via `metadata(spe)$polygons`.

The `fovdims` parameter specifies the FoV dimensions as a vector containing the x and y sizes. While FoV size and shape have changed over time, e.g. from rectangular to square, in recent CosMx versions they have stabilized at 4,256 pixels per side.

COSMX PROTEIN

    protfolder <- system.file( "extdata", "S01_prot", package="SpaceTrooper")

    prot <- readCosmxProteinSPE(dirName=protfolder,
                               sampleName="CosMx_Protein_Tonsil",
                               coordNames=c("CenterX_global_px", "CenterY_global_px"),
                               countMatFPattern="exprMat_file.csv",
                               metadataFPattern="metadata_file.csv",
                               polygonsFPattern="polygons.csv",
                               fovPosFPattern="fov_positions_file.csv",
                               fovdims=c(xdim=4256, ydim=4256)
    )

    # code line for shift correction
    metadata(prot)$fov_positions$y_global_px <- metadata(prot)$fov_positions$y_global_px - 4256

`readCosmxProteinSPE` is a wrapper around `readCosmxSPE`, thus the required input files and parameters are identical. Input files may be provided in compressed `csv.gz` file format, and the `countMatFPattern`, `metadataFPattern`, `polygonsFPattern` and `fovPosFPattern` parameters are used to specify the corresponding file name patterns.

XENIUM

    xeniumFolder <- system.file( "extdata", "Xenium_small", package="SpaceTrooper")

    xen <- readXeniumSPE(dirName=xeniumFolder,
                         sampleName="Xenium_small",
                         type=c("HDF5", "sparse"),
                         coordNames=c("x_centroid", "y_centroid"),
                         boundariesType=c("parquet", "csv"),
                         computeMissingMetrics=TRUE,
                         keepPolygons=FALSE,
                         countsFilePattern="cell_feature_matrix",
                         metadataFPattern="cells.csv.gz",
                         polygonsFPattern="cell_boundaries",
                         polygonsCol="polygons",
                         txPattern="transcripts",
                         addFOVs=FALSE

    )

    ## Computing missing metrics, this could take some time...

`readXeniumSPE` function requires the path to the directory containing the Xenium output files. This directory is typically generated automatically by the Xenium pipeline and usually includes the pattern `outs` in its name. If the directory exists but this pattern is missing from the provided path, a warning is issued to allow the path to be corrected.

Xenium outputs are often provided redundantly in multiple formats. The minimum set of files required to run the `SpaceTrooper` pipeline includes:

- expression matrix
- cell metadata
- cell polygons

Optionally:

- transcript file

The `dirName`, `sampleName` and `coordNames` parameters behave in the same way as in `readCosmxSPE`.

The remaining parameters are described below:

`type` specifies the format of the expression matrix to be loaded. Currently only the following two formats are supported: MEX for sparse matrices (Cell Ranger output) and HDF5. By default, the HDF5 file is used. If only the sparse MEX format is available, you should provide a vector containing only that format.

`boundariesType` specifies the format of the polygon file and must be either Parquet or CSV. By default, the function uses Parquet format. If only the CSV file is available, you should provide a vector containing only that format.

`computeMissingMetrics` is set to TRUE by default and enables the computation of `Area_um` and `AspectRatio` cell metrics from boundary polygons. IMPORTANT: it is strongly recommended to keep this parameter set to `TRUE` as both metrics are required to run `computeQScore()`, even if they are not used in the final formula.

`keepPolygons` controls whether polygons are retained in the `SpatialExperiment` object and is ONLY effective when `computeMissingMetrics=TRUE`. While missing metrics can be computed without keeping polygons, polygons cannot be retained without computing these metrics. However, polygons may also be loaded at a later stage (see the Load polygons subsection).

`countsFilePattern` and `polygonsFPattern` define file name patterns (excluding the file extension) for the expression matrix and polygon files, correspondingly, since their formats are already specified via `type` and `boundariesType`.

`metadataFPattern` requires the full file name (including extension) of the cell metadata file. Only `.csv.gz` and `.parquet` files are supported.

`polygonsCol` defines the name of the column in `colData` that stores polygon geometries.

Optionally, FoV information can be retrieved for each cell from the transcript file. The `txPattern` parameter specifies the file name pattern for the transcript file (Parquet format only), while `addFOVs` controls whether FoV information is extracted. This option is not mandatory and is therefore set to `FALSE` by default.

OF NOTE, because `readXeniumSPE` is a wrapper function around `SpatialExperimentIO::readXeniumSXE`, and multiple probe identifier sets could be available, Gene Symbols are selected and used as row names.

MERFISH/MERSCOPE

    merfishFolder <- system.file("extdata", "Merfish_Tiny", package="SpaceTrooper")

    mer <- readMerfishSPE(dirName=merfishFolder,
                          sampleName="Merfish_Tiny",
                          computeMissingMetrics=TRUE,
                          keepPolygons=FALSE,
                          boundariesType=c("parquet"),
                          countmatFPattern="cell_by_gene.csv",
                          metadataFPattern="cell_metadata.csv",
                          polygonsFPattern="cell_boundaries.parquet",
                          coordNames=c("center_x", "center_y"),
                          polygonsCol="polygons")

    ## Computing missing metrics, this could take a while...

    ## Warning in computeMissingMetricsMerfish(pol_file, colData, boundariesType, : Volume is used to compute Quality Score for MERFISH technology.
    ##                 For simplicity, it is renamed as Area_um.

As with the other data-reading functions, `readMerfishSPE` requires the path to the directory containing the MERFISH output files. The minimum set of inputs required to run the `SpaceTrooper` pipeline includes:

- expression matrix
- cell metadata
- cell polygons

The expression matrix and cell metadata are typically provided in CSV format.

Polygon data can be supplied either as a single Parquet file (recommended when available) or as multiple HDF5 files. In MERFISH/MERSCOPE experiments, HDF5 polygon files are generated per FoV and may easily scale to thousands of files, making this option computationally intensive and time-consuming. Fortunately, in more recent datasets, a Parquet file is usually available.

For this reason, it is recommended to override the default setting and provide a vector containing only `Parquet` to the `boundariesType` parameter.

IMPORTANT: please, pay attention to warnings. `computeMissingMetrics` parameter must be set to `TRUE` whenever it is desired to compute Quality Score (QS). In MERFISH experiments, transcript hybridization signals are captured also across the z dimension, and cell size is therefore represented by cell volume in the provided metadata.

In contrast, cell area can also be computed by `SpaceTrooper` from polygon boundaries obtained from the MERFISH experiment but from a single z plane only (by running `computeMissingMetricsMerfish()` with `useVolume=FALSE`). That said, cell volume appears to be a more appropriate measure of cell size for QS computation with MERFISH data.

In the current version of `SpaceTrooper`, when `computeMissingMetrics=TRUE`, the `volume` is renamed as `Area_um` for compatibility with the `computeQScore` function, whereas cell area is not computed.

### 0.2.2 Load polygons

For all supported technologies, polygons can be loaded after `SpatialExperiment` object creation. The function `readAndAddPolygonsToSPE` performs the following steps:

- reads the cell polygon file;
- standardizes polygon formats and converts them into an `sf` object;
- validates polygon geometries (e.g. checks that polygons are properly closed and contain at least four vertices etc.);
- removes cells from the `SpatialExperiment` object if no valid polygon is associated with them;
- stores the resulting `sf` object in a `colData` column whose name can be specified via the `polygonsCol` argument;

    # polygon loading
    spe <- readAndAddPolygonsToSPE(spe,
                                   polygonsCol="polygons",
                                   keepMultiPol=TRUE,
                                   boundariesType=c("csv", "HDF5", "parquet")
    )

    spe$polygons

    ## Simple feature collection with 905 features and 5 fields
    ## Active geometry column: global
    ## Geometry type: POLYGON
    ## Dimension:     XY
    ## Bounding box:  xmin: 151625.6 ymin: 18086.62 xmax: 155880.6 ymax: 22341.62
    ## CRS:           NA
    ## First 10 features:
    ##           cell_id is_multi multi_n fov cellID                         global
    ## f16_c1     f16_c1    FALSE       1  16      1 POLYGON ((155326.6 22322.62...
    ## f16_c10   f16_c10    FALSE       1  16     10 POLYGON ((155567.6 21777.62...
    ## f16_c100 f16_c100    FALSE       1  16    100 POLYGON ((153436.6 21099.62...
    ## f16_c101 f16_c101    FALSE       1  16    101 POLYGON ((154227.6 21091.62...
    ## f16_c102 f16_c102    FALSE       1  16    102 POLYGON ((153685.6 21087.62...
    ## f16_c103 f16_c103    FALSE       1  16    103 POLYGON ((154020.6 21059.62...
    ## f16_c104 f16_c104    FALSE       1  16    104 POLYGON ((155332.6 21066.62...
    ## f16_c105 f16_c105    FALSE       1  16    105 POLYGON ((153199.6 21077.62...
    ## f16_c106 f16_c106    FALSE       1  16    106 POLYGON ((154589.6 21068.62...
    ## f16_c107 f16_c107    FALSE       1  16    107 POLYGON ((154135.6 21075.62...
    ##                                   local
    ## f16_c1   POLYGON ((3701 4237, 3711 4...
    ## f16_c10  POLYGON ((3942 3692, 3947 3...
    ## f16_c100 POLYGON ((1811 3014, 1829 3...
    ## f16_c101 POLYGON ((2602 3006, 2614 3...
    ## f16_c102 POLYGON ((2060 3002, 2065 3...
    ## f16_c103 POLYGON ((2395 2974, 2416 2...
    ## f16_c104 POLYGON ((3707 2981, 3715 2...
    ## f16_c105 POLYGON ((1574 2992, 1577 2...
    ## f16_c106 POLYGON ((2964 2983, 3001 2...
    ## f16_c107 POLYGON ((2510 2990, 2515 2...

In some technologies, segmentation may produce multi-polygons (indicated as `TRUE` in the `is_multi` column of the `sf` object shown here), meaning that more than one polygon is associated with a single cell. This can cause issues when computing certain derived metrics, particularly the`AspectRatio`.

By default, the `keepMultiPol` parameter is set to `TRUE`. Under this setting, cells associated with multi-polygons are retained in the dataset; however, their `AspectRatio` is `NA`.

    # xenium polygon loading
    xen <- readAndAddPolygonsToSPE(xen, boundariesType=c("parquet"))

    # merfish polygon loading
    mer <- readAndAddPolygonsToSPE(mer, boundariesType=c("parquet"))

    # protein polygon loading
    prot <- readAndAddPolygonsToSPE(prot, boundariesType=c("csv"))

Multiple geometries files can be loaded into a `SpatialExperiment` object using the `readAndAddPolygonsToSPE` function. The `polygonsCol` parameter allows users to specify distinct column names under which these geometries are stored in `colData`.

## 0.3 Quality Control

### 0.3.1 Compute QC metrics

This section describes the cell metadata and quality metrics computed by the `spatialPerCellQC` function. When inspecting `colData`, additional metadata and metrics provided natively by each technology may also be present; these are technology-specific and are generally not shared across all platforms. For details on these additional fields, please refer to the corresponding technology reference documentation.

    spe <- spatialPerCellQC(spe, rmZeros=TRUE,
            negProbList=c("NegPrb", "Negative", "SystemControl"))

    xen <- spatialPerCellQC(xen, rmZeros=TRUE)

    mer <- spatialPerCellQC(mer, rmZeros=TRUE)

    prot <- spatialPerCellQC(prot, rmZeros=TRUE)

    # check added columns to colData
    colnames(colData(spe))

    ##  [1] "fov"                     "cellID"
    ##  [3] "Area"                    "AspectRatio"
    ##  [5] "CenterX_local_px"        "CenterY_local_px"
    ##  [7] "Width"                   "Height"
    ##  [9] "Mean.PanCK"              "Max.PanCK"
    ## [11] "Mean.CD68"               "Max.CD68"
    ## [13] "Mean.MembraneStain_B2M"  "Max.MembraneStain_B2M"
    ## [15] "Mean.CD45"               "Max.CD45"
    ## [17] "Mean.DAPI"               "Max.DAPI"
    ## [19] "sample_id"               "cell_id"
    ## [21] "polygons"                "sum"
    ## [23] "detected"                "subsets_NegPrb_sum"
    ## [25] "subsets_NegPrb_detected" "subsets_NegPrb_percent"
    ## [27] "total"                   "control_sum"
    ## [29] "control_detected"        "target_sum"
    ## [31] "target_detected"         "CenterX_global_px"
    ## [33] "CenterY_global_px"       "ctrl_total_ratio"
    ## [35] "log2Ctrl_total_ratio"    "CenterX_global_um"
    ## [37] "CenterY_global_um"       "Area_um"
    ## [39] "dist_border_x"           "dist_border_y"
    ## [41] "dist_border"             "log2AspectRatio"
    ## [43] "SignalDensity"           "log2SignalDensity"

This function is a wrapper around `scuttle::addPerCellQC()`, hence a subset of the reported metrics is derived from that function:

- `sample_id`: character string specifying the dataset sample name. This matches the value provided to the `sampleName` argument in the reading function;
- `sum`/`total`: numeric value representing total probe counts (RNA) or total protein intensity per cell. In this context, `sum` and `total` are equivalent;
- `subsets_controlProbeName_sum`: numeric value giving sum of the counts (intensities) obtained from each type of control probe (control protein). For each one, a separate column is created by replacing “controlProbeName” with the corresponding pattern from `negProbList` , e.g. negative control probes (RNA), negative codewords (RNA), negative control antibodies (protein), etc.;
- `subsets_controlProbeName_detected`: numeric value indicating the number of uniquely detected control probes (proteins) per each type;
- `subsets_controlProbeName_percent`: numeric value representing the percentage of counts (intensities) from each type of control probe (protein) relative to the total probe counts (protein intensities) per cell;
- `control_sum`: numeric value indicating total control probe counts (protein intensities) per cell. It is the sum of counts (intensities) of all types of controls;
- `control_detected`: numeric value representing the number of uniquely detected control probes (proteins) per cell;
- `target_sum`: numeric value giving target probe counts (protein intensities) per cell, obtained as `sum` - `control_sum`;
- `target_detected`: numeric value indicating number of uniquely detected target probes (proteins) per cell;
- `ctrl_total_ratio`: numeric value representing the ratio between total control probe counts (protein intensities) and total probe counts (protein intensities) per cell;
- `log2Ctrl_total_ratio`: numeric value indicating log2-transformed `ctrl_total_ratio`
- `Area_um`: numeric value giving cell area in μm² (for MERFISH technology it corresponds to `volume` in μm³);
- `dist_border_x`: numeric value representing the distance in pixels from the cell to the closest vertical border of its FoV (only available for CosMx);
- `dist_border_y`: numeric value representing the distance in pixels from the cell to the closest horizontal border of its FoV (only available for CosMx);
- `dist_border`: numeric value representing the distance from the cell to the nearest border of its FoV (only available for CosMx), computed as the minimum of `dist_border_x` and `dist_border_y`;
- `log2AspectRatio`: numeric value indicating log2-transformed ratio between cell maximum length along the x dimension and cell maximum length along the y dimension in pixels;
- `SignalDensity`: numeric value indicating the ratio between total probe counts (protein intensities) per cell and cell area in µm² (or volume in µm³ for MERFISH technology);
- `log2SignalDensity`: log2-transformed `SignalDensity`.

### 0.3.2 Compute Quality Score (QS)

This section provides further details about the core component of the method: the Quality Score (QS). As already mentioned in RNA and Protein tutorial vignettes, a random seed should be set to ensure reproducible results, as the method involves underlying stochastic processes.

IMPORTANT: when working with recent MERSCOPE datasets, or with any dataset that uses numeric cell identifiers, it is strongly recommended to convert cell IDs to character strings before calling the `computeQScore` function. This prevents errors during the selection of training examples when estimating the coefficients of the QS formula.

    # MERSCOPE cell_id conversion
    # spe$cell_id <- as.character(spe$cell_id)

    set.seed(1713)

    spe <- computeQScore(spe, verbose=TRUE)

    ## Outliers found for log2SignalDensity:

    ## HIGH: 19

    ## LOW: 38

    ## NO: 847

    ## Outliers found for Area_um:

    ## HIGH: 6

    ## NO: 898

    ## Outliers found for log2AspectRatio:

    ## HIGH: 11

    ## LOW: 5

    ## NO: 888

    ## Outliers found for log2Ctrl_total_ratio:

    ## HIGH: 9

    ## NO: 895

    ## Using final model formula:

    ## ~(log2SignalDensity + Area_um + I(abs(log2AspectRatio) * as.numeric(dist_border < 50)) + log2Ctrl_total_ratio)^2

    ## Chosen low qual examples: 51

    ## Chosen good quality examples (should match bad): 51

    ## Model coefficients for every term used in the formula:

    ## (Intercept)=-1.08 (Intercept)=0 log2SignalDensity=0.66 Area_um=-0.01 I(abs(log2AspectRatio) * as.numeric(dist_border < 50))=-0.89 log2Ctrl_total_ratio=-0.1 log2SignalDensity:Area_um=0 log2SignalDensity:I(abs(log2AspectRatio) * as.numeric(dist_border < 50))=-0.31 log2SignalDensity:log2Ctrl_total_ratio=-0.05 Area_um:I(abs(log2AspectRatio) * as.numeric(dist_border < 50))=-0.01 Area_um:log2Ctrl_total_ratio=0 I(abs(log2AspectRatio) * as.numeric(dist_border < 50)):log2Ctrl_total_ratio=0.07

When `verbose=TRUE`, additional information regarding the underlying steps is reported during the estimation of the QS formula coefficients. For this purpose, bad and good example cells are selected to train a model that has to assign an appropriate QS to each cell.

Bad example cells are identified separately for each metric according to several criteria, based on outlier detection through a statistical test. The specific test applied depends on the distribution of the metric: for asymmetric distributions the Medcouple method is used, whereas for symmetric distributions the Median Absolute Deviation (MAD) is applied, as implemented in `scuttle::isOutlier()` with default parameters.

For a detailed description of the selection criteria used for each metric, please refer to the Methods section of the paper.

When inspecting the printed output, the following information is reported:

- The first section summarizes the number of outliers identified for each metric. The three reported values correspond to outliers detected in the right tail, the left tail of the distribution, and the remaining non-outlier cells, respectively. Outlier detection is performed on the full dataset after excluding cells with zero counts, if these were retained when running the `spatialPerCellQC` function. This step is carried out for all metrics, even those that are not ultimately included in the final QS formula; however, outliers from excluded metrics are not used for model training.

- The next section reports the number of selected bad example cells, followed by the number of good example cells, which matches the former. Because the criteria for selecting good examples are intentionally permissive, these cells are sampled at random.

- The final section displays the QS formula used for model training and score computation, along with the estimated coefficients for each retained term and all pairwise interactions.

IMPORTANT: at present, QS computation requires `log2SignalDensity` as a mandatory metric in the QS formula. If an insufficient number of outliers is detected for this metric (fewer than 0.1% of the dataset after excluding zero-count cells), QS computation cannot proceed using the remaining metrics. In such cases, the provided code will still add a `QScore` column to `colData`. This column is populated if the minimum requirement is met and contains `NA` values otherwise.

    # safe run using tryCatch directly so errors are caught before evaluation
    out <- tryCatch(
      list(result=computeQScore(spe), error=NULL),
      error=function(e) list(result=NULL, error=e)
    )

    if (!is.null(out$error)) {
    message("Failed: ", out$error$message)
    colData(spe)$QScore <- NA
    } else {
    spe <- out$result
    }

### 0.3.3 Compute QS with custom formula

In `computeQScore`, the `modelFormula` parameter allows users to specify which metrics to include in the QS formula among the four currently supported: `log2SignalDensity`, `Area_um`, `log2AspectRatio`, and `log2Ctrl_total_ratio`. The supported border-effect expression is `I(abs(log2AspectRatio) * as.numeric(dist_border < 50))`; aspect ratio and border effect are available only for CosMx datasets.

The default is `NULL` and the formula is automatically estimated based on the available metrics, their outliers and the dataset technology. The most complete formula is generated as follows:

`~(log2SignalDensity + Area_um + I(abs(log2AspectRatio) * as.numeric(dist_border < 50)) + log2Ctrl_total_ratio)^2`

When user-provided, the formula is fitted as supplied. It must include `log2SignalDensity`; users may select a subset of the other supported metrics, use an additive formula, or choose interactions with `:` and `*`. SpaceTrooper does not add or remove terms. Predictor and transformation validation is separate from formula structure: arbitrary metrics and transformations such as `log1p(Area_um)` are rejected. The supported base QC metrics present in the custom formula are also used to compute outliers, define good and bad training cells, and construct `QScore_train`; supported metrics omitted from the formula do not contribute to those labels. Interactions alter the fitted model but do not create additional training metrics. In the supported CosMx border expression, `log2AspectRatio` is the training QC metric and `dist_border` is only an auxiliary variable. With `modelFormula=NULL`, the established default metric-selection and training-label workflow is unchanged.

    set.seed(1713)

    model_formula <- "~ log2Ctrl_total_ratio + log2SignalDensity"

    spe <- computeQScore(spe, modelFormula=model_formula, verbose=TRUE)

    ## Outliers found for log2Ctrl_total_ratio:

    ## HIGH: 9

    ## NO: 895

    ## Outliers found for log2SignalDensity:

    ## HIGH: 19

    ## LOW: 38

    ## NO: 847

    ## Using final model formula:

    ## ~ log2Ctrl_total_ratio + log2SignalDensity

    ## Chosen low qual examples: 42

    ## Chosen good quality examples (should match bad): 42

    ## Model coefficients for every term used in the formula:

    ## (Intercept)=-1.66 (Intercept)=0 log2Ctrl_total_ratio=-0.04 log2SignalDensity=0.95

    plotPolygons(spe, colourBy="QScore") +
        scale_fill_viridis_c(option="plasma")

Compared to the plot displayed in RNA vignette, QS values are higher for bigger cells and cells located on the FoV borders, as the cell size and border effect contributions are not considered in the QS computation.

### 0.3.4 Apply a trained QS model

The fitted model is stored in `metadata(spe)$QScore_model` and can be applied with `applyQScoreModel()` to an object containing the required QC metrics. Model transfer across datasets is generally discouraged because QS training is dataset-specific; when it is necessary, use datasets from the same experiment and technology, or train a supported formula that excludes incompatible terms.

    qs_model <- metadata(spe)$QScore_model
    spe <- applyQScoreModel(
        spe,
        qsModel=qs_model,
        scoreName="QScore_transferred"
    )

The historical `applyQCScoreModel()` interface remains available with a deprecation warning and retains `qcModel`, `QC_score`, and `QCScore_model_applied` naming.

### 0.3.5 Single metric flagging

The `SpaceTrooper` package allows the identification of numerically aberrant cells through outlier detection for each quantitative metric, using the same procedures applied internally by the `computeQScore` function. This enables metric-specific cell flagging in a manner analogous to standard scRNA-seq quality control workflows.

    # detecting outliers for cell area in Xenium dataset with both Medcouple
    # and MAD
    xen <- computeSpatialOutlier(xen, computeBy="Area_um", method="both")

    ## Warning in computeSpatialOutlier(xen, computeBy = "Area_um", method = "both"):
    ## Distribution is symmetric: mc is for asymmetric distributions. Use scuttle
    ## instead.

Pay attention to warnings: setting the method to `both`, applies both the Medcouple and MAD approaches to identify outliers. Outlier results are stored in two separate `colData` columns, with names formed by appending `_outlier_mc` and `_outlier_sc` to the variable name. If a warning indicates that the variable is symmetric, only the `_outlier_sc` column should be used for downstream analyses.

    spe <- computeSpatialOutlier(spe, computeBy="Mean.CD68", method="both")

    table(spe$Mean.CD68_outlier_mc)

    ##
    ## HIGH  LOW   NO
    ##   52    4  848

The three numbers correspond to outliers in the right tail (`HIGH`), in the left tail (`LOW`) of the variable distribution, and remaining non-outlier cells (`NO`).

## 0.4 Visualization

Variable distributions can be visualized using the `plotMetricsHist` function, as demonstrated in the RNA and Protein tutorial vignette. If the `computeSpatialOutlier` function has already been executed, the resulting columns can be provided as input to `useFences` parameter to display lines corresponding to the upper and lower thresholds, used to define `HIGH` and `LOW` outliers, respectively.

    spe <- computeSpatialOutlier(spe, computeBy="log2SignalDensity", method="both")

    plotMetricHist(spe, metric="log2SignalDensity",
                   useFences="log2SignalDensity_outlier_mc")

The threshold values are indicated in the plot legend.

The `plotZoomFovsMap` function provides a zoomed-in view of selected FoVs, with cell polygons colored according to the metric specified in the `colourBy` parameter. In addition, it includes an overview map of all FoVs, analogous to that produced by `plotCellsFovs` function.

    # plotting FoVs map and zoom in of selected FoV colored by QS
    plotZoomFovsMap(spe, fovs=16, colourBy="QScore", mapPointSize=1,
                    fovNumberSize=7, scaleBars=TRUE)

For this plot, `scaleBars` parameter controls whether scale bars are displayed in both plots simultaneously. To show the scale bar in only one of the two plots, set either `scaleBarMap=FALSE` or `scaleBarPol=FALSE`.

    plotZoomFovsMap(prot, fovs=344, colourBy="Area_um", mapPointSize=1,
                    fovNumberSize=7, scaleBarMap=TRUE, scaleBarPol=FALSE)

For the other plots, the `scaleBar` parameter can be set to `FALSE` to hide the scale bar.

    plotCentroids(spe, colourBy="QScore", size=3, scaleBar=FALSE) +
        scale_fill_viridis_c(option="plasma") +
        scale_color_viridis_c(option="plasma")

    plotPolygons(prot, colourBy="log2SignalDensity", scaleBar=FALSE) +
        scale_fill_viridis_c(option="mako")

## 0.5 Session Information

    sessionInfo()

    ## R version 4.6.1 (2026-06-24)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.4 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
    ## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
    ##
    ## locale:
    ##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
    ##  [3] LC_TIME=en_GB              LC_COLLATE=C
    ##  [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: America/New_York
    ## tzcode source: system (glibc)
    ##
    ## attached base packages:
    ## [1] stats4    stats     graphics  grDevices utils     datasets  methods
    ## [8] base
    ##
    ## other attached packages:
    ##  [1] ggplot2_4.0.3               SpaceTrooper_1.3.1
    ##  [3] SpatialExperiment_1.23.0    SingleCellExperiment_1.35.2
    ##  [5] SummarizedExperiment_1.43.0 Biobase_2.73.2
    ##  [7] GenomicRanges_1.65.1        Seqinfo_1.3.0
    ##  [9] IRanges_2.47.2              S4Vectors_0.51.6
    ## [11] BiocGenerics_0.59.12        generics_0.1.4
    ## [13] MatrixGenerics_1.25.0       matrixStats_1.5.0
    ## [15] BiocStyle_2.41.0
    ##
    ## loaded via a namespace (and not attached):
    ##   [1] RColorBrewer_1.1-3        jsonlite_2.0.0
    ##   [3] shape_1.4.6.1             magrittr_2.0.5
    ##   [5] ggbeeswarm_0.7.3          magick_2.9.1
    ##   [7] farver_2.1.2              rmarkdown_2.31
    ##   [9] vctrs_0.7.3               DelayedMatrixStats_1.35.0
    ##  [11] tinytex_0.60              rstatix_1.1.0
    ##  [13] htmltools_0.5.9           S4Arrays_1.13.0
    ##  [15] BiocNeighbors_2.7.2       broom_1.0.13
    ##  [17] Rhdf5lib_2.1.0            SparseArray_1.13.2
    ##  [19] Formula_1.2-6             rhdf5_2.57.10
    ##  [21] sass_0.4.10               KernSmooth_2.23-27
    ##  [23] bslib_0.12.0              cachem_1.1.0
    ##  [25] lifecycle_1.0.5           iterators_1.0.14
    ##  [27] pkgconfig_2.0.3           rsvd_1.0.5
    ##  [29] Matrix_1.7-6              R6_2.6.1
    ##  [31] fastmap_1.2.0             digest_0.6.39
    ##  [33] scater_1.41.2             dqrng_0.4.1
    ##  [35] irlba_2.3.7               ggpubr_1.0.0
    ##  [37] beachmat_2.29.0           labeling_0.4.3
    ##  [39] SpatialExperimentIO_1.5.0 abind_1.4-8
    ##  [41] compiler_4.6.1            proxy_0.4-29
    ##  [43] bit64_4.8.2               withr_3.0.3
    ##  [45] S7_0.2.2                  backports_1.5.1
    ##  [47] BiocParallel_1.47.0       carData_3.0-6
    ##  [49] viridis_0.6.5             DBI_1.3.0
    ##  [51] HDF5Array_1.41.2          R.utils_2.13.0
    ##  [53] ggsignif_0.6.4            DelayedArray_0.39.5
    ##  [55] rjson_0.2.23              classInt_0.4-11
    ##  [57] tools_4.6.1               units_1.0-1
    ##  [59] vipor_0.4.7               otel_0.2.0
    ##  [61] beeswarm_0.4.0            R.oo_1.27.1
    ##  [63] glue_1.8.1                h5mread_1.5.0
    ##  [65] rhdf5filters_1.25.4       grid_4.6.1
    ##  [67] sf_1.1-2                  gtable_0.3.6
    ##  [69] R.methodsS3_1.8.2         class_7.3-24
    ##  [71] tidyr_1.3.2               data.table_1.18.4
    ##  [73] BiocSingular_1.29.0       ScaledMatrix_1.21.0
    ##  [75] car_3.1-5                 XVector_0.53.0
    ##  [77] ggrepel_0.9.8             foreach_1.5.2
    ##  [79] pillar_1.11.1             limma_3.69.4
    ##  [81] robustbase_0.99-7         splines_4.6.1
    ##  [83] dplyr_1.2.1               lattice_0.23-1
    ##  [85] survival_3.8-9            bit_4.6.0
    ##  [87] tidyselect_1.2.1          locfit_1.5-9.12
    ##  [89] scuttle_1.23.1            sfheaders_0.4.5
    ##  [91] knitr_1.51                gridExtra_2.3.1
    ##  [93] bookdown_0.47             edgeR_4.11.6
    ##  [95] xfun_0.60                 statmod_1.5.2
    ##  [97] DropletUtils_1.33.0       DEoptimR_1.2-0
    ##  [99] yaml_2.3.12               evaluate_1.0.5
    ## [101] codetools_0.2-20          tibble_3.3.1
    ## [103] BiocManager_1.30.27       cli_3.6.6
    ## [105] arrow_25.0.0              jquerylib_0.1.4
    ## [107] dichromat_2.0-1           Rcpp_1.1.2
    ## [109] parallel_4.6.1            assertthat_0.2.1
    ## [111] sparseMatrixStats_1.25.0  glmnet_5.0
    ## [113] viridisLite_0.4.3         scales_1.4.0
    ## [115] e1071_1.7-17              purrr_1.2.2
    ## [117] rlang_1.3.0               cowplot_1.2.0