Skip to main content
PyPI Documentation Status Paper License

SKNY (SpatialKNifeY) - tumor microenvironment analysis for single-cell spatial omics

_images/SKYN_logo.svg

SKNY (SpatialKNifeY) is a Python package for spatial transcriptomics / spatial proteomics analysis of the tumor microenvironment (TME). It works on single-cell spatial omics data from Xenium, CosMx, PhenoCycler and any other platform that can be loaded into an anndata object, so it fits into scanpy, squidpy and stlearn workflows.

SKNY answers questions such as:

  • Where is the tumor border in this section, and how far is each location or cell from it?

  • How does gene expression change with distance from the tumor (intra-tumoral vs peri-tumoral)?

  • Which genes are differentially expressed between the tumor core and the surrounding stroma?

  • What is the density of immune cells (cells / mm²) in the tumor core, the tumor margin and distant tissue?

  • How do individual tumor nests (“single tumor microenvironment”) differ from each other?

Published in PLOS Computational Biology (2025): https://doi.org/10.1371/journal.pcbi.1012854 (see Citation).

Two workflows

1. Expression-based tumor contour on a grid (sk.pp.calculate_distance; the method in the paper)

The tissue is binned into a 10 µm grid, the tumor is contoured from marker gene expression (e.g. CDH1, EPCAM, keratins) with image processing, and the shortest-path distance from the contour to every grid bin is calculated. Downstream functions then give:

  • gene expression as a function of distance from the tumor border (sk.pl.distance_gene_plot),

  • clustering of all genes by their distance profile (sk.pl.clustering_heatmap),

  • differentially expressed genes between distance bands (sk.pl.deg_manhattan_plot),

  • per-tumor-nest (“single-TME”) pseudo-bulk expression inside each nest (sk.pp.convert_indivisual_solid) and in the band around it (sk.pp.convert_indivisual_peri).

2. Cell-label based regions: tumor core / margin / dist (sk.pp.calculate_region; since v0.3.0)

For cell-level data with annotated cell types (scanpy / Xenium 5K / CosMx / PhenoCycler), the tumor is defined by a cell label such as "Malignant". Every cell is assigned to the tumor core (within 15 µm of a tumor cell), the peri-tumoral margin (stroma within 100 µm of the core) or dist (farther tissue), using an all-cell tissue mask so that empty space is never counted. sk.pp.region_density then reports the density (cells / mm²) of any cell group per region, and sk.pl.region_map / sk.pl.region_density_plot draw the results.

Installation

pip install skny

SKNY supports Python 3.8 to 3.13 (tested with numpy 2 and pandas 3) and depends only on anndata, numpy, pandas, scipy, matplotlib, seaborn, plotly, opencv-python, networkx and pillow, so it installs alongside current scanpy / squidpy environments.

Workflow 1 (expression-based contour) expects a 10 µm grid built with stlearn.tl.cci.grid; install it with the extra if you do not have stlearn already:

pip install "skny[stlearn]"

Workflow 2 (cell-label based regions) needs no additional package.

Quick start

Workflow 1: distance from the expression-based tumor contour (Xenium example)

import skny as sk
import stlearn as st

# Load a Xenium run with stlearn 0.4.x (any AnnData with obs["imagecol"], obs["imagerow"] works;
# with stlearn >= 1.0 use the spatialdata readers and st.tl.cci.grid without n_cpus)
adata = st.ReadXenium(
    feature_cell_matrix_file="Xenium_FFPE_Human_Breast_Cancer_Rep1_cell_feature_matrix.h5",
    cell_summary_file="Xenium_FFPE_Human_Breast_Cancer_Rep1_cells.csv.gz",
    library_id="example", image_path=None, scale=1, spot_diameter_fullres=10,
)

# Bin cells into a 10 um grid
n_col = int((adata.obs.imagecol.max() - adata.obs.imagecol.min()) / 10)
n_row = int((adata.obs.imagerow.max() - adata.obs.imagerow.min()) / 10)
grid = st.tl.cci.grid(adata, n_row=n_row, n_col=n_col, n_cpus=8, verbose=False)

# Contour the tumor from marker genes and compute the distance of every bin from the contour
grid = sk.pp.calculate_distance(grid, pos_marker_ls=["CDH1"])

sk.pl.preprocessed_images(grid, figsize=(800, None))   # tumor mask, contour, equidistant curves
sk.pl.distance_gene_plot(grid, "KRT15")                 # expression vs distance from the tumor
sk.pl.clustering_heatmap(grid, std=True)                # all genes clustered by distance profile
sk.pl.deg_manhattan_plot(grid)                          # DEGs between intra- and peri-tumoral bands

# "Single tumor microenvironment": pseudo-bulk expression per tumor nest
solid = sk.pp.convert_indivisual_solid(grid)            # inside each nest  (-inf, 0] um
peri  = sk.pp.convert_indivisual_peri(grid, section=30) # 0 to 30 um band around each nest

Workflow 2: tumor core / margin / dist from a cell label

import skny as sk

# adata: one row per cell, coordinates in micrometres in adata.obsm["spatial"],
#        cell types in adata.obs["cell_type"], sample id in adata.obs["sample"]
sk.pp.calculate_region(adata, "Malignant", label_key="cell_type", sample_key="sample")
# -> adata.obs["region"] in {"core", "margin", "dist", "outside"}, adata.obs["region_dist_um"]

df = sk.pp.region_density(adata, "immune_subtype", sample_key="sample")
# -> columns: sample, region, group, n, area_mm2, density (cells / mm^2)

sk.pl.region_map(adata, sample="S1", highlight=["T cell", "Myeloid"])
sk.pl.region_density_plot(df, groups=["CD8+ T", "Treg", "M2-like"])

When to use SKNY (and how it relates to scanpy / squidpy)

  • scanpy handles clustering and differential expression; squidpy handles neighborhood graphs, co-occurrence and ligand-receptor analysis. Neither defines a tumor border or measures distance from it.

  • Use SKNY when your question is about the spatial relationship to the tumor: expression gradients from the tumor border, immune infiltration into the tumor core vs the margin, comparison of individual tumor nests, or anything stratified by distance from a tumor domain.

  • SKNY takes and returns AnnData objects, so you can keep using scanpy / squidpy before and after it (e.g. run sc.tl.rank_genes_groups on adata.obs["region"]).

Documentation and tutorials

Citation

If you use SKNY, please cite:

Sakai SA, Nomura R, Nagasawa S, Chi S, Suzuki A, Suzuki Y, Imai M, Nakamura Y, Yoshino T, Ishikawa S, Tsuchihara K, Kageyama S-I, Yamashita R. SpatialKNifeY (SKNY): Extending from spatial domain to surrounding area to identify microenvironment features with single-cell spatial omics data. PLOS Computational Biology 21(2): e1012854 (2025). https://doi.org/10.1371/journal.pcbi.1012854

@article{Sakai2025SKNY,
  title   = {SpatialKNifeY (SKNY): Extending from spatial domain to surrounding area to identify microenvironment features with single-cell spatial omics data},
  author  = {Sakai, Shunsuke A. and Nomura, Ryosuke and Nagasawa, Satoi and Chi, SungGi and Suzuki, Ayako and Suzuki, Yutaka and Imai, Mitsuho and Nakamura, Yoshiaki and Yoshino, Takayuki and Ishikawa, Shumpei and Tsuchihara, Katsuya and Kageyama, Shun-Ichiro and Yamashita, Riu},
  journal = {PLOS Computational Biology},
  year    = {2025},
  volume  = {21},
  number  = {2},
  pages   = {e1012854},
  doi     = {10.1371/journal.pcbi.1012854}
}

A CITATION.cff file is included in the repository (GitHub “Cite this repository” button).

License

MIT. See LICENSE.

History

0.3.1 (2026-11-03)

  • Dependencies relaxed: no more pins on numpy / numba; stlearn and scanpy are no longer required (install skny[stlearn] for the grid tutorial). SKNY now runs on Python 3.8 to 3.13 with numpy 2 and pandas 3.

  • Compatibility fixes for recent matplotlib / pandas (figure buffer access, row-wise z-score, applymap removal, NaN-safe distance-band handling in convert_indivisual_*).

  • New end-to-end regression test of the grid workflow on synthetic data (tests/test_grid_workflow.py, no external data needed).

  • Citation information (PLOS Computational Biology 2025) added to the README and a CITATION.cff file; llms.txt and AGENTS.md added for coding assistants.

  • PyPI metadata (keywords, project URLs, classifiers) and NumPy-style docstrings for all public functions.

  • Removed the unused skny console script (template leftover).

  • skny.pp.calculate_region now warns instead of raising when no cell carries the reference label (all cells become not_computed), as covered by the unit tests.

0.3.0 (2026-11-02)

  • New cell-label based region analysis (a variant of calculate_distance that works on cell-level AnnData instead of marker expression on a grid): skny.pp.calculate_region (tumor core / margin / dist from the Euclidean distance to the nearest reference cell, with an all-cell tissue mask), skny.pp.region_density (cells / mm^2 of any cell group per region), skny.pl.region_map and skny.pl.region_density_plot.

  • Tutorial notebooks/region_analysis and unit tests tests/test_region.py.

0.2.16 (2024-02-14)

0.2.8 (2024-02-01)

0.2.7 (2024-01-31)

0.2.6 (2024-01-31)

  • Practice of upload to PyPI.

0.2.5 (2024-01-30)

0.2.4 (2024-01-30)

0.2.3 (2024-01-30)

0.2.2 (2024-01-30)

0.2.1 (2024-01-30)

0.1.2 (2024-01-30)

0.1.1 (2024-01-30)

0.1.0 (2024-01-21)

  • First release on PyPI.

Metadata

Release files for skny 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for skny 0.3.1
File Size Uploaded Provenance Actions
skny-0.3.1.tar.gz 11.2 MB Without provenance Details for skny-0.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for skny 0.3.1
File Interpreter ABI Platform Actions
skny-0.3.1-py2.py3-none-any.whl Python 3, Python 2 none any Details for skny-0.3.1-py2.py3-none-any.whl

Total release size: 11.3 MB

Release files / skny-0.3.1.tar.gz

Download URL skny-0.3.1.tar.gz
Size 11.2 MB
Tags Source
SHA-256 checksum
How to use checksums
6bc514d357dacd74747dff5ef1d9257810457abb0a9c458dc7c44253f2962615
BLAKE2b-256 checksum
How to use checksums
7e8a14918802d98eeb926e9fc74f2b6b13c10bb891dab2917cc5e537d3f9e341
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.1

Release files / skny-0.3.1-py2.py3-none-any.whl

Download URL skny-0.3.1-py2.py3-none-any.whl
Size 36.7 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
2be2a8301361652dee4c952f7f576d36e824d49b863b8ce94ca11ea058dfb640
BLAKE2b-256 checksum
How to use checksums
701b5f846695d1e3fd0c5928de50ddbd9cc7b754871dfba0c0440958cec78418
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.1

Security for skny 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page