nano-scGPT / tasks /embedding.py
paradoxdan's picture
Upload 10 files
1aaaecc verified
Raw
History Blame Contribute Delete
4.19 kB
import os
import scanpy as sc
import argparse
from tqdm import tqdm
import numpy as np
import torch
from torch.utils.data import DataLoader
from nano_scgpt.scGPT_tokenizer import _check_log1ped, scGPTTokenizer, scGPTDataset
from nano_scgpt.model import scGPTModel
DEFAULT_INPUT_URL = "https://datasets.cellxgene.cziscience.com/d6761a21-e226-434f-9370-fbcc7e549aa0.h5ad"
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str, required=False, help="Path to the local input .h5ad file.")
parser.add_argument("--input_url", type=str, required=False, default=DEFAULT_INPUT_URL, help="URL to the input .h5ad file. Ignored if --input is provided.")
parser.add_argument("--output", type=str, default="scGPT_embeddings.npy", help="Path to save the output embeddings.")
parser.add_argument("--batch_size", type=int, default=64, help="Batch size for processing the data.")
args = parser.parse_args()
# Load the input data.
if args.input:
adata = sc.read(args.input)
else:
print(f"Downloading input data from {args.input_url}...")
if os.path.exists("data/tmp.h5ad"):
os.remove("data/tmp.h5ad")
adata = sc.read("data/tmp.h5ad", backup_url=args.input_url)
# Ensure gene symbols are available in adata.var['gene_symbol'].
if 'gene_symbol' not in adata.var.columns:
print("The input .h5ad file has no 'gene_symbol' column, checking if the var_names contains gene symbols...")
var_names = adata.var_names.astype(str)
if all([s.startswith("ENSG") for s in var_names[:100]]):
print("Detected Ensembl IDs in var_names. Checking column `feature_name` for gene symbols...")
if 'feature_name' in adata.var.columns:
adata.var['gene_symbol'] = adata.var['feature_name']
else:
raise ValueError("No gene symbols found in the input data. Please provide a .h5ad file with gene symbols in the 'gene_symbol' or 'feature_name' column or as var_names.")
else:
print("var_names appears to contain gene symbols. Adding to 'gene_symbol' column...")
adata.var['gene_symbol'] = var_names
adata.var_names = adata.var["gene_symbol"].astype(str)
adata.var_names_make_unique(join="_")
print(f"Embedding adata of shape {adata.shape}...")
# Normalize the data.
print("Normalizing the data...")
sc.pp.normalize_total(adata, target_sum=1e4)
if not _check_log1ped(adata.X):
sc.pp.log1p(adata)
tokenizer = scGPTTokenizer.from_pretrained("scGPT_human")
dataset = scGPTDataset(adata, tokenizer)
try:
num_workers = min(len(os.sched_getaffinity(0)) - 1, args.batch_size)
except AttributeError:
num_workers = min(os.cpu_count(), 0, args.batch_size)
dataloader = DataLoader(dataset, batch_size=args.batch_size, shuffle=False, collate_fn=dataset.collate_fn, num_workers=num_workers, pin_memory=True)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if torch.backends.mps.is_available():
device = torch.device("mps")
# Load Model.
model = scGPTModel.from_pretrained("scGPT_human")
model.eval()
model.to(device)
if device.type == "cuda":
model = torch.compile(model)
embeddings = []
with torch.no_grad(), torch.amp.autocast(device_type=device.type, enabled=device.type=="cuda"):
for batch in tqdm(dataloader, desc="Embedding cells"):
gene_ids, exprs, padding_mask = batch["gene_ids"], batch["exprs"], batch["padding_mask"]
gene_ids, exprs, padding_mask = gene_ids.to(device), exprs.to(device), padding_mask.to(device)
batch_embeddings = model.encode(gene_ids, exprs, padding_mask) # shape [B, D]
embeddings.append(batch_embeddings.cpu())
embeddings = torch.cat(embeddings, dim=0)
embeddings = embeddings / torch.linalg.norm(embeddings, dim=-1, keepdim=True)
# Save the embeddings to the output path
np.save(args.output, embeddings.numpy())
print(f"Saved {embeddings.shape[0]} embeddings of dimension {embeddings.shape[1]} to {args.output}")