Work with R¶
Much of the downstream analysis of spatial omics happens in R (sf,
SpatialExperiment, Seurat). This page shows how SMINT's outputs feed the R
scripts in the repository, and how to call R from Python with
smint.r_integration.
Where R fits in the workflow¶
flowchart LR
subgraph py["SMINT (Python)"]
S["Segmentation<br/>cells/nuclei_outlines.csv"]
R1["Same-section registration<br/>centroids_transformed.csv"]
R2["Sequential registration<br/>sm_transformed.csv"]
end
subgraph r["R scripts in the repository"]
ST["stitching/stitching.Rmd<br/>original stitching workflow"]
TA["assignment/transcript_assignment.R<br/>transcripts → cells"]
MI["integration/metabolomics_integration.R<br/>metabolites → cells"]
end
S -. "Python port:<br/>smint.segmentation.stitching" .- ST
S --> R1 --> TA
TA -- "SpatialExperiment<br/>(gene counts)" --> MI
R2 --> MI
MI --> OUT["SpatialExperiment<br/>genes + metabolites per cell"]
The R scripts are analysis templates, not a package: each starts with
/path/to/... placeholders that you edit before running. They need R ≥ 4 with
sf, sfheaders, data.table, dplyr, stringr, ggplot2, forcats,
Seurat and SpatialExperiment (Bioconductor).
The R scripts¶
stitching/stitching.Rmd — stitch tiled segmentations¶
The original stitching workflow, kept for reference and for data segmented
outside SMINT. run_distributed_segmentation now does the same job in Python
(smint.segmentation.stitching), so you
do not need this notebook for SMINT segmentations.
| Input | A folder of per-field-of-view Cellpose outline files (*.txt, one object per line as x1,y1,x2,y2,…) for nuclei and another for cells, plus a CSV of field-of-view positions in pixels (no header; columns x, y) |
| Steps | Place outlines in the global frame, convert pixels to µm (the pixel size, 0.124 µm, is set in the notebook — change it to yours), merge overlaps above 50%, trim overlaps below 25%, remove the remaining conflicts, then join nuclei to cells |
| Output | sf objects saved as sf_nuc_final_<sample>_<date>.rds and sf_cell_final_<sample>_<date>.rds, plus centroid CSVs nuc_centroids_…csv and cell_centroids_…csv |
assignment/transcript_assignment.R — assign transcripts to cells¶
Counts the transcripts inside each registered cell (or nucleus) polygon.
| Input | Xenium transcripts.csv.gz (x_location, y_location, feature_name, qv), and an .rds of sf polygons in the Xenium frame with a cell column |
| Steps | Keep transcripts with qv ≥ 20, spatially join points to polygons, count genes per cell |
| Output | A SpatialExperiment with a counts assay (genes × cells), saved as .rds |
integration/metabolomics_integration.R — add metabolites to cells¶
Joins registered spatial metabolomics pixels to the ST cells.
| Input | The registered SM table (coordinates in the ST frame; metabolite columns prefixed with X, as read_sm_matrix writes them), the ST SpatialExperiment with a logcounts assay, and the ST cell polygons as sf |
| Steps | Buffer each cell centroid by 20 µm, average the SM pixels inside, combine with the ST log-counts |
| Output | One SpatialExperiment with genes and metabolites as rows and cells as columns, saved as .rds |
Edit before running
Set the input paths. The script counts metabolites as the SM columns whose
names contain X and builds points from x_transformed / y_transformed,
the columns SMINT writes; adjust both if your table differs.
From SMINT outputs to R¶
transcript_assignment.R needs polygons in the Xenium frame. SMINT registers
centroids, so move each outline by its own registration offset, then build
the polygons in R.
1. In Python, shift each nucleus outline by the offset registration
applied to its centroid (this assumes you registered the
nuclei_summary.csv table converted to µm, as in the
Segmentation guide):
import pandas as pd
pixel_size_um = 0.5 # placeholder: µm per pixel of the segmented image
# Registered nuclei: one row per row of nuclei_summary.csv (converted to µm)
reg = pd.read_csv("results/reg/centroids_transformed.csv")
shift = pd.DataFrame({
"global_cell_id": reg["global_cell_id"],
"dx": reg["x_transformed"] - reg["x_centroid"],
"dy": reg["y_transformed"] - reg["y_centroid"],
})
outlines = pd.read_csv("results/seg_run/nuclei_outlines.csv")
outlines[["x", "y"]] *= pixel_size_um
outlines = outlines.merge(shift, on="global_cell_id")
outlines["x"] += outlines["dx"]
outlines["y"] += outlines["dy"]
outlines[["global_cell_id", "x", "y"]].to_csv("nuclei_outlines_registered.csv", index=False)
A per-object shift is exact for snapped nuclei and a close approximation for the smooth transform at the scale of one cell.
2. In R, turn the vertices into polygons with the cell column the
assignment script expects:
library(data.table)
library(sfheaders)
v <- fread("nuclei_outlines_registered.csv")
polys <- sf_polygon(v, x = "x", y = "y", polygon_id = "global_cell_id")
polys$cell <- polys$global_cell_id
saveRDS(polys, "aligned_sf.rds")
Point transcript_assignment.R at aligned_sf.rds. For the metabolomics
script, use sm_transformed.csv from
sequential registration,
run with orientation="stalign" so its coordinates overlay the ST cells.
Call R from Python¶
run_r_script runs a script with the system Rscript and needs nothing
beyond R on your PATH:
from smint.r_integration import run_r_script
code = run_r_script("to_sf.R") # returns the exit code
code, out, err = run_r_script("to_sf.R", return_output=True) # also captures output
code = run_r_script("analysis.R", args=["--input", "data.csv"]) # passed to the script
It returns 1 (with a logged error) when the script does not exist, and
otherwise R's exit code, so check it: a failing R script does not raise in
Python. On a cluster, load R first (for example module load R) in the shell
or job that runs Python.
In-process R with rpy2¶
smint.r_integration also has helpers built on rpy2 (pip install
"smint[r_integration]"): initialize_r(packages), pandas_to_r(df),
r_to_pandas(obj), and in smint.r_integration.r_bridge, exec_r_code,
r_function_to_python and load_r_analysis_script.
The rpy2 helpers are not tested
Without rpy2 these functions log "rpy2 is not installed" and return
None (or False for initialize_r). They have not been tested
against a current rpy2 release, so prefer run_r_script for anything
you depend on. Importing smint.r_integration without rpy2 also logs a
warning ("rpy2 package not available"), which you can ignore if you only
use run_r_script.
Next steps¶
- Data formats for every column SMINT writes
- Segmentation and Alignment guides