pysam 0.24 CRAM 实战指南:参考序列管理、远程 I/O、线程与性能优化
2026/9/12 8:05:25 网站建设 项目流程

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 用户的运行行为变化,是理解本主题的前提:

  1. 新写入的 CRAM 默认使用 CRAM 3.1 而非 3.0。CRAM 3.1 是更新的压缩格式版本,但下游消费者若不支持 3.1,读写会直接失败。
  2. 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.pyCRAM inspection requires --referencealignment_qc.pyCRAM 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.indexAlignmentFile关闭前的建索引流程),再用同一份 FASTA 读回验证。读与写必须使用完全相同的参考序列——这是 CRAM 正确性的第一原则。

REF_PATHREF_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()失败时,按下述顺序逐项排查:

  1. 确认FASTA 组装版本与 contig 名称chr11的命名差异是高频坑);
  2. 确认.fai存在,且各 contig 长度与比对头一致;
  3. 检查@SQ行中的M5UR字段
  4. 显式传reference_filename=,排除任何隐式查找路径;
  5. 确认CRAI 索引与当前 CRAM 匹配(文件更新后旧索引会导致随机访问错乱);
  6. 顺序读fetch(until_eof=True))做一次隔离实验,把"索引问题"与"参考问题"分开定位;
  7. 捕获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=参数同时存在于AlignmentFileVariantFileTabixFile上,用于控制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")

三条纪律:

  1. 绝不在进程间传递活动的 pysam 句柄或代理记录——句柄绑定 HTSlib 内部内存,跨进程传递必然出错;
  2. 平衡进程数与 HTSlibthreads:每个进程 4 线程 × 8 进程 = 32 线程,两者相乘会轻松导致 CPU 超订(oversubscription);
  3. 分区 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 暴露的很多对象(如PileupColumnPileupReadpersist=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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询