pysam 0.24 CRAM 实战指南:参考序列管理、远程 I/O、线程与性能优化
【免费下载链接】scientific-agent-skillsTurn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000+ scientists worldwide. 165 ready-to-use validated skills plus 100+ scientific databases covering biology, chemistry, medicine, and drug discovery. Compatible with Cursor, Claude Code, Codex, Pi, Antigravity, and the open Agent Skills standard.项目地址: https://gitcode.com/GitHub_Trending/cl/scientific-agent-skills
导读
本指南围绕 pysam 0.24.0(内嵌 HTSlib/samtools/bcftools 1.23.1)在 CRAM 压缩格式上的关键行为变化展开,系统讲解 CRAM 参考序列的正确使用与溯源、REF_PATH/REF_CACHE配置边界、BAM 到 CRAM 的批量转换与校验、远程文件随机访问、threads并发模型与访问模式选择,最终给出可落地的复现性检查清单。读完本文,你将能够写出确定性、可复现且高性能的 CRAM 读写与分析代码,并能系统排查"CRAM 打不开/解不开"这类高频故障。本文以仓库中的 cram_and_performance.md 为骨架,并结合 SKILL.md、migration_to_0_24.md 与配套脚本源码进行纵深佐证。
pysam 0.24 带来的两项 CRAM 行为变化
pysam 0.24 是第一个封装 HTSlib 1.22 及以上版本的 pysam 发行版(当前基线为 1.23.1)。随版本升级,它继承了两项影响所有 CRAM 用户的运行行为变化,是理解本主题的前提:
- 新写入的 CRAM 默认使用 CRAM 3.1 而非 3.0。CRAM 3.1 是更新的压缩格式版本,但下游消费者若不支持 3.1,读写会直接失败。
- HTSlib 默认不再连接 EBI CRAM 参考序列服务器。这意味着以往"没给参考也能自动联网下载"的隐性行为被移除,没有显式参考的 CRAM 解码可能直接报错。
为不兼容 CRAM 3.1 的消费者降级输出
若下游工具只认 CRAM 3.0,可通过format_options显式指定输出版本:
with pysam.AlignmentFile( "output.cram", "wc", header=header, reference_filename="reference.fa", format_options=["version=3.0"], ) as output: ...在 pysam 0.24 中,format_options按文档接受 Pythonstr类型列表(即["version=3.0"]而非 bytes)。此前 Python 3 下需要字节编码的变通写法在新版本中不再必要,可参考 migration_to_0_24.md 中的相关说明。
不过要提醒的是:降级到 3.0 只是兼容性过渡手段,官方建议优先升级下游工具链以支持 3.1,而不是长期强制输出旧版本。
为什么 CRAM 离不开参考序列
CRAM 的核心压缩策略是"只存储读段与参考序列之间的差异",而非完整的读段序列。因此,解码(读取)和编码(写入)CRAM 时,通常都要求一份与编码时一致的参考序列。
参考序列的身份信息通过@SQ头行中的字段表达:
M5:参考序列的 MD5 校验值,是"这份序列是否匹配"的最强证据;UR:参考序列的 URI/来源信息;- 仅靠 contig 名称不足以判断匹配——不同版本的组装可能使用完全相同的名字,却对应不同的碱基序列。
把参考序列当作数据溯源的一部分
正确的做法是把参考序列视为 CRAM 数据血缘(provenance)的组成部分,与数据文件本身同等对待:
- 保存创建 CRAM 时使用的精确 FASTA 文件;
- 保存其
.fai索引(可用pysam.faidx("reference.fa")生成); - 记录组装名称/版本与校验和(如 sha256 或 M5);
- 当参考缓存是唯一解码来源时,备份参考缓存内容;
- 定期核对contig 长度与
M5标签是否与比对头一致。
需要说明的是:虽然某些 CRAM 文件会把参考序列的全部或部分内嵌进文件本身(self-contained),但绝不能默认每个 CRAM 都是自包含的,显式提供参考仍是唯一可靠路径。仓库配套的 inspect_hts.py 和 alignment_qc.py 均在源码层面强制了这一点:CRAM 输入未传--reference时直接抛错(见inspect_hts.py中CRAM inspection requires --reference与alignment_qc.py中CRAM QC requires --reference),从工程上杜绝隐式联网查参考。
确定性的本地 CRAM 访问
对已知组装的单一场景,始终优先显式指定本地 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): ...注意模式字符串是"rc"(读 + CRAM),而不是 BAM 的"rb"。require_index=True会在执行区域查询前强制校验 CRAI 索引存在,避免在无索引时静默回退。
写入端使用同一份参考
写入 CRAM 时同样显式传参考,并且用template=source继承源文件的头信息:
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)写完后创建 CRAI 索引(pysam.samtools.index或AlignmentFile关闭前的建索引流程),再用同一份 FASTA 读回验证。读与写必须使用完全相同的参考序列——这是 CRAM 正确性的第一原则。
REF_PATH与REF_CACHE:只在有意的按 MD5 查找时才使用
HTSlib 提供两个环境变量用于"按 MD5 校验值查找参考序列"的机制:
| 环境变量 | 作用 |
|---|---|
REF_PATH | 冒号分隔的本地路径(可选 URL),用于按 M5 校验和定位参考序列 |
REF_CACHE | 检索到的参考序列被缓存的位置 |
一个典型的本地缓存目录布局形如:
/reference-cache/%2s/%2s/%s其中%s对应参考序列的 M5 值,%2s是其两位/四位前缀子目录,HTSlib 按此三级路径组织缓存文件。
关键原则:不要为了让报错消失而添加远程端点
pysam 0.24 有意继承了 HTSlib "移除隐式 EBI 抓取"的行为。远程参考查找会改变可复现性、隐私性、可用性与缓存行为,仅仅为了消除某个错误就配置REF_PATH指向公网服务器是不被推荐的。仓库文档与 migration_to_0_24.md 保持一致:只应在"有意的受管校验和查找"场景下配置这两个变量。
当确实需要远程按 MD5 查找时,至少做到:
- 尽量配置受控的机构级代理/缓存,而不是直连公网;
- 在远程源之前放置本地缓存;
- 确保下载经过校验和验证;
- 书面记录网络与保留(retention)行为;
- 绝不在 URL 与日志中携带凭据。
对于单个已知组装,reference_filename=显式传参通常比配置REF_PATH/REF_CACHE更清晰、更不易出错。
用封装的 samtools 批量转换 BAM 到 CRAM
需要批量转换时,优先使用 pysam 内嵌的pysam.samtools命令分发器,而不是写逐记录 Python 循环——成熟命令实现通常更快、测试更充分(见 common_workflows.md 中的同类建议)。
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, )这里两个要点:
-C表示输出 CRAM,-T reference.fa显式指定参考,-@ 4使用 4 个压缩线程;- 使用
-o输出到文件并配合catch_stdout=False,避免把二进制 CRAM 数据捕获进 Python 内存。这也是 SKILL.md 与迁移文档反复强调的规则:大输出或二进制输出务必走-o+catch_stdout=False。
转换后验证
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只做结构性检查(文件能否打开、容器是否完整),不是完整解码,更不是生物学意义上的有效性验证。可靠的做法是把记录总数和代表性记录与源 BAM 对比,例如用 alignment_qc.py 分别统计转换前后文件的聚合计数再核对。
CRAM 故障排查清单
当 CRAM 打开或fetch()失败时,按下述顺序逐项排查:
- 确认FASTA 组装版本与 contig 名称(
chr1与1的命名差异是高频坑); - 确认
.fai存在,且各 contig 长度与比对头一致; - 检查
@SQ行中的M5与UR字段; - 显式传
reference_filename=,排除任何隐式查找路径; - 确认CRAI 索引与当前 CRAM 匹配(文件更新后旧索引会导致随机访问错乱);
- 用顺序读(
fetch(until_eof=True))做一次隔离实验,把"索引问题"与"参考问题"分开定位; - 捕获HTSlib/samtools 的完整错误消息。
文档明确警告:不要为了绕过错误而关闭校验(如跳过索引检查),也不要用"名字相似"的组装去凑合解码——M5对不上就是错误,强行替换只会产出静默错误的结果。
远程 HTSlib I/O:构建与协议相关
HTSlib 能否读取 HTTP(S) 等远程 URL,取决于 wheel 的构建配置和可用的插件(libcurl 等)。即便支持,随机访问还要求远程端提供可达的索引文件,且服务器支持 range 请求。示例:
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): ...注意这里显式传了index_filename——随机区域查询必须有配套索引。
大规模分析前的远程访问预检
由于远程支持是构建相关、协议相关的,正式跑大规模分析前应:
- 先发起一个小型已知区域查询验证连通性;
- 核对索引 URL 的精确性(例如
.bam.bai还是.csi); - 确认内容的版本/不可变性(远程文件被覆盖会导致索引错位);
- 确认重试、超时行为,确保凭据不出现在日志中;
- 根据访问模式预估请求数量。
一个必须牢记的教训:大量细碎的随机小查询,往往比一次性下载/暂存整个文件更慢、更贵。远程文件适合少量区域查询;若分析需要遍历或大量随机命中,先把文件 stage 到本地更划算。另外,绝不要把 bearer token 或密钥直接写进提交到仓库的 URL 中。
threads=:压缩/解压线程,不是 Python 分析并行
threads=参数同时存在于AlignmentFile、VariantFile、TabixFile上,用于控制HTSlib 内部的压缩/解压缩线程数(api_reference.md 中签名默认值均为threads=1):
with pysam.AlignmentFile("sample.bam", "rb", threads=4) as bam: ...它不会并行化 Python 侧的过滤、pileup 解释或统计分析。使用注意点:
- 线程越多,内存占用与 I/O 争用越高;
- 当存储或网络是瓶颈时,增加线程的收益迅速衰减(甚至更慢);
threads > 1不能与ignore_truncation=True同时使用。
Python 线程安全:句柄不共享,迭代器要独立
pysam 在大量 I/O 密集操作时会释放 GIL,但并非每条代码路径都经过全面的线程安全验证。因此核心规则是:
- 不要在多线程间共享一个活动的文件句柄;
- 优先每个 worker 独立打开一个句柄;
- 或者当开销可接受时,使用独立的
fetch(..., multiple_iterators=True)迭代器(multiple_iterators是 API 中明确提供的参数,见 api_reference.md)。
不同文件类型的多迭代器机制不同:Variant 迭代器使用fetch(..., reopen=True),而Tabix 与 alignment 迭代器使用multiple_iterators=True。混用或漏用都会导致迭代器互相干扰。
另一点代价敏感:对远程文件,每个小迭代器都 reopen 一次会非常昂贵——尽量复用较少的、较大的迭代器。
多进程并行:按 contig/窗口分区,进程内重开文件
把工作按独立的 contig 或互不重叠的窗口分区,并在每个进程内部重新打开文件:
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")三条纪律:
- 绝不在进程间传递活动的 pysam 句柄或代理记录——句柄绑定 HTSlib 内部内存,跨进程传递必然出错;
- 平衡进程数与 HTSlib
threads:每个进程 4 线程 × 8 进程 = 32 线程,两者相乘会轻松导致 CPU 超订(oversubscription); - 分区 pileup/coverage 时必须包含边界上下文:读段会跨过分区边界,因此在边界处要取重叠区域计算,再把结果裁剪回目标窗口,否则边界位置的覆盖度/碱基计数会系统性偏小。
访问模式选择:三种情况三种做法
优先顺序扫描的场景
- 需要每条记录;
- 文件没有索引;
- 远程请求开销占主导;
- 在计算聚合 QC。
此时用fetch(until_eof=True)顺序流式读取(变体文件用普通迭代),不依赖索引。这正是仓库脚本 alignment_qc.py 的默认行为——全文件扫描无需索引,并把该语义明确写入文档字符串。
优先索引查询的场景
- 只需要基因组的一小部分区域;
- 区域分组且有序;
- 有可用且最新的索引。
技巧:按 contig/start排序区域请求以提升局部性;当多个窗口高度重叠且不希望重复处理时,先合并重叠窗口。
优先原生批量命令的场景
- 排序、合并、建索引、格式转换;
- VCF 归一化;
- 产生标准命令行输出。
封装的 samtools/bcftools 实现通常比等价 Python 循环更快、测试更充分。使用时分发器每个参数独立传字符串,大输出用-o+catch_stdout=False(详见 common_workflows.md 与 SKILL.md)。
Pileup 与 Coverage 性能要点
针对深度统计类任务,按开销从低到高选择 API:
count():适合按重叠记录计数的场景(注意其默认read_callback="nofilter");count_coverage():高效返回某个区间的稠密 A/C/G/T 碱基计数数组(默认 base quality 15、read_callback="all"),适合需要"零覆盖位置也要有值"的覆盖度谱;pileup():需要碱基/读段级状态时使用,但 Python 侧开销更高。
其余性能纪律:
- 默认 pileup 深度上限是 8000(api_reference.md 中
max_depth默认值),调整它会显著影响内存与结果; - 不要对相邻的每个变异位点各做一次 pileup;把变异分组到窗口内一次遍历,或对全列遍历一遍;
- 不要把全部读段或 pileup 代理对象物化成 list——这既浪费内存又会因代理对象失效引入隐性 bug。
文件句柄与代理对象的生命周期
pysam 暴露的很多对象(如PileupColumn、PileupRead、persist=False的 FASTX 记录)是由 HTSlib 内存支撑的代理对象,其有效性绑定到所属文件与迭代器的生命周期:
- 使用这些代理时,必须保持其所属文件与迭代器存活;
- 若数据需要活过迭代(例如收集到列表后再用),立即复制出原始值(位置、碱基、质量、长度等标量),而不是保存代理引用。
common_workflows.md 中"Exact Pileup at a Position"等示例也遵循同一原则:在迭代器作用域内消费column/pileup_read,并当场提取数值。
可复现性检查清单
把 CRAM 工作流固化下来,逐项记录与验证:
- 固定 pysam 版本:
pysam==0.24.0(安装方式见 SKILL.md,推荐uv pip install "pysam==0.24.0"); - 记录
pysam.__version__与pysam.__samtools_version__(运行时确认内嵌 HTSlib 为 1.23.1); - 记录参考 FASTA 的校验和与组装版本;
- 记录CRAM 输出版本(3.1 还是降级的 3.0);
- 记录命令行参数与过滤阈值;
- 原子写入或写入新路径(仓库所有脚本均拒绝覆盖已有输出,见 inspect_hts.py 的
open("x")逻辑); - 写入后验证代表性区域;
- 避免隐藏的联网参考依赖——显式传
reference_filename=,绝不依赖隐式 EBI 抓取。
最后回到版本基线:本指南全部结论针对 pysam 0.24.0 / HTSlib 1.23.1,若升级版本请先核对 sources.md 中记录的权威来源与 migration_to_0_24.md 的回归清单,再调整本指南中的版本相关行为(CRAM 3.1 默认值、format_options的 str 语义、EBI 参考查找移除等均为版本相关结论)。
【免费下载链接】scientific-agent-skillsTurn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000+ scientists worldwide. 165 ready-to-use validated skills plus 100+ scientific databases covering biology, chemistry, medicine, and drug discovery. Compatible with Cursor, Claude Code, Codex, Pi, Antigravity, and the open Agent Skills standard.项目地址: https://gitcode.com/GitHub_Trending/cl/scientific-agent-skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考