Author: Dario Righelli
Date: 2025-10-30

# Spatial Data Quality Control with SpaceTrooper Package

Dario Righelli

#### 30 October 2025

# 1 Introduction

This vignette introduces the `SpaceTrooper` package for preprocessing and quality control of imaging-based spatial omics, from platforms like Xenium, Merfish, and CosMx. We cover data loading, quality control, and results visualization.

# 2 Installation

To install `SpaceTrooper`, use the following commands:

    # Install BiocManager if not already installed, then install SpaceTrooper
    if (!requireNamespace("BiocManager", quietly = TRUE))
        install.packages("BiocManager")
    BiocManager::install("SpaceTrooper")

# 3 Data Loading

In this section, we load data from various platforms using the package’s functions. The goal is to provide a uniform `SpatialExperiment` object across all technologies, allowing for consistent QC analysis.

The functions in `SpaceTrooper` compute missing metrics as needed and allow for the inclusion of polygons with the `keep_polygons` argument. This stores polygons in the `colData` of the `SpatialExperiment`. We suggest to use the `keep_polygons` argument for technologies like Xenium and Merfish/Merscope because we already load the polygons to compute missing metrics in these cases.

Note that when loading data with `readXeniumSPE` in HDF5 format, this creates a `DelayedArray` count assay in the `SpatialExperiment`, which could lead to some issues in downstream analysis, in case this data structure is not supported by the used package.

    library(SpatialExperiment)
    # Load the SpaceTrooper library
    library(SpaceTrooper)

    # Load Xenium data into a Spatial Experiment object (SPE)
    xeniumFolder <- system.file( "extdata", "Xenium_small", package="SpaceTrooper")
    (xen <- readXeniumSPE(xeniumFolder, computeMissingMetrics=TRUE,
                            keepPolygons=TRUE))

    ## class: SpatialExperiment
    ## dim: 4 6
    ## metadata(2): polygons technology
    ## assays(1): counts
    ## rownames(4): ABCC11 ACTA2 ACTG2 ADAM9
    ## rowData names(3): ID Symbol Type
    ## colnames(6): 1 2 ... 5 6
    ## colData names(11): X cell_id ... Area_um polygons
    ## reducedDimNames(0):
    ## mainExpName: NULL
    ## altExpNames(0):
    ## spatialCoords names(2) : x_centroid y_centroid
    ## imgData names(1): sample_id

    colData(xen)

    ## DataFrame with 6 rows and 11 columns
    ##           X   cell_id transcript_counts control_probe_counts
    ##   <integer> <integer>         <integer>            <integer>
    ## 1         1         1               384                    0
    ## 2         2         2               146                    0
    ## 3         3         3                81                    0
    ## 4         4         4               314                    0
    ## 5         5         5               639                    0
    ## 6         6         6               270                    0
    ##   control_codeword_counts total_counts nucleus_area   sample_id AspectRatio
    ##                 <integer>    <integer>    <numeric> <character>   <numeric>
    ## 1                       0          385     70.71469    sample01    0.800000
    ## 2                       0          146      6.41219    sample01    0.687499
    ## 3                       0           81     32.78344    sample01    0.699999
    ## 4                       0          315     68.18594    sample01    0.897439
    ## 5                       0          640    102.95625    sample01    1.448982
    ## 6                       0          270     33.41562    sample01    1.333327
    ##     Area_um                           polygons
    ##   <numeric>                               <sf>
    ## 1   305.211 1:list(c(849.787475585..:FALSE:...
    ## 2   176.606 2:list(c(817.0625, 816..:FALSE:...
    ## 3   263.938 3:list(c(849.787475585..:FALSE:...
    ## 4   427.810 4:list(c(823.224975585..:FALSE:...
    ## 5   424.604 5:list(c(840.650024414..:FALSE:...
    ## 6   416.882 6:list(c(847.237487792..:FALSE:...

    # Load Merfish data into an SPE with parquet boundaries
    merscopeFolder <- system.file("extdata", "Merfish_Tiny", package="SpaceTrooper")
    (mer <- readMerfishSPE(merscopeFolder, boundariesType="parquet",
                            computeMissingMetrics=TRUE, keepPolygons=TRUE))

    ## class: SpatialExperiment
    ## dim: 10 50
    ## metadata(2): polygons technology
    ## assays(1): counts
    ## rownames(10): PDK4 CCL26 ... TBX21 FAP
    ## rowData names(0):
    ## colnames(50): 26985800002100002 26985800002100003 ... 26985800003100008
    ##   26985800003100010
    ## colData names(27): cell_id fov ... polygons sample_id
    ## reducedDimNames(0):
    ## mainExpName: NULL
    ## altExpNames(0):
    ## spatialCoords names(2) : center_x center_y
    ## imgData names(0):

    colData(mer)

    ## DataFrame with 50 rows and 27 columns
    ##                             cell_id       fov    volume     min_x     min_y
    ##                         <integer64> <integer> <numeric> <numeric> <numeric>
    ## 26985800002100002 26985800002100002       188   67.4877   1456.56   149.926
    ## 26985800002100003 26985800002100003       188  572.6167   1285.17   150.447
    ## 26985800002100009 26985800002100009       188  180.6029   1439.18   150.461
    ## 26985800002100016 26985800002100016       188  236.3058   1369.52   162.237
    ## 26985800002100017 26985800002100017       188  211.9732   1447.71   161.462
    ## ...                             ...       ...       ...       ...       ...
    ## 26985800003100005 26985800003100005       264   816.136   1544.29   58.1212
    ## 26985800003100006 26985800003100006       264  1557.570   1559.25   60.7662
    ## 26985800003100007 26985800003100007       264   891.561   1545.13   64.4227
    ## 26985800003100008 26985800003100008       264  1094.290   1527.17   68.4953
    ## 26985800003100010 26985800003100010       264  1092.694   1518.86   77.0072
    ##                       max_x     max_y anisotropy transcript_count
    ##                   <numeric> <numeric>  <numeric>        <integer>
    ## 26985800002100002   1459.78   152.855    1.30646                0
    ## 26985800002100003   1288.84   167.175    4.56560                0
    ## 26985800002100009   1444.56   154.525    1.32596                0
    ## 26985800002100016   1374.25   168.944    1.45674                0
    ## 26985800002100017   1453.91   165.686    1.60233                1
    ## ...                     ...       ...        ...              ...
    ## 26985800003100005   1556.39   67.0141    1.56662              220
    ## 26985800003100006   1571.92   75.6834    1.19477              504
    ## 26985800003100007   1557.11   75.1396    1.61050              237
    ## 26985800003100008   1540.24   82.4848    1.33467              611
    ## 26985800003100010   1532.71   88.7656    1.28149              597
    ##                   perimeter_area_ratio  solidity  DAPI_raw DAPI_high_pass
    ##                              <numeric> <numeric> <integer>      <numeric>
    ## 26985800002100002             1.460466   7.00000  10031450        1275615
    ## 26985800002100003             0.699506   6.74990  10566420        1569192
    ## 26985800002100009             0.897958   7.00000  17483674        1128739
    ## 26985800002100016             0.839058   6.54357   9699598         165876
    ## 26985800002100017             0.852500   6.84923  31223309         328891
    ## ...                                ...       ...       ...            ...
    ## 26985800003100005             0.435879   6.81694 619728199        3074276
    ## 26985800003100006             0.303557   6.89371 995974204        5756449
    ## 26985800003100007             0.421047   6.87562 624145706        3240592
    ## 26985800003100008             0.398144   6.37886 781206643        4199120
    ## 26985800003100010             0.365497   6.90240 843343768        4555631
    ##                   Cellbound2_raw Cellbound2_high_pass PolyT_raw PolyT_high_pass
    ##                        <integer>            <numeric> <integer>       <numeric>
    ## 26985800002100002        7545395               683236   8272827         1053956
    ## 26985800002100003       17739873              1396788  19038054         2727259
    ## 26985800002100009       12073443               318845  17359008         1107675
    ## 26985800002100016        8454232               118069  18952192          272687
    ## 26985800002100017       18054960               204996  27813347          281936
    ## ...                          ...                  ...       ...             ...
    ## 26985800003100005      217119396              1612132 401433689         2243790
    ## 26985800003100006      476710032              5098785 711413405         4688473
    ## 26985800003100007      293781198              2945993 408437826         2077031
    ## 26985800003100008      298710416              2714216 520983160         2927650
    ## 26985800003100010      287395414              2720645 543653205         3137387
    ##                   Cellbound3_raw Cellbound3_high_pass Anti.Ms.CD45RA_raw
    ##                        <integer>            <numeric>        <integer64>
    ## 26985800002100002        1978031             190336.1            3646677
    ## 26985800002100003        7399745             618972.0           23340256
    ## 26985800002100009        3404019             121075.1            9552347
    ## 26985800002100016        2824879              64292.2           11682873
    ## 26985800002100017        4834605              98919.5           12042800
    ## ...                          ...                  ...                ...
    ## 26985800003100005       61317799               893250           57497557
    ## 26985800003100006      104833992              1750080          104260941
    ## 26985800003100007       56278321               714652           60108349
    ## 26985800003100008       62275042              1006426           74054883
    ## 26985800003100010       59351838              1007649           71828467
    ##                   Anti.Ms.CD45RA_high_pass Cellbound1_raw Cellbound1_high_pass
    ##                                  <numeric>    <integer64>            <numeric>
    ## 26985800002100002                 306526.2       11993880              1068209
    ## 26985800002100003                1932074.2       31455099              2395973
    ## 26985800002100009                 250153.3       18873810               466134
    ## 26985800002100016                 105143.5       13044814               147990
    ## 26985800002100017                  97753.7       28441170               245080
    ## ...                                    ...            ...                  ...
    ## 26985800003100005                   435947      373898626              2618444
    ## 26985800003100006                   921107      690727218              6323352
    ## 26985800003100007                   512397      478131337              3649511
    ## 26985800003100008                   681909      438161375              4097784
    ## 26985800003100010                   578224      339102882              3006323
    ##                     Area_um AspectRatio
    ##                   <numeric>   <numeric>
    ## 26985800002100002    6.4274    0.908763
    ## 26985800002100003   54.5349    4.563174
    ## 26985800002100009   17.2003    0.755732
    ## 26985800002100016   22.5053    1.418292
    ## 26985800002100017   20.1879    0.681541
    ## ...                     ...         ...
    ## 26985800003100005   77.7273    0.735266
    ## 26985800003100006  148.3400    1.177586
    ## 26985800003100007   84.9105    0.893979
    ## 26985800003100008  104.2181    1.070487
    ## 26985800003100010  104.0661    0.848952
    ##                                                             polygons
    ##                                                                 <sf>
    ## 26985800002100002 26985800002100002:list(c(1457.03135009..:FALSE:...
    ## 26985800002100003 26985800002100003:list(c(1285.17346897..:FALSE:...
    ## 26985800002100009 26985800002100009:list(c(1439.17997851..:FALSE:...
    ## 26985800002100016 26985800002100016:list(c(1369.58905121..:FALSE:...
    ## 26985800002100017 26985800002100017:list(c(1448.74199550..:FALSE:...
    ## ...                                                              ...
    ## 26985800003100005 26985800003100005:list(c(1548.14720174..:FALSE:...
    ## 26985800003100006 26985800003100006:list(c(1559.24912423..:FALSE:...
    ## 26985800003100007 26985800003100007:list(c(1547.70509276..:FALSE:...
    ## 26985800003100008 26985800003100008:list(c(1532.80367814..:FALSE:...
    ## 26985800003100010 26985800003100010:list(c(1519.93351811..:FALSE:...
    ##                     sample_id
    ##                   <character>
    ## 26985800002100002    sample01
    ## 26985800002100003    sample01
    ## 26985800002100009    sample01
    ## 26985800002100016    sample01
    ## 26985800002100017    sample01
    ## ...                       ...
    ## 26985800003100005    sample01
    ## 26985800003100006    sample01
    ## 26985800003100007    sample01
    ## 26985800003100008    sample01
    ## 26985800003100010    sample01

Despite main differences across the technologies provided data, we can see that all the technologies have the same mandatory `cell_id` and `AspectRatio` columns.

# 4 Data Analysis based on CosMx

The package offers several functions for spatial data analysis, including quality control and visualization.

This tutorial focuses on CosMx data, which provides Fields of View (FoVs) with cell identifiers. Note that FoVs are unique to CosMx and are not available for other technologies like Xenium and Merfish/Merscope.

For CosMx data, you don’t need to `keep_polygons` initially because the metrics can be computed without them. Polygons can be loaded later if needed.

    # Reload CosMx data with sample name and without polygons
    cosmxFolder <- system.file(file.path("extdata", "CosMx_DBKero_Tiny"),
                            package="SpaceTrooper")
    spe <- readCosmxSPE(cosmxFolder, sampleName="DBKero_CosMx")
    ## a custom function for the labels of this dataset
    source(system.file(file.path("scripts", "labelsfunct.R"),
                        package="SpaceTrooper"))
    spe <- addLabelsDBKero(spe)

    spe

    ## class: SpatialExperiment
    ## dim: 1010 905
    ## metadata(4): fov_positions fov_dim polygons technology
    ## assays(1): counts
    ## rownames(1010): RAMP2 CD83 ... NegPrb09 NegPrb10
    ## rowData names(0):
    ## colnames(905): f16_c1 f16_c10 ... f16_c98 f16_c99
    ## colData names(22): fov cellID ... labels labels_colors
    ## reducedDimNames(0):
    ## mainExpName: NULL
    ## altExpNames(0):
    ## spatialCoords names(2) : CenterX_global_px CenterY_global_px
    ## imgData names(1): sample_id

# 5 Field of Views (FOVs) Visualization

The `plotCellsFovs` function shows a map of the FoVs within an experiment. This plot is specific to CosMx data and uses cell centroids.

    # Plot the cells within their respective Field of Views (FOVs)
    plotCellsFovs(spe)

Because the dataset is a subset of just one Field of View of the original experiment, we are able to see the identifier of the FoV in black and the centroids of the cells in purple.

When an experiment has multiple FoVs, you can see the map and the topological organization of the FoVs, together with their identifiers.

# 6 Visualizing cell types

To focus on cell centroids, use the `plotCentroidsSPE` function. It provides a `colour_by` argument, similar to `scater`, to colour the centroids by the values present in the indicated column. Additionally, if you have a column with a color palette it automatically maps it to the values passed in the `colour_by` argument.

    # Plot the centroids of the cells in the SPE
    plotCentroids(spe, colourBy="labels")

    # Plot the centroids of the cells in the SPE with personalized labels
    plotCentroids(spe, colourBy="labels", palette="labels_colors")

This allow us to see the cell centroids coloured by their cell types giving us an idea of the cell population in the entire experiment.

Additionally, the `palette` argument allows to use a user-defined palette for the associated centroids. The `palette` value must by a column in the object `colData`.

Notice that with this plot the FoV identifier is not shown.

# 7 Quality control

The `spatialPerCellQC` function, inspired by `scater::addPerCellQC`, computes additional metrics for each cell in the `SpatialExperiment`. It also allows for the detection of negative control probes, which is crucial for QC.

By default, it automatically removes 0 counts cells, but this can be handled with the `rmZeros` argument.

    # Perform per-cell quality control checks
    spe <- spatialPerCellQC(spe, rmZeros=TRUE,
            negProbList=c("NegPrb", "Negative", "SystemControl"))
    names(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] "labels"                  "labels_colors"
    ## [23] "sum"                     "detected"
    ## [25] "subsets_NegPrb_sum"      "subsets_NegPrb_detected"
    ## [27] "subsets_NegPrb_percent"  "total"
    ## [29] "control_sum"             "control_detected"
    ## [31] "target_sum"              "target_detected"
    ## [33] "CenterX_global_px"       "CenterY_global_px"
    ## [35] "ctrl_total_ratio"        "log2Ctrl_total_ratio"
    ## [37] "CenterX_global_um"       "CenterY_global_um"
    ## [39] "Area_um"                 "dist_border_x"
    ## [41] "dist_border_y"           "dist_border"
    ## [43] "log2AspectRatio"         "CountArea"
    ## [45] "log2CountArea"

# 8 Metrics Histograms

You can investigate individual metrics by viewing their histograms. For outliers, use the `use_fences` argument to display the fences computed by `computeSpatialOutlier` (see next chunk).

    # Plot a histogram of counts (sum)
    plotMetricHist(spe, metric="sum")

    # Plot a histogram of cell areas (Area_um)
    plotMetricHist(spe, metric="Area_um")

    # Plot a histogram of proportion of counts respect to the cell area in micron
    plotMetricHist(spe, metric="log2CountArea")

These plots show, respectively, the distributions of the total counts (`sum`), of the Area in micron (`Area_um`) and the relationship between the counts and the Area of each cell (`log2CountArea`).

# 9 Spatial Outlier Detection

Spatial outlier detection is another critical step in QC. While the flag score addresses some metrics, other outlier detection methods may be needed.

The `computeSpatialOutlier` function allows the computation of the medcouple statistics on a specified metric (`compute_by` argument). The medcouple is specifically designed for asymmetric distributions, indeed the function stamps a warning message when this requisite is not satisfied. It can also use `scuttle::isOutlier` for symmetric distributions. The `method` argument supports `mc`, `scuttle`, or `both`.

This outlier detection approach can be used to decide if and which cells can be discarded on a singular metric.

    # Identify spatial outliers based on cell area (Area_um)
    spe <- computeSpatialOutlier(spe, computeBy="Area_um", method="both")

    # Identify spatial outliers based on mean DAPI intensity
    spe <- computeSpatialOutlier(spe, computeBy="Mean.DAPI", method="both")
    names(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] "labels"                  "labels_colors"
    ## [23] "sum"                     "detected"
    ## [25] "subsets_NegPrb_sum"      "subsets_NegPrb_detected"
    ## [27] "subsets_NegPrb_percent"  "total"
    ## [29] "control_sum"             "control_detected"
    ## [31] "target_sum"              "target_detected"
    ## [33] "CenterX_global_px"       "CenterY_global_px"
    ## [35] "ctrl_total_ratio"        "log2Ctrl_total_ratio"
    ## [37] "CenterX_global_um"       "CenterY_global_um"
    ## [39] "Area_um"                 "dist_border_x"
    ## [41] "dist_border_y"           "dist_border"
    ## [43] "log2AspectRatio"         "CountArea"
    ## [45] "log2CountArea"           "Area_um_outlier_mc"
    ## [47] "Area_um_outlier_sc"      "Mean.DAPI_outlier_mc"
    ## [49] "Mean.DAPI_outlier_sc"

If we computed outliers with the `computeSpatialOutlier` function, we can also visualize which fences have been used to create the filter on the cells.

    # Plot a histogram with fences to identify outliers using the medcouple
    plotMetricHist(spe, metric="Area_um", useFences="Area_um_outlier_mc")

    # Plot a histogram with fences to identify outliers using scuttle
    plotMetricHist(spe, metric="Area_um", useFences="Area_um_outlier_sc")

    # Plot a histogram with fences to identify outliers using the medcouple
    plotMetricHist(spe, metric="Mean.DAPI", useFences="Mean.DAPI_outlier_mc")

    # Plot a histogram with fences to identify outliers using scuttle
    plotMetricHist(spe, metric="Mean.DAPI", useFences="Mean.DAPI_outlier_sc")

We visualize the fences computed with medcouple and scuttle outlier detection approaches, to directly inspect differences and the amount of detected outlier each method detected.

If we want, we can already use these fences to remove the computed outliers.

# 10 The Quality Score

Next, we use `computeQCScore` to calculate a flag score based on previously computed metrics. The quality score combines transcript counts related to cell area, the aspect ratio of each cell, and its distance from the FoV border (only for CosMx, this last one is not used otherwise).

See the `help(computeQCScore)` details section for additional details.

    # Calculate quality scores for each cell
    spe <- computeQCScore(spe)
    names(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] "labels"                  "labels_colors"
    ## [23] "sum"                     "detected"
    ## [25] "subsets_NegPrb_sum"      "subsets_NegPrb_detected"
    ## [27] "subsets_NegPrb_percent"  "total"
    ## [29] "control_sum"             "control_detected"
    ## [31] "target_sum"              "target_detected"
    ## [33] "CenterX_global_px"       "CenterY_global_px"
    ## [35] "ctrl_total_ratio"        "log2Ctrl_total_ratio"
    ## [37] "CenterX_global_um"       "CenterY_global_um"
    ## [39] "Area_um"                 "dist_border_x"
    ## [41] "dist_border_y"           "dist_border"
    ## [43] "log2AspectRatio"         "CountArea"
    ## [45] "log2CountArea"           "Area_um_outlier_mc"
    ## [47] "Area_um_outlier_sc"      "Mean.DAPI_outlier_mc"
    ## [49] "Mean.DAPI_outlier_sc"    "QC_score"

Logical filters can then be computed using `computeQCScoreFlags`, which requires thresholds for various metrics. Currently, the function considers:

- Quality Score (`qsThreshold`): Cells with scores below this threshold (default 0.5) are flagged for exclusion. This value can be used to indicate the quantile for the filtering when setting the `useQSQuantiles` argument to `TRUE`.

- Quality Score Quantiles (`useQSQuantiles`): Option to filter based on quantiles (default `FALSE`).

    # Compute flags to identify cells for filtering
    spe <- computeQCScoreFlags(spe, qsThreshold=0.5)
    names(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] "labels"                  "labels_colors"
    ## [23] "sum"                     "detected"
    ## [25] "subsets_NegPrb_sum"      "subsets_NegPrb_detected"
    ## [27] "subsets_NegPrb_percent"  "total"
    ## [29] "control_sum"             "control_detected"
    ## [31] "target_sum"              "target_detected"
    ## [33] "CenterX_global_px"       "CenterY_global_px"
    ## [35] "ctrl_total_ratio"        "log2Ctrl_total_ratio"
    ## [37] "CenterX_global_um"       "CenterY_global_um"
    ## [39] "Area_um"                 "dist_border_x"
    ## [41] "dist_border_y"           "dist_border"
    ## [43] "log2AspectRatio"         "CountArea"
    ## [45] "log2CountArea"           "Area_um_outlier_mc"
    ## [47] "Area_um_outlier_sc"      "Mean.DAPI_outlier_mc"
    ## [49] "Mean.DAPI_outlier_sc"    "QC_score"
    ## [51] "low_qcscore"

    table(spe$low_qcscore)

    ##
    ## FALSE  TRUE
    ##   818    86

We detected 88 cells to be removed.

# 11 Additional metrics to filter out cells

While for other metrics such as the total counts and the negative prob ratio, the function `computeThresholdFlags` considers:

- Total Counts (`totalThreshold`): Minimum count threshold (default 0).

- Negative Probe Ratio (`ctrlTotRatioThreshold`): Minimum ratio of negative probes to total counts (default 0.1).

    spe <- computeThresholdFlags(spe, totalThreshold=0,
                                    ctrlTotRatioThreshold=0.1)
    table(spe$threshold_flags)

    ##
    ## FALSE
    ##   904

In this example, we don’t find any cell to be removed.

# 12 Adding Polygon and Visualization

To better understand the quality score values we start to load the polygons, giving us a better overview of the cells characteristics.

We can load and add polygons to the SPE object using the following functions. Each technology has its own `readPolygons` function to standardize the loaded `sf` object and handle different file types.

    # Read polygon data associated with cells in the SPE
    # the polygon file path is stored in the spe metadata
    pols <- readPolygonsCosmx(metadata(spe)$polygons)

    # Add the polygon data to the SPE object
    spe <- addPolygonsToSPE(spe, pols)

Once the polygons are stored in an `sf` object within `colData`, they can be visualized using functions based on the `ggplot2` library.

    # Plot the polygons of the selected cells
    plotPolygons(spe, bgColor="white")

Showing the cells on a white background for better visualization.

    # Plot polygons colored by cell area
    plotPolygons(spe, colourBy="log2AspectRatio")

    plotPolygons(spe, colourBy="Area_um")

We can see in `yellow` and `darkviolet` that there are few cells with extreme values of `log2AspectRatio` and `Area_um` in micron.

    plotPolygons(spe, colourBy="QC_score")

    plotPolygons(spe, colourBy="low_qcscore")

We can see that the quality score is able to detect both these aspects and highlight the cells that are mostly isolated on the FoV border or showing a weird confomation.

We always recommend to be aware of the cell populations in the under-study context, before proceeding to remove the detected cells.

# 13 Fov Zoom and Map

The `plotZoomFovsMap` function allows you to visualize a map of the FoVs with a zoom-in of selected FoVs, colored by the `colour_by` argument.

    plotZoomFovsMap(spe, fovs=16, colourBy="QC_score")

    plotZoomFovsMap(spe, fovs=16, colourBy="low_qcscore")

We see on the left side the map of all the FoVs (only the FoV 16 in this case), together with the poligons on the right, coloured by the quality score. Allowing us to have a better view of a specific tissue area in the whole experiment.

# 14 Conclusion

In this vignette, we explored the main functionalities of the `SpaceTrooper` package for spatial data analysis. Main steps shown are: * data loading: Xenium, Merfish/Merscope, CosMx * polygons loading: Xenium, Merfish/Merscope, CosMx * quality control: + outlier detection: medcouple and scuttle MAD + flag score: a score combining transcript counts, cell area, aspect ratio and distance from the FoV border * visualization: + centroids: with ggplot2 + polygons: sf + ggplot2

# 15 Session Information

    sessionInfo()

    ## R version 4.5.1 Patched (2025-08-23 r88802)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.3 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /home/biocbuild/bbs-3.22-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] SpaceTrooper_1.0.0          SpatialExperiment_1.20.0
    ##  [3] SingleCellExperiment_1.32.0 SummarizedExperiment_1.40.0
    ##  [5] Biobase_2.70.0              GenomicRanges_1.62.0
    ##  [7] Seqinfo_1.0.0               IRanges_2.44.0
    ##  [9] S4Vectors_0.48.0            BiocGenerics_0.56.0
    ## [11] generics_0.1.4              MatrixGenerics_1.22.0
    ## [13] matrixStats_1.5.0           BiocStyle_2.38.0
    ##
    ## loaded via a namespace (and not attached):
    ##   [1] DBI_1.2.3                 gridExtra_2.3
    ##   [3] rlang_1.1.6               magrittr_2.0.4
    ##   [5] scater_1.38.0             e1071_1.7-16
    ##   [7] compiler_4.5.1            DelayedMatrixStats_1.32.0
    ##   [9] SpatialExperimentIO_1.2.0 sfheaders_0.4.4
    ##  [11] vctrs_0.6.5               shape_1.4.6.1
    ##  [13] pkgconfig_2.0.3           fastmap_1.2.0
    ##  [15] backports_1.5.0           magick_2.9.0
    ##  [17] XVector_0.50.0            labeling_0.4.3
    ##  [19] scuttle_1.20.0            rmarkdown_2.30
    ##  [21] ggbeeswarm_0.7.2          tinytex_0.57
    ##  [23] purrr_1.1.0               bit_4.6.0
    ##  [25] glmnet_4.1-10             xfun_0.53
    ##  [27] cachem_1.1.0              beachmat_2.26.0
    ##  [29] jsonlite_2.0.0            rhdf5filters_1.22.0
    ##  [31] DelayedArray_0.36.0       Rhdf5lib_1.32.0
    ##  [33] BiocParallel_1.44.0       irlba_2.3.5.1
    ##  [35] broom_1.0.10              parallel_4.5.1
    ##  [37] R6_2.6.1                  bslib_0.9.0
    ##  [39] RColorBrewer_1.1-3        limma_3.66.0
    ##  [41] car_3.1-3                 jquerylib_0.1.4
    ##  [43] iterators_1.0.14          Rcpp_1.1.0
    ##  [45] bookdown_0.45             assertthat_0.2.1
    ##  [47] knitr_1.50                R.utils_2.13.0
    ##  [49] splines_4.5.1             Matrix_1.7-4
    ##  [51] tidyselect_1.2.1          viridis_0.6.5
    ##  [53] dichromat_2.0-0.1         abind_1.4-8
    ##  [55] yaml_2.3.10               codetools_0.2-20
    ##  [57] lattice_0.22-7            tibble_3.3.0
    ##  [59] withr_3.0.2               S7_0.2.0
    ##  [61] evaluate_1.0.5            sf_1.0-21
    ##  [63] survival_3.8-3            units_1.0-0
    ##  [65] proxy_0.4-27              pillar_1.11.1
    ##  [67] BiocManager_1.30.26       ggpubr_0.6.2
    ##  [69] carData_3.0-5             KernSmooth_2.23-26
    ##  [71] foreach_1.5.2             ggplot2_4.0.0
    ##  [73] sparseMatrixStats_1.22.0  scales_1.4.0
    ##  [75] class_7.3-23              glue_1.8.0
    ##  [77] tools_4.5.1               BiocNeighbors_2.4.0
    ##  [79] robustbase_0.99-6         data.table_1.17.8
    ##  [81] ScaledMatrix_1.18.0       locfit_1.5-9.12
    ##  [83] ggsignif_0.6.4            cowplot_1.2.0
    ##  [85] rhdf5_2.54.0              grid_4.5.1
    ##  [87] tidyr_1.3.1               DropletUtils_1.30.0
    ##  [89] edgeR_4.8.0               beeswarm_0.4.0
    ##  [91] BiocSingular_1.26.0       HDF5Array_1.38.0
    ##  [93] vipor_0.4.7               rsvd_1.0.5
    ##  [95] Formula_1.2-5             cli_3.6.5
    ##  [97] viridisLite_0.4.2         S4Arrays_1.10.0
    ##  [99] arrow_22.0.0              dplyr_1.1.4
    ## [101] DEoptimR_1.1-4            gtable_0.3.6
    ## [103] R.methodsS3_1.8.2         rstatix_0.7.3
    ## [105] sass_0.4.10               digest_0.6.37
    ## [107] classInt_0.4-11           ggrepel_0.9.6
    ## [109] SparseArray_1.10.0        dqrng_0.4.1
    ## [111] rjson_0.2.23              farver_2.1.2
    ## [113] htmltools_0.5.8.1         R.oo_1.27.1
    ## [115] lifecycle_1.0.4           h5mread_1.2.0
    ## [117] statmod_1.5.1             bit64_4.6.0-1