skills/geniml/references/scembed.md
Verified against geniml==0.8.4 release source, current Gtars
0.9.2, and official BEDbase documentation on 2026-07-23.
scEmbed learns region embeddings from scATAC-seq accessibility and pools them to represent cells. The primary paper reports that pre-trained region embeddings can support clustering and transfer to unseen datasets. Do not turn that result into a universal accuracy claim; performance depends on assay, reference corpus, universe, filtering, cell types, and split design.
Primary source: LeRoy et al. (2024), Fast clustering and cell-type annotation of scATAC data with pre-trained embeddings, doi:10.1093/nargab/lqae073.
Use:
from geniml.scembed.main import ScEmbed
from geniml.region2vec.utils import Region2VecDataset
from geniml.tokenization.utils import tokenize_anndata
from gtars.tokenizers import Tokenizer
Do not use from geniml.scembed import ScEmbed: the 0.8.4 package
__init__ does not export it. The installed geniml scembed command parses
legacy MatrixMarket options but its 0.8.4 command body does no training or
encoding.
The source method ScEmbed.encode(adata) is public, but 0.8.4's nested token
handling does not match the current tokenize_anndata return shape observed
with modern Gtars. Require a pinned synthetic smoke test before relying on
that convenience method. For production, pre-tokenize explicitly, inspect the
shape, and keep the exact versions locked.
The AnnData object must satisfy:
obs) are cells;var) are accessible regions/features;var["chr"], var["start"], and var["end"] describe each feature;X is sparse CSR for bounded tokenization performance;Confirm matrix orientation. A 10x peak-by-barcode MatrixMarket file is often
transposed when constructing AnnData; inspect dimensions instead of copying a
blind .T.
Do not expose barcodes, patient IDs, phenotypes, rare cell labels, or raw
intervals in logs. An .h5ad may contain identifying metadata in obs,
uns, embeddings, and file provenance. Output only bounded aggregate counts
unless the user explicitly approves disclosure.
Split before fitting or selecting anything:
Randomly splitting cells from the same donor leaks donor- and batch-specific accessibility. Building a consensus universe from all patients can also leak test-set feature prevalence even when labels are hidden.
Audit a local manifest without printing metadata values:
python skills/geniml/scripts/corpus_auditor.py \
--manifest data/cells.tsv \
--group-column patient_id \
--split-column split \
--assembly-column assembly
Use a local, checksummed universe from the training partition:
from gtars.tokenizers import Tokenizer
tokenizer = Tokenizer.from_bed("refs/training_universe.bed")
Record:
len(tokenizer).Do not use Tokenizer.from_pretrained("organization/model") unless the user
approves a network download and supplies a pinned revision and expected
hashes. A model and tokenizer are compatible only when the exact universe,
special-token IDs, and model vocabulary size agree.
import pyarrow as pa
import pyarrow.parquet as pq
import scanpy as sc
from geniml.tokenization.utils import tokenize_anndata
adata = sc.read_h5ad("data/train.h5ad")
adata.X = adata.X.tocsr()
encoded_cells = tokenize_anndata(adata, tokenizer)
cells = [encoded["input_ids"] for encoded in encoded_cells]
table = pa.table({
"tokens": pa.array(cells, type=pa.list_(pa.int32()))
})
pq.write_table(table, "work/train_tokens.parquet")
Before writing:
len(cells) == adata.n_obs;[0, len(tokenizer));The upstream issue
databio/geniml#14
(opened 2025-09-05) proposes moving away from one .gtok file per cell.
Prefer the single Parquet corpus for current work; treat .gtok as legacy.
from geniml.region2vec.utils import Region2VecDataset
from geniml.scembed.main import ScEmbed
dataset = Region2VecDataset(
"work/train_tokens.parquet",
shuffle=True,
)
model = ScEmbed(
tokenizer=tokenizer,
embedding_dim=100,
pooling_method="mean",
device="cpu",
)
model.train(
dataset,
window_size=5,
epochs=10,
min_count=10,
num_cpus=4,
seed=42,
)
Bound cells, nonzeros, tokens per cell, workers, epochs, checkpoint frequency,
RAM, and disk. Region2VecDataset loads the full Parquet token column into
memory. Training uses Gensim and Torch; Gensim checkpoint loading is unsafe for
untrusted .model files.
Generate a run plan first:
python skills/geniml/scripts/embedding_plan.py \
--mode scembed \
--data work/train_tokens.parquet \
--universe refs/training_universe.bed \
--output-dir work/scembed \
--assembly GRCh38 \
--embedding-dim 100 --epochs 10 --workers 4 --seed 42
from pathlib import Path
import shutil
bundle = Path("models/scembed")
model.export(str(bundle))
shutil.copyfile(
"refs/training_universe.bed",
bundle / "universe.bed",
)
As in Region2Vec, the 0.8.4 export utility writes checkpoint.pt and
config.yaml but does not write the tokenizer universe. Add the exact
validated universe.bed yourself and generate checksums.
Inspect before loading:
python skills/geniml/scripts/model_artifact_inspector.py \
--model-dir models/scembed
python skills/geniml/scripts/tokenizer_compatibility.py \
--model-dir models/scembed \
--universe refs/training_universe.bed \
--assembly GRCh38
Then, for a trusted local bundle:
from geniml.scembed.main import ScEmbed
model = ScEmbed.from_pretrained("models/scembed")
This classmethod is local. In contrast,
ScEmbed(model_path="organization/model") downloads three files through
Hugging Face Hub. Never trigger that constructor implicitly. Pin a revision,
cache path, expected size, and checksums when a download is explicitly
approved.
checkpoint.pt is loaded with Torch weights_only=True. Continue to treat it
as untrusted until verified and load in an isolated, resource-bounded
environment. Never inspect it using pickle.
After a pinned synthetic smoke test confirms the installed convenience API:
embeddings = model.encode(adata, pooling="mean")
assert embeddings.shape[0] == adata.n_obs
adata.obsm["X_scembed"] = embeddings
If the smoke fails, do not patch around token nesting silently. Pin a known compatible Geniml/Gtars pair or implement an explicit, tested projection using the verified token IDs and model contract. Never substitute a different universe to make shapes fit.
For Scanpy downstream analysis:
import scanpy as sc
sc.pp.neighbors(adata, use_rep="X_scembed")
sc.tl.leiden(adata, resolution=0.5, random_state=42)
sc.tl.umap(adata, random_state=42)
UMAP and Leiden are exploratory unless validated on held-out donors. Store software versions, seeds, neighborhood parameters, and the embedding checksum.
The release contains geniml.scembed.annotation.Annotator, which queries a
Qdrant collection and can use local or remote endpoints. That is a separate
network/data-disclosure decision: embeddings and metadata can be sensitive.
Do not create or contact an annotation server without explicit approval.
For any KNN annotation:
k and score thresholds on validation donors;Never send raw barcodes, patient metadata, or interval lists to a hosted vector store by default.
Report:
Do not select clusters, labels, or universe parameters by inspecting the test UMAP. If pre-trained public models were trained on overlapping donors or datasets, document that possible leakage.