sc_tools.qc — Quality Control#

QC metrics, spot filtering, sample classification, and HTML report generation.

import sc_tools.qc as qc

qc.calculate_qc_metrics(adata, mt_pattern="^MT-")
qc.filter_cells(adata, min_genes=200)
qc.filter_genes(adata, min_cells=3)
fig = qc.qc_2x2_grid(adata)

Metrics and Filtering#

Thin wrappers around scanpy pp.calculate_qc_metrics, pp.filter_cells, pp.filter_genes, and pp.highly_variable_genes.

sc_tools.qc.calculate_qc_metrics(adata, *, mt_pattern='^(MT-|mt-|Mt)', hb_pattern='^(HB|Hb|HBEGF)', qc_vars=None, inplace=True, percent_top=(50, 100, 200, 500), log1p=False, modality='visium', **kwargs)[source]#

Compute QC metrics for spots/cells and genes (wrap scanpy).

Optionally marks mitochondrial and hemoglobin genes and adds pct_counts_mt (and pct_counts_hb) to adata.obs.

Parameters:
  • adata (AnnData) – Annotated data (raw counts recommended).

  • mt_pattern (str or compiled regex or None) – Pattern to mark mitochondrial genes in adata.var (default MT- / mt- / Mt). If None, mt genes are not marked and pct_counts_mt is not computed. Automatically set to None for protein-based modalities (e.g. "imc").

  • hb_pattern (str or compiled regex or None) – Pattern to mark hemoglobin genes for pct_counts_hb. If None, not computed. Automatically set to None for protein-based modalities.

  • qc_vars (list of str or None) – If None, built from mt_pattern and hb_pattern: [‘mt’] and optionally [‘mt’,’hb’]. Passed to scanpy as qc_vars (column names in adata.var).

  • inplace (bool) – If True, add metrics to adata.obs and adata.var (default True).

  • percent_top (list of int or None) – Passed to scanpy (default (50, 100, 200, 500)). Automatically capped to n_vars to avoid IndexError on small panels.

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

  • modality (str) – Data modality. Protein-based modalities ("imc") skip MT/HB patterns.

  • **kwargs (Any) – Passed to scanpy.pp.calculate_qc_metrics.

Returns:

If inplace is False, returns (obs_df, var_df) concatenation as per scanpy; otherwise None.

Return type:

DataFrame or None

sc_tools.qc.filter_cells(adata, min_counts=None, min_genes=None, max_counts=None, max_genes=None, inplace=True, **kwargs)[source]#

Filter out spots/cells by counts and number of genes (wrap scanpy).

Parameters:
  • adata (AnnData) – Annotated data (should have total_counts and n_genes_by_counts in obs from calculate_qc_metrics).

  • min_counts (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • min_genes (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • max_counts (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • max_genes (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • inplace (bool) – If True, filter in place (default True).

  • **kwargs (Any) – Passed to scanpy.pp.filter_cells.

Returns:

Filtered object if inplace=False, else None.

Return type:

AnnData or None

sc_tools.qc.filter_genes(adata, min_counts=None, min_cells=None, max_counts=None, max_cells=None, inplace=True, **kwargs)[source]#

Filter out genes by counts and number of cells (wrap scanpy).

Parameters:
  • adata (AnnData) – Annotated data.

  • min_counts (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • min_cells (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • max_counts (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • max_cells (int or None) – Thresholds; None means do not apply. Passed to scanpy.

  • inplace (bool) – If True, filter in place (default True).

  • **kwargs (Any) – Passed to scanpy.pp.filter_genes.

Returns:

Filtered object if inplace=False, else None.

Return type:

AnnData or None

sc_tools.qc.highly_variable_genes(adata, flavor='seurat', n_top_genes=None, min_mean=0.0125, max_mean=3, min_disp=0.5, max_disp=inf, batch_key=None, subset=False, inplace=True, **kwargs)[source]#

Mark or subset highly variable genes (wrap scanpy).

Parameters:
  • adata (AnnData) – Annotated data (normalized, e.g. log1p, recommended for seurat_v3).

  • flavor (str) – ‘seurat’, ‘seurat_v3’, or ‘cell_ranger’ (default ‘seurat’).

  • n_top_genes (int or None) – If set, use this many top genes (common for seurat_v3).

  • min_mean (float) – Passed to scanpy (flavor-dependent).

  • max_mean (float) – Passed to scanpy (flavor-dependent).

  • min_disp (float) – Passed to scanpy (flavor-dependent).

  • max_disp (float) – Passed to scanpy (flavor-dependent).

  • batch_key (str or None) – If set, compute HVGs per batch (e.g. ‘sample’).

  • subset (bool) – If True, subset adata to HVGs (default False).

  • inplace (bool) – If True, add ‘highly_variable’ etc. to adata.var (default True).

  • **kwargs (Any) – Passed to scanpy.pp.highly_variable_genes.

Returns:

If inplace=False, returns the var DataFrame with HVG columns; else None.

Return type:

DataFrame or None

Spatially Variable Genes#

Wraps squidpy gr.spatial_autocorr (Moran’s I).

sc_tools.qc.spatially_variable_genes(adata, *, mode='moran', coord_key='spatial', genes=None, n_top_genes=None, threshold_i=None, copy_var=True, n_perms=100, n_jobs=1, **kwargs)[source]#

Compute spatially variable genes using squidpy (Moran’s I or Geary’s C).

Builds spatial neighbors if missing, runs spatial autocorrelation, and optionally writes results to adata.var (spatial_moran_i, spatial_pval, spatially_variable).

Parameters:
  • adata (AnnData) – Annotated data with adata.obsm[coord_key] (default ‘spatial’).

  • mode (str) – ‘moran’ or ‘geary’ (default ‘moran’).

  • coord_key (str) – Key in adata.obsm for coordinates (default ‘spatial’).

  • genes (list of str or None) – Genes to test; if None, uses adata.var_names (or highly_variable if present).

  • n_top_genes (int or None) – If set, mark this many top genes by statistic as spatially_variable.

  • threshold_i (float or None) – If set, mark genes with Moran’s I >= threshold_i as spatially_variable.

  • copy_var (bool) – If True, copy I and pval from uns to adata.var and add spatially_variable (default True).

  • n_perms (int) – Permutations for p-value (default 100). Passed to squidpy.

  • n_jobs (int) – Parallel jobs (default 1). Passed to squidpy.

  • **kwargs (Any) – Passed to squidpy.gr.spatial_autocorr.

Returns:

Autocorrelation results (index = genes) if copy_var is False or for inspection; otherwise None (results in adata.uns and adata.var).

Return type:

DataFrame or None

sc_tools.qc.spatially_variable_genes_per_library(adata, library_id_col='library_id', *, mode='moran', coord_key='spatial', n_top_genes=None, threshold_i=None, n_perms=100, n_jobs=1, **kwargs)[source]#

Run spatially_variable_genes on each library (sample) separately and store results in uns.

Spatial neighbors are built per sample, so each library is subset and processed independently. If library_id_col is not in adata.obs, returns None without error (caller should skip SVG and continue other QC).

Parameters:
  • adata (AnnData) – Annotated data with adata.obs[library_id_col] and adata.obsm[coord_key].

  • library_id_col (str) – Column in adata.obs identifying library/sample (default ‘library_id’).

  • mode (str) – Passed to spatially_variable_genes for each subset.

  • coord_key (str) – Passed to spatially_variable_genes for each subset.

  • n_top_genes (int | None) – Passed to spatially_variable_genes for each subset.

  • threshold_i (float | None) – Passed to spatially_variable_genes for each subset.

  • n_perms (int) – Passed to spatially_variable_genes for each subset.

  • n_jobs (int) – Passed to spatially_variable_genes for each subset.

  • **kwargs (Any) – Passed to spatially_variable_genes for each subset.

Returns:

Per-library DataFrames (index=genes, columns=spatial_i, spatial_pval, spatially_variable) stored in adata.uns[‘spatial_variable_per_library’]. Returns None if library_id_col is missing from adata.obs (caller should skip SVG).

Return type:

dict[str, DataFrame] or None

Sample-Level QC#

Per-sample metrics, adaptive MAD outlier detection, pass/fail classification.

sc_tools.qc.filter_spots(adata, modality='visium', min_counts=None, min_genes=None, max_pct_mt=None, sample_col=None, inplace=True)[source]#

Remove low-quality spots/cells using modality-aware defaults.

Parameters:
  • adata (AnnData) – Must have obs columns total_counts and n_genes_by_counts (from calculate_qc_metrics).

  • modality (str) – One of visium, visium_hd, xenium, cosmx, imc.

  • min_counts (int/float or None) – Override modality defaults. None means use the modality default (which itself may be None for max_pct_mt).

  • min_genes (int/float or None) – Override modality defaults. None means use the modality default (which itself may be None for max_pct_mt).

  • max_pct_mt (int/float or None) – Override modality defaults. None means use the modality default (which itself may be None for max_pct_mt).

  • sample_col (str or None) – If provided, log removal counts per sample.

  • inplace (bool) – If True, filter in place and return None.

Returns:

Filtered copy if inplace=False; else None.

Return type:

AnnData or None

sc_tools.qc.compute_sample_metrics(adata, sample_col='library_id', modality='visium', spaceranger_dirs=None)[source]#

Compute per-sample aggregate QC metrics.

Parameters:
  • adata (AnnData) – Must have QC columns in obs (total_counts, n_genes_by_counts, optionally pct_counts_mt).

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

  • modality (str) – Modality name (for future modality-specific metrics).

  • spaceranger_dirs (dict or None) – Mapping sample name -> Space Ranger outs/ directory. If provided, sequencing metrics are parsed from metrics_summary.csv.

Returns:

Indexed by sample with aggregate metric columns.

Return type:

pd.DataFrame

sc_tools.qc.classify_samples(metrics, modality='visium', thresholds=None, mad_multiplier=3.0, min_cohort_size_for_outlier=5)[source]#

Classify samples as pass/fail using absolute thresholds and MAD outlier detection.

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

  • modality (str) – Modality for default thresholds.

  • thresholds (dict or None) – Override default absolute thresholds. Keys match _SAMPLE_THRESHOLDS.

  • mad_multiplier (float) – Base MAD multiplier (adapted by cohort size).

  • min_cohort_size_for_outlier (int) – Skip outlier detection if fewer samples than this.

Returns:

Input DataFrame with added columns: qc_pass, qc_fail_reasons, qc_flag_absolute, qc_flag_outlier.

Return type:

pd.DataFrame

sc_tools.qc.save_pass_fail_lists(classified, output_dir, sample_col='library_id')[source]#

Write qc_sample_pass.csv and qc_sample_fail.csv.

Parameters:
  • classified (pd.DataFrame) – Output of classify_samples.

  • output_dir (str or Path) – Directory for output CSVs.

  • sample_col (str) – Name for the sample index column in output.

Returns:

(pass_path, fail_path)

Return type:

tuple of Path

sc_tools.qc.apply_qc_filter(adata, classified, sample_col='library_id', modality='visium', output_path=None, backup_path=None, min_counts=None, min_genes=None, max_pct_mt=None)[source]#

Full QC pipeline: backup, spot-level filter, sample removal, save.

Parameters:
  • adata (AnnData) – Raw AnnData (will be modified in place).

  • classified (pd.DataFrame) – Output of classify_samples with qc_pass column.

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

  • modality (str) – Modality for spot-filter defaults.

  • output_path (str or Path or None) – Save filtered AnnData here.

  • backup_path (str or Path or None) – Save full (unfiltered) backup here before filtering.

  • min_counts (int | None) – Override spot-filter defaults.

  • min_genes (int | None) – Override spot-filter defaults.

  • max_pct_mt (float | None) – Override spot-filter defaults.

Returns:

Filtered AnnData (spots filtered, failed samples removed).

Return type:

AnnData

HTML Reports#

sc_tools.qc.generate_qc_report(adata, metrics, classified, figures_dir, output_path, sample_col='library_id', modality='visium', title='QC Report', adata_post=None)[source]#

Generate a self-contained HTML QC report (legacy API).

Deprecated since version Use: generate_pre_filter_report, generate_post_filter_report, or generate_post_integration_report instead. This function is kept for backward compatibility.

Parameters:
  • adata (AnnData) – Pre-filter AnnData used for QC.

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

  • classified (pd.DataFrame) – Output of classify_samples (with qc_pass, qc_fail_reasons).

  • figures_dir (str or Path) – Base figures directory (unused for plot generation but kept for API compat).

  • output_path (str or Path) – Path for the output HTML file.

  • sample_col (str) – Sample column name.

  • modality (str) – Modality string for display.

  • title (str) – Report title.

  • adata_post (AnnData or None) – Post-filter AnnData.

Return type:

Path to the generated HTML file.

sc_tools.qc.generate_pre_filter_report(adata, metrics, classified, output_dir, *, sample_col='library_id', modality='visium', title='Pre-filter QC Report', date_stamp=None, segmentation_masks_dir=None)[source]#

Generate a pre-filter QC HTML report (Phase 1 entry).

Parameters:
  • adata (AnnData) – Pre-filter AnnData (raw counts, concatenated).

  • metrics (DataFrame) – Output of compute_sample_metrics.

  • classified (DataFrame) – Output of classify_samples.

  • output_dir (str | Path) – Directory for the output HTML file.

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

  • modality (str) – Modality string for display.

  • title (str) – Report title.

  • date_stamp (str | None) – YYYYMMDD string (default: today).

  • segmentation_masks_dir (str | Path | None) – Optional path to mask TIFFs for segmentation scoring.

Return type:

Path to the generated HTML file.

sc_tools.qc.generate_post_filter_report(adata_pre, adata_post, metrics, classified, output_dir, *, sample_col='library_id', modality='visium', title='Post-filter QC Report', date_stamp=None, segmentation_masks_dir=None)[source]#

Generate a post-filter QC HTML report (Phase 1-2 exit).

Parameters:
  • adata_pre (AnnData) – Pre-filter AnnData.

  • adata_post (AnnData) – Post-filter/annotated AnnData.

  • metrics (DataFrame) – Output of compute_sample_metrics.

  • classified (DataFrame) – Output of classify_samples.

  • output_dir (str | Path) – Directory for the output HTML file.

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

  • modality (str) – Modality string for display.

  • title (str) – Report title.

  • date_stamp (str | None) – YYYYMMDD string (default: today).

  • segmentation_masks_dir (str | Path | None) – Optional path to mask TIFFs for segmentation scoring.

Return type:

Path to the generated HTML file.

sc_tools.qc.generate_post_integration_report(adata, output_dir, *, embedding_keys=None, batch_key=None, celltype_key=None, sample_col='library_id', cluster_key='leiden', modality='visium', title='Post-integration QC Report', date_stamp=None, segmentation_masks_dir=None, comparison_df=None)[source]#

Generate a post-integration QC HTML report (Phase 3 exit).

Parameters:
  • adata (AnnData) – Normalized/integrated AnnData.

  • output_dir (str | Path) – Directory for the output HTML file.

  • embedding_keys (dict[str, str] | None) – Dict mapping method name to obsm key (auto-detected if None).

  • batch_key (str | None) – Batch column in obs (auto-detected from raw_data_dir/batch/library_id).

  • celltype_key (str | None) – Cell type column in obs (optional; skip bio metrics if absent).

  • sample_col (str) – Sample column for cluster distribution plot.

  • cluster_key (str) – Cluster column (default: leiden).

  • modality (str) – Modality string for display.

  • title (str) – Report title.

  • date_stamp (str | None) – YYYYMMDD string (default: today).

  • segmentation_masks_dir (str | Path | None) – Optional path to mask TIFFs for segmentation scoring.

  • comparison_df (DataFrame | None) – Pre-computed integration benchmark DataFrame. When provided, skips recomputing compare_integrations() (saves significant time on large datasets).

Return type:

Path to the generated HTML file.

sc_tools.qc.generate_post_celltyping_report(adata, output_dir, *, celltype_key='celltype', embedding_keys=None, batch_key=None, sample_col='library_id', cluster_key='leiden', modality='visium', title='Post-celltyping QC Report', date_stamp=None, segmentation_masks_dir=None, comparison_df=None, comparison_df_p3=None, integration_test_dir=None, marker_genes=None)[source]#

Generate a post-celltyping QC HTML report (Phase 4 exit).

Re-evaluates integration quality using validated cell type labels, making bio conservation metrics (ARI, NMI, ASW celltype) meaningful. Optionally re-scores all candidate integration embeddings stored in integration_test_dir.

Parameters:
  • adata (AnnData) – Cell-typed AnnData (with validated cell type labels).

  • output_dir (str | Path) – Directory for the output HTML file.

  • celltype_key (str) – Column in adata.obs with validated cell type labels. Required – raises ValueError if missing.

  • embedding_keys (dict[str, str] | None) – Dict mapping method name to obsm key (auto-detected if None).

  • batch_key (str | None) – Batch column in obs (auto-detected from raw_data_dir/batch/library_id).

  • sample_col (str) – Sample column for cluster distribution plot.

  • cluster_key (str) – Cluster column (default: leiden).

  • modality (str) – Modality string for display.

  • title (str) – Report title.

  • date_stamp (str | None) – YYYYMMDD string (default: today).

  • segmentation_masks_dir (str | Path | None) – Optional path to mask TIFFs for segmentation scoring.

  • comparison_df (DataFrame | None) – Pre-computed integration benchmark DataFrame (Phase 4, validated celltypes). When provided, skips recomputing compare_integrations().

  • comparison_df_p3 (DataFrame | None) – Pre-computed integration benchmark DataFrame from Phase 3 (using preliminary Leiden labels). Shown alongside Phase 4 values for comparison. Optional.

  • integration_test_dir (str | Path | None) – Path to results/tmp/integration_test/ directory containing per-method {method}.h5ad files from Phase 3 benchmark. If provided, embeddings are loaded and re-scored with validated cell type labels.

  • marker_genes (dict[str, list[str]] | None) – Optional dict mapping cell type name to a list of marker genes. When provided, a marker dotplot is included in the report.

Return type:

Path to the generated HTML file.

Raises:

ValueError – If celltype_key is not found in adata.obs.

sc_tools.qc.generate_segmentation_qc_report(adata, masks_dir, output_dir, *, sample_col='library_id', modality='visium', title='Segmentation QC Report', date_stamp=None)[source]#

Generate a standalone date-versioned segmentation QC HTML report.

Discovers mask files in masks_dir, computes per-ROI segmentation quality scores via sc_tools.bm.segmentation.score_segmentation, and optionally compares multiple mask methods per ROI using compare_segmentations.

Parameters:
  • adata (AnnData) – AnnData with sample annotations (used for context only).

  • masks_dir (str | Path) – Directory containing mask TIFF / NPY / NPZ files.

  • output_dir (str | Path) – Directory for the output HTML file.

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

  • modality (str) – Modality string for display.

  • title (str) – Report title.

  • date_stamp (str | None) – YYYYMMDD string (default: today).

Returns:

Path to the generated HTML file, or None if no masks found.

Return type:

Path or None

sc_tools.qc.generate_all_qc_reports(adata_pre, metrics, classified, output_dir, *, adata_post=None, adata_integrated=None, sample_col='library_id', modality='visium', date_stamp=None, embedding_keys=None, batch_key=None, celltype_key=None, cluster_key='leiden', segmentation_masks_dir=None)[source]#

Generate all three QC reports in one call.

Parameters:
  • adata_pre (AnnData) – Pre-filter AnnData (for pre-filter report).

  • metrics (DataFrame) – Output of compute_sample_metrics.

  • classified (DataFrame) – Output of classify_samples.

  • output_dir (str | Path) – Directory for all HTML reports.

  • adata_post (AnnData | None) – Post-filter AnnData (for post-filter report). Skipped if None.

  • adata_integrated (AnnData | None) – Post-integration AnnData (for post-integration report). Skipped if None.

  • **kwargs – Remaining keyword arguments (sample_col, modality, date_stamp, embedding_keys, batch_key, celltype_key, cluster_key, segmentation_masks_dir) are passed through to individual report generators.

  • sample_col (str)

  • modality (str)

  • date_stamp (str | None)

  • embedding_keys (dict[str, str] | None)

  • batch_key (str | None)

  • celltype_key (str | None)

  • cluster_key (str)

  • segmentation_masks_dir (str | Path | None)

Returns:

Mapping of report type to output path.

Return type:

dict[str, Path]

QC Plots#

sc_tools.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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.qc.plots.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