sc_tools.pl — Plotting#

Plotting utilities for spatial omics data following the scanpy API pattern.

import sc_tools.pl as pl

pl.multipage_spatial_pdf(adata, keys=["Hallmark/HYPOXIA"], output_path="out.pdf")
pl.qc_2x2_grid(adata)
pl.plot_gsea_dotplot(results_df)

Spatial Plots#

sc_tools.pl.spatial.plot_spatial_plain_he(adata, library_id, ax, image_key='hires')[source]#

Plot plain H&E tissue image for a library (no spots overlay).

Parameters:
  • adata (AnnData) – Full AnnData with adata.uns[‘spatial’][library_id][‘images’][image_key].

  • library_id (str) – Key in adata.uns[‘spatial’].

  • ax (Axes) – Matplotlib axes to draw on.

  • image_key (str) – Key in spatial[‘images’] (default ‘hires’).

Return type:

None

sc_tools.pl.spatial.plot_spatial_categorical(adata, library_id, color, ax, title=None, palette=None, legend_loc='right margin', frameon=False, **kwargs)[source]#

Plot spatial overlay of a categorical variable (e.g. annotation, solidity).

Parameters:
  • adata (AnnData) – Subset AnnData for this library (e.g. adata[adata.obs[‘library_id’] == library_id]).

  • library_id (str) – Key in adata.uns[‘spatial’].

  • color (str) – Column name in adata.obs (categorical).

  • ax (Axes) – Matplotlib axes.

  • title (str, optional) – Axis title. If None, uses color.

  • palette (dict, optional) – Category -> color mapping.

  • legend_loc (str) – Passed to scanpy (default ‘right margin’).

  • frameon (bool) – Passed to scanpy (default False).

  • **kwargs (Any) – Passed to sc.pl.spatial.

Return type:

None

sc_tools.pl.spatial.plot_spatial_continuous(adata, library_id, color, ax, title=None, cmap='coolwarm', vmin=None, vmax=None, frameon=False, values=None, **kwargs)[source]#

Plot spatial overlay of a continuous variable (e.g. score).

Parameters:
  • adata (AnnData) – Subset AnnData for this library.

  • library_id (str) – Key in adata.uns[‘spatial’].

  • color (str) – Column name in adata.obs (numeric). Ignored if values is provided.

  • ax (Axes) – Matplotlib axes.

  • title (str, optional) – Axis title. If None, uses color or “Score”.

  • cmap (str) – Colormap name (default ‘coolwarm’).

  • vmin (float, optional) – Color scale limits.

  • vmax (float, optional) – Color scale limits.

  • frameon (bool) – Passed to scanpy (default False).

  • values (Series or ndarray, optional) – If provided, use these values for the overlay (length/index must match adata.obs_names). Use when scores are in obsm instead of obs.

  • **kwargs (Any) – Passed to sc.pl.spatial.

Return type:

None

sc_tools.pl.spatial.multipage_spatial_pdf(adata, library_id_col, panels, output_path, figsize=(18, 12), dpi=300)[source]#

Create a multipage PDF with one page per library and N spatial panels per page.

Parameters:
  • adata (AnnData) – Full AnnData with obs[library_id_col], uns[‘spatial’], and any obs columns required by the panels.

  • library_id_col (str) – Column in adata.obs that identifies the library/sample.

  • panels (list of dict) – List of panel specs. Each dict must have a "type" key ("he", "categorical", or "continuous"). "he" needs no extra keys. "categorical" needs "obs_col" and "title" (optional "palette"). "continuous" needs "title" and either "obs_col" or "values" (optional "cmap", "vmin", "vmax").

  • output_path (str) – Path to the output PDF file.

  • figsize (tuple) – Figure size per page (default (18, 12)).

  • dpi (int) – DPI for saved pages (default 300).

Return type:

None

Heatmaps#

sc_tools.pl.heatmaps.signature_score_heatmap(adata, sig_columns, annotation_cols, sort_by, category_orders=None, cluster=False, sig_prefix='sig:', sig_suffix='_z', vmin=-3, vmax=3, figsize=None, solidity_colors_hex=None, legend_title=None)[source]#

Build heatmap or clustermap of signature scores with annotation bars.

Annotation columns are given as display_name -> obs column name. sort_by is list of display names (primary, secondary). category_orders maps display name -> ordered list of categories; columns not in it are ordered by mean score (descending).

Parameters:
  • adata (AnnData) – Object with signature scores in obs[sig_columns] and annotation columns.

  • sig_columns (list) – Obs column names for signature scores.

  • annotation_cols (dict) – Display name -> obs column name, e.g. {‘Patient’: ‘library_id’, ‘Solidity’: ‘tumor_type’}.

  • sort_by (list) – [primary, secondary] display names for sorting.

  • category_orders (dict, optional) – Display name -> list of category order. Missing names: order by mean score.

  • cluster (bool) – If True, build clustermap with within-group clustering; else heatmap only.

  • sig_prefix (str) – Stripped from sig_columns for row labels.

  • sig_suffix (str) – Stripped from sig_columns for row labels.

  • vmin (float) – Color scale for score matrix.

  • vmax (float) – Color scale for score matrix.

  • figsize (tuple, optional) – (width, height). Default heatmap (16,12), clustermap (18,14).

  • solidity_colors_hex (dict, optional) – For backward compatibility: category -> hex for second annotation (e.g. Solidity).

  • legend_title (str, optional) – Title for legend (e.g. ‘Solidity’).

Return type:

tuple[Figure, Any | None]

Returns:

  • fig (Figure) – The figure (caller can save with st.pl.save_figure).

  • g (seaborn.ClusterGrid or None) – If cluster=True, the ClusterGrid for further tweaks; else None.

sc_tools.pl.heatmaps.cluster_within_groups(data_matrix, group_labels, method='average', metric='euclidean')[source]#

Cluster rows within each group; preserve group order.

Parameters:
  • data_matrix (np.ndarray) – Data matrix (n_samples x n_features).

  • group_labels (np.ndarray) – Group label per row (same length as data_matrix).

  • method (str) – Linkage method (default ‘average’).

  • metric (str) – Distance metric (default ‘euclidean’).

Returns:

Reordered row indices (cluster within each group, groups in order).

Return type:

np.ndarray

sc_tools.pl.heatmaps.annotation_colors_from_categories(annotations, column_colors=None, default_hex=None)[source]#

Build per-column color lists (RGB) for annotation bars.

Parameters:
  • annotations (pd.DataFrame) – Index = sample ids, columns = annotation names; values = category labels.

  • column_colors (dict, optional) – Maps column name -> {category: (r,g,b)}. If None, uses seaborn Set3 for each column.

  • default_hex (dict, optional) – Maps column name -> {category: hex}. Converted to RGB; overridden by column_colors.

Returns:

column name -> list of (r,g,b) in row order.

Return type:

dict

sc_tools.pl.heatmaps.get_obs_category_colors(adata, obs_col, store_if_missing=True)[source]#

Get category -> RGB color mapping for a categorical obs column using the scanpy convention: adata.uns[f’{obs_col}_colors’] is a list of hex strings (one per category in order). If missing or length mismatch, create a default palette and optionally store it in adata.uns.

Parameters:
  • adata (AnnData) – Object with obs[obs_col] categorical and optionally uns[f’{obs_col}_colors’].

  • obs_col (str) – Name of the categorical column in adata.obs.

  • store_if_missing (bool) – If True (default), when colors are missing or invalid, create a palette and set adata.uns[f’{obs_col}_colors’] to a list of hex strings.

Returns:

Map from category value to (r, g, b) in [0, 1]. None if obs_col is not present or not categorical.

Return type:

dict or None

Enrichment#

sc_tools.pl.plot_gsea_dotplot(result_df, top_n=10, groups=None, figsize=(8, 6), output_path=None)[source]#

Dot plot of gene set enrichment results.

Dot size encodes statistical significance (-log10 adjusted p-value); dot color encodes normalized enrichment score (NES) or odds ratio. Compatible with output from both run_ora() and run_gsea_pseudobulk().

Parameters:
  • result_df (pd.DataFrame) – Long-format enrichment results. Must contain at minimum the columns group, gene_set, p_adj. Optional: NES (used for color if present, otherwise odds_ratio is used, then defaults to black).

  • top_n (int) – Top N gene sets to display per group (ranked by p_adj ascending). Default 10.

  • groups (list[str] or None) – Subset of groups to display. Defaults to all groups in result_df.

  • figsize (tuple) – Figure size in inches (width, height). Default (8, 6).

  • output_path (str, Path, or None) – If provided, save the figure to this path at 300 DPI. Parent directories are created if needed.

Returns:

The figure object.

Return type:

matplotlib.figure.Figure

QC Plots#

Re-exported from sc_tools.qc.plots for st.pl.qc_* usage. See sc_tools.qc — Quality Control for full documentation.

sc_tools.pl.qc_2x2_grid(adata, *, total_counts_col='total_counts', n_genes_col='n_genes_by_counts', pct_mt_col='pct_counts_mt', output_dir=None, basename='qc_2x2', dpi=300, figsize=(10, 10), modality='visium')[source]#

Plot 2x2 QC grid: total_counts, n_genes, log1p(total_counts), pct_counts_mt.

Panels: (1,1) total_counts histogram, (1,2) n_genes_by_counts histogram, (2,1) log1p(total_counts) histogram, (2,2) pct_counts_mt histogram if present, else log1p(n_genes_by_counts).

Parameters:
  • adata (AnnData) – Annotated data with obs containing total_counts and n_genes_by_counts (from calculate_qc_metrics). pct_counts_mt optional.

  • total_counts_col (str) – Obs column for total counts (default ‘total_counts’).

  • n_genes_col (str) – Obs column for number of genes (default ‘n_genes_by_counts’).

  • pct_mt_col (str) – Obs column for percent mitochondrial (default ‘pct_counts_mt’).

  • output_dir (str or Path or None) – If set, save PDF and PNG here (default None).

  • basename (str) – Base name for files (default ‘qc_2x2’).

  • dpi (int) – DPI for PNG (default 300).

  • figsize (tuple) – Figure size (default (10, 10)).

  • modality (str)

Returns:

The figure (caller may show or save).

Return type:

matplotlib.figure.Figure

sc_tools.pl.qc_2x4_pre_post(adata_pre, adata_post, *, total_counts_col='total_counts', n_genes_col='n_genes_by_counts', pct_mt_col='pct_counts_mt', output_dir=None, basename='qc_2x4_pre_post', dpi=300, figsize=(16, 10), modality='visium')[source]#

Plot pre- vs post-filter QC: 2 rows x 4 columns. Left 2x2 = pre-filter (raw) metrics; right 2x2 = post-filter metrics. Use this so post-filter distributions (e.g. after filter_cells/filter_genes) are directly comparable to pre.

Panels: row0 = total_counts (pre), n_genes (pre) | total_counts (post), n_genes (post); row1 = log1p(total_counts) (pre), pct_mt (pre) | log1p(total_counts) (post), pct_mt (post).

Parameters:
  • adata_pre (AnnData) – Pre-filter (raw) adata with total_counts, n_genes_by_counts (and optionally pct_counts_mt).

  • adata_post (AnnData) – Post-filter (and optionally normalized) adata. Should have been filtered so that n_obs and metric distributions differ from pre. Must have same obs column names.

  • total_counts_col (str) – Obs column for total counts (default ‘total_counts’).

  • n_genes_col (str) – Obs column for number of genes (default ‘n_genes_by_counts’).

  • pct_mt_col (str) – Obs column for percent mitochondrial (default ‘pct_counts_mt’).

  • output_dir (str or Path or None) – If set, save PDF and PNG here (default None).

  • basename (str) – Base name for files (default ‘qc_2x4_pre_post’).

  • dpi (int) – DPI for PNG (default 300).

  • figsize (tuple) – Figure size (default (16, 10)).

  • modality (str)

Returns:

The figure.

Return type:

matplotlib.figure.Figure

sc_tools.pl.qc_spatial_multipage(adata, library_id_col, output_path, *, total_counts_col='total_counts', pct_mt_col='pct_counts_mt', figsize=(18, 6), dpi=300, common_scale=True)[source]#

Multipage spatial QC report: one page per sample with 1x3 panels (total_count, log1p(total_count), % mt).

When common_scale is True (default), the same vmin/vmax is used for each metric across all pages so color scales are comparable across samples.

Requires adata.obs[library_id_col], adata.obsm[‘spatial’], and adata.uns[‘spatial’] with per-library images for spatial plots.

Parameters:
  • adata (AnnData) – Annotated data with spatial coords and (optionally) H&E in uns[‘spatial’].

  • library_id_col (str) – Column in adata.obs identifying library/sample.

  • output_path (str or Path) – Path to output PDF (e.g. figures/QC/raw/qc_spatial_multipage.pdf).

  • total_counts_col (str) – Obs column for total counts (default ‘total_counts’).

  • pct_mt_col (str) – Obs column for percent mitochondrial (default ‘pct_counts_mt’).

  • figsize (tuple) – Figure size per page (default (18, 6)).

  • dpi (int) – DPI for saved PDF (default 300).

  • common_scale (bool) – If True, use global vmin/vmax (99th percentile) per metric across all spots so every page uses the same color scale (default True).

Return type:

None

sc_tools.pl.qc_violin_metrics(adata, *, keys=None, groupby=None, output_dir=None, basename='qc_violin', dpi=300, figsize=None)[source]#

Multi-panel violin plot for QC metrics (n_genes_by_counts, total_counts, pct_counts_mt).

Uses scanpy’s violin with show=False and captures the figure for saving. Requires adata.obs columns from calculate_qc_metrics.

Parameters:
  • adata (AnnData) – Annotated data with obs containing total_counts, n_genes_by_counts, pct_counts_mt.

  • keys (list of str or None) – Obs columns to plot (default: n_genes_by_counts, total_counts, pct_counts_mt).

  • groupby (str or None) – Optional obs column to stratify violins (e.g. library_id, sample).

  • output_dir (str or Path or None) – If set, save PDF and PNG here.

  • basename (str) – Base name for files (default ‘qc_violin’).

  • dpi (int) – DPI for PNG (default 300).

  • figsize (tuple or None) – Figure size; if None, scanpy default is used.

Return type:

matplotlib.figure.Figure

sc_tools.pl.qc_scatter_counts_genes(adata, *, x='total_counts', y='n_genes_by_counts', color='pct_counts_mt', output_dir=None, basename='qc_scatter', dpi=300, figsize=(6, 5))[source]#

Scatter plot: total_counts (x) vs n_genes_by_counts (y), colored by pct_counts_mt.

Uses scanpy’s scatter with show=False. Requires adata.obs from calculate_qc_metrics.

Parameters:
  • adata (AnnData) – Annotated data with obs columns for x, y, and color.

  • x (str) – Obs column names (defaults: total_counts, n_genes_by_counts, pct_counts_mt).

  • y (str) – Obs column names (defaults: total_counts, n_genes_by_counts, pct_counts_mt).

  • color (str) – Obs column names (defaults: total_counts, n_genes_by_counts, pct_counts_mt).

  • output_dir (str or Path or None) – If set, save PDF and PNG here.

  • basename (str) – Base name for files (default ‘qc_scatter’).

  • dpi (int) – DPI for PNG (default 300).

  • figsize (tuple) – Figure size (default (6, 5)).

Return type:

matplotlib.figure.Figure

sc_tools.pl.plot_highly_variable_genes(adata, *, output_dir=None, basename='hvg', dpi=300, figsize=(6, 4))[source]#

Plot mean vs dispersion (or normalized dispersion) with highly variable genes highlighted.

Requires adata.var with ‘highly_variable’ and flavor-specific columns (means, dispersions or dispersions_norm). Uses sc.pl.highly_variable_genes(show=False).

Parameters:
  • adata (AnnData) – Annotated data after highly_variable_genes (e.g. seurat flavor).

  • output_dir (str or Path or None) – If set, save PDF and PNG here.

  • basename (str) – Base name for files (default ‘hvg’).

  • dpi (int) – DPI for PNG (default 300).

  • figsize (tuple) – Figure size (default (6, 4)).

Return type:

matplotlib.figure.Figure

sc_tools.pl.plot_spatially_variable_genes(adata, *, x_axis='mean', color_by='spatially_variable', output_dir=None, basename='svg', dpi=300, figsize=(6, 5))[source]#

Scatter: x = mean expression (or rank), y = Moran’s I (spatial_i), colored by spatially_variable or pval.

If adata.uns[‘spatial_variable_per_library’] exists (from spatially_variable_genes_per_library), one subplot per library is drawn. Otherwise requires adata.var with spatial_i.

Parameters:
  • adata (AnnData) – Annotated data after spatially_variable_genes or with uns[‘spatial_variable_per_library’].

  • x_axis (str) – ‘mean’ or ‘rank’: x-axis (default ‘mean’).

  • color_by (str) – ‘spatially_variable’ or ‘spatial_pval’ (default ‘spatially_variable’).

  • output_dir (str or Path or None) – If set, save PDF and PNG here.

  • basename (str) – Base name for files (default ‘svg’).

  • dpi (int) – DPI for PNG (default 300).

  • figsize (tuple) – Figure size per panel (default (6, 5)).

Return type:

matplotlib.figure.Figure

sc_tools.pl.qc_sample_comparison_bar(metrics, metric_cols=None, classified=None, output_dir=None, basename='qc_sample_comparison', dpi=300, log_scale=False)[source]#

Bar chart per metric, one bar per sample, sorted by value.

Failed samples (from classified) are highlighted in red.

Parameters:
  • metrics (pd.DataFrame) – Output of compute_sample_metrics (indexed by sample).

  • metric_cols (list of str or None) – Columns to plot (default: n_genes_median, total_counts_median, pct_mt_median, n_spots).

  • classified (pd.DataFrame or None) – If provided (output of classify_samples), failed samples shown in red.

  • output_dir (str or Path or None) – If set, save PDF and PNG.

  • basename (str) – Base filename.

  • dpi (int) – DPI for PNG.

  • log_scale (bool) – If True, transform values with log10(x + 1) and annotate y-ticks with original-scale labels (default False).

Return type:

matplotlib.figure.Figure

sc_tools.pl.qc_sample_violin_grouped(adata, sample_col='library_id', keys=None, classified=None, output_dir=None, basename='qc_sample_violin', dpi=300, log_scale=False)[source]#

Violin plots grouped by sample for direct distribution comparison.

Parameters:
  • adata (AnnData) – Annotated data with QC columns in obs.

  • sample_col (str) – Column in obs identifying samples.

  • keys (list of str or None) – Obs columns to plot (default: n_genes_by_counts, total_counts, pct_counts_mt).

  • classified (pd.DataFrame or None) – If provided, failed sample names are marked with (FAIL) suffix.

  • output_dir (str or Path or None) – If set, save PDF and PNG.

  • basename (str) – Base filename.

  • dpi (int) – DPI for PNG.

  • log_scale (bool) – If True, apply log10(x + 1) to all keys except pct_counts_mt (which stays linear). Custom y-tick annotations are added (default False).

Return type:

matplotlib.figure.Figure

sc_tools.pl.qc_sample_scatter_matrix(metrics, metric_cols=None, classified=None, output_dir=None, basename='qc_sample_scatter_matrix', dpi=300)[source]#

Pairwise scatter of sample-level metrics with pass/fail coloring.

Parameters:
  • metrics (pd.DataFrame) – Output of compute_sample_metrics.

  • metric_cols (list of str or None) – Columns for scatter matrix (default: n_spots, n_genes_median, total_counts_median, pct_mt_median).

  • classified (pd.DataFrame or None) – If provided, color points by pass (blue) / fail (red).

  • output_dir (str or Path or None) – If set, save PDF and PNG.

  • basename (str) – Base filename.

  • dpi (int) – DPI for PNG.

Return type:

matplotlib.figure.Figure

Utilities#

sc_tools.pl.save_figure(fig, basename, output_dir, dpi=300, dt=None, bbox_inches='tight', pad_inches=0.1, create_pdf_png_folders=True)[source]#

Save a figure in two formats with a versioned filename under pdf/ and png/.

Creates:
  • output_dir/pdf/YYDDMM.hh.mm.basename.pdf

  • output_dir/png/YYDDMM.hh.mm.basename.png

Parameters:
  • fig (matplotlib.figure.Figure) – Figure to save.

  • basename (str) – Base name (no extension), e.g. “volcano_faceted”.

  • output_dir (str or Path) – Root directory for figures (e.g. “figures/process_colocalization”). Subdirs “pdf” and “png” are created inside it.

  • dpi (int, optional) – DPI for raster (PNG). Default 300.

  • dt (datetime, optional) – Timestamp for version prefix; if None, uses now().

  • bbox_inches (str, optional) – Passed to savefig. Default “tight”.

  • pad_inches (float, optional) – Passed to savefig. Default 0.1.

  • create_pdf_png_folders (bool, optional) – If True (default), save under output_dir/pdf/ and output_dir/png/.

Returns:

(path_to_pdf, path_to_png)

Return type:

tuple of Path