Technology Focus: 10x Genomics Xenium#
To follow along with this tutorial, you can download the data already in SpatialData format from here.
Description of prior dataset acquisition and segmentation#
Dataset Acquisition (10x Genomics Xenium)#
For this tutorial, we used data from the 10x Genomics Xenium Prime Breast Cancer FFPE dataset. We downloaded the data using:
curl -O https://s3-us-west-2.amazonaws.com/10x.files/samples/xenium/3.0.0/
Xenium_Prime_Breast_Cancer_FFPE/Xenium_Prime_Breast_Cancer_FFPE_outs.zip
Subsetting the Field of View#
We subset the dataset using the following bounding box (X, Y coordinates):
2631.31, 8503.97
4150.72, 8503.97
4150.72, 10298.85
2631.31, 10298.85
Cell Segmentation Methods#
We applied two segmentation approaches for comparison to the Xenium Multi-Modal Cell Segmentation:
BIDCell — SydneyBioX/BIDCell
Proseg (v2.0.5) — dcjones/proseg
Reference scRNA-seq Dataset for Training & QC#
For BIDCell training, as well as supervised quality control (see below in this tutorial), we used the Janesick et al. scRNA-seq breast cancer dataset following:
LMWeber Script on loading Chromium data: https://github.com/lmweber/STexampleData/blob/devel/inst/scripts/ make-data-Janesick-breastCancer-Chromium.R
OSTA Guide on processing the Chromium data: https://lmweber.org/OSTA/pages/seq-deconvolution.html
Download commands:
wget -O count_matrix.h5 "https://www.ncbi.nlm.nih.gov/geo/download/
?acc=GSM7782698&format=file&file=GSM7782698%5Fcount%5Fraw%5Ffeature%5Fbc%5Fmatrix%2Eh5"
wget -O Cell_Barcode_Type_Matrices.xlsx "https://zenodo.org/records/10076046/files/
Cell_Barcode_Type_Matrices.xlsx?download=1"
Segmentation Parameters#
Proseg
Used default parameters.
BIDCell
Annotated elongated cell types:
endo
stromal
myoepi
perivas
Additional settings:
test_epoch = 4test_steps = 100
All other parameters left at default.
Import into SpatialData#
After segmentation, all outputs were imported into SpatialData objects. For a demonstration on how to get data into the right format, please refer to this tutorial. These processed SpatialData objects are also uploaded to the tutorial repository so users can directly run the workflow without repeating the preprocessing steps.
Read and crop SpatialData objects#
[1]:
import anndata as ad
import matplotlib.patches as patches
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scanpy as sc
import seaborn as sns
import spatialdata as sd
import spatialdata_plot # noqa
import segtraq
segtraq.settings.n_jobs = -1 # Use all available CPU cores
# warnings.filterwarnings(action="ignore")
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/.venv/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
[2]:
sdata_xenium_ws = sd.read_zarr("../../data/xenium_5K_data/xenium.zarr")
sdata_bidcell_ws = sd.read_zarr("../../data/xenium_5K_data/bidcell.zarr")
sdata_proseg_ws = sd.read_zarr("../../data/xenium_5K_data/proseg2.zarr")
no parent found for <ome_zarr.reader.Label object at 0x7fff7dd1a120>: None
no parent found for <ome_zarr.reader.Label object at 0x7fff7dde1d10>: None
The SpatialData objects can be subset via spatialdata.query.bounding_box().
[3]:
bb_xmin = 800
bb_ymin = 1150
bb_w = 200
bb_h = 300
bb_xmax = bb_xmin + bb_w
bb_ymax = bb_ymin + bb_h
[4]:
f, ax = plt.subplots(figsize=(5, 5))
sdata_xenium_ws.pl.render_shapes("cell_boundaries").pl.show(ax=ax)
rect = patches.Rectangle((bb_xmin, bb_ymin), bb_w, bb_h, linewidth=5, edgecolor="red", facecolor="none")
ax.add_patch(rect)
[4]:
<matplotlib.patches.Rectangle at 0x7fff370e9d10>
[5]:
def crop(ws):
return ws.query.bounding_box(
axes=["x", "y"],
min_coordinate=[bb_xmin, bb_ymin],
max_coordinate=[bb_xmax, bb_ymax],
target_coordinate_system="global",
)
sdata_xenium = crop(sdata_xenium_ws)
sdata_bidcell = crop(sdata_bidcell_ws)
sdata_proseg = crop(sdata_proseg_ws)
Initialize SegTraQ objects#
Next, we initialize SegTraQ objects, the core interface for computing SegTraQ metrics. During initialization, all inputs are are validated via validate_spatialdata(). Don’t worry if you do not know which parameters you need up front; just put in your spatialdata object, and SegTraQ will tell you which arguments are wrong/missing.
The four segmentation methods apply different filters to the points prior to segmentation. Proseg and BIDCell remove control probes (e.g. NegControlProbe, Intergenic_Region) before segmentation. In addition, Proseg and BIDCell exclude transcripts with a quality value (qv, the Xenium per-transcript decoding quality score) < 20.
For a fair comparison of metrics, like the percentage of unassigned transcripts, across methods, we subset the transcripts to include only gene-specific probes with qv ≥ 20. This is also compatible with the preprocessing for the cell feature matrix by 10x Genomics.
[6]:
st_xenium = segtraq.SegTraQ(
sdata_xenium, images_key="image", tables_centroid_x_key="x_centroid", tables_centroid_y_key="y_centroid"
)
st_bidcell = segtraq.SegTraQ(
sdata_bidcell,
images_key="image",
points_background_id=0,
tables_area_key=None,
tables_centroid_x_key="cell_centroid_x",
tables_centroid_y_key="cell_centroid_y",
)
st_proseg = segtraq.SegTraQ(
sdata_proseg,
images_key="image",
points_cell_id_key="assignment",
points_background_id=2**32 - 1,
points_gene_key="gene",
tables_area_key=None,
tables_cell_id_key="cell",
shapes_cell_id_key="cell",
tables_centroid_x_key="centroid_x",
tables_centroid_y_key="centroid_y",
)
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/utils.py:1854: UserWarning: Duplicate IDs detected in index 'cell_id' for shapes 'nucleus_boundaries'. Resetting and renaming index to `segtraq_id` to ensure uniqueness.
nucleus_shapes = _ensure_index(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: UserWarning: Filtering control probes matching prefixes ('NegControlProbe_', 'antisense_', 'NegControlCodeword', 'BLANK_', 'Blank-', 'NegPrb', 'DeprecatedCodeword_', 'UnassignedCodeword_', 'Intergenic_Region_') and transcripts with qv < 20 from transcript points in-place; control probes are also removed from the expression table if present. Configure `min_qv`, `control_prefixes`, and `inplace` via `filter_kwargs`.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: RuntimeWarning: Gene sets differ between assigned transcript points and the expression table. 0 genes occur only in points (e.g. []), and 455 occur only in the table (e.g. ['CARD9', 'CDH22', 'VAMP5', 'POU2F3', 'ZMYND10']). Genes only occuring in the table can be introduced by cropping (`SpatialData`), but differences may also be due to the filtering used to generate the transcript data and expression matrix. Please check that `filter_kwargs`, particularly `min_qv` and `control_prefixes`, are consistent with the filtering used to generate the expression table.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: RuntimeWarning: Cell IDs differ between assigned transcript points and the expression table. 0 cells occur only in points (e.g. []), and 6 occur only in the table (e.g. ['aphffbmb-1', 'aphemnlm-1', 'iceeeemj-1', 'oemhhohn-1', 'apnfcbpf-1']). Cells only occuring in the table can reflect zero-count cells, but differences may also be due to the filtering used to generate the transcript data and expression matrix. Please check that `filter_kwargs`, particularly `min_qv` and `control_prefixes`, are consistent with the filtering used to generate the expression table.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:162: RuntimeWarning: No area column specified for tables. Area will be automatically computed from shapes.
validate_spatialdata(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: UserWarning: Filtering control probes matching prefixes ('NegControlProbe_', 'antisense_', 'NegControlCodeword', 'BLANK_', 'Blank-', 'NegPrb', 'DeprecatedCodeword_', 'UnassignedCodeword_', 'Intergenic_Region_') and transcripts with qv < 20 from transcript points in-place; control probes are also removed from the expression table if present. Configure `min_qv`, `control_prefixes`, and `inplace` via `filter_kwargs`.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: RuntimeWarning: Gene sets differ between assigned transcript points and the expression table. 0 genes occur only in points (e.g. []), and 438 occur only in the table (e.g. ['CARD9', 'CDH22', 'VAMP5', 'POU2F3', 'HSD17B6']). Genes only occuring in the table can be introduced by cropping (`SpatialData`), but differences may also be due to the filtering used to generate the transcript data and expression matrix. Please check that `filter_kwargs`, particularly `min_qv` and `control_prefixes`, are consistent with the filtering used to generate the expression table.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: RuntimeWarning: Cell IDs differ between assigned transcript points and the expression table. 0 cells occur only in points (e.g. []), and 18 occur only in the table (e.g. [np.int32(14112), np.int32(15585), np.int32(14626), np.int32(13762), np.int32(15971)]). Cells only occuring in the table can reflect zero-count cells, but differences may also be due to the filtering used to generate the transcript data and expression matrix. Please check that `filter_kwargs`, particularly `min_qv` and `control_prefixes`, are consistent with the filtering used to generate the expression table.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:162: UserWarning: points_background_id '4294967295' is not the most common cell ID among points (most common is '2021'). This may indicate that your background ID is not correctly set. If you are sure that 'points_background_id' is correct, you can ignore this warning.
validate_spatialdata(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:162: RuntimeWarning: No area column specified for tables. Area will be automatically computed from shapes.
validate_spatialdata(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/utils.py:1854: UserWarning: Duplicate IDs detected in index 'cell_id' for shapes 'nucleus_boundaries'. Resetting and renaming index to `segtraq_id` to ensure uniqueness.
nucleus_shapes = _ensure_index(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: UserWarning: Filtering control probes matching prefixes ('NegControlProbe_', 'antisense_', 'NegControlCodeword', 'BLANK_', 'Blank-', 'NegPrb', 'DeprecatedCodeword_', 'UnassignedCodeword_', 'Intergenic_Region_') and transcripts with qv < 20 from transcript points in-place; control probes are also removed from the expression table if present. Configure `min_qv`, `control_prefixes`, and `inplace` via `filter_kwargs`.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: RuntimeWarning: Gene sets differ between assigned transcript points and the expression table. 0 genes occur only in points (e.g. []), and 447 occur only in the table (e.g. ['CACNG5', 'CARD9', 'CDH22', 'VAMP5', 'POU2F3']). Genes only occuring in the table can be introduced by cropping (`SpatialData`), but differences may also be due to the filtering used to generate the transcript data and expression matrix. Please check that `filter_kwargs`, particularly `min_qv` and `control_prefixes`, are consistent with the filtering used to generate the expression table.
sdata_new = _filter_control_and_low_quality_transcripts(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/SegTraQ.py:190: RuntimeWarning: Cell IDs differ between assigned transcript points and the expression table. 14 cells occur only in points (e.g. [np.int64(12228), np.int64(1094), np.int64(7463), np.int64(426), np.int64(5402)]), and 47 occur only in the table (e.g. [np.int64(16007), np.int64(12296), np.int64(12948), np.int64(7957), np.int64(5272)]). Cells only occuring in the table can reflect zero-count cells, but differences may also be due to the filtering used to generate the transcript data and expression matrix. Please check that `filter_kwargs`, particularly `min_qv` and `control_prefixes`, are consistent with the filtering used to generate the expression table.
sdata_new = _filter_control_and_low_quality_transcripts(
Load reference scRNA-seq data and transfer labels#
To evaluate how SegTraQ metrics differ between cell types, we first transfer labels from the reference scRNA-seq dataset to the spatial data using segtraq.run_label_transfer.
[7]:
# Load scRNA-seq data
adata_ref = ad.read_h5ad("../../data/xenium_5K_data/BC_scRNAseq_Janesick.h5ad")
For easier access, we store the SpatialData object into a dictionary.
[8]:
st_dict = {"xenium": st_xenium, "bidcell": st_bidcell, "proseg": st_proseg}
[9]:
# Transfer labels
for _method, st in st_dict.items():
st.run_label_transfer(adata_ref, ref_cell_type="celltype_major", ref_raw_counts_layer="raw", inplace=True)
WARNING: adata.X seems to be already log-transformed.
WARNING: adata.X seems to be already log-transformed.
WARNING: adata.X seems to be already log-transformed.
We can visualize the spatial data and color cell masks by transferred labels using spatialdata-plot.
[10]:
# Define color palette for plotting
col_celltype = {
"T": "#fb8072",
"B": "#bc80bd",
"macro": "#910290",
"dendritic": "#fdb462",
"mast": "#959059",
"perivas": "#fed9a6",
"endo": "#a6cee3",
"myoepi": "#2782bb",
"DCIS1": "#3c7761",
"DCIS2": "#66a61e",
"tumor": "#66c2a5",
"stromal": "#d45943",
"Unknown": "#808080",
}
Cells with a transcripts count < 10 and a gene count < 5 are not considered in label transfer and their returned transferred_cell_type returns np.nan, and missing values are subsequently replaced with the label “Unknown”.
[11]:
# Replace NaN with Unknown for plotting
for _method, st in st_dict.items():
s = st.sdata.tables["table"].obs["transferred_cell_type"]
if pd.api.types.is_categorical_dtype(s):
s = s.cat.add_categories(["Unknown"])
st.sdata.tables["table"].obs["transferred_celltype_plot"] = s.fillna("Unknown")
/scratch/jobs/61866415/ipykernel_1844593/1864087363.py:4: DeprecationWarning: is_categorical_dtype is deprecated and will be removed in a future version. Use isinstance(dtype, pd.CategoricalDtype) instead
if pd.api.types.is_categorical_dtype(s):
/scratch/jobs/61866415/ipykernel_1844593/1864087363.py:4: DeprecationWarning: is_categorical_dtype is deprecated and will be removed in a future version. Use isinstance(dtype, pd.CategoricalDtype) instead
if pd.api.types.is_categorical_dtype(s):
/scratch/jobs/61866415/ipykernel_1844593/1864087363.py:4: DeprecationWarning: is_categorical_dtype is deprecated and will be removed in a future version. Use isinstance(dtype, pd.CategoricalDtype) instead
if pd.api.types.is_categorical_dtype(s):
[12]:
plt.style.use("dark_background")
[13]:
def feature_spatial_plot(sdata, shapes_key, feature, col_palette, method):
axes = plt.subplots(1, 2, figsize=(10, 5), constrained_layout=True)[1].flatten()
# compute per-cell color
labels = sdata.tables["table"].obs[feature].unique().astype(str).tolist()
cols = [col_palette[lab] for lab in labels]
# Dapi image
sdata.pl.render_images("image").pl.show(ax=axes[0], title=f"{method}: DAPI image", coordinate_systems="global")
# Plot - Cell boundaries
sdata.tables["table"].obs["region"] = shapes_key
sdata.set_table_annotates_spatialelement("table", region=shapes_key)
sdata.pl.render_shapes(shapes_key, color=feature, palette=cols, groups=labels).pl.show(
ax=axes[1], title=f"{method}: Cell masks colored by {feature}", coordinate_systems="global"
)
10 Xenium MultiModal Cell Segmentation
[14]:
feature_spatial_plot(st_xenium.sdata, "cell_boundaries", "transferred_celltype_plot", col_celltype, "xenium")
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/.venv/lib/python3.13/site-packages/spatialdata/_core/spatialdata.py:477: UserWarning: Converting `region_key: region` to categorical dtype.
convert_region_column_to_categorical(table)
BIDCell Segmentation
[15]:
feature_spatial_plot(st_bidcell.sdata, "cell_boundaries", "transferred_celltype_plot", col_celltype, "BIDCell")
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/.venv/lib/python3.13/site-packages/spatialdata/_core/spatialdata.py:477: UserWarning: Converting `region_key: region` to categorical dtype.
convert_region_column_to_categorical(table)
Proseg Segmentation
[16]:
feature_spatial_plot(st_proseg.sdata, "cell_boundaries_z1", "transferred_celltype_plot", col_celltype, "Proseg")
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/.venv/lib/python3.13/site-packages/spatialdata/_core/spatialdata.py:477: UserWarning: Converting `region_key: region` to categorical dtype.
convert_region_column_to_categorical(table)
The dominant cell types are DCIS2, T and B cells. Proseg has the highest proportion of cells assigned to “Unknown”, i.e. transcript counts <10 and gene_counts <5.
[17]:
method_dfs = []
for method, st in st_dict.items():
df = st.sdata["table"].obs["transferred_celltype_plot"].value_counts(normalize=True).reset_index()
df.columns = ["celltype", "proportion"]
df["method"] = method
method_dfs.append(df)
plot_df = pd.concat(method_dfs, ignore_index=True)
plot_wide = plot_df.pivot(index="method", columns="celltype", values="proportion").fillna(0)
method_order = ["xenium", "bidcell", "proseg"]
plot_wide = plot_wide.loc[method_order]
fig, ax = plt.subplots(figsize=(2, 4))
plot_wide.plot(kind="bar", stacked=True, color=[col_celltype.get(c, "grey") for c in plot_wide.columns], ax=ax)
ax.set_ylabel("Proportion")
ax.set_xlabel("Method")
# Legend text in white
legend = ax.legend(title="Cell type", bbox_to_anchor=(1.05, 1), loc="upper left", frameon=False)
fig.tight_layout()
plt.show()
/scratch/jobs/61866415/ipykernel_1844593/990135166.py:24: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all Axes decorations.
fig.tight_layout()
Running SegTraQ QC metrics#
Now the data is ready to compute SegTraQ QC metrics. Below we demonstrate how to run the individual modules.
Baseline module#
The baseline (bl) module computes basic quality-control metrics such as the number of cells, the percentage of unassigned transcripts, and the number of transcripts and genes per cell. When inplace=True, global metrics are stored in the uns slot, and per-cell metrics are stored in the obs slot of the table within the SpatialData object.
[18]:
for method, st in st_dict.items():
st.bl.num_cells(inplace=True)
st.bl.perc_unassigned_transcripts(inplace=True)
st.bl.transcripts_per_cell()
st.bl.genes_per_cell()
st.bl.mean_transcripts_per_gene_per_cell()
num_cells = st.sdata.tables["table"].uns["num_cells"]
p_unassigned = st.sdata.tables["table"].uns["perc_unassigned_transcripts"]
mean_tx = st.sdata.tables["table"].obs["transcript_count"].mean()
mean_gn = st.sdata.tables["table"].obs["gene_count"].mean()
mean_tgc = st.sdata.tables["table"].obs["mean_transcripts_per_gene"].mean()
print(f"\n{method}")
print(f"#cells: {num_cells}; %unassigned: {p_unassigned}")
print(f"mean #transcripts: {mean_tx}; mean #genes: {np.mean(mean_gn)}")
print(f"mean #transcripts per gene per cell: {mean_tgc}")
xenium
#cells: 751; %unassigned: 7.701023026425406
mean #transcripts: 110.6444740346205; mean #genes: 91.77762982689747
mean #transcripts per gene per cell: 1.129141779055404
bidcell
#cells: 766; %unassigned: 7.9956894157380765
mean #transcripts: 108.11227154046998; mean #genes: 89.4425587467363
mean #transcripts per gene per cell: 1.0992300606892813
proseg
#cells: 756; %unassigned: 0.8153109622668755
mean #transcripts: 118.04497354497354; mean #genes: 93.23941798941799
mean #transcripts per gene per cell: 1.1219463970863126
The number of detected cells is comparable across Xenium, BIDCell, and Proseg. Proseg leaves the smallest fraction of transcripts unassigned (0.81%). The number of transcripts and genes per cell can be visualized per cell type, as shown below.
[19]:
# method for visualization
def boxplot_per_celltype(st_dict, feature, q=1):
dfs = []
for method, st in st_dict.items():
obs = st.sdata["table"].obs[st.sdata["table"].obs["transferred_cell_type"].notna()]
tmp = obs[["transferred_cell_type", feature]].copy()
tmp["method"] = method
dfs.append(tmp)
df = pd.concat(dfs, ignore_index=True)
df = df[(df[feature] <= df[feature].quantile(q))]
fig, ax = plt.subplots(figsize=(15, 5))
sns.boxplot(
data=df,
x="transferred_cell_type",
y=feature,
hue="method",
showcaps=True,
showfliers=False,
palette="Set2",
ax=ax,
)
fig.tight_layout()
plt.show()
Proseg yields the highest mean number of transcripts per cell, driven by increased transcript detection in DCIS2 and T, B and stromal cells, which constitute the dominant cell populations in the dataset. In contrast, Proseg detects fewer transcripts in some of the less abundant cell types, such as DCIS1 and myoepithelial cells.
[20]:
boxplot_per_celltype(st_dict, "transcript_count")
The number of genes detected per cell correlates strongly with the number of transcripts per cell.
[21]:
boxplot_per_celltype(st_dict, "gene_count")
On average, the number of transcripts per gene per cell is approximately 1, indicating that across methods we detect roughly one to two transcripts per gene per cell. Among the methods, Proseg shows the highest mean number of transcripts per gene per cell when the Unknown cluster (cells with transcript counts < 10 and gene counts < 5) is excluded.
Interestingly, Proseg detects more transcripts per gene per cell across all cell types, even in cases where the total transcript and gene counts per cell are lower, e.g., DCIS1 and myoepithelial cells. This pattern suggests that transcripts are concentrated among fewer genes per cell, potentially reflecting more homogeneous within-cell expression profiles (i.e. reduced gene-level variability).
[22]:
boxplot_per_celltype(st_dict, "mean_transcripts_per_gene")
The baseline module also facilitates the computation of morphological features.
[23]:
for _method, st in st_dict.items():
st.bl.morphological_features(n_jobs=8)
[24]:
features = [
"cell_area",
"perimeter",
"circularity",
"solidity",
"convexity",
"elongation",
"eccentricity",
"compactness",
]
# Collect features into one dataframe
all_feats = []
for method, st in st_dict.items():
obs = st.sdata["table"].obs[st.sdata["table"].obs["transferred_cell_type"].notna()]
feat = obs[features]
tmp = feat.copy()
tmp["method"] = method
all_feats.append(tmp)
df = pd.concat(all_feats, ignore_index=True)
df["method"] = df["method"].astype(str)
feature_cols = [c for c in df.columns if c != "method"]
fig, ax = plt.subplots(2, 6, figsize=(6 * 4, 2 * 3))
ax = ax.flatten()
for i, feat_name in enumerate(feature_cols):
sns.kdeplot(data=df, x=feat_name, hue="method", palette="Set2", common_norm=False, fill=False, ax=ax[i])
ax[i].set_title(f"Distribution of {feat_name}")
ax[i].set_xlabel(feat_name)
ax[i].set_ylabel("Density")
fig.tight_layout()
plt.show()
Xenium detects rounder cells, characterized by higher circularity and solidity, while BIDCell recovers a wider distribution of cell shapes, including both low and high circularity and solidity. BIDCell incorporates a loss term enforcing cell type–specific elongation, which may explain the detection of less circular cell shapes. Notably, the same cell types specified as elongated during training (perivascular, myoepithelial, stromal, and endothelial cells) also exhibit the highest median elongation in the results, highlighting the strong influence of the prior on the inferred cell shapes.
[25]:
obs = st_bidcell.sdata["table"].obs[st_bidcell.sdata["table"].obs["transferred_cell_type"].notna()]
df = obs[["transferred_cell_type", "elongation"]]
medians = df.groupby("transferred_cell_type").median()
order = medians.sort_values(by="elongation").index
fig, ax = plt.subplots(figsize=(10, 3))
sns.boxplot(
data=df,
x="transferred_cell_type",
y="elongation",
order=order,
showcaps=True,
showfliers=False,
palette=col_celltype,
ax=ax,
)
ax.set_title("BIDCell - cell shape representation")
fig.tight_layout()
plt.show()
/scratch/jobs/61866415/ipykernel_1844593/3127299547.py:3: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
medians = df.groupby("transferred_cell_type").median()
/scratch/jobs/61866415/ipykernel_1844593/3127299547.py:8: FutureWarning:
Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.
sns.boxplot(
Proseg, in contrast, does not rely solely on membrane stainings. Instead, it incorporates transcript positions, which may extend beyond the membrane boundary due to diffusion. This may explain why perivascular or myoepithelial cells do not appear among the most elongated cell types. Importantly, a segmentation mask that accurately delineates cell geometry may still misassign transcripts (e.g. due to diffusion or spatial overlap), while a mask that poorly represents cell shape may nonetheless correctly assign transcripts. Therefore, it is essential to evaluate not only the geometric accuracy of cell shapes but also the accuracy of transcript assignment to cells. We will compute metrics to evaluate transcript assignment accuracy below.
[26]:
obs = st_proseg.sdata["table"].obs[st_proseg.sdata["table"].obs["transferred_cell_type"].notna()]
df = obs[["transferred_cell_type", "elongation"]]
medians = df.groupby("transferred_cell_type").median()
order = medians.sort_values(by="elongation").index
fig, ax = plt.subplots(figsize=(10, 3))
sns.boxplot(
data=df,
x="transferred_cell_type",
y="elongation",
order=order,
showcaps=True,
showfliers=False,
palette=col_celltype,
ax=ax,
)
ax.set_title("Proseg - cell shape representation")
fig.tight_layout()
plt.show()
/scratch/jobs/61866415/ipykernel_1844593/2145051797.py:3: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
medians = df.groupby("transferred_cell_type").median()
/scratch/jobs/61866415/ipykernel_1844593/2145051797.py:8: FutureWarning:
Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.
sns.boxplot(
Clustering stability module#
The clustering stability (cs) module provides metrics for assessing the stability of clustering results across different resolutions and random subsets of genes.
Let`s first perform Leiden clustering and visualize the clusters in the UMAP space.
[27]:
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
axs = axs.flatten()
for i, (method, st) in enumerate(st_dict.items()):
adata = st.sdata.tables["table"].copy()
adata.layers["counts"] = adata.X
sc.pp.normalize_total(adata, inplace=True)
sc.pp.log1p(adata)
sc.pp.pca(adata)
sc.pp.neighbors(adata)
sc.tl.umap(adata)
sc.tl.leiden(adata, flavor="igraph", n_iterations=2)
sc.pl.umap(
adata,
color="leiden",
palette="Set2",
ax=axs[i],
show=False,
title=method,
)
plt.tight_layout()
plt.show()
/scratch/jobs/61866415/ipykernel_1844593/1436914766.py:8: UserWarning: Some cells have zero counts
sc.pp.normalize_total(adata, inplace=True)
/scratch/jobs/61866415/ipykernel_1844593/1436914766.py:8: UserWarning: Some cells have zero counts
sc.pp.normalize_total(adata, inplace=True)
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/.venv/lib/python3.13/site-packages/scipy/sparse/_index.py:210: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil and dok are more efficient.
self._set_arrayXarray(i, j, x)
Next, we compute clustering stability metrics and plot these.
[28]:
ccds = {}
silhouette_scores = {}
purities = {}
aris = {}
for method, st in st_dict.items():
ccds[method] = st.cs.cluster_connectedness()
silhouette_scores[method] = st.cs.silhouette_score()
purities[method] = st.cs.purity()
aris[method] = st.cs.adjusted_rand_index()
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/src/segtraq/cs/utils.py:218: FutureWarning: The `igraph` implementation of leiden clustering is *orders of magnitude faster*. Set the flavor argument to (and install if needed) 'igraph' to use it.
In the future, the default backend for leiden will be igraph instead of leidenalg. To achieve the future defaults please pass: `flavor='igraph'` and `n_iterations=2`. `directed` must also be `False` to work with igraph’s implementation.
sc.tl.leiden(
[29]:
results_df = pd.DataFrame(
{
"Method": list(ccds.keys()),
"Connectedness": list(ccds.values()),
"Silhouette Score": list(silhouette_scores.values()),
"Purity": list(purities.values()),
"ARI": list(aris.values()),
}
)
[30]:
fig, ax = plt.subplots(figsize=(4, 3))
sns.scatterplot(
data=results_df,
x="Connectedness",
y="Silhouette Score",
hue="Method",
style="Method",
s=100,
palette="Set2",
ax=ax,
)
ax.set_xlabel("Connectedness (↑)")
ax.set_ylabel("Silhouette Score (↑)")
ax.set_title("Connectedness vs. Silhouette Score")
ax.set_xlim(0, 1)
ax.set_ylim(-1, 1)
legend = ax.legend(title="Method", frameon=False, bbox_to_anchor=(1.05, 1), loc="upper left")
fig.tight_layout()
plt.show()
[31]:
fig, ax = plt.subplots(figsize=(4, 3))
sns.scatterplot(
data=results_df,
x="Purity",
y="ARI",
hue="Method",
style="Method",
s=100,
palette="Set2",
ax=ax,
)
ax.set_xlabel("Purity (↑)")
ax.set_ylabel("ARI (↑)")
ax.set_title("Purity vs. ARI")
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
legend = ax.legend(title="Method", frameon=False, bbox_to_anchor=(1.05, 1), loc="upper left")
fig.tight_layout()
plt.show()
Proseg achieves the highest adjusted Rand index (ARI) and clustering purity, as well as the lowest mean cosine distance. Together, these results suggest that Proseg retrieves the most internally consistent expression profiles and could indicate less contaminated single cell signatures.
The silhouette score can also be computed on the transferred labels. We will first visualize transferred labels in the UMAP space.
[32]:
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
axs = axs.flatten()
for i, (method, st) in enumerate(st_dict.items()):
adata = st.sdata.tables["table"].copy()
# normalizing and log-transforming the counts
sc.pp.normalize_total(adata, inplace=True)
sc.pp.log1p(adata)
# computing a PCA and neighbors
sc.pp.pca(adata)
sc.pp.neighbors(adata)
sc.tl.umap(adata)
sc.pl.umap(
adata,
color="transferred_cell_type",
palette=col_celltype,
ax=axs[i],
show=False,
title=method,
)
plt.tight_layout()
plt.show()
/scratch/jobs/61866415/ipykernel_1844593/3004694059.py:9: UserWarning: Some cells have zero counts
sc.pp.normalize_total(adata, inplace=True)
WARNING: adata.X seems to be already log-transformed.
/scratch/jobs/61866415/ipykernel_1844593/3004694059.py:9: UserWarning: Some cells have zero counts
sc.pp.normalize_total(adata, inplace=True)
/g/huber/users/meyerben/notebooks/spatial_transcriptomics/SegTraQ/.venv/lib/python3.13/site-packages/scipy/sparse/_index.py:210: SparseEfficiencyWarning: Changing the sparsity structure of a csr_matrix is expensive. lil and dok are more efficient.
self._set_arrayXarray(i, j, x)
Proseg achieves the highest silhouette score for the transferred labels, indicating higher intra-cluster coherence and improved inter-cluster separation; this is also visually consistent with the UMAP embedding.
[33]:
silhouette_scores_labels = {}
for method, st in st_dict.items():
silhouette_scores_labels[method] = st.cs.silhouette_score(cell_type_key="transferred_cell_type")
silhouette_scores_labels
[33]:
{'xenium': -0.04281586408615112,
'bidcell': -0.026861874386668205,
'proseg': 0.02360549382865429}
Region similarity module#
While individual genes may exhibit subcellular localization patterns, the overall distribution of transcripts, when averaged across genes, is expected to be relatively smooth and approximately uniform within a cell. Based on this assumption, the region similarity module evaluates the similarity of gene expression profiles across different subcellular compartments. Deviations from this expected intra-cellular consistency can serve as indicators of transcript contamination originating from neighboring cells.
Intersection over Union (IoU)
Proseg shows the lowest intersection over union (IoU) between the cell and nucleus. It relies on transcript assignment rather than explicit morphology-based segmentation, allowing transcripts that are repositioned outside the cell membrane (e.g. due to transcript diffusion) to be reassigned to the cell of origin. This often results in larger inferred cell areas (or perimeters, as shown above) relative to the nucleus or less aligned cell and nucleus shapes, leading to lower cell–nucleus IoU values.
[34]:
plt.style.use("default")
def plot_IoU(sdata, shapes_key, method, ax):
# note that copy.deepcopy() can have unwanted effects on the attrs, hence sd.deepcopy() is preferred
sdata_plot = sd.deepcopy(sdata)
sdata_plot["table"] = sdata_plot["table"][(sdata_plot["table"].obs["iou"].notna())]
sdata_plot.tables["table"].obs["region"] = shapes_key
sdata_plot.set_table_annotates_spatialelement("table", region=shapes_key)
sdata_plot.pl.render_shapes(
element="nucleus_boundaries",
fill_alpha=0.2,
outline_alpha=1.0,
outline_width=0.5,
outline_color="black",
).pl.render_shapes(
element=shapes_key,
color="iou",
cmap="viridis",
fill_alpha=0.5,
outline_alpha=1.0,
outline_width=0.5,
outline_color="black",
).pl.show(ax=ax, title=f"{method}: Overlay of nuclei and cell masks colored by IoU", colorbar=True)
[35]:
fig, axes = plt.subplots(1, 3, figsize=(15, 5), constrained_layout=True)
for i, (method, st) in enumerate(st_dict.items()):
st.rs.match_nuclei_to_cells(n_jobs=-1)
plot_IoU(st.sdata, "cell_boundaries", method, axes[i])
Similarity between expression in cell and nucleus
Once, the “best-matching” nucleus is determined for each cell based on IoU, we can compute the similarity between cell and nuclear expression.
[36]:
for _method, st in st_dict.items():
st.rs.similarity_nucleus_cell(n_jobs=-1)
[37]:
def histogram_feature(
sdata_dict,
feature,
figsize=(8, 4),
significant_only=True,
bins=30,
):
plt.style.use("default")
all_feats = []
for method, st in sdata_dict.items():
obs = st.sdata[st.tables_key].obs
if significant_only:
p_value_col = f"{feature}_p_value"
if feature == "border_admixture_score":
p_value_col = "border_admixture_p_value"
obs = obs.loc[obs[p_value_col] < 0.05]
feat = obs[feature].to_frame(feature)
feat["method"] = method
all_feats.append(feat)
df = pd.concat(all_feats, ignore_index=True)
df["method"] = df["method"].astype(str)
methods = list(sdata_dict.keys())
palette = dict(zip(methods, sns.color_palette("Set2", len(methods)), strict=False))
fig, ax = plt.subplots(figsize=figsize)
sns.histplot(
data=df,
x=feature,
hue="method",
bins=bins,
stat="count",
element="step",
# edgecolor=None,
palette=palette,
ax=ax,
alpha=0.3,
line_kws=dict(color="method", linewidth=2.5, label="KDE"),
kde=True,
)
# Median for each method
for method in methods:
median = df.loc[df["method"] == method, feature].median()
ax.axvline(
median,
color=palette[method],
linestyle="--",
linewidth=2,
)
ax.set_ylabel("Number of cells")
plt.tight_layout()
Among cells with unexpectedly low nucleus–cell similarity (similarity_nucleus_cell_p_value < 0.05), ProSeg shows more negative residuals (similarity_nucleus_cell) than Xenium and BIDCell, indicating a stronger difference between the nuclear and whole-cell expression profiles than expected. This is partly expected from the different assignment strategies. Xenium and BIDCell assign transcripts to cells based on spatial overlap with the inferred cell boundaries, whereas ProSeg can assign
transcripts to a cell even when they lie outside its final 2D cell boundary. Because the nuclear expression profile is also defined by spatial overlap with the nuclear boundary, the nucleus and whole-cell profiles are more spatially coupled for Xenium and BIDCell. Accordingly, among these cells, Xenium and BIDCell show less negative residuals than ProSeg. Thus, ProSeg’s lower nucleus–cell similarity may partly reflect its transcript reassignment strategy rather than poorer segmentation.
[38]:
histogram_feature(st_dict, "similarity_nucleus_cell", significant_only=True)
While similarity_nucleus_cell compares the nuclear transcript profile with the final whole-cell expression profile, similarity_nucleus_cytoplasm more directly compares two spatial regions within the cell. It compares the cell’s transcripts regionally overlapping with the nucleus and the non-nuclear region (cytoplasm). It therefore asks whether the transcript composition is consistent across these two regions of the same cell. Here, Proseg again shows the lowest consistency between
nuclear and cytoplasmic expression profiles.
[39]:
for _method, st in st_dict.items():
st.rs.similarity_nucleus_cytoplasm(n_jobs=-1)
[40]:
histogram_feature(st_dict, "similarity_nucleus_cytoplasm")
Admixture (contamination) of the cell border
Unlike the nucleus-based similarity metrics, the border_admixture_score more directly tests for contamination from neighboring cells. Rather than asking whether two regions within a cell have different expression profiles, it asks whether the cell border specifically resembles a mixture of the cell center and the surrounding neighborhood more strongly than expected from finite transcript sampling alone. This helps distinguish general within-cell expression differences from differences that
specifically point toward a contribution from neighboring cells.
Here, Xenium has the largest number of cells with unexpectedly strong border admixture (border_admixture_p_value < 0.05), meaning that for more Xenium cells, the border resembles the surrounding neighborhood more than would be expected by chance. Xenium also shows the largest median positive residual among these cells, indicating that the excess neighborhood-like signal at the border tends to be stronger. In contrast, BIDCell and Proseg — both transcript-informed segmentation methods — show
substantially fewer cells with evidence of unexpected border admixture, and the effect is generally weaker when it occurs.
[41]:
for _method, st in st_dict.items():
st.rs.border_admixture_score(n_jobs=-1)
[42]:
histogram_feature(st_dict, "border_admixture_score")
While it is important to consider metrics that do not depend on the availability of high-quality reference scRNA-seq data—and therefore do not rely on the performance of label transfer—a limitation of the region similarity module is that it cannot distinguish between homogeneous and heterogeneous cellular neighborhoods. In homogeneous neighborhoods, a high border_admixture_score does not necessarily indicate contamination. Nevertheless, when comparing methods globally or on a cell-type
level, this is a useful unsupervised contamination metric that does not rely on reference scRNA-seq data.
When cell type labels are available, the metric can still be compared on a cell-type level. The plot compares border_admixture_score across segmentation methods within each transferred cell type, restricted to cells with unexpectedly strong border admixture (border_admixture_p_value < 0.05). The individual points and boxplots show the magnitude of the excess neighborhood-like signal at the border, while the annotated cell counts show how many cells of each cell type are significantly
affected. This helps reveal whether differences between segmentation methods are driven by particular cell types rather than representing a global effect.
[43]:
def boxplot_per_celltype_sig(st_dict, feature, q=1, significant_only=True):
dfs = []
for method, st in st_dict.items():
obs = st.sdata[st.tables_key].obs[st.sdata[st.tables_key].obs["transferred_cell_type"].notna()].copy()
if (feature == "similarity_top_bottom") and significant_only:
obs = obs.loc[obs["similarity_top_bottom_p_value"] < 0.05].copy()
obs["transferred_cell_type"] = obs["transferred_cell_type"].cat.remove_unused_categories()
tmp = obs[["transferred_cell_type", feature]].copy()
tmp["method"] = method
dfs.append(tmp)
df = pd.concat(dfs, ignore_index=True)
df = df[df[feature] <= df[feature].quantile(q)]
fig, ax = plt.subplots(figsize=(15, 5))
sns.boxplot(
data=df,
x="transferred_cell_type",
y=feature,
hue="method",
showcaps=True,
showfliers=False,
palette="Set2",
boxprops={"alpha": 0.5},
ax=ax,
)
sns.stripplot(
data=df,
x="transferred_cell_type",
y=feature,
hue="method",
dodge=True,
jitter=True,
palette="Set2",
alpha=1,
size=3,
legend=False,
ax=ax,
)
celltypes = df["transferred_cell_type"].unique()
methods = list(st_dict.keys())
n_methods = len(methods)
box_width = 0.8
offsets = np.linspace(
-box_width / 2 + box_width / (2 * n_methods),
box_width / 2 - box_width / (2 * n_methods),
n_methods,
)
# Position labels slightly above the data
ymin, ymax = ax.get_ylim()
y_text = ymax + 0.02 * (ymax - ymin)
for i, celltype in enumerate(celltypes):
for j, method in enumerate(methods):
n = len(df.loc[(df["transferred_cell_type"] == celltype) & (df["method"] == method)])
ax.text(
i + offsets[j],
y_text,
f"n={n}",
ha="center",
va="bottom",
fontsize=8,
rotation=90,
)
# Make room for annotations
ax.set_ylim(ymin, ymax + 0.12 * (ymax - ymin))
fig.tight_layout()
plt.show()
[44]:
boxplot_per_celltype_sig(st_dict, "border_admixture_score")
Supervised module#
The sp (supervised) module provides metrics to evaluate how well cell profiles in a spatial transcriptomics dataset agree with a reference single-cell RNA-seq (scRNA-seq) dataset with cell type annotations.
Unlike scRNA-seq, contamination in spatial transcriptomics measurements mostly originates from the local tissue context.
By comparing spatial expression profiles to a high-quality scRNA-seq reference, the supervised module aims to quantify this mismatch. Specifically, we compute metrics that measure:
how well each spatial cell matches its expected cell type,
how much its expression resembles other (neighboring) cell types, and
if it is possible to predict that a cell of one cell type is adjacent to a different cell type.
To obtain cell-type specific marker genes, we define positive and negative markers in the annotated scRNA-seq via markers_from_reference.
Computing cell-type specific markers
[45]:
markers = {}
for method, st in st_dict.items():
markers[method] = st.markers_from_reference(
adata_ref,
ref_cell_type="celltype_major",
ref_raw_counts_layer="raw",
n_jobs=16,
)
WARNING: adata.X seems to be already log-transformed.
WARNING: adata.X seems to be already log-transformed.
WARNING: adata.X seems to be already log-transformed.
A list of positive and negative markers computed for each cell type is shown below. We will use xenium for demonstration.
[46]:
for cell_type, sign in markers["xenium"].items():
pos = sign["positive"]
neg = sign["negative"]
print(f"{cell_type} | positive: {', '.join(pos)}")
print(f"{cell_type} | negative: {', '.join(neg)}\n")
B | positive: PAX5, MS4A1, BANK1, FCRL1, BLK, FCRL2, AIM2, TNFRSF13B, CD19, POU2AF1, SCIMP, FCRL5, P2RX5, CD79A, PARP15, CD79B, TNFRSF13C, POU2F2, FCRLA, STAP1, CD22, COL4A3, SP140, NIBAN3, CR1, ADAM28, PLCG2, FCMR, CD40, TLR6, ST6GAL1, DERL3, KCNN4, CD27, CD52, ITGB7, BLNK, CNR2, COL4A4, SPIB, IRF4, TNFRSF17, ZBP1, PTK2B, FCGR2B, CDK14, PTPN6, TNFAIP8, SP110, ATM, FCRL3, COCH, CCDC141, NCF1, CD38, CCR6, ITGAL, RELT, PIM2, SMCHD1, ANKRD13A, NCOA3, PDK1, LRRK2, KCNA3, NLRP1, TLR1, SLAMF1, SLAMF6, GPR174, JAK3, GRK5, NLRC5, SMAP2, BCL2, SYVN1, TENT5C, ORAI2, CASP10, CYSLTR1, CD83, LPXN, TRAF5, REL, BTN3A1, SYNRG, ATP2B1, SEMA4B
B | negative: ABCA10, ABCA8, ABCC8, ABCC9, ABI3BP, AC007906.2, ACVRL1, ADAM12, ADAM33, ADAMDEC1, ADAMTS1, ADAMTS12, ADAMTS14, ADAMTS2, ADAMTS4, ADAMTS5, ADCYAP1, ADGRE2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRA2A, ADTRP, AFAP1L2, AGR3, AIF1, ALDH7A1, ALPK3, ALPL, ANGPT1, ANGPTL2, ANGPTL4, ANXA3, ANXA8, APLNR, AQP1, ASPN, ATL1, AVPR1A, AXIN2, BAALC, BARX2, BCL6B, BDKRB2, BMP2, BMPR1B, BNC2, BPIFB1, BRINP1, C1QTNF1, C3AR1, C4B, C5AR1, C7, CACNA1B, CACNG4, CADM3, CALB2, CALCRL, CALML3, CARD10, CARD14, CARD9, CAVIN2, CAVIN3, CCDC102B, CCL14, CCL19, CCL22, CCL28, CCN3, CCN4, CCR4, CCR5, CD1E, CD209, CD248, CD274, CD302, CD33, CD34, CD40LG, CD7, CD8A, CDH13, CDH23, CDH3, CDH5, CDH6, CDKN2B, CEBPA, CELSR3, CENPA, CEP112, CES1, CFC1, CGNL1, CHAD, CHST8, CLCN5, CLDN5, CLEC10A, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CLEC7A, CLGN, CLIC3, CMA1, CMKLR1, CMYA5, CNN1, CNRIP1, CNTNAP1, COL10A1, COL17A1, COL7A1, COLEC12, CPVL, CPXM2, CRB2, CSF1, CSF2RA, CSF3R, CSPG4, CST7, CTLA4, CTSG, CTSW, CUX2, CX3CL1, CXCL2, CXCR6, CYP27A1, DCLK1, DDR2, DKK3, DLK1, DLL4, DLX4, DMD, DNAH7, DNASE1L3, DNM1, DPT, DPY19L2, DSC3, DSG3, EBF2, ECRG4, EDN1, EDNRA, EGF, EGFR, ELN, EMP1, ENPP1, ENPP2, ENPP3, ENTPD2, EPB41L3, EPHA2, EPHB1, EREG, ERG, ERRFI1, ESM1, ETV1, F13A1, F2R, F2RL3, F3, F7, FAM107A, FAP, FAT2, FAT4, FBLN2, FBLN5, FCER1A, FCGR3A, FES, FGF1, FGF2, FGF7, FGFR2, FILIP1L, FLRT3, FLT1, FLT3, FLT4, FMN1, FMOD, FOLH1, FOLR2, FOXC2, FOXD2, FPR1, FREM1, FRZB, FSCN1, FST, FZD4, GABRD, GABRP, GHR, GIMAP5, GIMAP8, GJC1, GLI2, GLT8D2, GPR39, GPR4, GPRC5B, GRAMD2B, GRIK3, GRIK4, GRP, GUCY1A2, GZMA, GZMB, GZMK, HAS2, HAVCR2, HBEGF, HCK, HDC, HEYL, HGF, HLX, HMCN1, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, HS6ST2, ICOS, IDO1, IGF1, IL17RB, IL18, IL18R1, IL1R1, IL1RL1, IL2RB, IL33, IL34, IL3RA, IL6R, INHBA, IRS1, IRS2, IRX1, ITGA2, ITGA2B, ITGA5, ITGA7, ITIH5, JAG1, JAM2, KANK2, KCNF1, KCNJ8, KCNK10, KCNK15, KCNK17, KCNMA1, KCNMB1, KDR, KIT, KLHL13, KLK10, KLK6, KLRD1, KLRG1, LAMB3, LAMC2, LAMP3, LCN2, LCNL1, LDB3, LEF1, LGR4, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LOX, LOXL2, LPAR1, LPL, LRG1, LRRC15, LTK, LURAP1, MAOB, MAP1A, MAP1B, MAP2, MAP3K7CL, MAPT, ME1, MECOM, MEIS2, MERTK, MET, MFAP5, MLXIPL, MME, MMP11, MMP9, MMRN2, MNDA, MPP1, MRC1, MS4A2, MSR1, MTNR1B, MUC16, MUC5B, MYCL, MYH11, MYLK, NAV2, NCCRP1, NCF2, NGFR, NKX3-1, NLRP2, NLRP3, NOS1AP, NOS3, NOSTRIN, NPNT, NPR1, NPY1R, NR4A3, NR5A2, NRN1, NRP2, NTRK2, OLFM1, OLFML3, OLR1, OPRD1, OXTR, P2RX7, P2RY13, P2RY14, P2RY2, P2RY6, PALMD, PAMR1, PBK, PCDH10, PCDH17, PCDH18, PDE10A, PDE2A, PDE3A, PDE5A, PDGFA, PDGFD, PDGFRA, PDGFRB, PDGFRL, PDLIM4, PDPN, PDZK1, PGF, PGR, PHEX, PHF21B, PHLDB2, PI16, PIEZO2, PILRA, PITX1, PLA2G7, PLAGL1, PLAUR, PLCE1, PLEKHH2, PLVAP, PLXDC1, PMAIP1, PODXL, POTEI, POTEJ, PPARG, PPP1R1C, PRDM6, PRF1, PRKAA2, PRKCQ, PROCR, PROM1, PROX1, PRRX1, PTAFR, PTGDR, PTGES, PTGFRN, PTGIR, PTGIS, PTGS1, PTHLH, PTK7, PTPRB, PTPRM, RAPGEF3, RASD1, RASSF4, RBMS3, RBPMS, RCAN2, RELN, RERG, RGS5, RIMS3, RIPK4, RNF180, RNF39, ROBO4, ROR2, RTN1, RUNX1T1, S100A1, SAMD5, SASH1, SCGB1D2, SCN9A, SDC2, SELP, SEPTIN4, SEPTIN5, SERPINA5, SERPINB5, SERPINE1, SFRP1, SH2D1A, SH2D2A, SH3RF2, SHANK3, SHD, SIGLEC1, SIGLEC9, SIM2, SIRPA, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC2A9, SLC35G2, SLC6A1, SLC7A11, SLCO2A1, SLCO2B1, SLIT2, SLIT3, SMOC2, SMPD3, SNCA, SOD3, SOSTDC1, SOX10, SOX17, SOX18, SOX9, SPA17, SPAG17, SPATA18, SPOCK1, SPON1, SPON2, SPRY2, SRPX, STEAP1, STEAP4, SUGCT, SULF1, SVEP1, SYNPO2, TAL1, TBX2, TBXA2R, TCL1A, TDO2, TEK, TFAP2B, TFPI2, TGFB2, TGFBR3, TGM2, TH, THBD, THY1, TIE1, TIGIT, TLR2, TLR4, TLR9, TMEM119, TMEM255B, TMEM45B, TMEM98, TNFSF8, TNS4, TP63, TPPP3, TPSG1, TREM2, TRH, TRIM29, TRO, TRPC6, TSPAN18, TSPAN7, TUBB2B, TUBB3, TUBB6, TWIST1, TWIST2, UBASH3A, UNC5A, VCAM1, VEGFC, VGLL3, VSIG4, VSNL1, VSTM2L, VTCN1, WFDC1, WNT2, ZFHX4, ZNF683
DCIS1 | positive: BMPR1B, PITX1, SCGB1D2, OPRD1, SLC16A1, CHAD, EREG, SMPD3, MUC16, KCNF1, PCDH10, ITGB6, AGR2, MUC5B, TMEM45B, DLX4, CA12, GIPC1, S100A1, POTEI, ESR1, SAPCD2, ME1, SLC12A2, CEACAM5, ITGA3, CACNG4, NQO1, ROR2, IGFBP2, TUBB3, SOX9, SLC30A8, REEP6, PRKAR1A, BARX2, G6PD, SOD1, CACNA1D, PIK3R2, CYP27A1, PDE10A, SPA17, VTCN1, P2RY2, AGR3, SDC4, ENTPD2, SPATA18, HK2, C4B, HES6, PLXNB1, RIPK4, NKX3-1, IGSF3, FAAH, CTNND2, NECTIN1, CFB, NTN4, SIAE, DTNA, THSD4, KLF5, SLC7A11, LASP1, FASN, CASZ1, MCM3AP, XBP1, VSTM2L, MACC1, CMYA5, STX1A, ATP7B, RMND1, WFS1, SERPINA5, POTEF, LMX1B, TDO2, SERPINB5, SASH1, KCNK15, TNFRSF12A, SULF2, TRIM29, CGNL1, PLAT, SLC9A3R2, TRPS1, ST14, BAALC, CD151, FOXD2, DNAH7, NECTIN2, CELSR3, DEPTOR, PADI2, VAV3, NINJ1, IL17RB, PRKAA2, ABCA10, NOD2, PRODH, PPFIA3, LRATD2, TUBD1
DCIS1 | negative: ABCA8, ABCC12, ABCC8, ABCC9, ABI3, ABI3BP, AC007906.2, ACVRL1, ADA, ADAM12, ADAM33, ADAMDEC1, ADAMTS12, ADAMTS14, ADAMTS4, ADAMTS5, ADCYAP1, ADGRE2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRA2A, ADRB2, ADTRP, AIM2, ALPL, ANGPT1, ANXA8, APLNR, ASPN, ATL1, AVPR1A, BANK1, BCL11B, BCL2A1, BCL6B, BLK, BMP2, BMP6, BNC2, BPIFB1, BRINP1, C1QTNF1, C3AR1, C7, CACNA1B, CALB2, CALCRL, CARD9, CASP10, CAVIN2, CAVIN3, CCDC102B, CCDC141, CCL14, CCL19, CCN3, CCN4, CCR1, CCR4, CCR5, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD226, CD247, CD27, CD274, CD28, CD302, CD33, CD34, CD38, CD3G, CD40, CD40LG, CD5, CD7, CD72, CD79A, CD79B, CD83, CD86, CDH13, CDH23, CDH5, CDH6, CEP112, CFC1, CHST8, CLDN5, CLEC10A, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CLEC7A, CMA1, CMKLR1, CNR2, CNRIP1, CNTNAP1, COCH, COL10A1, COL4A3, COL4A4, COLEC12, CPVL, CPXM2, CR1, CRB2, CSF2RA, CSF3R, CST7, CTLA4, CTSG, CTSW, CUX2, CXADR, CXCL2, CXCR3, CXCR6, CYSLTR1, DCLK1, DERL3, DIO2, DLK1, DLL4, DNASE1L3, DNM1, DPT, DPY19L2, DSG3, EBF2, EDN1, EDNRA, EGF, EGR2, ENPP2, ENPP3, EPB41L3, EPHB1, ERG, ESM1, F2RL3, F7, FAM107A, FAP, FAT4, FCER1A, FCGR2B, FCMR, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FES, FGF1, FGF2, FGF7, FGR, FILIP1L, FLT1, FLT3, FLT4, FOLH1, FOLR2, FOXC2, FPR1, FREM1, FRZB, FSCN1, FUT7, GABRD, GBP5, GHR, GIMAP5, GIMAP8, GJC1, GLI2, GPR174, GPR34, GPR39, GPR4, GRIK4, GRP, GUCY1A2, GZMA, GZMB, GZMK, HAS2, HBEGF, HCK, HDAC9, HDC, HEYL, HGF, HLX, HMCN1, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, HSPA6, ICAM2, ICOS, IDO1, IGF1, IL12RB1, IL18R1, IL1RL1, IL21R, IL2RB, IL33, IL3RA, IL6R, INHBA, IRF4, ITGA7, ITIH5, ITK, JAM2, KCNA3, KCNH6, KCNJ8, KCNK10, KCNK17, KCNN3, KDR, KIAA0408, KIT, KLK10, KLK6, KLRD1, KLRG1, LAIR1, LAMP3, LCK, LCN2, LCNL1, LDB3, LEF1, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LMO2, LOX, LPAR1, LPL, LRRC15, LRRK2, LTK, LURAP1, LY96, MAP1A, MAP2, MAP3K7CL, MERTK, MFAP5, MLXIPL, MMP9, MMRN2, MNDA, MPP1, MRC1, MS4A1, MS4A2, MTNR1B, NCCRP1, NCF1, NCF2, NIBAN3, NLRP2, NLRP3, NOS3, NOSTRIN, NPR1, NR4A3, NR5A2, NRN1, NRROS, OLR1, P2RX1, P2RX5, P2RX7, P2RY13, P2RY14, P2RY6, PARP15, PAX5, PCDH17, PCDH18, PCDH8, PDE2A, PDE3A, PDGFD, PDGFRL, PDPN, PGF, PGR, PHEX, PI16, PIEZO2, PILRA, PLA2G7, PLCE1, POTEJ, POU2AF1, POU2F2, PPP1R1C, PRDM6, PRF1, PRICKLE1, PRKCQ, PROCR, PROX1, PTAFR, PTGDR, PTGES, PTGIR, PTGIS, PTGS1, PTPRB, RASSF2, RASSF4, RCAN2, RECK, RELN, RGS5, RIMS3, ROBO4, RUNX1T1, S1PR1, SCIMP, SCN9A, SDC2, SDK2, SELP, SELPLG, SEMA7A, SEPTIN4, SFRP4, SH2D1A, SH2D2A, SH3RF2, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SIM2, SLAMF1, SLAMF6, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC35G2, SLC6A1, SLC7A7, SLCO2A1, SLIT3, SMOC2, SNCA, SOD3, SOSTDC1, SOX17, SOX18, SOX5, SPIB, SPOCK1, SPON1, SRPX, STAP1, SUGCT, SVEP1, TAL1, TBX2, TBXA2R, TCL1A, TEK, TFAP2B, TGFB2, TGFBR3, TGM2, TH, THBD, THEMIS, TIE1, TIGIT, TLR1, TLR4, TLR6, TLR7, TLR9, TMEM119, TMEM255B, TNFRSF13B, TNFRSF13C, TNFRSF17, TNFSF8, TPSG1, TREML2, TRH, TRO, TRPC1, TRPC6, TSPAN18, TSPAN7, TUBB2B, TUBB6, TWIST1, TWIST2, UBASH3A, VCAM1, VEGFC, VGLL3, VSIG4, VSNL1, WFDC1, WNT2, WNT4, WNT5B, ZBP1, ZEB1, ZFHX4, ZNF683
DCIS2 | positive: BPIFB1, PDZK1, DTNA, AGR3, PLAT, BAALC, STEAP4, TSPAN8, PPP1R1B, NAT1, ESR1, EEF1A2, LRG1, PSD3, SIM2, SLC1A1, NPY1R, TRH, KCNK15, CFB, CEACAM5, CLDN7, TUBB3, AGR2, MUC1, CCND1, GREB1, PTK6, NOS1AP, PRLR, F7, EGF, GRIK3, THSD4, NECTIN2, HSPB1, GDF15, SERPINA5, PBX1, LASP1, EMP2, PSMC5, HES4, FSIP1, TPBG, INPP4B, OCLN, PPM1D, NQO1, ERBB3, MAP7, XBP1, TUBB2A, PMAIP1, SEZ6L2, SOX18, ACE, KIAA0408, TRIB3, CACNA1B, TNNT2, MAPK15, FGFR4, CELSR1, CTNND2, PLCE1, PTGES, CFC1, MAPT, FLRT3, AXIN2, LAD1, MACC1, CHAD, PTHLH, TBX3, TFPI2, NEBL, DHCR7, TLK2, FIBCD1, SPAG17, BMP8B, NOSTRIN, CCL22, BCAM, GHR, ADRA2A, OLFM1, TCIM, TNFRSF12A, SYTL2, BDKRB2, RERG, DSG2, EFS, ENPP1, TACC2, IRS1, NLRP2, SLC9A3R1, FRAS1, WNT4, TENT5C, DMKN, EFR3B, KCNK1, SIX1
DCIS2 | negative: ABCA10, ABCA8, ABCC9, ABI3, ABI3BP, AC007906.2, ACSL4, ACVRL1, ADA, ADAM12, ADAM33, ADAMDEC1, ADAMTS1, ADAMTS12, ADAMTS14, ADAMTS2, ADAMTS4, ADAMTS5, ADCYAP1, ADGRE2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRB2, ADTRP, AIM2, ALPL, ANGPT1, ANGPTL2, ANXA8, APLNR, ASPN, ATL1, AVPR1A, BANK1, BCL11B, BCL2A1, BCL6B, BLK, BMP2, BMP6, BMPR1B, BNC2, BRINP1, C1QTNF1, C3AR1, C4B, C7, CADM3, CALB2, CALCRL, CARD9, CAVIN2, CAVIN3, CCDC102B, CCDC141, CCL14, CCL19, CCN3, CCN4, CCR1, CCR4, CCR5, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD226, CD247, CD248, CD27, CD274, CD28, CD302, CD33, CD34, CD38, CD3D, CD3G, CD40, CD40LG, CD5, CD72, CD79A, CD79B, CD86, CD8A, CDH13, CDH23, CDH5, CDH6, CEP112, CES1, CLDN5, CLEC10A, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CLEC7A, CMA1, CMKLR1, CNN1, CNR2, CNRIP1, CNTNAP1, COCH, COL10A1, COL4A3, COL4A4, COLEC12, CPVL, CPXM2, CR1, CRB2, CSF2RA, CSPG4, CST7, CTLA4, CTSG, CTSW, CUX2, CX3CL1, CXCL2, CXCR3, CXCR6, CYSLTR1, DCLK1, DERL3, DIO2, DLK1, DLL4, DLX4, DMD, DNASE1L3, DPT, DPY19L2, DSG3, EBF2, ECRG4, EDNRA, EGR2, ELN, ENPP2, ENPP3, EPHB1, ESM1, ETV1, F2RL3, FAM107A, FAP, FAT2, FAT4, FBLN5, FCER1A, FCGR2B, FCGR3A, FCMR, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FES, FGF1, FGF2, FGF7, FGR, FLT1, FLT3LG, FLT4, FOLH1, FOLR2, FOXC2, FOXD2, FPR1, FREM1, FRZB, FSCN1, FUT7, GABRD, GABRP, GBP5, GIMAP5, GIMAP8, GJC1, GLI2, GLT8D2, GPR174, GPR34, GPR4, GRK5, GRM4, GRP, GUCY1A2, GZMA, GZMB, GZMK, HAS2, HBEGF, HCK, HDAC9, HDC, HEYL, HGF, HLX, HMCN1, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, ICAM2, IDO1, IGF1, IL12RB1, IL18, IL18R1, IL1RL1, IL21R, IL2RB, IL33, IL34, IL3RA, IL6R, INHBA, IRF4, IRX1, ITGA7, ITIH5, ITK, JAM2, JAM3, KCNA3, KCNF1, KCNJ8, KCNK10, KCNK17, KCNMB1, KCNN3, KDR, KIT, KLHL13, KLK10, KLK6, KLRD1, KLRG1, KLRK1, LAIR1, LAMP3, LCK, LCN2, LCNL1, LDB3, LEF1, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LOX, LPAR1, LPL, LRRC15, LTK, LURAP1, LY75, LY96, MAOB, MAP1A, MAP2, MAP3K7CL, MERTK, MFAP5, MME, MMP9, MMRN2, MNDA, MPP1, MRC1, MS4A1, MS4A2, MUC16, NCCRP1, NCF1, NGFR, NIBAN3, NLRP3, NOD2, NOS3, NPNT, NPR1, NR4A3, NR5A2, NRN1, NRROS, NTRK2, OLR1, OPRD1, P2RX1, P2RX5, P2RX7, P2RY13, P2RY14, P2RY6, PALMD, PAMR1, PARP15, PAX5, PCDH10, PCDH17, PCDH18, PCDH8, PDE10A, PDE2A, PDE3A, PDGFD, PDGFRA, PDGFRB, PDPN, PGF, PGR, PHEX, PI16, PIEZO2, PILRA, PLA2G7, PLAGL1, PLCG2, PLEKHH2, PLXDC1, POTEI, POTEJ, POU2AF1, POU2F2, PPP1R1C, PRF1, PRICKLE1, PRKCQ, PROCR, PROM1, PROX1, PRRX1, PTGDR, PTGIR, PTGIS, PTGS1, PTPRB, RBMS3, RCAN2, RECK, RELN, RGS5, RIMS3, RNF180, ROBO4, ROR2, RTN1, RUNX1T1, S1PR1, SAMD5, SASH1, SATB1, SCGB1D2, SCIMP, SCN9A, SDC2, SDK2, SELP, SEMA7A, SEPTIN4, SERPINB5, SFRP4, SH2D1A, SH3RF2, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SLAMF1, SLAMF6, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC35G2, SLC6A1, SLC7A7, SLCO2A1, SLIT2, SMPD3, SNCA, SOD3, SOSTDC1, SOX10, SOX17, SOX5, SPATA18, SPIB, SPOCK1, SPON1, SRPX, STAP1, STAT4, SUGCT, SULF1, SVEP1, SYNPO2, TAL1, TBX2, TBXA2R, TCL1A, TDO2, TEK, TFAP2B, TGFB2, TGFBR3, TH, THBD, THEMIS, TIE1, TIGIT, TLR1, TLR4, TLR6, TLR7, TLR9, TMEM119, TMEM255B, TMEM98, TNFRSF13B, TNFRSF17, TNFSF8, TPPP3, TPSG1, TRBC1, TREM2, TREML2, TRO, TRPC1, TRPC6, TSPAN18, TSPAN7, TUBB2B, TUBB6, TWIST1, TWIST2, UBASH3A, VCAM1, VEGFC, VGLL3, VSIG4, VSNL1, VTCN1, WFDC1, WNT2, WNT5B, ZBP1, ZEB1, ZFHX4, ZNF683
T | positive: IL7R, CD2, TRAC, CD96, CD3E, ZAP70, ITK, CD3D, IL2RB, KLRK1, TRBC1, CD6, CD8A, GZMA, THEMIS, CD28, CD5, CD3G, CD247, BCL11B, LCK, STAT4, CCR5, TCF7, GBP5, RASGRP1, ITGAL, SLAMF1, PRDM1, NLRC5, KCNA3, KLRD1, CXCR6, CD40LG, SLFN5, ZNF683, CD7, GIMAP4, CCR4, LAT, SH2D1A, AOAH, PRKCQ, PRF1, CTSW, GZMK, KLRG1, CD226, TIGIT, FYN, TRAF1, LEPROTL1, FLT3LG, JAK3, FCMR, CD52, SP140, ITGB7, CASP8, CST7, CTLA4, SIGIRR, NFATC2, CXCR3, APOBEC3G, ADGRE5, RUNX3, SLAMF6, GPR174, CD27, ZBP1, ATM, SYNRG, BTN3A1, SEMA4D, STK4, UBASH3A, PTGDR, GIMAP5, SH2D2A, TNFSF8, SELPLG, SATB1, CDC14A, CD44, PBXIP1, RBL2, CCND2, NLRP1, PARP15, KLF12, KAT2B, LEF1, PTGER4, ICOS, TC2N, LPXN, PTK2B, TNFAIP8, TUBA4A, OGT, TMEM173, IL21R, IL12RB1, S1PR1, LY75, LBH, CD58, CFLAR, SMCHD1, CD47, CYLD
T | negative: ABCA1, ABCA10, ABCA8, ABCC3, ABCC8, ABCC9, ABI3BP, AC007906.2, ACVRL1, ADAM12, ADAM28, ADAM33, ADAMDEC1, ADAMTS1, ADAMTS12, ADAMTS14, ADAMTS2, ADAMTS4, ADAMTS5, ADCYAP1, ADGRB2, ADGRE2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRA2A, AEBP1, AFAP1L2, AGAP1, AGR3, AIF1, AIM2, ALDH2, ALDH7A1, ALPK3, ALPL, ANGPT1, ANGPTL2, ANGPTL4, ANO1, ANXA3, ANXA8, AP1S3, APLNR, AQP1, AR, ASPN, ATP7B, AVPR1A, BAALC, BACE2, BANK1, BCL6B, BDKRB2, BGN, BLK, BLNK, BMP2, BMP2K, BMP6, BMPR1B, BNC2, BPIFB1, BRINP1, BRIP1, C1QC, C1QTNF1, C3AR1, C5AR1, C7, CA12, CACNA1B, CACNA1D, CACNG4, CADM3, CALB2, CALCRL, CALML3, CARD10, CARD14, CARD9, CAVIN2, CAVIN3, CCDC102B, CCDC170, CCDC50, CCDC80, CCL14, CCN1, CCN2, CCN4, CCR1, CD14, CD163, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD248, CD274, CD302, CD33, CD34, CD38, CD40, CD68, CD79A, CD79B, CD83, CD86, CDH11, CDH13, CDH3, CDH5, CDH6, CDK14, CEACAM5, CEBPA, CELSR1, CEP112, CES1, CFB, CFC1, CGNL1, CHAD, CHST8, CLCN5, CLDN5, CLEC10A, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CLEC7A, CLGN, CLIC3, CMA1, CMKLR1, CMYA5, CNN1, CNR2, CNRIP1, CNTNAP1, COCH, COL10A1, COL17A1, COL18A1, COL4A3, COL4A4, COL5A1, COL5A2, COL7A1, COLEC12, COMP, CPVL, CPXM2, CR1, CRB2, CSF1R, CSF2RA, CSF3R, CSPG4, CTHRC1, CTNND2, CTSG, CTSL, CUX2, CX3CL1, CXADR, CXCL12, CXCL16, CXCL2, CYP24A1, CYP27A1, DAB2, DCLK1, DDR2, DEPTOR, DERL3, DKK3, DLK1, DLL4, DLX4, DMD, DMKN, DMXL2, DNAH7, DNASE1L3, DNM1, DPY19L2, DSC3, DSG2, DSG3, DTNA, DUSP5, EBF2, ECRG4, EDNRA, EEF1A2, EFNB2, EFR3B, EFS, EGF, EGFL7, EGFR, ELN, EMP1, EMP2, ENG, ENPP1, ENPP2, ENPP3, ENTPD2, EPAS1, EPB41L3, EPHA2, EPHB1, ERBB3, EREG, ERG, ERRFI1, ESM1, ESR1, EXO1, F13A1, F2RL3, F3, F7, FAAH, FAM107A, FAP, FAT2, FAT4, FBLN2, FBLN5, FBP1, FCER1A, FCGR2A, FCGR2B, FCGR3A, FCRL1, FCRL2, FCRL5, FCRLA, FES, FGF1, FGF2, FGF7, FGFR2, FGFR3, FGFR4, FHL2, FIBCD1, FILIP1L, FLRT3, FLT1, FLT3, FMN1, FOLH1, FOLR2, FOXC2, FPR1, FRAS1, FREM1, FRMD6, FRZB, FSCN1, FSIP1, FST, FUT7, FZD4, GABRD, GABRP, GAS6, GATA2, GHR, GJC1, GLI2, GLT8D2, GPR34, GPR39, GPR4, GPRC5B, GRAMD2B, GREB1, GREB1L, GRIK3, GRIK4, GRM4, GRP, GUCY1A2, GXYLT2, GZMB, HAS2, HAVCR2, HBEGF, HCK, HDAC9, HDC, HES4, HEYL, HGF, HK2, HLX, HMCN1, HMOX1, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE2, HS6ST2, HSPA6, HSPG2, HTRA1, IDO1, IGF1, IGSF3, IL18, IL18R1, IL1RL1, IL33, IL34, IL3RA, INHBA, IRF4, IRS1, IRS2, IRX1, ITGA2, ITGA2B, ITGA3, ITGA7, ITGB6, ITIH5, JAG1, JAM2, KANK2, KCNF1, KCNH6, KCNJ8, KCNK1, KCNK10, KCNK15, KCNK17, KCNMA1, KCNMB1, KCNN3, KDR, KIF18B, KIT, KLF5, KLK10, KLK6, KMO, LAD1, LAMB3, LAMC1, LAMC2, LAMP3, LCN2, LCNL1, LDB3, LGMN, LGR4, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LMO2, LMX1B, LOX, LOXL1, LOXL2, LPAR1, LPL, LRP1, LRRC15, LRRK2, LTBP2, LTK, LUM, LURAP1, LY96, MACC1, MAFB, MAOB, MAP1A, MAP1B, MAP2, MAP3K20, MAP7, MAPK15, MAPT, ME1, MECOM, MEIS1, MEIS2, MERTK, MET, MFAP5, MLF1, MLXIPL, MME, MMP14, MMP9, MMRN2, MNDA, MNX1, MPP1, MRC1, MRC2, MS4A1, MS4A2, MS4A6A, MSR1, MST1R, MTNR1B, MUC16, MUC5B, MYCL, MYCN, MYH11, MYLK, MYO1B, MYO1E, NAT1, NCCRP1, NCF1, NCF2, NEBL, NECTIN1, NEK2, NGFR, NIBAN3, NOS1AP, NOSTRIN, NOTCH3, NPNT, NPR1, NPY1R, NR4A3, NR5A2, NRN1, NRP1, NRP2, NTN4, NTRK2, OCLN, OLFM1, OLFML3, OLR1, OPRD1, OVOL2, OXTR, P2RX1, P2RY13, P2RY14, P2RY2, P2RY6, PADI2, PALMD, PAMR1, PARM1, PAX5, PBK, PCDH10, PCDH17, PCDH18, PCDH8, PDE10A, PDE2A, PDE3A, PDE5A, PDGFA, PDGFRA, PDGFRB, PDGFRL, PDLIM4, PDPN, PDZK1, PGF, PGR, PHEX, PHF21B, PHGDH, PIEZO2, PILRA, PITX1, PLA2G7, PLA2R1, PLAGL1, PLAT, PLAUR, PLCE1, PLCG2, PLEKHH2, PLTP, PLVAP, PLXNB1, PMAIP1, PODXL, POTEF, POTEI, POU2AF1, PPARG, PPM1H, PPP1R1B, PRDM6, PRICKLE1, PRKAA2, PRLR, PROCR, PRODH, PROM1, PROX1, PRRX1, PSD3, PTAFR, PTGES, PTGFRN, PTGIR, PTGIS, PTGS1, PTK6, PTK7, PTPRB, PTPRG, RAPGEF3, RAPGEF5, RASD1, RASSF4, RBMS3, RBPMS, RCAN2, RECQL4, REEP6, RELN, RERG, RET, RGL3, RGS5, RIMS3, RIPK4, RNF180, RNF39, RNF43, ROBO4, ROR2, RRM2, RTN1, RUNX1T1, S100A1, S1PR3, SAMD5, SAPCD2, SASH1, SCGB1D2, SCIMP, SCN9A, SDC2, SDC4, SDK2, SEMA4B, SEMA7A, SEPTIN4, SEPTIN5, SERPINA5, SERPINB5, SERPINE1, SERPINH1, SEZ6L2, SFRP1, SGK1, SH3RF2, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SIM2, SIRPA, SIX1, SLAMF8, SLC11A1, SLC16A1, SLC18A2, SLC1A1, SLC1A3, SLC2A9, SLC30A8, SLC35G2, SLC6A1, SLC7A11, SLC7A7, SLC9A3R2, SLCO2A1, SLCO2B1, SLIT2, SLIT3, SMOC2, SMPD3, SMTN, SNCA, SOD3, SORCS1, SOSTDC1, SOX10, SOX17, SOX18, SOX5, SOX9, SPAG17, SPATA18, SPECC1, SPIB, SPOCK1, SPON1, SPON2, SPRY2, SRPX, ST14, STAP1, STEAP1, STEAP4, SULF1, SULF2, SVEP1, SYNM, SYNPO2, TACC2, TAL1, TBC1D30, TBX2, TBX3, TBXA2R, TCIM, TCL1A, TDO2, TEK, TFAP2B, TFPI2, TGFB2, TGM2, THBD, THBS2, THSD4, THY1, TIE1, TIMP3, TLR1, TLR2, TLR4, TLR6, TLR7, TLR9, TMEM119, TMEM255B, TMEM97, TMEM98, TNC, TNFRSF12A, TNFRSF13B, TNFRSF13C, TNFRSF17, TNFRSF21, TNFSF13, TNNT2, TNS4, TNXB, TP63, TPBG, TPPP3, TPSG1, TREM2, TREML2, TRH, TRIM29, TRO, TRPC6, TRPM2, TSPAN7, TSPAN8, TUBB2A, TUBB2B, TUBB3, TUBB6, TWIST1, TWIST2, TYMS, UNC5A, VCAM1, VCL, VEGFC, VGLL3, VSIG4, VSNL1, VTCN1, WDR90, WFDC1, WFS1, WNT2, WNT5B, ZFAT, ZFHX4, ZNF296
dendritic | positive: LCNL1, CLEC4C, GZMB, CSF2RA, DNASE1L3, IL3RA, SCN9A, EPHB1, SHD, LILRA4, CLIC3, PHEX, FLT3, LAMP3, IDO1, CLCN5, TCL1A, RUNX2, FUT7, ZFAT, IRF7, DUSP5, LTK, TLR9, TLR7, SPIB, CCDC50, CD83, NIBAN3, IRF4, CUX2, SMPD3, MAP1A, KCNK10, P2RY14, KCNK17, CMKLR1, NR4A3, RASSF4, MYCL, RIMS3, MS4A6A, SELPLG, CXCR3, ZDHHC17, ERN1, MAP3K14, TRAF1, RASSF2, DERL3, BLK, RELT, BLNK, LILRB4, RASD1, HS3ST3A1, SLC7A11, FCER1A, LAIR1, IL6R, LY75, IL7R, ADA, SEMA7A, P2RX1, NCCRP1, EPHA2, CTSC, SLC7A5, KCTD5, MAPKAPK2, JAK2, PLXNC1, IFI44L, NCF1, TNFRSF17, MX1, SULF2, P2RY6, FSCN1, CD274, HCK, GAS6, VEGFB, HLX, KIF2A, ETV6, IL21R, TREML2, CYSLTR1, WNT5B, KAT2B, PTGIR, TUBB6, TNFRSF21, ALDH2, MYO1E, NPC1, FES, CD68, ADAM12, CCL19, ZNF296, ZBTB33, RFTN1, MAN2B1, OAS1, IFNAR2, TSPAN13, TRAF4, HDAC9, CCDC102B, MDFIC, ZEB1, CST7, PLEKHO1, SLC2A1, RUBCN
dendritic | negative: ABCA10, ABCA8, ABCC12, ABCC8, ABCC9, ABI3, ABI3BP, AC007906.2, ACVRL1, ADAM33, ADAMTS1, ADAMTS12, ADAMTS14, ADAMTS2, ADAMTS4, ADAMTS5, ADCYAP1, ADGRB2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRA2A, AFAP1L2, AGR3, AIM2, ALPK3, ALPL, ANGPT1, ANGPTL2, ANGPTL4, ANXA3, ANXA8, APLNR, AQP1, ASPN, ATL1, AURKB, AVPR1A, BAALC, BACE2, BARX2, BCL6B, BDKRB2, BMP2, BMP6, BMP8B, BMPR1B, BNC2, BPIFB1, BRINP1, C1QTNF1, C3AR1, C5AR1, C7, CA12, CACNA1B, CACNG4, CALB2, CALML3, CARD10, CARD14, CARD9, CAVIN2, CAVIN3, CCDC141, CCL14, CCN3, CCN4, CCR4, CD19, CD1C, CD1D, CD209, CD22, CD247, CD27, CD28, CD33, CD34, CD3G, CD40LG, CD72, CD79B, CDH13, CDH3, CDH5, CDH6, CDKN2B, CEBPA, CEP112, CES1, CFC1, CGNL1, CHST8, CLDN5, CLEC12A, CLEC14A, CLEC5A, CLEC7A, CLGN, CMA1, CMYA5, CNN1, CNRIP1, CNTNAP1, COL10A1, COL17A1, COL4A3, COL4A4, COLEC12, COMP, CPXM2, CR1, CRB2, CSF1, CSPG4, CTLA4, CTNND2, CTSG, CX3CL1, CXADR, CXCL2, CXCR6, CYP27A1, DCLK1, DDR2, DIO2, DKK3, DLK1, DLL4, DLX4, DNAH5, DNAH7, DPT, DPY19L2, DSC3, DSG3, EBF2, ECRG4, EDN1, EDNRA, EFR3B, EGF, EGFR, EGR2, ELN, ENPP3, ENTPD2, EREG, ERG, ERRFI1, ESM1, ETV1, F2R, F2RL3, F3, F7, FAM107A, FAT2, FAT4, FBLN5, FCGR2A, FCGR2B, FCGR3A, FCMR, FCRL1, FCRL2, FCRL5, FCRLA, FGF1, FGF2, FGF7, FGFR2, FGFR4, FILIP1L, FLRT3, FLT1, FLT3LG, FLT4, FMOD, FOLH1, FOLR2, FOXD2, FPR1, FRAS1, FREM1, FRZB, FST, GABRD, GABRP, GHR, GIMAP5, GJC1, GLT8D2, GPR34, GPR4, GPRC5B, GRIK3, GRK5, GRP, GUCY1A2, GZMA, GZMK, HAS2, HBEGF, HDC, HES6, HEYL, HGF, HMCN1, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS6ST2, HSPA6, ICOS, IGF1, IGSF3, IL17RB, IL18, IL1RL1, IL2RB, IL33, IL34, INHBA, IRS1, IRX1, ITGA2B, ITGA7, ITIH5, ITK, JAM2, JAM3, KANK2, KCNF1, KCNH6, KCNJ8, KCNK15, KCNMB1, KCNN3, KCNN4, KDR, KIAA0408, KIF18B, KIT, KLK10, KLK6, KLRD1, LAMB3, LAMC2, LCK, LCN2, LDB3, LEF1, LGR4, LIF, LILRB5, LMO2, LOX, LOXL2, LPAR1, LRG1, LRRC15, LURAP1, MAOB, MAP1B, MAP2, MAP3K7CL, MAPK11, MAPK15, MAPT, ME1, MECOM, MEIS2, MERTK, MET, MFAP5, MLXIPL, MME, MMP9, MMRN2, MNX1, MRC1, MS4A1, MS4A2, MSR1, MTNR1B, MUC16, MUC5B, MYH11, MYLK, NAT1, NEK2, NLRP3, NOS1AP, NOS3, NOSTRIN, NPNT, NPR1, NPY1R, NR5A2, NRN1, NTRK2, OLFM1, OLFML3, OLR1, OPRD1, OXTR, P2RX5, P2RY2, PALMD, PAMR1, PAX5, PBK, PCDH10, PCDH17, PCDH18, PDE10A, PDE2A, PDE3A, PDE5A, PDGFA, PDGFD, PDGFRA, PDGFRL, PDLIM4, PDPN, PDZK1, PGF, PGR, PI16, PILRA, PITX1, PLA2G7, PLA2R1, PLCE1, PLEKHH2, PODXL, POTEI, POTEJ, POU2AF1, PPARG, PPFIA3, PRDM6, PRF1, PRICKLE1, PRKAA2, PRKCQ, PROCR, PROM1, PROX1, PRRX1, PTAFR, PTGIS, PTGS1, PTHLH, PTPRB, PTPRM, RAPGEF3, RASGRP1, RBPMS, RCAN2, RECK, RELN, RERG, RGS5, RIPK4, RNF180, RNF39, ROBO4, ROR2, RTN1, RUNX1T1, S100A1, SAMD5, SASH1, SCGB1D2, SCIMP, SDC2, SELP, SEPTIN4, SEPTIN5, SERPINA5, SERPINB5, SERPINE1, SFRP1, SFRP4, SH2D1A, SH2D2A, SH3RF2, SHANK3, SIGLEC1, SIGLEC9, SIM2, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC35G2, SLC6A1, SLC7A7, SLCO2A1, SLIT2, SLIT3, SMOC2, SMTN, SOD3, SORCS1, SOSTDC1, SOX10, SOX17, SOX18, SOX5, SOX9, SPA17, SPAG17, SPATA18, SPOCK1, SPON1, SPRY2, STAP1, STEAP1, STEAP4, STX1A, SUGCT, SULF1, SVEP1, TAL1, TBX2, TBXA2R, TDO2, TEK, TFAP2B, TGFB2, TGFBR3, TGM2, TH, TICRR, TIE1, TIGIT, TLR4, TMEM119, TMEM255B, TMEM45B, TMEM98, TNFRSF12A, TNFRSF13B, TNFRSF13C, TNS4, TP63, TPPP3, TPSG1, TREM2, TRIB3, TRIM29, TRPC1, TRPC6, TSPAN18, TSPAN7, TUBB3, TWIST1, TWIST2, VEGFC, VGLL3, VSIG4, VSNL1, VSTM2L, VTCN1, WNT2, WNT4, WWC1, ZBP1, ZFHX4, ZNF683
endo | positive: SOX17, CDH5, HOXD9, NR5A2, SLCO2A1, PLVAP, PTPRB, CCL14, ROBO4, CLEC14A, ADGRL4, SHANK3, KDR, FLT1, CD34, CALCRL, TEK, ENG, CLDN5, HSPG2, BCL6B, PCDH17, NPR1, MMRN2, DLL4, AQP1, PALMD, APLNR, SLC35G2, NRN1, PROX1, FOLH1, TAL1, TSPAN7, TMEM255B, PDE2A, TIE1, NOSTRIN, EGFL7, CAVIN2, CDH13, HOXD10, TSPAN18, FLT4, ACVRL1, ADAMTS4, EMP1, IL33, F2RL3, IL3RA, SELP, CPXM2, FAM107A, ESM1, THBD, BMP2, NOS3, GPR4, PTPRM, HBEGF, TGM2, FAT4, GABRD, PODXL, FZD4, SOX18, PIEZO2, TBXA2R, ADAMTS1, JAM2, RGS5, MET, VEGFC, ANGPTL2, RELN, S1PR1, BMP6, KCNN3, EPAS1, CAVIN3, ITGA5, NRP2, CNRIP1, C1QTNF1, EDN1, GJC1, ALPK3, RAPGEF3, HTRA1, TGFBR3, ERG, ICAM2, ABI3, LOX, PROCR, MECOM, RCAN2, CEP112, PDE10A, BDKRB2, PGF, ITIH5, GIMAP8, PRCP, PDGFD, EFNB2, HLX, C7, TNXB, RNF180, TRPC6, SASH1, ABCC9, OLFM1, PPARG, GPRC5B, PDGFB, FGF2, CARD10, NPNT, RAPGEF5, FSCN1, LMO2, TGFBR2, ZEB1, ELK3, CXCL12, GIMAP4, ELN, DOCK9, MAPK11
endo | negative: ABCA10, ABCA8, ABCC12, ABCC8, AC007906.2, ADAMDEC1, ADAMTS14, ADCYAP1, ADGRE2, ADORA2B, ADORA3, ADRA1B, ADTRP, AGR3, ANGPT1, ANXA8, ASPN, ATL1, BAALC, BANK1, BARX2, BCL2A1, BLK, BLNK, BMP8B, BMPR1B, BPIFB1, BRINP1, C3AR1, C4B, C5AR1, CACNG4, CADM3, CALB2, CALML3, CARD9, CCL22, CCN4, CCR1, CCR4, CCR5, CCR6, CD19, CD1C, CD1E, CD209, CD22, CD226, CD247, CD27, CD28, CD33, CD38, CD3G, CD40LG, CD5, CD7, CD72, CD79A, CD83, CD86, CD8A, CEBPA, CES1, CFC1, CLEC10A, CLEC12A, CLEC4C, CLEC5A, CLEC7A, CLGN, CLIC3, CMA1, CMKLR1, CMYA5, CNR2, COCH, COL10A1, COL4A3, COL4A4, CR1, CRB2, CSF3R, CST7, CTLA4, CTNND2, CTSG, CTSW, CUX2, CXADR, CXCR3, CXCR6, DCLK1, DERL3, DLK1, DLX4, DNAH7, DNM1, DPT, DSC3, DSG3, ECRG4, EGF, EGR2, ENPP3, ENTPD2, EREG, F7, FAT2, FCER1A, FCGR2A, FCGR2B, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FGF1, FGFR2, FLRT3, FLT3, FOLR2, FPR1, FREM1, FST, FUT7, GABRP, GLI2, GPR174, GPR39, GRIK3, GRIK4, GRM4, GRP, GZMA, GZMB, GZMK, HAS2, HAVCR2, HCK, HDC, HGF, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, ICOS, IDO1, IL12RB1, IL18, IL18R1, IL1RL1, IL21R, IL2RB, IL34, IL6R, IRF4, IRS1, IRS2, IRX1, ITGA2B, ITK, KCNA3, KCNF1, KCNH6, KCNK10, KCNK15, KCNK17, KCNMA1, KCNN4, KIAA0408, KIF18B, KIT, KLHL13, KLK10, KLK6, KLRD1, KMO, LAMB3, LAMC2, LAMP3, LCN2, LCNL1, LDB3, LGR4, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LPAR1, LRG1, LRRK2, LTK, MAOB, MAP1A, MAPT, ME1, MLXIPL, MME, MNDA, MNX1, MS4A1, MS4A2, MTNR1B, MUC16, MUC5B, MYCL, NCCRP1, NCF1, NCF2, NEK2, NIBAN3, NKX3-1, NLRP2, NLRP3, NOD2, NOS1AP, NPY1R, NR4A3, NTRK2, OLR1, OPRD1, P2RX1, P2RX5, P2RX7, P2RY13, P2RY14, PAX5, PCDH10, PCDH8, PDE3A, PDZK1, PGR, PHEX, PHF21B, PI16, PILRA, PLA2G7, PLEKHH2, PMAIP1, POTEI, POTEJ, POU2AF1, POU2F2, PPFIA3, PPP1R1C, PRF1, PRKCQ, PRODH, PROM1, PTGDR, PTGES, PTGIS, PTGS1, PTHLH, RIMS3, RIPK4, RNF39, ROR2, RTN1, S100A1, SCGB1D2, SCIMP, SCN9A, SDK2, SERPINB5, SFRP4, SH2D1A, SH2D2A, SH3RF2, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SIM2, SLAMF6, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC6A1, SLC7A11, SLIT2, SMPD3, SOSTDC1, SOX10, SPA17, SPAG17, SPATA18, SPIB, SPOCK1, SPON1, STAP1, STEAP1, SUGCT, TCL1A, TFAP2B, TH, TIGIT, TLR1, TLR6, TLR7, TLR9, TNFRSF13B, TNFRSF13C, TNFRSF17, TNFSF8, TNS4, TP63, TPSG1, TREM2, TREML2, TRH, TRIM29, TRPM2, TUBB2B, TWIST1, TWIST2, UBASH3A, VGLL3, VSIG4, VSNL1, VSTM2L, VTCN1, WNT2, WNT4, WNT5B, ZBP1, ZFHX4, ZNF296, ZNF683
macro | positive: SIGLEC1, MS4A6A, SLC11A1, CD163, LILRB5, PLA2G7, CSF1R, MSR1, CSF3R, VSIG4, CLEC7A, SLC1A3, CPVL, CD14, FCGR3A, MRC1, CD68, OLR1, C1QC, CLEC10A, P2RY13, SLAMF8, FOLR2, CD209, F13A1, MERTK, AIF1, FCGR2A, NCF2, HSPA6, MNDA, CD86, CSF2RA, C3AR1, CLEC5A, TLR2, SIGLEC9, RASSF4, SLCO2B1, CD33, FPR1, HAVCR2, AOAH, ADORA3, PLTP, CLEC12A, LILRB2, IL18, ADGRE2, TREM2, NLRP3, GPR34, HCK, PILRA, MPP1, PTGS1, CR1, CD1D, TLR1, FCGR2B, CCR1, SLC7A7, CD1E, DAB2, LILRB4, CMKLR1, TLR4, FCER1A, ADAMDEC1, GIMAP8, HPSE, P2RX7, FGR, LY96, SCIMP, TLR7, CD1C, FMN1, CARD9, RTN1, TNFSF13, KCNMB1, C5AR1, LAIR1, CTSC, PTAFR, HMOX1, TLR6, ADAM28, LRRK2, ABI3, CD72, MAFB, P2RY6, EPB41L3, LGMN, SIRPA, DMXL2, MMP9, FES, ARRB2, COLEC12, NRROS, CASP1, BCL2A1, LPAR6, SLC2A9, KCNMA1, CTSL, LRP1, CEBPA, HGF, CD302, TYMP, CXCL16, TRPM2, LIPA, CREG1, ENPP2, VSIR, CD38, SNX10
macro | negative: ABCA10, ABCA8, ABCC12, ABCC8, ABCC9, ABI3BP, AC007906.2, ACVRL1, ADAM12, ADAM33, ADAMTS1, ADAMTS12, ADAMTS14, ADAMTS2, ADAMTS4, ADAMTS5, ADCYAP1, ADGRB2, ADGRL4, ADRA1B, ADRA2A, AFAP1L2, AGR3, ALDH7A1, ALPK3, ALPL, ANGPT1, ANGPTL2, ANXA3, ANXA8, APLNR, AQP1, ASPN, ATL1, AVPR1A, AXIN2, BAALC, BACE2, BANK1, BARX2, BCL6B, BDKRB2, BLK, BMP2, BMP6, BMP8B, BMPR1B, BPIFB1, BRINP1, C1QTNF1, C4B, C7, CACNA1B, CACNG4, CADM3, CALB2, CALCRL, CALML3, CARD10, CAVIN2, CAVIN3, CCDC102B, CCL14, CCL19, CCL28, CCN2, CCN3, CCN4, CCR4, CD19, CD248, CD27, CD34, CD40LG, CD7, CD79A, CD79B, CD8A, CDH11, CDH13, CDH3, CDH5, CDH6, CDKN2B, CELSR3, CENPA, CEP112, CGNL1, CHAD, CLDN5, CLEC14A, CLEC4C, CLGN, CLIC3, CMA1, CMYA5, CNN1, CNR2, CNTNAP1, COCH, COL10A1, COL17A1, COL4A3, COL4A4, COL7A1, COMP, CPXM2, CRB2, CSPG4, CTLA4, CTNND2, CTSG, CTSW, CUX2, CX3CL1, CXCR3, CXCR6, DCLK1, DDR2, DERL3, DIO2, DKK3, DLK1, DLL4, DLX4, DMD, DNAH5, DNAH7, DNASE1L3, DPT, DPY19L2, DSC3, DSG3, EBF2, ECRG4, EDN1, EDNRA, EFNB2, EFR3B, EGF, EGFR, ELN, ENPP3, ENTPD2, EPHA2, EPHB1, EREG, ERG, ERRFI1, ESM1, ETV1, F2R, F2RL3, F3, F7, FAM107A, FAP, FAT2, FAT4, FBLN5, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FGF1, FGF2, FGF7, FGFR2, FGFR4, FLRT3, FLT1, FLT4, FMOD, FOLH1, FOXC2, FOXD2, FREM1, FRZB, FST, FZD4, GABRD, GABRP, GHR, GJC1, GLI2, GLT8D2, GPR174, GPR4, GRIK3, GRIK4, GRM4, GRP, GUCY1A2, GXYLT2, GZMB, GZMK, HAS2, HDC, HEYL, HMCN1, HOXA5, HOXD10, HOXD9, HPGD, HPSE2, HS3ST3A1, HS6ST2, ICOS, IGSF3, IL17RB, IL18R1, IL1RL1, IL33, IL34, IL3RA, INHBA, IRF4, IRS1, IRX1, ITGA2B, ITGA7, ITIH5, JAG1, JAM2, JAM3, KANK2, KCNF1, KCNH6, KCNJ8, KCNK10, KCNK15, KCNK17, KCNN3, KDR, KIAA0408, KIF18B, KIT, KLHL13, KLK10, KLK6, KLRD1, LAMB3, LAMC2, LAMP3, LCN2, LCNL1, LDB3, LEF1, LGR4, LIF, LILRA4, LOX, LOXL2, LRG1, LRRC15, LTK, MAOB, MAP1A, MAP1B, MAP2, MAP3K7CL, MAPK11, MAPT, MECOM, MEIS2, MET, MFAP5, MLXIPL, MME, MMP11, MMRN2, MS4A1, MS4A2, MTNR1B, MUC16, MUC5B, MYH11, NAV2, NCCRP1, NGFR, NIBAN3, NKX3-1, NLRP2, NOS1AP, NOS3, NOSTRIN, NPNT, NPR1, NPY1R, NR4A3, NR5A2, NRN1, NTRK2, OPRD1, OXTR, P2RX1, P2RX5, P2RY2, PALMD, PAMR1, PAX5, PBK, PCDH10, PCDH17, PCDH18, PCDH8, PDE10A, PDE2A, PDE3A, PDE5A, PDGFD, PDGFRA, PDGFRB, PDGFRL, PDLIM4, PDPN, PDZK1, PGF, PGR, PHEX, PHF21B, PHLDB2, PI16, PIEZO2, PITX1, PLA2R1, PLCE1, PLEKHH2, PODXL, POTEI, POTEJ, POU2AF1, PPP1R1C, PRF1, PRICKLE1, PRKAA2, PRKCQ, PROM1, PROX1, PRRX1, PTGDR, PTGES, PTGIS, PTHLH, PTK7, PTP4A3, PTPRB, RAPGEF3, RASD1, RBMS3, RBPMS, RCAN2, RECK, RELN, RERG, RGS5, RIMS3, RIPK4, RNF180, RNF39, ROBO4, ROR2, RUNX1T1, S100A1, S1PR1, SAMD5, SCGB1D2, SDK2, SELP, SEMA7A, SEPTIN4, SEPTIN5, SERPINA5, SERPINB5, SERPINE1, SFRP1, SFRP4, SH2D1A, SH2D2A, SH3RF2, SHANK3, SHD, SIGLEC6, SIM2, SLAMF6, SLC18A2, SLC35G2, SLC6A1, SLC7A11, SLCO2A1, SLIT2, SLIT3, SMOC2, SMPD3, SOD3, SORCS1, SOSTDC1, SOX10, SOX17, SOX18, SOX5, SOX9, SPA17, SPAG17, SPATA18, SPIB, SPOCK1, SPON1, SPON2, SRPX, STAP1, STEAP1, STEAP4, SUGCT, SULF1, SVEP1, SYNPO2, TAL1, TBX2, TBXA2R, TCL1A, TDO2, TEK, TFAP2B, TGFBR3, TH, THBS2, THY1, TIE1, TIGIT, TLR9, TMEM119, TMEM45B, TMEM98, TNFRSF13B, TNFRSF13C, TNFRSF17, TNS4, TP63, TPPP3, TPSG1, TRH, TRIB3, TRIM29, TRO, TRPC1, TSPAN18, TSPAN7, TUBB2B, TUBB3, TUBB6, TWIST1, TWIST2, UBASH3A, UNC5A, VEGFC, VGLL3, VSNL1, VSTM2L, VTCN1, WFDC1, WNT2, WNT4, WNT5B, ZFAT, ZFHX4, ZNF296, ZNF683
mast | positive: HDC, IL1RL1, KIT, MS4A2, SLC18A2, ADCYAP1, TPSG1, CTSG, HPGDS, LIF, CMA1, HPGD, ENPP3, GATA2, ADGRE2, IL18R1, CSF1, ADRB2, KLRG1, PTGS1, SIGLEC6, FER, P2RX1, STXBP5, BMP2K, ACSL4, FOXP1, MCTP2, ABCC1, TMEM255B, CAVIN2, MAOB, CD33, C3AR1, MEIS2, CD274, AGAP1, LAT, CD22, CALB2, PLAUR, AHR, ABCA1, AP1S3, CD82, IRS2, SGK1, BACE2, PPM1H, CD44, NEK6, ABCC4, ARRB2, ADGRE5, RASSF5, CNST
mast | negative: ABCA10, ABCC12, ABCC3, ABCC9, ABI3, ABI3BP, AC007906.2, ACVRL1, ADA, ADAM28, ADAM33, ADAMTS1, ADAMTS12, ADAMTS2, ADAMTS4, ADGRL4, ADORA2B, ADRA1B, ADTRP, AEBP1, AFAP1L2, AIF1, AIM2, ALDH2, ALDH7A1, ALPK3, ANGPTL2, ANGPTL4, ANKRD13A, ANO1, AOAH, APLNR, AQP1, ATL1, AURKB, AVPR1A, AXIN2, BANK1, BARX2, BCL11B, BCL6B, BGN, BLK, BLNK, BMP8B, BMPR1B, BPIFB1, C1QC, C1QTNF1, C5AR1, CACNA1B, CACNG4, CALCRL, CALML3, CARD14, CASP1, CAVIN3, CCDC102B, CCDC170, CCDC50, CCDC80, CCL14, CCN1, CCN2, CCN4, CCR1, CCR5, CD14, CD151, CD163, CD19, CD1D, CD2, CD209, CD247, CD248, CD27, CD28, CD34, CD3D, CD3E, CD3G, CD40, CD5, CD6, CD79A, CD79B, CD86, CD8A, CD96, CDH11, CDH13, CDH23, CDH3, CDH5, CDH6, CDK14, CDKN2B, CEBPA, CELSR1, CFB, CHAD, CLCN5, CLDN5, CLEC10A, CLEC14A, CLEC4C, CLEC7A, CLGN, CLIC3, CLSTN3, CMKLR1, CMYA5, CNN1, COL10A1, COL17A1, COL4A5, COL5A1, COL5A2, COL7A1, COLEC12, COMP, CPVL, CPXM2, CR1, CSF1R, CSF2RA, CSF3R, CSPG4, CTHRC1, CTNND2, CTSC, CTSK, CTSL, CX3CL1, CXCL12, CXCR3, CYP1B1, CYP27A1, DAB2, DCLK1, DCN, DERL3, DIO2, DKK3, DLK1, DLL4, DMD, DMKN, DNAH5, DNASE1L3, DNM1, DOCK9, DSG3, DTNA, DUSP5, EBF2, ECRG4, EDN1, EDNRA, EFNB2, EFR3B, EFS, EGF, EGFL7, EGFR, ELK3, ELN, EMP1, EMP2, ENPP1, ENTPD2, EPB41L3, EPHA2, EPHB1, ERRFI1, ESR1, F13A1, F2R, F3, F7, FAAH, FAP, FAT2, FBLN2, FBLN5, FCGR2A, FCGR2B, FCGR3A, FCMR, FCRL1, FCRL2, FCRL5, FGF1, FGF7, FGFR2, FGFR3, FGFR4, FHL2, FIBCD1, FILIP1L, FLRT3, FLT1, FLT3, FLT4, FMOD, FOLR2, FRAS1, FRMD6, FRZB, FST, FUT7, FZD4, GAS6, GBP5, GHR, GIMAP4, GIMAP8, GJC1, GLT8D2, GPR39, GPRC5B, GRK5, GUCY1A2, GXYLT2, GZMA, GZMB, HBEGF, HCK, HDAC9, HES4, HES6, HEYL, HMCN1, HMOX1, HOXD9, HSPA6, HSPD1, HTRA1, ICAM2, IDO1, IFI44L, IGF1, IGSF3, IL1R1, IL2RB, IL33, IL34, IL3RA, IL6R, IL7R, INHBA, INPP4B, IRF4, IRX1, ITGA2, ITGA5, ITGA7, ITGAL, ITGB7, ITK, JAG1, JAM3, KAT2B, KCNA3, KCNH6, KCNJ8, KCNK1, KCNK15, KCNK17, KCNMB1, KCNN4, KCTD5, KDR, KIAA0408, KIF18B, KLF12, KLK6, KLRK1, KMO, LAD1, LAMB3, LAMC1, LAMC2, LAMP3, LBH, LCK, LCNL1, LGMN, LILRA4, LILRB5, LIPA, LMX1B, LOX, LOXL1, LOXL2, LPAR1, LPAR6, LRATD2, LRG1, LRP1, LRRK2, LTBP2, LTK, LUM, LY96, MACC1, MAFB, MAP1A, MAP1B, MAP2, MAP3K14, MAP3K20, MAPK11, MAPK15, MAPT, MDFIC, MEIS1, MERTK, MET, MFAP5, MLF1, MLXIPL, MME, MMP11, MMP14, MMRN2, MNDA, MNX1, MRC1, MRC2, MS4A1, MS4A6A, MSR1, MX1, MYCL, MYH11, MYLK, MYO1B, MYO1E, NBL1, NCF2, NECTIN1, NECTIN2, NGFR, NIBAN3, NINJ1, NKX3-1, NLRP1, NLRP2, NLRP3, NOSTRIN, NOTCH3, NPC1, NPR1, NPY1R, NR5A2, NRN1, NTN4, NTRK2, OAS1, OLFML3, OLR1, OPRD1, ORAI2, OVOL2, OXTR, P2RX5, P2RY13, PALMD, PAMR1, PARM1, PAX5, PBK, PCDH17, PCDH18, PDE10A, PDE5A, PDGFA, PDGFB, PDGFD, PDGFRA, PDGFRB, PDGFRL, PDK1, PDLIM4, PDPN, PDZK1, PGF, PHEX, PHLDB2, PIEZO2, PILRA, PITX1, PLA2G7, PLA2R1, PLCE1, PLEKHH2, PLTP, PLVAP, PLXDC1, PMAIP1, PODXL, POTEI, POU2AF1, POU2F2, PPARG, PRCP, PRDM1, PRICKLE1, PRKAA2, PRLR, PRODH, PROX1, PRRX1, PSD3, PTGFRN, PTGIS, PTHLH, PTK7, PTP4A3, PTPRB, PTPRG, PTPRM, RAPGEF3, RASD1, RASGRP1, RASSF4, RBMS3, RBPMS, RCAN2, RERG, RGL3, RGS5, RIPK4, ROBO4, ROR2, RUBCN, RUNX1T1, RUNX2, S100A1, S1PR3, SAMD5, SAPCD2, SASH1, SCGB1D2, SCIMP, SCN9A, SDC2, SDC4, SELENBP1, SELP, SELPLG, SEMA4B, SEPTIN4, SEPTIN5, SERPINB5, SERPINE1, SFRP4, SH3RF2, SHANK3, SHD, SIGLEC1, SIM2, SIRPA, SIX1, SLAMF1, SLAMF8, SLC11A1, SLC16A1, SLC1A3, SLC2A1, SLC2A9, SLC35G2, SLC6A1, SLC7A11, SLC7A5, SLC7A7, SLC9A3R2, SLCO2A1, SLIT2, SLIT3, SMOC2, SMPD3, SMTN, SNCA, SNX10, SOD3, SORCS1, SOSTDC1, SOX10, SOX17, SOX18, SOX5, SP140, SPA17, SPAG17, SPATA18, SPIB, SPOCK1, SPON1, SPON2, SRPX, ST6GAL1, STAT4, STEAP1, STEAP4, SULF1, SULF2, SVEP1, SYNPO2, SYVN1, TACC2, TBX2, TBX3, TCF7, TCIM, TCL1A, TEK, TENT5C, TFAP2B, TGFB3, TGM2, THBD, THBS2, THEMIS, THY1, TIMP2, TLK2, TLR1, TLR2, TLR4, TLR6, TLR7, TLR9, TMEM119, TMEM45B, TMEM97, TMEM98, TNC, TNFRSF13B, TNFRSF13C, TNNT2, TNS4, TNXB, TP63, TPPP3, TRAC, TRAF1, TRBC1, TREM2, TRIB3, TRIM29, TRPC6, TSPAN13, TSPAN18, TUBB2A, TWIST1, UNC5A, VCAM1, VCL, VEGFC, VSIG4, VSNL1, VSTM2L, WFS1, WWC1, ZAP70, ZBTB33, ZEB1, ZFAT, ZNF296
myoepi | positive: TRIM29, IRX1, TNS4, SOSTDC1, FAT2, COL17A1, LAMB3, SAMD5, TFAP2B, SOX10, AC007906.2, CALML3, DSG3, ECRG4, CDH3, FST, F3, TP63, MME, VSNL1, KLK6, DSC3, SERPINB5, KLK10, NTRK2, BRINP1, MAOB, MYH11, SFRP1, ANXA8, SH3RF2, CX3CL1, TNC, ADORA2B, CNN1, PROM1, GABRP, CDH13, PDLIM4, GRP, ERRFI1, LCN2, CRB2, PAMR1, LAMC2, MYLK, IL34, ANXA3, NGFR, PGR, VTCN1, FGF1, DST, CSPG4, KCNMB1, TUBB2B, LTBP2, COL7A1, SYNM, FGFR2, MET, CES1, ITGA2, RNF39, CXCL2, CADM3, HOXA5, EGFR, ADTRP, ATL1, CCL28, KCNN4, SDK2, ADAMTS5, KLHL13, DMD, DKK3, CCN3, OXTR, CMYA5, NAV2, TMEM98, ABCC3, NTN4, GRAMD2B, LGR4, KIT, FGF2, CDH23, SNCA, TLR2, COL4A5, DMKN, RNF180, LURAP1, TGFB2, ETV1, PALMD, JAG1, GRIK3, ALDH7A1, PDGFA, ITGA3, FHL2, CARD10, SOX9, CXADR, WNT4, S100A1, RIPK4, BARX2, WWC1
myoepi | negative: ABCA8, ABCC12, ABCC8, ABCC9, ABI3BP, ACVRL1, ADA, ADAM33, ADAMDEC1, ADAMTS12, ADAMTS14, ADAMTS4, ADCYAP1, ADGRL4, ADORA3, ADRA1B, ADRA2A, AIM2, ALPL, ANGPT1, APLNR, ASPN, AVPR1A, BANK1, BCL2A1, BCL6B, BDKRB2, BLK, BMP2, BMP6, BNC2, BPIFB1, C4B, C7, CALCRL, CCDC102B, CCDC141, CCL14, CCL19, CCN4, CCR1, CCR4, CCR6, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD226, CD27, CD274, CD28, CD33, CD34, CD38, CD3G, CD40LG, CD5, CD7, CD72, CD79A, CD79B, CD86, CDH6, CFC1, CLDN5, CLEC10A, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CMA1, CMKLR1, CNR2, CNRIP1, CNTNAP1, COCH, COL10A1, COL4A3, COL4A4, COLEC12, CR1, CST7, CTLA4, CTSG, CUX2, CXCR3, CXCR6, CYSLTR1, DCLK1, DERL3, DLK1, DLL4, DLX4, DNASE1L3, DPT, DPY19L2, EBF2, ENPP2, ENPP3, EPHB1, EREG, ESM1, F2RL3, FAM107A, FAT4, FCER1A, FCGR2B, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FGF7, FGR, FLT1, FLT4, FOLH1, FOLR2, FOXD2, FPR1, FREM1, FUT7, GABRD, GIMAP5, GIMAP8, GJC1, GLI2, GPR174, GPR4, GRIK4, GRM4, GUCY1A2, GZMA, GZMB, GZMK, HAS2, HBEGF, HCK, HDC, HEYL, HGF, HLX, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, ICAM2, ICOS, IDO1, IGF1, IL12RB1, IL18R1, IL1RL1, IL21R, IL3RA, IRF4, KCNA3, KCNF1, KCNH6, KCNJ8, KCNK10, KCNN3, KDR, KLRD1, KLRG1, LAMP3, LCNL1, LDB3, LILRA4, LILRB2, LILRB4, LILRB5, LPL, LRRC15, LTK, LY96, MAP1A, MAP3K7CL, MECOM, MFAP5, MLXIPL, MNDA, MRC1, MS4A1, MS4A2, MTNR1B, NCF1, NIBAN3, NLRP3, NOD2, NOS3, NPNT, NPR1, NR4A3, NR5A2, NRN1, NRROS, OLR1, OPRD1, P2RX1, P2RX5, P2RX7, P2RY13, P2RY14, PAX5, PCDH17, PCDH18, PCDH8, PDE10A, PDE2A, PDE3A, PHEX, PHF21B, PI16, PIEZO2, PILRA, POTEI, POTEJ, POU2AF1, PPFIA3, PPP1R1C, PRDM6, PRF1, PTGDR, PTGIR, PTGIS, PTGS1, PTPRB, RCAN2, RECK, RELN, RGS5, ROBO4, RUNX1T1, S1PR1, SCIMP, SCN9A, SELP, SEMA7A, SFRP4, SH2D1A, SH2D2A, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SLAMF6, SLC11A1, SLC18A2, SLC1A3, SLC35G2, SLC6A1, SLIT2, SMPD3, SOD3, SOX17, SPIB, SPOCK1, SPON1, STAP1, TAL1, TBX2, TBXA2R, TCL1A, TDO2, TEK, TH, TIE1, TIGIT, TLR7, TLR9, TMEM119, TMEM255B, TNFRSF13B, TNFRSF13C, TNFRSF17, TNFSF8, TPSG1, TREML2, TRH, TSPAN18, TSPAN7, TWIST1, TWIST2, UBASH3A, VSIG4, WFDC1, WNT2, ZBP1, ZFHX4, ZNF683
perivas | positive: GUCY1A2, CDH6, EBF2, AVPR1A, TRPC6, GJC1, TBX2, RGS5, ABCC9, ITGA7, ADRA1B, HEYL, SLC6A1, CSPG4, TPPP3, KCNJ8, SEPTIN4, CCDC102B, PDGFRB, NOTCH3, COL18A1, EDNRA, C1QTNF1, PGF, AFAP1L2, JAG1, SDC2, MYO1B, SYNPO2, PDGFA, FILIP1L, PDE5A, SOX5, LDB3, ANGPT1, DPY19L2, ADAMTS4, TDO2, PDE3A, CD248, ADAMTS12, RCAN2, RBMS3, SOD3, THY1, PRRX1, BGN, SLIT3, STEAP4, EPAS1, DKK3, MYLK, FRZB, PCDH18, SEPTIN5, ADAMTS1, COL5A2, CNTNAP1, ADAMTS2, VCL, MAP2, COL5A1, PHLDB2, PLXDC1, NPNT, ADAMTS14, S1PR3, FLT1, SMOC2, AEBP1, LBH, PAMR1, MYH11, SPRY2, TIMP3, PPARG, LOXL2, RBPMS, MAP3K20, SPON2, PTPRG, PTP4A3, MAP3K7CL, CCN2, GRK5, CCND2, WFDC1, ADRA2A, DMD, CAVIN3, PIEZO2, EPHA2, MAP1B, PLVAP, PLCE1, SPECC1, SULF1, FZD4, CALCRL, LAMC1, AQP1, DDR2, HES4, PARM1, FOXC2, LPL, NRP1, KANK2, SERPINH1, RERG, TRO, ENG, PALLD, SMTN, MYO1E, TNC, MAF, RFTN1, FGF7, KLF12
perivas | negative: ABCA10, ABCC12, ABCC8, ABI3BP, AC007906.2, ADAM28, ADAMDEC1, ADCYAP1, ADGRE2, ADORA2B, ADORA3, ADRB2, ADTRP, AGR3, AIF1, AIM2, ALPK3, ANXA8, BAALC, BANK1, BARX2, BCL11B, BCL2A1, BCL6B, BDKRB2, BLK, BLNK, BMP6, BMP8B, BMPR1B, BNC2, BPIFB1, BRINP1, C3AR1, C4B, CACNA1B, CACNG4, CADM3, CALML3, CARD10, CARD14, CARD9, CASP10, CCL22, CCL28, CCR1, CCR4, CCR6, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD27, CD274, CD33, CD38, CD7, CD72, CD79A, CD79B, CD83, CD86, CD8A, CDH13, CDH23, CDKN2B, CEBPA, CELSR3, CENPA, CES1, CGNL1, CHAD, CHST8, CLEC10A, CLEC12A, CLEC4C, CLEC5A, CLEC7A, CLGN, CLIC3, CMA1, CMKLR1, CMYA5, CNR2, COL10A1, COL17A1, COL4A3, COL4A4, COLEC12, COMP, CPVL, CPXM2, CR1, CRB2, CSF2RA, CSF3R, CST7, CTNND2, CTSG, CTSW, CUX2, CX3CL1, CXADR, CXCL2, CXCR3, CXCR6, CYP27A1, CYSLTR1, DERL3, DLK1, DLL4, DLX4, DNAH5, DNASE1L3, DPT, DSC3, DSG3, ECRG4, EDN1, EFR3B, EGR2, ENPP3, ENTPD2, EPB41L3, EPHB1, EREG, ERG, F2RL3, F7, FAT2, FAT4, FBLN5, FCER1A, FCGR2A, FCGR2B, FCGR3A, FCMR, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FGFR4, FGR, FLRT3, FLT3, FLT4, FOLH1, FOLR2, FPR1, FREM1, FST, FUT7, GABRP, GBP5, GIMAP5, GPR174, GPR34, GRIK4, GRM4, GRP, GZMA, GZMB, GZMK, HAVCR2, HBEGF, HCK, HDC, HMCN1, HMOX1, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, HSPA6, IDO1, IGF1, IL18, IL18R1, IL1RL1, IL33, IL3RA, IL6R, IRF4, IRS2, IRX1, ITK, JAM2, KCNF1, KCNH6, KCNK10, KCNK15, KCNMA1, KCNN3, KCNN4, KIAA0408, KIT, KLHL13, KLK10, KLK6, KLRD1, KLRG1, KMO, LAMP3, LCK, LCN2, LCNL1, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LOX, LPAR1, LRG1, LRRC15, LRRK2, LTK, LY75, MAOB, MAP1A, MAPK11, ME1, MET, MFAP5, MLXIPL, MME, MNDA, MNX1, MRC1, MS4A1, MS4A2, MSR1, MTNR1B, MUC16, MUC5B, MYCL, NCF1, NCF2, NIBAN3, NKX3-1, NLRP3, NOD2, NOS1AP, NOS3, NPR1, NR4A3, NR5A2, NRROS, OLR1, OPRD1, OXTR, P2RX1, P2RX5, P2RX7, P2RY13, P2RY2, P2RY6, PAX5, PCDH10, PCDH17, PCDH8, PDE2A, PDGFD, PDGFRA, PDGFRL, PDLIM4, PDPN, PDZK1, PHEX, PHF21B, PI16, PILRA, PLA2G7, PLAUR, PLCG2, PLXNC1, PMAIP1, POTEI, POTEJ, POU2AF1, POU2F2, PPFIA3, PPP1R1C, PRDM6, PRF1, PRICKLE1, PRKCQ, PRLR, PRODH, PROM1, PROX1, PTAFR, PTGES, PTGIS, PTGS1, RECK, RELN, RIMS3, RIPK4, RNF39, ROBO4, ROR2, RTN1, S100A1, SAMD5, SCGB1D2, SCIMP, SCN9A, SDK2, SELP, SEMA7A, SERPINA5, SERPINB5, SFRP1, SFRP4, SH2D1A, SH3RF2, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SIM2, SIRPA, SLAMF6, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC35G2, SLC7A11, SLC7A7, SLCO2A1, SMPD3, SNCA, SNX10, SORCS1, SOSTDC1, SOX10, SOX17, SPA17, SPAG17, SPIB, SPOCK1, SPON1, STAP1, STEAP1, TAL1, TCL1A, TEK, TFAP2B, TFPI2, TGM2, TIE1, TIGIT, TLR1, TLR2, TLR6, TLR7, TLR9, TMEM255B, TNFRSF13B, TNFRSF13C, TNFRSF17, TNFSF8, TNNT2, TNS4, TP63, TPSG1, TREM2, TRH, TRIM29, TRPM2, TSPAN18, TSPAN7, TUBB2B, TUBB3, TWIST2, VSIG4, VSNL1, VTCN1, WNT2, WNT4, ZBP1, ZFHX4, ZNF296, ZNF683
stromal | positive: POSTN, DCN, ADAM33, RUNX1T1, PDGFRA, LUM, FAP, PLEKHH2, THBS2, COL5A1, COL5A2, AEBP1, CCN2, CDH11, SVEP1, TMEM119, FGF7, FBLN2, DCLK1, CCDC80, CXCL12, ABI3BP, ADAMTS2, FBLN5, SRPX, CCN4, SPON1, HMCN1, MFAP5, PTGIS, SERPINE1, ELN, SLIT2, SPOCK1, INHBA, COL10A1, TWIST2, ADAMTS12, FREM1, ADAM12, RBMS3, CD248, LRP1, PDGFRB, PCDH18, PRRX1, ZFHX4, TWIST1, SPON2, MMP14, GLT8D2, BGN, ANGPTL2, HPSE2, ROR2, EMP1, LPAR1, THY1, CTSK, CTHRC1, ABCA10, WNT2, PDPN, LOX, TIMP2, VCAM1, IGF1, CPXM2, LRRC15, PRICKLE1, HAS2, ABCA8, CCN1, HTRA1, DNM1, PDGFRL, PLXDC1, ASPN, BNC2, MRC2, OLFML3, SOD3, PI16, F2R, SULF1, DPT, RELN, PDGFD, COLEC12, STEAP1, COL4A4, SFRP4, GLI2, FAT4, TIMP3, IL33, VGLL3, SLIT3, EDNRA, LOXL1, PTK7, PLAGL1, TNXB, HGF, MMP11, ALPL, SMOC2, RECK, COMP, GXYLT2, SUGCT, TRO, HSPG2, SDC2, LTBP2, EGFR, FRMD6, C7, FMOD, NBL1, FRZB, PTGFRN, CD34, CYP1B1, JAM3, MEIS1, ACVRL1, ANGPTL4, COL18A1, TMEM98, DAB2, FILIP1L, JAM2, CDKN2B, IL1R1, EGR2, DIO2, TRPC1, MEIS2, PLA2R1, TGFB3, CLSTN3
stromal | negative: ABCC12, ABI3, AC007906.2, ADAMDEC1, ADCYAP1, ADGRE2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRB2, ADTRP, AIM2, ALPK3, ANGPT1, ANXA3, ANXA8, APLNR, AURKB, AVPR1A, BAALC, BANK1, BARX2, BCL11B, BCL2A1, BCL6B, BDKRB2, BLK, BMP2, BMP6, BMP8B, BMPR1B, BPIFB1, BRINP1, C4B, CACNG4, CADM3, CALB2, CALML3, CARD10, CARD14, CARD9, CASP10, CAVIN2, CCDC141, CCL14, CCL22, CCL28, CCN3, CCR1, CCR4, CCR5, CCR6, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD226, CD247, CD27, CD274, CD28, CD33, CD38, CD3G, CD40LG, CD5, CD7, CD72, CD79A, CD79B, CD83, CD86, CD8A, CDH13, CDH5, CDH6, CELSR3, CENPA, CFC1, CHAD, CHST8, CLDN5, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CLEC7A, CLGN, CLIC3, CMA1, CNR2, COCH, COL17A1, COL4A3, CR1, CRB2, CSF3R, CST7, CTLA4, CTSG, CTSW, CUX2, CXADR, CXCL2, CXCR3, CXCR6, CYSLTR1, DERL3, DLL4, DLX4, DMD, DNAH5, DNASE1L3, DPY19L2, DSC3, DSG3, EBF2, ECRG4, EDN1, EGF, ENPP3, ENTPD2, EPHB1, EREG, ERG, ESM1, F2RL3, FAM107A, FAT2, FCER1A, FCGR2B, FCMR, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FGFR4, FGR, FLRT3, FLT1, FLT3, FLT4, FOLH1, FOXD2, FPR1, FUT7, GABRD, GABRP, GIMAP5, GIMAP8, GPR174, GPR39, GPR4, GRIK3, GRIK4, GRM4, GRP, GUCY1A2, GZMA, GZMB, GZMK, HAVCR2, HBEGF, HCK, HDAC9, HDC, HLX, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HS3ST3A1, ICAM2, ICOS, IDO1, IL12RB1, IL18, IL18R1, IL1RL1, IL21R, IL2RB, IL3RA, IL6R, IRF4, IRX1, ITGA2B, ITGA7, ITK, KCNA3, KCNF1, KCNH6, KCNJ8, KCNK10, KCNK17, KCNMB1, KCNN3, KCNN4, KDR, KIF18B, KIT, KLHL13, KLK10, KLK6, KLRD1, KLRG1, KLRK1, KMO, LAMB3, LAMC2, LAMP3, LCK, LCN2, LCNL1, LDB3, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LMO2, LRG1, LTK, MAOB, MAP2, MAP3K7CL, MECOM, MET, MLXIPL, MMRN2, MNDA, MNX1, MS4A1, MS4A2, MTNR1B, MUC16, MUC5B, MYCL, MYH11, NAV2, NCCRP1, NCF1, NEK2, NIBAN3, NKX3-1, NLRP2, NLRP3, NOD2, NOS1AP, NOS3, NOSTRIN, NPNT, NPR1, NPY1R, NR4A3, NR5A2, NRROS, NTRK2, OLR1, OPRD1, OXTR, P2RX1, P2RX5, P2RX7, P2RY13, P2RY14, P2RY2, P2RY6, PALMD, PAX5, PBK, PCDH10, PCDH17, PCDH8, PDE2A, PDE3A, PDZK1, PGR, PHEX, PHF21B, PILRA, PITX1, PLA2G7, PLCE1, PLCG2, PMAIP1, POTEI, POTEJ, POU2AF1, POU2F2, PPFIA3, PPP1R1C, PRF1, PRKAA2, PRKCQ, PROCR, PRODH, PROM1, PROX1, PTAFR, PTGDR, PTGS1, PTPRB, RASGRP1, RERG, RGS5, RIMS3, RIPK4, RNF39, ROBO4, RTN1, S100A1, S1PR1, SCGB1D2, SCIMP, SCN9A, SDK2, SELP, SERPINA5, SERPINB5, SH2D1A, SH2D2A, SH3RF2, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SIM2, SLAMF1, SLAMF6, SLC11A1, SLC18A2, SLC1A3, SLC2A9, SLC6A1, SLC7A11, SLC7A5, SLCO2A1, SMPD3, SNCA, SOSTDC1, SOX10, SOX17, SOX18, SPA17, SPAG17, SPIB, STAP1, TAL1, TBXA2R, TCL1A, TDO2, TEK, TFAP2B, TH, THBD, TICRR, TIE1, TIGIT, TLR6, TLR7, TLR9, TMEM255B, TMEM45B, TNFRSF13B, TNFRSF13C, TNFRSF17, TNFSF8, TNS4, TP63, TPPP3, TPSG1, TREM2, TREML2, TRH, TRIM29, TRPC6, TSPAN7, TUBA4A, TUBB2B, UBASH3A, UNC5A, VSNL1, VSTM2L, VTCN1, WNT5B, ZBP1, ZFAT, ZNF296, ZNF683
tumor | positive: FIBCD1, CLGN, TMEM97, MLXIPL, CDH2, ECM1, ITPRID2, SELENBP1, ITGA2B, NRAS, CYP24A1, CCDC170, DLK1, CACNA1B, MTNR1B, MUC1, PPM1D, FASN, ACE, GRIK4, IKZF2, RNF43, BRIP1, FGFR3, SPAG17, PBK, ABCC8, POTEJ, ABCC12, PBX1, PSMC5, UGDH, SLC12A2, TH, WDR90, INPP4B, SREBF1, ERBB3, PLXNB1, GREB1L, AR, POTEF, TLK2, PHF21B, ADGRB2, UNC5A, PPP1R1C, PCDH8, TNNT2, KCNH6, MLF1, SORCS1, KIF18B, TUBD1, CCND1, RGL3, HSF4, FADS2, SLC2A4RG, F7, MAP7, SIX1, EXO1, TONSL, SLC30A8, GSR, ITGB6, RRM2, GSE1, OVOL2, MAPK15, FGFR4, GRM4, MYCN, LMX1B, TCIM, CENPA, HES1, ANO1, GPR39, TICRR, EHF, TBC1D30, MST1R, TBX3, TCEA3, PRDM6, CHST8, HS6ST2, TC2N, PPM1H, RECQL4, TYMS, FBP1, CARD14, SLC25A39, PHGDH, FRAS1, KMO, NEK2, BMI1, AURKB, HSPD1, DNAH5, RET, MNX1
tumor | negative: ABCA10, ABCA8, ABCC9, ABI3, ABI3BP, AC007906.2, ACSL4, ACVRL1, ADA, ADAM12, ADAM33, ADAMDEC1, ADAMTS1, ADAMTS12, ADAMTS14, ADAMTS2, ADAMTS4, ADAMTS5, ADCYAP1, ADGRE2, ADGRL4, ADORA2B, ADORA3, ADRA1B, ADRA2A, ADTRP, AFAP1L2, AIF1, AIM2, ALPL, ANGPT1, ANGPTL2, ANGPTL4, ANXA3, ANXA8, AOAH, APLNR, APOBEC3G, ASPN, ATL1, AVPR1A, BAALC, BANK1, BCL11B, BCL2A1, BCL6B, BDKRB2, BLK, BMP2, BMP6, BMPR1B, BNC2, BPIFB1, BRINP1, C1QTNF1, C3AR1, C4B, C7, CACNG4, CADM3, CALB2, CALCRL, CALML3, CASP1, CASP10, CAVIN2, CAVIN3, CCDC102B, CCDC141, CCL14, CCL19, CCL22, CCN3, CCN4, CCND2, CCR1, CCR4, CCR5, CCR6, CD19, CD1C, CD1D, CD1E, CD209, CD22, CD226, CD247, CD248, CD27, CD274, CD28, CD302, CD33, CD34, CD38, CD3D, CD3E, CD3G, CD40, CD40LG, CD5, CD7, CD72, CD79A, CD79B, CD86, CD8A, CDH11, CDH13, CDH23, CDH5, CDH6, CEP112, CES1, CLDN5, CLEC10A, CLEC12A, CLEC14A, CLEC4C, CLEC5A, CLEC7A, CLIC3, CMA1, CMKLR1, CNN1, CNR2, CNRIP1, CNTNAP1, COCH, COL10A1, COL17A1, COL4A3, COL4A4, COLEC12, COMP, CPVL, CPXM2, CR1, CRB2, CSF1, CSF2RA, CSF3R, CSPG4, CST7, CTLA4, CTSG, CTSW, CUX2, CX3CL1, CXCL2, CXCR3, CXCR6, CYP27A1, CYSLTR1, DCLK1, DERL3, DKK3, DLL4, DLX4, DMD, DNASE1L3, DPT, DPY19L2, DSC3, DSG3, EBF2, ECRG4, EDN1, EDNRA, ELN, ENPP2, ENPP3, EPHB1, EREG, ESM1, ETV1, F13A1, F2RL3, F3, FAM107A, FAP, FAT2, FAT4, FBLN2, FBLN5, FCER1A, FCGR2A, FCGR2B, FCGR3A, FCMR, FCRL1, FCRL2, FCRL3, FCRL5, FCRLA, FES, FGF1, FGF2, FGF7, FGR, FILIP1L, FLT1, FLT3LG, FLT4, FMOD, FOLH1, FOLR2, FOXC2, FPR1, FREM1, FRZB, FSCN1, FST, FUT7, FYN, GABRD, GABRP, GAS6, GBP5, GIMAP5, GIMAP8, GJC1, GLI2, GLT8D2, GPR174, GPR34, GPR4, GRIK3, GRK5, GRP, GUCY1A2, GZMA, GZMB, GZMK, HAS2, HAVCR2, HCK, HDC, HEYL, HGF, HLX, HMCN1, HOXA5, HOXD10, HOXD9, HPGD, HPGDS, HPSE, HPSE2, HS3ST3A1, HSPA6, ICAM2, IDO1, IFI44L, IGF1, IL12RB1, IL18, IL18R1, IL1RL1, IL21R, IL2RB, IL33, IL34, IL3RA, IL6R, INHBA, IRF4, IRS2, IRX1, ITGA7, ITGAL, ITIH5, ITK, JAG1, JAK3, JAM2, JAM3, KCNA3, KCNF1, KCNJ8, KCNK10, KCNK17, KCNMB1, KCNN3, KCNN4, KDR, KIAA0408, KIT, KLHL13, KLK10, KLK6, KLRD1, KLRG1, KLRK1, LAIR1, LAMP3, LBH, LCK, LCN2, LCNL1, LDB3, LIF, LILRA4, LILRB2, LILRB4, LILRB5, LOX, LPAR1, LPL, LRRC15, LRRK2, LTK, LURAP1, LY75, LY96, MAOB, MAP1A, MAP3K7CL, MERTK, MFAP5, MME, MMP9, MMRN2, MNDA, MPP1, MRC1, MS4A1, MS4A2, MSR1, MUC16, MYH11, NAV2, NCCRP1, NCF1, NCF2, NGFR, NIBAN3, NLRP1, NLRP3, NOD2, NOS3, NOSTRIN, NPNT, NPR1, NR4A3, NR5A2, NRN1, NRP2, NRROS, NTRK2, OLFML3, OLR1, OPRD1, P2RX1, P2RX5, P2RX7, P2RY13, P2RY14, P2RY6, PALMD, PAMR1, PARP15, PAX5, PCDH10, PCDH17, PCDH18, PDE2A, PDE3A, PDGFD, PDGFRA, PDGFRB, PDPN, PDZK1, PGF, PGR, PHEX, PI16, PIEZO2, PLA2G7, PLAGL1, PLCE1, PLCG2, PLEKHH2, PLXDC1, POU2AF1, POU2F2, PPFIA3, PRF1, PRICKLE1, PRKCQ, PROCR, PROM1, PROX1, PRRX1, PTAFR, PTGDR, PTGIR, PTGIS, PTGS1, PTPRB, PTPRM, RASGRP1, RASSF2, RASSF4, RBMS3, RCAN2, RECK, RELN, RGS5, RIMS3, RNF180, RNF39, ROBO4, ROR2, RTN1, RUNX1T1, RUNX2, S1PR1, SAMD5, SASH1, SATB1, SCGB1D2, SCIMP, SCN9A, SDC2, SDK2, SELP, SELPLG, SEMA7A, SEPTIN4, SERPINB5, SFRP1, SFRP4, SH2D1A, SHANK3, SHD, SIGLEC1, SIGLEC6, SIGLEC9, SLAMF1, SLAMF6, SLAMF8, SLC11A1, SLC18A2, SLC1A3, SLC35G2, SLC6A1, SLC7A11, SLC7A7, SLCO2A1, SLCO2B1, SLIT2, SLIT3, SMPD3, SNCA, SOD3, SOSTDC1, SOX10, SOX17, SOX5, SP140, SPIB, SPOCK1, SPON1, SPRY2, SRPX, ST6GAL1, STAP1, STAT4, SUGCT, SULF1, SVEP1, SYNPO2, TAL1, TBX2, TBXA2R, TCF7, TCL1A, TDO2, TEK, TFAP2B, TGFB2, TGFBR3, TGM2, THBD, THEMIS, THY1, TIE1, TIGIT, TLR1, TLR2, TLR4, TLR6, TLR7, TLR9, TMEM119, TMEM255B, TNFRSF13B, TNFRSF17, TNFSF8, TNS4, TP63, TPPP3, TPSG1, TRBC1, TREM2, TREML2, TRH, TRIM29, TRO, TRPC1, TRPC6, TSPAN18, TSPAN7, TUBB2B, TUBB6, TWIST1, TWIST2, UBASH3A, VCAM1, VGLL3, VSIG4, VSIR, VSNL1, VTCN1, WFDC1, WNT2, WNT4, WNT5B, ZAP70, ZBP1, ZEB1, ZFHX4, ZNF683
Below, we show the number of negative markers that overlap with the positive markers of each cell type. To reliably estimate contamination, each cell type should share at least ~5 negative markers with the positive marker set of every other cell type. If these overlaps are too small, contamination estimates become unstable.
In such cases, the marker definition can be relaxed by adjusting the thresholds used in markers_from_reference above.
[47]:
ctypes = list(markers["xenium"].keys())
overlap_df = pd.DataFrame(0, index=ctypes, columns=ctypes, dtype=int)
for c in ctypes:
neg_c = set(markers["xenium"][c].get("negative", []))
for d in ctypes:
pos_d = set(markers["xenium"][d].get("positive", []))
overlap_df.loc[d, c] = len(neg_c & pos_d)
overlap_df
[47]:
| B | DCIS1 | DCIS2 | T | dendritic | endo | macro | mast | myoepi | perivas | stromal | tumor | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| B | 0 | 50 | 48 | 41 | 26 | 44 | 29 | 47 | 41 | 49 | 48 | 57 |
| DCIS1 | 49 | 0 | 20 | 75 | 50 | 37 | 48 | 50 | 12 | 41 | 37 | 23 |
| DCIS2 | 39 | 16 | 0 | 78 | 41 | 27 | 40 | 56 | 5 | 29 | 25 | 12 |
| T | 26 | 48 | 52 | 0 | 27 | 37 | 24 | 43 | 33 | 28 | 47 | 66 |
| dendritic | 50 | 66 | 63 | 73 | 0 | 49 | 42 | 70 | 48 | 53 | 56 | 70 |
| endo | 108 | 90 | 92 | 110 | 101 | 0 | 94 | 86 | 63 | 45 | 66 | 95 |
| macro | 71 | 76 | 74 | 102 | 49 | 60 | 0 | 74 | 48 | 79 | 54 | 88 |
| mast | 28 | 27 | 29 | 37 | 24 | 24 | 20 | 0 | 20 | 28 | 27 | 30 |
| myoepi | 97 | 33 | 58 | 98 | 85 | 55 | 85 | 70 | 0 | 59 | 61 | 75 |
| perivas | 87 | 49 | 60 | 109 | 70 | 6 | 80 | 92 | 34 | 0 | 24 | 71 |
| stromal | 98 | 70 | 84 | 118 | 88 | 31 | 94 | 109 | 42 | 40 | 0 | 90 |
| tumor | 22 | 16 | 6 | 66 | 29 | 22 | 27 | 38 | 14 | 26 | 28 | 0 |
Marker purity
To quantify how well each segmented cell in the spatial transcriptomics data matches its annotated cell type, we defined a marker-based purity (marker_balanced_accuracy) score that jointly evaluates the expression of positive (positive_marker_recall) and the absence of neighborhood-associated negative markers (negative_marker_avoidance). The method accounts for the spatial context of each cell and is motivated by the assumption that differences between scRNA-seq and spatial
transcriptomics-derived cell type profiles arise mainly from local contamination by neighboring cells.
[48]:
for method, st in st_dict.items():
purity = st.sp.marker_purity(
cell_type_key="transferred_cell_type",
markers=markers[method],
inplace=True,
)
INFO Creating graph using `None` transform and `1` libraries.
INFO Creating graph using `None` transform and `1` libraries.
INFO Creating graph using `None` transform and `1` libraries.
Proseg exhibits the highest marker_balanced_accuracy across most cell types, with the exception of DCIS1. To better interpret the overall purity scores, it is informative to examine the positive_marker_recall and negative_marker_avoidance scores separately, as they capture distinct aspects of marker specificity.
[49]:
boxplot_per_celltype(st_dict, "marker_balanced_accuracy")
The negative_marker_avoidance score quantifies the extent to which a focal cell expresses positive markers of neighboring cell types, and thus reflects contamination. Overall, contamination levels are low; however, increased contamination is observed for DCIS1.
[50]:
def box_strip_plot_per_celltype(
st_dict,
feature,
celltype_col="transferred_cell_type",
q=1.0,
figsize=(15, 5),
palette="Set2",
):
dfs = []
for method, st in st_dict.items():
obs = st.sdata.tables["table"].obs
tmp = obs[[celltype_col, feature]].copy()
tmp = tmp[tmp[celltype_col].notna()]
tmp["method"] = method
dfs.append(tmp)
df = pd.concat(dfs, ignore_index=True).dropna(subset=[celltype_col, feature])
if q < 1:
df = df[df[feature] <= df[feature].quantile(q)]
fig, ax = plt.subplots(figsize=figsize)
sns.boxplot(
data=df,
x=celltype_col,
y=feature,
hue="method",
showcaps=True,
showfliers=False,
palette=palette,
ax=ax,
)
sns.stripplot(
data=df,
x=celltype_col,
y=feature,
hue="method",
dodge=True,
jitter=0.2,
alpha=0.6,
linewidth=0,
palette=palette,
ax=ax,
)
# avoid duplicate legends
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, title="method")
ax.tick_params(axis="x", rotation=45)
ax.set_xlabel("Cell type")
ax.set_ylabel(feature)
ax.legend(title="method", bbox_to_anchor=(1.02, 1), loc="upper left")
fig.tight_layout()
plt.show()
[51]:
box_strip_plot_per_celltype(st_dict, "negative_marker_avoidance")
In contrast, the positive_marker_recall score measures how consistently a cell expresses markers expected for its assigned cell type.
[52]:
boxplot_per_celltype(st_dict, "positive_marker_recall")
These metrics are most informative when considered jointly. For example, we plot the mean marker_balanced_accuracy (a supervised measure of both how well positive markers are captured and negative markers are avoided) against the mean border_admixture_score, an unsupervised contamination metric. In this combined view, we can see that Proseg shows slighltly better performance than the other two methods.
[53]:
rows = []
for _method, st in st_dict.items():
corr_ncv_vs_center = st.sdata.tables["table"].obs["border_admixture_score"].median()
negative_F1 = st.sdata.tables["table"].obs["marker_balanced_accuracy"].median()
rows.append(
{
"Method": _method,
"Border_admixture_score": corr_ncv_vs_center,
"marker_balanced_accuracy": negative_F1,
}
)
results_df = pd.DataFrame(rows)
fig, ax = plt.subplots(figsize=(4, 3))
sns.scatterplot(
data=results_df,
x="Border_admixture_score",
y="marker_balanced_accuracy",
hue="Method",
style="Method",
s=100,
palette="Set2",
ax=ax,
)
ax.set_xlabel("Border_admixture_score (↓)")
ax.set_ylabel("marker_balanced_accuracy (↑)")
ax.set_title("Border_admixture_score vs. marker_balanced_accuracy")
legend = ax.legend(title="Method", frameon=False, bbox_to_anchor=(1.05, 1), loc="upper left")
fig.tight_layout()
plt.show()
Neighborhood contamination
Marker purity summarizes how well a cell matches its own markers and avoids neighborhood-relevant negatives. In many cases, we also want to quantify
how many contaminating transcripts are present per cell and
which neighboring cell types contribute to this signal. We therefore compute neighborhood contamination.
[54]:
for method, st in st_dict.items():
per_cell_df, _, cont_bin, _ = st.sp.neighbor_contamination(
cell_type_key="transferred_cell_type", markers=markers[method]
)
Among the evaluated methods, Proseg yields the smallest number of cells showing detectable contamination.
[55]:
methods_df = []
for method, st in st_dict.items():
obs = st.sdata.tables["table"].obs
counts = obs.loc[obs["contamination_counts"] > 0, "transferred_cell_type"].value_counts().rename(method)
methods_df.append(counts)
merged = pd.concat(methods_df, axis=1)
merged
[55]:
| xenium | bidcell | proseg | |
|---|---|---|---|
| transferred_cell_type | |||
| T | 28 | 37 | 10 |
| DCIS1 | 11 | 6 | 7 |
| DCIS2 | 9 | 4 | 6 |
| B | 8 | 4 | 2 |
| macro | 8 | 13 | 10 |
| dendritic | 6 | 5 | 4 |
| stromal | 6 | 6 | 7 |
| endo | 2 | 6 | 2 |
| mast | 1 | 2 | 7 |
| myoepi | 1 | 2 | 3 |
| perivas | 1 | 2 | 0 |
| tumor | 0 | 0 | 0 |
The spatial plot below shows the number of contaminating transcripts in each cell.
[56]:
def plot_feature_labels(sdata, method, feature, axes, i):
labels = sdata.tables["table"].obs["transferred_celltype_plot"].unique().astype(str).tolist()
cols = [col_celltype[lab] for lab in labels]
sdata.tables["table"].obs["region"] = "cell_boundaries"
sdata.set_table_annotates_spatialelement("table", region="cell_boundaries")
sdata.pl.render_shapes(
"cell_boundaries",
color="transferred_celltype_plot",
palette=cols,
groups=labels,
outline_color="white",
outline_width=0.5,
).pl.show(ax=axes[0, i], title=f"{method}:Cell boundaries colored by cell type", coordinate_systems="global")
sdata.pl.render_shapes(
"cell_boundaries",
color=feature,
outline_color="white",
outline_width=0.5,
).pl.show(ax=axes[1, i], title=f"{method}: {feature}", coordinate_systems="global")
[57]:
fig, axes = plt.subplots(2, 3, figsize=(15, 10), constrained_layout=True)
axes = np.atleast_2d(axes)
for i, (method, st) in enumerate(st_dict.items()):
plot_feature_labels(st.sdata, method, "contamination_counts", axes, i)
WARNING render_shapes: Found 155 NaN values in color data. These observations will be colored with the 'na_color'.
WARNING render_shapes: Found 181 NaN values in color data. These observations will be colored with the 'na_color'.
WARNING render_shapes: Found 236 NaN values in color data. These observations will be colored with the 'na_color'.
The heatmap below summarizes contamination strength for each source–target cell-type pair. Each entry represents the mean contamination strength across all evaluable target cells of the given target cell type, where the source-specific contamination strength is computed as the fraction of transcripts in the target cell that correspond to contamination-relevant markers of the source cell type.
The bubble plot illustrates the contamination strength (bubble color) and the number of evaluable target cells (bubble size).
[58]:
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from matplotlib.colors import Normalize
vmax = max(st.sdata.tables["table"].uns["contamination_strength_matrix"].max().max() for st in st_dict.values())
norm = Normalize(vmin=0, vmax=vmax)
cell_type_order = list(next(iter(st_dict.values())).sdata.tables["table"].uns["contamination_strength_matrix"].columns)
fig, axes = plt.subplots(1, 3, figsize=(15, 5), constrained_layout=True)
for i, (method, st) in enumerate(st_dict.items()):
cont_strength = st.sdata.tables["table"].uns["contamination_strength_matrix"]
cont_n = st.sdata.tables["table"].uns["contamination_evaluable_cells_matrix"]
# Force same source/target order and include missing rows/columns.
cont_strength = cont_strength.reindex(
index=cell_type_order,
columns=cell_type_order,
)
cont_n = cont_n.reindex(
index=cell_type_order,
columns=cell_type_order,
)
plot_df = (
cont_strength.stack(dropna=False)
.rename("contamination_strength")
.reset_index()
.rename(columns={"level_0": "source", "level_1": "target"})
)
plot_df["n_evaluable"] = cont_n.stack(dropna=False).values
plot_df = plot_df.dropna(subset=["n_evaluable"])
plot_df["target"] = pd.Categorical(
plot_df["target"],
categories=cell_type_order,
ordered=True,
)
plot_df["source"] = pd.Categorical(
plot_df["source"],
categories=cell_type_order,
ordered=True,
)
sns.scatterplot(
data=plot_df,
x="target",
y="source",
size="n_evaluable",
hue="contamination_strength",
sizes=(20, 600),
palette="Reds",
hue_norm=norm,
edgecolor="black",
legend=False,
ax=axes[i],
)
axes[i].set_title(method, fontsize=12)
axes[i].set_xlabel("Target Cell Type")
axes[i].set_xlim(-0.5, len(cell_type_order) - 0.5)
axes[i].set_ylim(len(cell_type_order) - 0.5, -0.5)
axes[i].set_xticks(range(len(cell_type_order)))
axes[i].set_xticklabels(cell_type_order, rotation=45, ha="right")
axes[i].set_yticks(range(len(cell_type_order)))
if i == 0:
axes[i].set_ylabel("Source Cell Type")
axes[i].set_yticklabels(cell_type_order)
else:
axes[i].set_ylabel("")
axes[i].set_yticklabels([])
plt.show()
Mutually exclusive co-expression rate (MECR)
The mutually exclusive co-expression rate (MECR) is a measure for whether combinations of positive and negative markers (computed with a more stringent setting to increase mutual exclusivity, vote_frac_pos=0.3) co-occur less often than expected under independence (using Fisher’s exact test). By conditioning on the marginal detection frequencies of each gene, Fisher’s exact test does not favor methods with low overall transcript counts.
[59]:
markers = {}
for method, st in st_dict.items():
markers[method] = st.markers_from_reference(
adata_ref,
ref_cell_type="celltype_major",
min_pos_frac=0.3,
ref_raw_counts_layer="raw",
n_jobs=16,
)
mecr = st.sp.mutually_exclusive_coexpression_rate(
markers=markers[method],
)
WARNING: adata.X seems to be already log-transformed.
WARNING: adata.X seems to be already log-transformed.
WARNING: adata.X seems to be already log-transformed.
Across all methods, marker pairs with significant mutual exclusivity show substantially lower co-expression than expected under independence. Although the differences are minor, ProSeg shows the strongest depletion of co-expression among these marker pairs.
[60]:
rows = []
for method, st in st_dict.items():
tbl = st.sdata.tables["table"]
mecr_df = tbl.uns["mutually_exclusive_coexpression_rate"]
# Keep marker pairs with significant mutual exclusivity
df_sig = mecr_df.loc[
mecr_df["odds_ratio"].notna()
& np.isfinite(mecr_df["odds_ratio"])
& mecr_df["pvalue"].notna()
& np.isfinite(mecr_df["pvalue"])
& (mecr_df["odds_ratio"] < 1)
& (mecr_df["pvalue"] < 0.05)
].copy()
rows.extend(
{
"method": str(method),
"Fisher_OR": row["odds_ratio"],
}
for _, row in df_sig.iterrows()
)
df = pd.DataFrame(rows)
# Order methods by mean OR
mean_order = df.groupby("method")["Fisher_OR"].mean().sort_values().index.tolist()
means = df.groupby("method")["Fisher_OR"].mean().reindex(mean_order)
xtick_labels = [f"{m}\nmean: {means[m]:.2f}" for m in mean_order]
plt.figure(figsize=(6, 4))
ax = sns.violinplot(
data=df,
x="method",
y="Fisher_OR",
order=mean_order,
palette="Set2",
linewidth=2,
)
sns.stripplot(
data=df,
x="method",
y="Fisher_OR",
order=mean_order,
color="black",
size=2.5,
alpha=0.35,
jitter=0.25,
ax=ax,
)
# OR = 1 corresponds to independence
ax.axhline(
1,
ls="--",
lw=1,
color="gray",
alpha=0.6,
)
ax.set_xticklabels(xtick_labels)
ax.set_ylabel("Mutually exclusive marker co-expression (odds ratio)")
ax.set_xlabel("")
ax.set_title("Significantly mutually exclusive marker pairs")
plt.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
3D Volume Module#
The volume (vl) accessor provides metrics to assess how well a segmentation method resolves cell overlaps in 3D. Spatial transcriptomics tissue sections have a finite thickness (~4–10 µm), so cells can overlap along the z-dimension and 2D segmentation methods may introduce mixing by assigning transcripts from overlapping cells to the same mask. In this module, we introduce metrics to quantify sensitivity to 3D overlap and evaluate how well quasi-3D methods (e.g. Proseg) disentangle
transcripts from overlapping cells.
For a detailed description of this module, please refer to this tutorial.
Top-bottom z consistency
To detect potential z-overlap mixing within segmented cells, we compare gene expression profiles between the bottom and top z-regions of each cell. similarity_top_bottom reports the observed cosine similarity relative to its permutation-based null expectation, with negative residuals indicating lower consistency across z than expected. In the histogram below, we show these residuals only for cells with unexpectedly low top–bottom similarity (similarity_top_bottom_p_value < 0.05).
[61]:
for _method, st in st_dict.items():
_cos_sim = st.vl.similarity_top_bottom()
Proseg (a quasi-3D method) shows the fewest cells with unexpectedly different expression profiles between the top and bottom of the cell (similarity_top_bottom_p_value < 0.05). Moreover, even among these cells, the differences between top and bottom are generally smaller than for Xenium and BIDCell, suggesting more consistent transcript composition across z.
[62]:
histogram_feature(st_dict, "similarity_top_bottom")
In volume.ipynb, we explore the distribution of transcripts along the z-dimension in more depth and put it into context with ovrlpy, a package for detecting 3D overlap in spatial transcriptomics.
Point statistic metrics#
The point statistics (ps) module is designed to compare the distribution of a set of transcripts in the cell relative to its cell centroid or cell border. The idea is to compute the distances of the transcripts to a reference point in the cell, either the cell centroid or the cell boundaries and aggregate this measure per transcript id.
distance to membrane metric
In this metric we compute the distance to the segmented cell membrane of each transcript coordinate and aggregate this metric per transcript id as mean.
[63]:
plt.style.use("dark_background")
def plot_metric(st, method, metric, genes, ax):
sdata_plot = sd.deepcopy(st.sdata)
sdata_plot[st.tables_key] = sdata_plot[st.tables_key][(sdata_plot[st.tables_key].obs[metric].notna())]
sdata_plot.tables[st.tables_key].obs["region"] = st.shapes_key
sdata_plot.set_table_annotates_spatialelement(st.tables_key, region=st.shapes_key)
sdata_plot.pl.render_shapes(
element=st.nucleus_shapes_key,
fill_alpha=0.2,
outline_alpha=1.0,
outline_width=0.5,
outline_color="black",
).pl.render_shapes(
element=st.shapes_key,
color=metric,
cmap="viridis",
fill_alpha=0.5,
outline_alpha=1.0,
outline_width=0.5,
outline_color="black",
).pl.render_points(
st.points_key,
color=st.points_gene_key,
groups=genes,
palette=["red"] * len(genes),
).pl.show(ax=ax, title="Cell/nuclei seg. for " + method + " " + metric, colorbar=True)
return fig, axes
First, we compute both the average distance to the cell membrane across all previously defined negative and positive markers for the cell type “DCIS1”.
[64]:
border_distance_negative = {}
border_distance_positive = {}
for method, st in st_dict.items():
border_distance_negative[method] = st.ps.distance_to_membrane(
markers[method]["DCIS1"]["negative"],
cell_type_key="transferred_celltype_plot",
cell_type_query=["DCIS1"],
restrict_to_within_boundary=True,
inplace=False,
)
border_distance_positive[method] = st.ps.distance_to_membrane(
markers[method]["DCIS1"]["positive"],
cell_type_key="transferred_celltype_plot",
restrict_to_within_boundary=True,
cell_type_query=["DCIS1"],
inplace=False,
)
[65]:
keys = []
values = []
for key, df in border_distance_negative.items():
for val in df["distance_to_cell_membrane_norm_166_genes"]:
keys.append(key)
values.append(val)
result_negative = pd.DataFrame({"method": keys, "distance": values, "marker": "negative"})
keys = []
values = []
for key, df in border_distance_positive.items():
for val in df["distance_to_cell_membrane_norm_78_genes"]:
keys.append(key)
values.append(val)
result_positive = pd.DataFrame({"method": keys, "distance": values, "marker": "positive"})
[66]:
result = pd.concat([result_negative, result_positive])
result = result.dropna()
[67]:
plt.figure(figsize=(6, 4))
ax = sns.violinplot(
data=result,
x="method",
y="distance",
hue="marker",
palette="Set2",
linewidth=2,
)
ax.set_ylabel("distance to membrane")
ax.set_xlabel("")
ax.set_title("Distance to membrane across cells for DCIS1")
plt.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
We can plot now the distance to membrane for the positive and negative markers across the different segmentation algorithms and for the cell type “DCIS1”.
We can also run the method inplace and plot the results in space
[68]:
for _method, st in st_dict.items():
st.ps.distance_to_membrane(
markers[method]["DCIS1"]["negative"],
cell_type_key="transferred_celltype_plot",
cell_type_query=["DCIS1"],
restrict_to_within_boundary=True,
inplace=True,
)
[69]:
fig, axes = plt.subplots(1, 3, figsize=(15, 10), constrained_layout=True)
for i, (method, st) in enumerate(st_dict.items()):
plot_metric(
st,
method,
"distance_to_cell_membrane_norm_166_genes",
markers[method]["DCIS1"]["negative"][:20],
axes[i],
)
We can identify cells in which the distribution of negative markers is clearly more skewed than for thers and that these values differ again greatly between the segmentation algorithms.
Metrics orthogonolisation#
In order to look at the co-linearity of the computed metrics, we will compute the correlation matrix (scaled covariance matrix) of all the metrics. Since the directionality (positively or negatively correlated) does not matter, we take the absolute value of the correlation coefficient as our orthogonality metric. Like this, we can visualise which metrics correlate
[70]:
# code adapted from claude.ai
for method, st in st_dict.items():
df = st.sdata.tables["table"].obs
df_numeric = df.select_dtypes(include="number")
df = pd.DataFrame(df_numeric, columns=df_numeric.columns, index=df_numeric.index)
# correlation does not work with NA values. Fill NA with columnwise mean
df = df.apply(lambda x: x.fillna(x.mean()), axis=0)
correlation_matrix = df.corr()
# take absolute value instead - sign is not important, only co-linearity
correlation_matrix = correlation_matrix.abs()
plt.figure(figsize=(10, 8))
sns.heatmap(
correlation_matrix,
annot=False,
cmap="coolwarm",
center=0,
square=True,
linewidths=0.5,
cbar_kws={"shrink": 0.8},
vmin=0,
vmax=1,
xticklabels=correlation_matrix.columns,
yticklabels=correlation_matrix.columns,
)
plt.xticks(fontsize=7)
plt.yticks(fontsize=7)
plt.title(f"Absolute Correlation Matrix Heatmap {method}")
plt.tight_layout()
plt.show()
There are quite some metrics which correlate with the segmented cell area, such as total counts of all transcripts and metrics related to this. We note as well that the correlation of metrics differs between algorithms.
Session Info#
[71]:
print(sd.__version__) # spatialdata
print(spatialdata_plot.__version__)
0.8.0
0.4.0