170 lines
5.1 KiB
Python
170 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Prepare AnnData for scvi-tools models.
|
|
|
|
This script handles QC filtering, HVG selection, and layer setup.
|
|
Output is ready for any scvi-tools model.
|
|
|
|
Usage:
|
|
python prepare_data.py input.h5ad output.h5ad --batch-key batch --n-hvgs 2000
|
|
python prepare_data.py input.h5ad output.h5ad --no-filter # Skip QC filtering
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
|
|
def prepare_data(
|
|
adata,
|
|
batch_key=None,
|
|
n_top_genes=2000,
|
|
min_genes=200,
|
|
max_genes=5000,
|
|
max_mito_pct=20.0,
|
|
min_cells=3,
|
|
skip_filter=False
|
|
):
|
|
"""
|
|
Prepare AnnData for scvi-tools.
|
|
|
|
Parameters
|
|
----------
|
|
adata : AnnData
|
|
Raw count data
|
|
batch_key : str, optional
|
|
Batch column for batch-aware HVG selection
|
|
n_top_genes : int
|
|
Number of highly variable genes
|
|
min_genes : int
|
|
Minimum genes per cell
|
|
max_genes : int
|
|
Maximum genes per cell
|
|
max_mito_pct : float
|
|
Maximum mitochondrial percentage
|
|
min_cells : int
|
|
Minimum cells per gene
|
|
skip_filter : bool
|
|
Skip QC filtering (use if already filtered)
|
|
|
|
Returns
|
|
-------
|
|
AnnData prepared for scvi-tools
|
|
"""
|
|
import scanpy as sc
|
|
import numpy as np
|
|
from model_utils import get_mito_genes
|
|
|
|
adata = adata.copy()
|
|
print(f"Input: {adata.shape[0]} cells, {adata.shape[1]} genes")
|
|
|
|
if not skip_filter:
|
|
# Calculate QC metrics
|
|
adata.var['mt'] = get_mito_genes(adata)
|
|
sc.pp.calculate_qc_metrics(adata, qc_vars=['mt'], inplace=True)
|
|
|
|
# Filter cells
|
|
n_before = adata.n_obs
|
|
adata = adata[adata.obs['n_genes_by_counts'] >= min_genes].copy()
|
|
adata = adata[adata.obs['n_genes_by_counts'] <= max_genes].copy()
|
|
adata = adata[adata.obs['pct_counts_mt'] < max_mito_pct].copy()
|
|
print(f"Filtered cells: {n_before} → {adata.n_obs}")
|
|
|
|
# Filter genes
|
|
n_genes_before = adata.n_vars
|
|
sc.pp.filter_genes(adata, min_cells=min_cells)
|
|
print(f"Filtered genes: {n_genes_before} → {adata.n_vars}")
|
|
|
|
# Store raw counts in layer
|
|
adata.layers["counts"] = adata.X.copy()
|
|
|
|
# HVG selection
|
|
if batch_key is not None and batch_key in adata.obs.columns:
|
|
print(f"Selecting {n_top_genes} HVGs (batch-aware: {batch_key})")
|
|
sc.pp.highly_variable_genes(
|
|
adata,
|
|
n_top_genes=n_top_genes,
|
|
flavor="seurat_v3",
|
|
batch_key=batch_key,
|
|
layer="counts"
|
|
)
|
|
else:
|
|
print(f"Selecting {n_top_genes} HVGs")
|
|
# Need to normalize for non-seurat_v3
|
|
sc.pp.normalize_total(adata, target_sum=1e4)
|
|
sc.pp.log1p(adata)
|
|
sc.pp.highly_variable_genes(adata, n_top_genes=n_top_genes)
|
|
# Restore counts to X
|
|
adata.X = adata.layers["counts"].copy()
|
|
|
|
# Subset to HVGs
|
|
n_hvg = adata.var['highly_variable'].sum()
|
|
adata = adata[:, adata.var['highly_variable']].copy()
|
|
print(f"Selected {n_hvg} highly variable genes")
|
|
|
|
print(f"Output: {adata.shape[0]} cells, {adata.shape[1]} genes")
|
|
|
|
return adata
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Prepare AnnData for scvi-tools",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Basic preparation
|
|
python prepare_data.py raw.h5ad prepared.h5ad
|
|
|
|
# With batch-aware HVG selection
|
|
python prepare_data.py raw.h5ad prepared.h5ad --batch-key sample
|
|
|
|
# Custom parameters
|
|
python prepare_data.py raw.h5ad prepared.h5ad --n-hvgs 3000 --max-mito 15
|
|
|
|
# Skip filtering (data already QC'd)
|
|
python prepare_data.py filtered.h5ad prepared.h5ad --no-filter
|
|
"""
|
|
)
|
|
parser.add_argument("input", help="Input h5ad file")
|
|
parser.add_argument("output", help="Output h5ad file")
|
|
parser.add_argument("--batch-key", help="Batch column for HVG selection")
|
|
parser.add_argument("--n-hvgs", type=int, default=2000, help="Number of HVGs (default: 2000)")
|
|
parser.add_argument("--min-genes", type=int, default=200, help="Min genes per cell (default: 200)")
|
|
parser.add_argument("--max-genes", type=int, default=5000, help="Max genes per cell (default: 5000)")
|
|
parser.add_argument("--max-mito", type=float, default=20.0, help="Max mito %% (default: 20)")
|
|
parser.add_argument("--min-cells", type=int, default=3, help="Min cells per gene (default: 3)")
|
|
parser.add_argument("--no-filter", action="store_true", help="Skip QC filtering")
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
import scanpy as sc
|
|
except ImportError:
|
|
print("Error: scanpy required. Install with: pip install scanpy")
|
|
sys.exit(1)
|
|
|
|
# Load data
|
|
print(f"Loading {args.input}...")
|
|
adata = sc.read_h5ad(args.input)
|
|
|
|
# Prepare
|
|
adata = prepare_data(
|
|
adata,
|
|
batch_key=args.batch_key,
|
|
n_top_genes=args.n_hvgs,
|
|
min_genes=args.min_genes,
|
|
max_genes=args.max_genes,
|
|
max_mito_pct=args.max_mito,
|
|
min_cells=args.min_cells,
|
|
skip_filter=args.no_filter
|
|
)
|
|
|
|
# Save
|
|
print(f"Saving to {args.output}...")
|
|
adata.write_h5ad(args.output)
|
|
print("Done!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|