Module: Volume (3D)#

Spatial transcriptomics tissue sections are not truly 2D: they typically have a physical thickness of ~4–10 µm. Even for a relatively thin 5 µm section, cells can partially overlap along the z-dimension, depending on tissue orientation and how the sample is cut. As a result, methods that treat the data as purely 2D will often introduce neighborhood contamination, because transcripts from overlapping cells may be assigned to the same segmentation mask.

f a6c27abcc7cd4f8cb06bb72e93c74f39

To assess how well a segmentation method resolves cell overlaps in 3D, we provide a set of metrics in the volume (vl) accessor.

In this module, we introduce metrics to quantify how strongly a method is affected by 3D overlap, and how well it can separate overlapping cells across z (e.g. quasi-3D approaches such as Proseg), or in other words, how well it can disentangle transcripts from overlapping cells.

To follow along with this tutorial, you can download the data from here.

[1]:
%load_ext autoreload
%autoreload 2

Load data into SegTraQ and run label transfer#

[2]:
import warnings

import anndata as ad
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
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

We load previously built SpatialData objects from 10x Genomics Xenium data segmented with Xenium’s multimodal cell segmentation (sdata_xenium), with Proseg v2.0.5 (sdata_proseg2) and Proseg v3.1.0 (sdata_proseg3).

[3]:
sdata_xenium = sd.read_zarr("../../data/xenium_v1_data/sdata_xenium_crop.zarr")
sdata_proseg2 = sd.read_zarr("../../data/xenium_v1_data/sdata_proseg_v2_crop.zarr/")
sdata_proseg3 = sd.read_zarr("../../data/xenium_v1_data/sdata_proseg_v3_crop.zarr/")

Next, we initialize SegTraQ objects.

[4]:
st_xenium = segtraq.SegTraQ(
    sdata_xenium,
    tables_centroid_x_key=None,
    tables_centroid_y_key=None,
    points_background_id=-1,  # "UNASSIGNED" for Xenium prime
)

st_proseg2 = segtraq.SegTraQ(
    sdata_proseg2,
    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",
)

st_proseg3 = segtraq.SegTraQ(
    sdata_proseg3,
    points_cell_id_key="assignment",
    points_background_id=None,
    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",
    filter_kwargs={"min_qv": None},
)

For easier access, we store the SpatialData object into a dictionary.

[5]:
st_dict = {"xenium": st_xenium, "proseg2": st_proseg2, "proseg3": st_proseg3}

We can then transfer labels from a reference scRNA-seq dataset.

[6]:
adata_ref = ad.read_h5ad("../../data/xenium_5K_data/BC_scRNAseq_Janesick.h5ad")

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.

Inspect z distribution of transcripts#

We will first examine the z-distribution of transcripts in Xenium data. Z-drift can occur when the slide is not perfectly flat—for example due to tissue cutting and mounting—or due to imaging-related effects (e.g. microscope settings or slight unevenness of the slide and imaging surface).

[7]:
def plot_transcripts_across_z_bins(sdata, method, n_z_bins, xy_bin_size=5.0):
    df = sdata["transcripts"].compute()

    df["z_bin"] = pd.cut(df["z"], n_z_bins, labels=False)

    df["x_bin"] = (df["x"] / xy_bin_size).astype(int)
    df["y_bin"] = (df["y"] / xy_bin_size).astype(int)

    x_minb, x_maxb = df["x_bin"].min(), df["x_bin"].max()
    y_minb, y_maxb = df["y_bin"].min(), df["y_bin"].max()
    nx = x_maxb - x_minb + 1
    ny = y_maxb - y_minb + 1

    fig, axes = plt.subplots(
        1,
        n_z_bins,
        figsize=(2.2 * n_z_bins, 4),
        sharex=True,
        sharey=True,
        constrained_layout=True,
    )
    axes = np.atleast_1d(axes)

    for i in range(n_z_bins):
        ax = axes[i]

        plane = df[df["z_bin"] == i]
        counts = (
            plane.groupby(["y_bin", "x_bin"])
            .size()
            .reindex(
                pd.MultiIndex.from_product(
                    [range(y_minb, y_maxb + 1), range(x_minb, x_maxb + 1)], names=["y_bin", "x_bin"]
                ),
                fill_value=0,
            )
            .values.reshape(ny, nx)
        )

        im = ax.imshow(
            counts,
            origin="lower",
            aspect="equal",
            extent=[
                x_minb * xy_bin_size,
                (x_maxb + 1) * xy_bin_size,
                y_minb * xy_bin_size,
                (y_maxb + 1) * xy_bin_size,
            ],
        )
        ax.set_title(f"Z plane {i + 1}")
        ax.set_xlabel("x")
        if i == 0:
            ax.set_ylabel("y")

    cbar = fig.colorbar(im, ax=axes.ravel().tolist(), location="right", shrink=0.9)
    cbar.set_label(f"#transcripts per {xy_bin_size}x{xy_bin_size} bin")

    fig.suptitle(f"{method}: #transcripts per {xy_bin_size}x{xy_bin_size} bin", fontsize=14)
    plt.show()

In the raw Xenium data, we can still observe a pronounced z-drift across the tissue. In lower z-planes (e.g. Z plane 3), transcripts are more densely detected on the left side of the field of view, whereas in higher z-planes the transcript density shifts towards the right.

Proseg v3 explicitly corrects for this type of depth-related variation and produces normalized z values, as can be seen here (TODO - Check newest proseg version for raw z-coordinates. If still missing, copy them from Xenium as was done for proseg v2 in io.ipynb).

[8]:
for method, st in st_dict.items():
    plot_transcripts_across_z_bins(st.sdata, method, 10)
../_images/notebooks_volume_16_0.png
../_images/notebooks_volume_16_1.png
../_images/notebooks_volume_16_2.png

Top–bottom z consistency#

To identify cells that may contain transcripts from overlapping cells across the z-axis, we compare the transcript composition at the top and bottom of each cell. A single, correctly segmented cell is expected to have broadly consistent expression across depth, whereas overlapping or incorrectly merged cells may contain different expression profiles at different z positions.

For each cell, transcripts are taken from the bottom and top fractions of its z-range (default: 30% each). Their gene-expression profiles are transformed with PFlog1pPF and compared using cosine similarity. Because profiles with fewer transcripts tend to appear less similar due to finite-count sampling, the observed similarity is compared with a permutation-based expectation. The reported similarity_top_bottom is the residual from this expectation: values around zero indicate the expected similarity, while negative values indicate that the top and bottom are less similar than expected. The accompanying lower-tail p-value identifies cells with evidence for unusually low similarity.

By default, global z-drift is corrected before defining the top and bottom regions (correct_z_drift=True). Cells without sufficient transcripts or detected genes in both regions are not evaluated.

[9]:
for method, st in st_dict.items():
    if method == "proseg3":
        _cos_sim = st.vl.similarity_top_bottom(
            correct_z_drift=False
        )  # correct for z drift already done internally in proseg
    else:
        _cos_sim = st.vl.similarity_top_bottom()

Below, we compare the top–bottom similarity residual only among cells with a significant lower-tail permutation p-value (similarity_top_bottom_p_value < 0.05). These cells have top and bottom profiles that are less similar than expected under the conditional null.

[10]:
def histogram_feature(
    sdata_dict,
    feature,
    figsize=(5, 3),
    significant_only=True,
    bins=60,
):
    all_feats = []

    for method, st in sdata_dict.items():
        obs = st.sdata[st.tables_key].obs

        if (feature == "similarity_top_bottom") and significant_only:
            obs = obs.loc[obs["similarity_top_bottom_p_value"] < 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",
        fill=False,
        palette=palette,
        ax=ax,
        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()

Although the median residual among significant cells is similar across methods, Xenium has more cells that deviate significantly from the null expectation, suggesting that these deviations occur more frequently rather than being stronger in magnitude.

[11]:
histogram_feature(
    st_dict,
    feature="similarity_top_bottom",
    significant_only=True,
)
../_images/notebooks_volume_22_0.png

We next compare top–bottom similarity residuals across transferred cell types, considering only cells with a significant lower-tail p-value to reduce variation related to cell-type-specific composition.

[12]:
def boxplot_per_celltype(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()

similarity_top_bottom differs between cell types, with B cells accounting for much of the difference in the number of significant cells across methods (312 in Xenium, 142 in Proseg v2, and 109 in Proseg v3).

[13]:
boxplot_per_celltype(st_dict, "similarity_top_bottom")
../_images/notebooks_volume_26_0.png

Heterotypic overlap area/fraction (detecting 3D overlaps)#

This metric is designed to quantify where a quasi-3D method detects overlaps across z. It can only be computed for methods that output per-z-layer cell polygons (e.g. Proseg), because it explicitly compares cell boundaries between different z layers.

Because each cell has can have polygons across more than one z layer (cell_boundaries_z0, …), we first pick a single representative polygon per cell: the polygon with the largest area across z. We then compare this representative polygon to polygons from other z layers, excluding polygons from the same cell and restricting to different cell types (based on transferred_cell_type). Cells for which no label could be assigned (transferred_cell_type is NaN) can either be treated as a separate category (default: treat_as_label) or excluded from the analysis.

For each cell we report:

heterotypic_overlap_area: total area where the representative polygon overlaps with polygons of other cell types in different z layers

heterotypic_overlap_fraction: the same overlap area normalized by the cell’s polygon area

This metric is not intended as a standalone quality score, but is most informative when interpreted alongside other measures (see next chapter).

[14]:
proseg_dict = st_dict.copy()
proseg_dict.pop("xenium", None)
[14]:
<segtraq.SegTraQ.SegTraQ at 0x7fff7e48eba0>
[15]:
for _method, st in proseg_dict.items():
    if "proseg" in method:
        shapes_key_list = ["cell_boundaries_z0", "cell_boundaries_z1", "cell_boundaries_z2", "cell_boundaries_z3"]
        st.vl.fraction_heterotypic_overlap(unknown_policy="exclude", shapes_key_list=shapes_key_list)

Below we can see the distribution of the heterotypic_overlap_area and heterotypic_overlap_fraction in proseg v2 and v3. Some cells have heterotypic overlap fractions > 50%.

[16]:
def density_plot_feature(sdata_dict, feature, figsize=(5, 3)):
    all_feats = []
    for method, st in sdata_dict.items():
        obs = st.sdata[st.tables_key].obs
        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)

    plt.figure(figsize=figsize)
    sns.kdeplot(data=df, x=feature, hue="method", common_norm=False, palette="Set2", fill=False)
    plt.tight_layout()
[17]:
density_plot_feature(proseg_dict, "heterotypic_overlap_area")
../_images/notebooks_volume_32_0.png
[18]:
density_plot_feature(proseg_dict, "heterotypic_overlap_fraction")
../_images/notebooks_volume_33_0.png

Spatially, it is often more informative to visualize cells with high heterotypic_overlap_area rather than high heterotypic_overlap_fraction. The fraction normalizes by cell size, so very small cells (often partial cells near the top or bottom z-planes due to tissue cutting) can show high values (often close to 1) even when the absolute overlap is negligible. In contrast, the overlap area highlights regions where a substantial amount of tissue is involved in cross-type overlap.

Let’s have a look at a cell with a high heterotypic_overlap_area in proseg2. It is a stromal cell that overlaps a dendritic cell (black cross marks cell centroid).

[19]:
# Identify cell with high heterotypic_overlap_area
tbl = st_proseg3.sdata.tables[st_proseg3.tables_key]
rank = 2
idx = tbl.obs["heterotypic_overlap_area"].nlargest(rank).index[rank - 1]
x0, y0 = (
    tbl.obs.loc[idx, st_proseg3.tables_centroid_x_key] / 0.2125,
    tbl.obs.loc[idx, st_proseg3.tables_centroid_y_key] / 0.2125,
)

# 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",
}

axes = plt.subplots(1, 3, figsize=(21, 7), constrained_layout=True)[1].flatten()

s = st_proseg3.sdata.tables[st_proseg3.tables_key].obs["transferred_cell_type"]
if pd.api.types.is_categorical_dtype(s):
    s = s.cat.add_categories(["Unknown"])

st_proseg3.sdata.tables[st_proseg3.tables_key].obs["transferred_celltype_plot"] = s.fillna("Unknown")

labels = st_proseg3.sdata.tables[st_proseg3.tables_key].obs["transferred_celltype_plot"].unique().astype(str).tolist()
cols = [col_celltype[lab] for lab in labels]

# Bottom plane
st_proseg3.sdata.tables[st_proseg3.tables_key].obs["region"] = "cell_boundaries_z0"
st_proseg3.sdata.set_table_annotates_spatialelement(st_proseg3.tables_key, region="cell_boundaries_z0")

st_proseg3.sdata.pl.render_shapes(
    "cell_boundaries_z0", color="transferred_celltype_plot", palette=cols, groups=labels
).pl.show(ax=axes[0], title="Bottom: Cell masks colored by transferred cell type", coordinate_systems="global")

# Top plane
st_proseg3.sdata.tables[st_proseg3.tables_key].obs["region"] = "cell_boundaries_z1"
st_proseg3.sdata.set_table_annotates_spatialelement(st_proseg3.tables_key, region="cell_boundaries_z1")

st_proseg3.sdata.pl.render_shapes(
    "cell_boundaries_z1", color="transferred_celltype_plot", palette=cols, groups=labels
).pl.show(ax=axes[1], title="Top: Cell masks colored by transferred cell type", coordinate_systems="global")

st_proseg3.sdata.pl.render_shapes("cell_boundaries_z1", color="heterotypic_overlap_area").pl.show(
    ax=axes[2], title="Bottom: Cell masks colored by heterotypic_overlap_area", coordinate_systems="global"
)

# Add landmark at centroid of cell with high heterotypic overlap area
for ax in axes:
    ax.scatter([x0], [y0], marker="+", s=400, c="black", linewidths=2, zorder=10)
WARNING  render_shapes: Found 16 NaN values in color data. These observations will be colored with the 'na_color'.
../_images/notebooks_volume_35_1.png

Mean VSI per cell (ovrlpy-based vertical signal integrity)#

Ovrlpy is a package for detecting 3D overlap in spatial transcriptomics by using transcript coordinates. It computes a Vertical Signal Integrity (VSI) map that highlights regions where the transcriptome signal is consistent across depth versus regions that likely contain vertical mixing (e.g. overlapping cells or tissue folds). Conceptually, VSI compares local gene expression between a virtual top and virtual bottom subslice of the tissue: high VSI indicates strong agreement, while low VSI suggests potential 3D overlap or other depth-related artifacts.

To connect this pixel-level map to our 3D metrics, we compute mean VSI per cell by sampling the VSI value at each transcript’s (x,y) position and averaging these values across all transcripts assigned to the same cell.

We can run the compute the VSI map and extract the mean VSI per cell.

[20]:
n_celltypes = st_xenium.sdata.tables[st_xenium.tables_key].obs["transferred_cell_type"].nunique()

for _method, st in st_dict.items():
    _mean_vsi = st.vl.vertical_signal_integrity_per_cell(ovrlpy_init_kwargs={"n_components": n_celltypes})
Running vertical adjustment
Creating gene expression embeddings for visualization
determining pseudocells
found 371 pseudocells
sampling expression:
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  2.62it/s]
Modeling 10 pseudo-celltype clusters;
Creating signal integrity map
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:02<00:00,  1.85it/s]
Running vertical adjustment
Creating gene expression embeddings for visualization
determining pseudocells
found 371 pseudocells
sampling expression:
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  3.35it/s]
Modeling 10 pseudo-celltype clusters;
Creating signal integrity map
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:02<00:00,  1.95it/s]
Running vertical adjustment
Creating gene expression embeddings for visualization
determining pseudocells
found 364 pseudocells
sampling expression:
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  5.56it/s]
Modeling 10 pseudo-celltype clusters;
Creating signal integrity map
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:02<00:00,  1.96it/s]

We compare similarity_top_bottom with vertical signal integrity. VSI is an independent spatial measure of vertical mixing, so their association can indicate whether expression-level depth inconsistency coincides with transcript-level vertical signal disruption.

[21]:
n = len(st_dict)
fig, axes = plt.subplots(1, n, figsize=(5 * n, 4), sharey=True)
axes = np.atleast_1d(axes)

for ax, (method, st) in zip(axes, st_dict.items(), strict=False):
    df = (
        st.sdata.tables[st.tables_key]
        .obs[
            [
                "vertical_signal_integrity",
                "similarity_top_bottom",
                "similarity_top_bottom_p_value",
                "transcript_count",
            ]
        ]
        .dropna()
    )

    r = np.corrcoef(df["vertical_signal_integrity"], df["similarity_top_bottom"])[0, 1]
    r2 = r**2

    sns.regplot(
        data=df,
        x="vertical_signal_integrity",
        y="similarity_top_bottom",
        scatter_kws={"alpha": 0.6},
        line_kws={"color": "red"},
        # lowess=True,
        ci=95,
        ax=ax,
    )

    ax.set_title(f"{method} (R² = {r2:.3f})")
    ax.set_xlabel("vertical_signal_integrity")
    ax.set_ylabel("similarity_top_bottom")

fig.tight_layout()
plt.show()
../_images/notebooks_volume_40_0.png

In addition, proseg v2 and v3 shows large heterotypic_overlap_fraction values at low mean_vsi, this supports the idea that Proseg is successfully detecting substantial cross-type overlap in regions with poor vertical signal integrity. In other words, low-VSI regions (where the raw transcript field appears vertically inconsistent and prone to 3D mixing) coincide with locations where Proseg identifies strong heterotypic overlap across z-layers. This suggests that the overlap signal captured by ovrlpy (low VSI) corresponds to biologically and geometrically meaningful overlap events that Proseg can partially represent in its quasi-3D segmentation output.

We evaluate this relation in cell types individually to reduce confounding from large cell-type–specific differences in transcript abundance.

[22]:
n = len(proseg_dict)
fig, axes = plt.subplots(1, n, figsize=(4 * n, 3), sharex=False, sharey=False)
axes = np.atleast_1d(axes)

for j, (method, st) in enumerate(proseg_dict.items()):
    obs = st.sdata.tables[st.tables_key].obs

    df = obs.loc[
        obs["transferred_cell_type"] == "DCIS2", ["vertical_signal_integrity", "heterotypic_overlap_fraction"]
    ].dropna()

    ax = axes[j]
    r = np.corrcoef(df["vertical_signal_integrity"], df["heterotypic_overlap_fraction"])[0, 1]

    sns.regplot(
        data=df,
        x="vertical_signal_integrity",
        y="heterotypic_overlap_fraction",
        scatter_kws={"alpha": 0.6},
        line_kws={"color": "red"},
        ci=95,
        ax=ax,
    )

    ax.set_title(f"{method} (R={r:.3f})")
    ax.set_xlabel("vertical_signal_integrity")
    ax.set_ylabel("heterotypic_overlap_fraction")

fig.tight_layout()
plt.show()
../_images/notebooks_volume_42_0.png

Wrapper to run all metrics (including label transfer). By default, this does not run ovrlpy to reduce runtime, but it can be enabled with run_ovrlpy=True.

[23]:
sdata_proseg2 = sd.read_zarr("../../data/xenium_v1_data/sdata_proseg_v2_crop.zarr/")

st_proseg2 = segtraq.SegTraQ(
    sdata_proseg2,
    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",
)

shapes_key_list = ["cell_boundaries_z0", "cell_boundaries_z1", "cell_boundaries_z2", "cell_boundaries_z3"]

st_proseg2.run_volume(
    adata_ref=adata_ref,
    ref_cell_type="celltype_major",
    ref_raw_counts_layer="raw",
    heterotypic_overlap_kwargs={"shapes_key_list": shapes_key_list},
)

st_proseg2.sdata.tables[st_proseg2.tables_key].obs.columns
WARNING: adata.X seems to be already log-transformed.
[23]:
Index(['cell', 'original_cell_id', 'centroid_x', 'centroid_y', 'centroid_z',
       'fov', 'cluster', 'volume', 'scale', 'population', 'region',
       'cell_area', 'similarity_top_bottom', 'similarity_top_bottom_p_value',
       'transcript_count', 'gene_count', 'transferred_cell_type',
       'pearson_score', 'heterotypic_overlap_area',
       'heterotypic_overlap_fraction'],
      dtype='object')

Session Info#

[24]:
print(sd.__version__)  # spatialdata
print(spatialdata_plot.__version__)
0.8.0
0.4.0