skills/pysam/references/cram_and_performance.md
This reference targets pysam 0.24.0, which embeds HTSlib/samtools/bcftools 1.23.1.
Pysam 0.24 is the first pysam release to wrap HTSlib 1.22 or later. Two inherited operational changes matter:
For compatibility with a consumer that cannot read CRAM 3.1:
with pysam.AlignmentFile(
"output.cram",
"wc",
header=header,
reference_filename="reference.fa",
format_options=["version=3.0"],
) as output:
...
In pysam 0.24, format_options accepts a list of Python str values as
documented.
CRAM commonly stores differences from a reference rather than complete read sequences. A matching reference can therefore be required to decode or encode records.
Reference identity is represented by M5 MD5 values and often UR fields in
@SQ header lines. Contig names alone are not sufficient.
Treat the reference as part of the data provenance:
.faiSome CRAM files embed all or part of their reference, but do not assume that every CRAM is self-contained.
Prefer an explicit local FASTA:
import pysam
pysam.faidx("reference.fa")
with pysam.AlignmentFile(
"sample.cram",
"rc",
reference_filename="reference.fa",
require_index=True,
threads=4,
) as cram:
for read in cram.fetch("chr1", 1_000, 2_000):
...
Use the same reference when writing:
with pysam.AlignmentFile("input.bam", "rb") as source, pysam.AlignmentFile(
"output.cram",
"wc",
template=source,
reference_filename="reference.fa",
threads=4,
) as destination:
for read in source.fetch(until_eof=True):
destination.write(read)
Then create a CRAI and read the output back with the same FASTA.
REF_PATH and REF_CACHEUse these HTSlib environment variables only when reference-by-MD5 lookup is intentional:
REF_PATH: colon-separated local paths and optionally URLs used to find a
sequence by its M5 checksumREF_CACHE: location where retrieved references are cachedA local cache layout commonly uses:
/reference-cache/%2s/%2s/%s
Do not add a remote endpoint merely to make an error disappear. Network reference lookup changes reproducibility, privacy, availability, and cache behavior. Pysam 0.24 intentionally inherits HTSlib's removal of implicit EBI fetching.
When remote lookup is required:
An explicit reference_filename= is usually clearer for a single known
assembly.
For bulk conversion:
import pysam.samtools
pysam.samtools.view(
"-@",
"4",
"-C",
"-T",
"reference.fa",
"-o",
"output.cram",
"input.bam",
catch_stdout=False,
)
pysam.samtools.index(
"-@",
"4",
"output.cram",
catch_stdout=False,
)
Using -o plus catch_stdout=False avoids capturing binary CRAM output in
Python memory.
Validate:
pysam.samtools.quickcheck("-v", "output.cram")
with pysam.AlignmentFile(
"output.cram",
"rc",
reference_filename="reference.fa",
) as cram:
first = next(cram.fetch(until_eof=True), None)
quickcheck is structural, not a full decode or biological validation.
Compare record counts and selected records to the source.
When CRAM open/fetch fails, check in this order:
.fai exists and lengths match the alignment header.@SQ M5 and UR fields.reference_filename= explicitly.Do not disable validation or substitute a similarly named assembly.
Depending on how the wheel/build was configured, HTSlib can read HTTP(S) and other plugin-backed URLs. Random access also needs a reachable index and range request support.
with pysam.AlignmentFile(
"https://example.org/data/sample.bam",
"rb",
index_filename="https://example.org/data/sample.bam.bai",
) as bam:
for read in bam.fetch("chr1", 1_000, 2_000):
...
Remote support is build- and protocol-dependent. Before large analysis:
Many tiny random queries can be slower and more expensive than downloading or staging a file once. Never put bearer tokens or secrets directly in committed URLs.
threads= on AlignmentFile, VariantFile, and TabixFile controls HTSlib
compression/decompression threads:
with pysam.AlignmentFile("sample.bam", "rb", threads=4) as bam:
...
It does not parallelize Python filtering, pileup interpretation, or statistical analysis. More threads can increase memory and I/O contention, and returns diminish when storage or network is the bottleneck.
threads > 1 cannot be combined with ignore_truncation=True.
Pysam releases the GIL for many I/O-intensive operations, but not every code path has been comprehensively validated for thread safety.
Avoid sharing one active file handle across threads. Prefer:
fetch(..., multiple_iterators=True) iterators when the
overhead is acceptableVariant iterators use fetch(..., reopen=True); Tabix and alignment iterators
use multiple_iterators=True.
Reopening a remote file for every small iterator can be especially expensive.
Partition work by independent contigs or nonoverlapping windows, but reopen files inside each process:
def process_region(bam_path: str, contig: str, start: int, stop: int):
with pysam.AlignmentFile(bam_path, "rb", threads=1) as bam:
return bam.count(contig, start, stop, read_callback="all")
Do not pass live pysam handles or proxy records between processes. Balance
process count and HTSlib threads; multiplying both can oversubscribe CPUs.
When partitioning pileup or coverage, include any necessary boundary context and trim results back to the target windows. Reads overlap partition boundaries.
Use fetch(until_eof=True) for alignments and normal iteration for variants.
Sort region requests by contig/start to improve locality. Merge heavily overlapping windows when duplicate processing is not desired.
The wrapped samtools/bcftools implementations are usually faster and more tested than equivalent Python loops.
count() is appropriate for overlap-record counts.count_coverage() efficiently returns dense A/C/G/T arrays for an interval.pileup() is needed for base/read-level state but has more Python overhead.Pysam exposes proxy objects backed by HTSlib memory. Keep the owning file and iterator alive while using:
PileupColumnPileupReadpersist=False FASTX recordsCopy primitive values if data must outlive iteration.
pysam==0.24.0)pysam.__version__ and pysam.__samtools_version__