Bio.bgzf模块

读写BGZF压缩文件(BAM中使用的GZIP变体)。

SAM/BAM文件格式(序列比对/映射)以纯文本格式(SAM)和压缩二进制格式(BAM)提供。后者使用修改后的gzip压缩形式,称为BGZF(Block GNU Zip Format),它可以应用于任何文件格式,以提供高效随机访问的压缩。在http://samtools.sourceforge.net/SAM1.pdf上描述了bgzf和sam/bam文件格式

在使用BGZF文件进行随机访问之前,请阅读下面有关“虚拟偏移”的文本。

本模块的目的

Python gzip库可用于读取BGZF文件,因为对于解压缩而言,它们只是(专门的)gzip文件。本模块旨在促进对BGZF文件的随机访问(使用“虚拟偏移”的概念),以及写入BGZF文件(这意味着使用大小合适的gzip块,并在gzip头文件中写入额外的“bc”字段)。与gzip库一样,zlib库在内部使用。

除了随机访问和写入BAM文件所需之外,BGZF格式还可以用于其他顺序数据(从一个记录接着另一个记录的意义上讲),例如Bio.SeqIO中支持的大多数序列数据格式(如FASTA、FASTQ、GenBank等)或大型MAF比对。

Bio.SeqIO索引函数使用此模块支持BGZF文件。

BGZF技术简介

gzip文件格式允许多个压缩块,每个压缩块可以是一个独立的gzip文件。有趣的是,这意味着您可以使用Unix cat 通过连接两个或多个gzip文件将它们合并为一个文件。此外,每个挡路可以有几个压缩级别中的一个(包括未压缩,由于gzip头文件的缘故,这实际上会占用更多一点的空间)。

bam的设计者意识到,虽然随机访问存储在传统gzip文件中的数据很慢,但将文件分成gzip块将允许快速随机访问每个挡路。要访问特定的解压缩数据,您只需要知道它从哪个挡路开始(gzip挡路开始的偏移量),以及需要阅读挡路(解压缩后的)内容的程度。

这样做的一个问题是高效地找到gzip挡路大小。您可以使用标准的gzip文件进行解压,但这需要对每个挡路进行解压缩--这会相当慢。此外,典型的gzip文件可能使用非常大的块。

bgzf的不同之处在于,每个gzip挡路的压缩大小被限制为2^16字节,并且gzip头部中的一个额外的‘BC’字段记录了这个大小。传统的解压缩工具可以忽略这一点,并像解压缩任何其他gzip文件一样解压缩该文件。

关键是您可以查看第一个bgzf挡路,从这个‘bc’头找出它有多大,从而立即搜索到第二个挡路,依此类推。

该BAM索引方案使用64位“虚拟偏移量”记录读取位置,包括 coffset << 16 | uoffset ,在哪里 coffset 是包含读取开始的BGZF挡路的文件偏移量(使用最多64-16=48位的无符号整数),以及 uoffset 是(解压缩的)挡路(无符号16位整数)内的偏移量。

这将您限制为bam文件,其中最后一个挡路以2^48字节(或256PB)开始,每个挡路的解压缩大小最多为2^16字节(64KB)。注意,这与bgzf‘bc’字段大小匹配,后者将每个挡路的压缩大小限制为2^16字节,允许bam文件使用不带gzip压缩的bgzf(对于内存中的中间文件很有用,以减少cpu负载)。

有关命名空间的警告

使用“from XXX import”被认为是个坏主意 * “在Python中,因为它污染了命名空间。这是Bio.bgzf(和标准Python库gzip)的一个真正问题,因为它们包含一个名为open的函数,即假设您这样做:

>>> from Bio.bgzf import *
>>> print(open.__module__)
Bio.bgzf

或,

>>> from gzip import *
>>> print(open.__module__)
gzip

请注意,OPEN函数已被替换。如果需要,您可以通过导入内置的OPEN函数来“修复”此问题:

>>> from builtins import open

但是,我们建议改用显式名称空间,例如

>>> from Bio import bgzf
>>> print(bgzf.open.__module__)
Bio.bgzf

示例

这是一个用BGZF压缩的普通GenBank文件,所以可以用gzip解压。

>>> import gzip
>>> handle = gzip.open("GenBank/NC_000932.gb.bgz", "r")
>>> assert 0 == handle.tell()
>>> line = handle.readline()
>>> assert 80 == handle.tell()
>>> line = handle.readline()
>>> assert 143 == handle.tell()
>>> data = handle.read(70000)
>>> assert 70143 == handle.tell()
>>> handle.close()

我们也可以使用BGZF阅读器访问该文件-但请注意文件偏移量,下面将对此进行说明:

>>> handle = BgzfReader("GenBank/NC_000932.gb.bgz", "r")
>>> assert 0 == handle.tell()
>>> print(handle.readline().rstrip())
LOCUS       NC_000932             154478 bp    DNA     circular PLN 15-APR-2009
>>> assert 80 == handle.tell()
>>> print(handle.readline().rstrip())
DEFINITION  Arabidopsis thaliana chloroplast, complete genome.
>>> assert 143 == handle.tell()
>>> data = handle.read(70000)
>>> assert 987828735 == handle.tell()
>>> print(handle.readline().rstrip())
f="GeneID:844718"
>>> print(handle.readline().rstrip())
     CDS             complement(join(84337..84771,85454..85843))
>>> offset = handle.seek(make_virtual_offset(55074, 126))
>>> print(handle.readline().rstrip())
    68521 tatgtcattc gaaattgtat aaagacaact cctatttaat agagctattt gtgcaagtat
>>> handle.close()

请注意,句柄的偏移量看起来与BGZF文件不同。这就把我们带到了BGZF的关键一点,那就是挡路的架构:

>>> handle = open("GenBank/NC_000932.gb.bgz", "rb")
>>> for values in BgzfBlocks(handle):
...     print("Raw start %i, raw length %i; data start %i, data length %i" % values)
Raw start 0, raw length 15073; data start 0, data length 65536
Raw start 15073, raw length 17857; data start 65536, data length 65536
Raw start 32930, raw length 22144; data start 131072, data length 65536
Raw start 55074, raw length 22230; data start 196608, data length 65536
Raw start 77304, raw length 14939; data start 262144, data length 43478
Raw start 92243, raw length 28; data start 305622, data length 0
>>> handle.close()

在此示例中,前三个块是“满的”,并保存65536字节的未压缩数据。第四个挡路未满,可容纳43478字节。最后,还有一个特殊的空的第五个挡路,它在磁盘上占用28个字节,用作‘文件结束’(EOF)标记。如果缺少此文件,则可能是您的BGZF文件不完整。

通过提前读取70,000字节,我们进入第二个BGZF挡路,此时,BGZF虚拟偏移量开始看起来与gzip库公开的解压缩数据中的简单偏移量不同。

例如,考虑寻找解压缩位置196734。由于196734=65536+65536+65536+126=65536*3+126,这相当于跳过前三个块(在此特定示例中,解压缩后的大小均为65536-这并不总是成立)并从第四个挡路的第126字节开始(解压缩后)。对于BGZF,我们需要知道第四个挡路的偏移量55074和挡路内部的偏移量126后才能得到BGZF的虚拟偏移量。

>>> print(55074 << 16 | 126)
3609329790
>>> print(bgzf.make_virtual_offset(55074, 126))
3609329790

因此,对于该bgzf文件,解压缩位置196734对应于虚拟偏移3609329790。但是,另一个具有不同内容的BGZF文件将或多或少地进行压缩,因此压缩块的大小将不同。这意味着未压缩偏移和压缩虚拟偏移之间的映射取决于您使用的BGZF文件。

如果您要通过此模块访问BGZF文件,只需使用handle.tell()方法来记录稍后可能想要使用handle.Seek()返回的位置的虚拟偏移量。

BGZF虚拟偏移的问题是,虽然可以比较它们(哪个偏移在文件中最先出现),但您不能安全地减去它们以获得它们之间的数据大小,也不能添加/减去相对偏移。

当然,您可以使用Bio.SeqIO使用BgzfReader解析此文件,尽管使用gzip.open(.)没有任何好处,除非您希望索引BGZF压缩序列文件:

>>> from Bio import SeqIO
>>> handle = BgzfReader("GenBank/NC_000932.gb.bgz")
>>> record = SeqIO.read(handle, "genbank")
>>> handle.close()
>>> print(record.id)
NC_000932.1

文本模式

与标准库gzip.open(.)一样,BGZF代码默认以二进制模式打开文件。

您可以请求以文本模式打开文件,但要注意这是硬编码为简单的“latin1”(也称为“iso-8859-1”)编码(包括所有ASCII字符),该编码可以很好地与大多数西欧语言配合使用。但是,它与更广泛使用的UTF-8编码并不完全兼容。

在像UTF-8这样的可变宽度编码中,Unicode文本输出中的一些单个字符由原始二进制形式的多个字节表示。这在bgzf中是有问题的,因为我们不能总是孤立地解码每个挡路-一个unicode字符可能会被分成两个块。这甚至可能发生在固定宽度的Unicode编码中,因为BGZF挡路的大小不是固定的。

因此,此模块目前仅限于支持单字节Unicode编码,如ASCII、“latin1”(它是ASCII的超集)或其他可能的字符映射(未实现)。

此外,与Python3上的默认文本模式不同,我们不会尝试实现通用的换行模式。这会将各种操作系统新行约定,如Windows(CRLF或“rn”)、Unix(只有LF,“n”)或旧Mac(只有CR,“r”)转换为只有LF(“n”)。这里我们有同样的问题--挡路末尾的“r”是不完整的视窗风格的新行吗?

相反,您将按原样获得CR(“r”)和LF(“n”)字符。

如果您的数据是UTF-8或任何其他不兼容的编码,您必须使用二进制模式,并自己解码适当的片段。

Bio.bgzf.open(filename, mode='rb')

打开BGZF文件进行读取、写入或追加。

如果请求文本模式,为了避免多字节字符,这将被硬编码为使用“latin1”编码,并按原样传递“r”和“n”(不实现通用换行模式)。

如果您的数据是UTF-8或任何其他不兼容的编码,您必须使用二进制模式,并自己解码适当的片段。

Bio.bgzf.make_virtual_offset(block_start_offset, within_block_offset)

从挡路起点和挡路偏移内计算bgzf虚拟偏移。

BAM索引方案使用64位“虚拟偏移量”记录读取位置,该虚拟偏移量在C术语中包括:

挡路_START_OFFSET<<16|在_挡路_OFFSET内

这里挡路_START_OFFSET是BGZF挡路START(无符号整数,最大使用64-16=48位)的文件偏移量,在(解压缩)挡路内的_挡路_OFFSET(无符号16位整数)。

>>> make_virtual_offset(0, 0)
0
>>> make_virtual_offset(0, 1)
1
>>> make_virtual_offset(0, 2**16 - 1)
65535
>>> make_virtual_offset(0, 2**16)
Traceback (most recent call last):
...
ValueError: Require 0 <= within_block_offset < 2**16, got 65536
>>> 65536 == make_virtual_offset(1, 0)
True
>>> 65537 == make_virtual_offset(1, 1)
True
>>> 131071 == make_virtual_offset(1, 2**16 - 1)
True
>>> 6553600000 == make_virtual_offset(100000, 0)
True
>>> 6553600001 == make_virtual_offset(100000, 1)
True
>>> 6553600010 == make_virtual_offset(100000, 10)
True
>>> make_virtual_offset(2**48, 0)
Traceback (most recent call last):
...
ValueError: Require 0 <= block_start_offset < 2**48, got 281474976710656
Bio.bgzf.split_virtual_offset(virtual_offset)

将64位BGZF虚拟偏移划分为挡路开始&在挡路偏移内。

>>> (100000, 0) == split_virtual_offset(6553600000)
True
>>> (100000, 10) == split_virtual_offset(6553600010)
True
Bio.bgzf.BgzfBlocks(handle)

用于检查BGZF块的低级调试功能。

需要使用内置OPEN函数以二进制读取模式打开的BGZF压缩文件。不要使用此bgzf模块或gzip模块的open函数中的句柄,因为这将对文件进行解压缩。

作为迭代器返回挡路开始偏移量(请参见虚拟偏移量)、挡路长度(添加这些以作为下一个挡路的开始)和块内容的解压缩长度(在bgzf中限制为65536),作为迭代器-每个bgzf挡路返回一个元组。

>>> from builtins import open
>>> handle = open("SamBam/ex1.bam", "rb")
>>> for values in BgzfBlocks(handle):
...     print("Raw start %i, raw length %i; data start %i, data length %i" % values)
Raw start 0, raw length 18239; data start 0, data length 65536
Raw start 18239, raw length 18223; data start 65536, data length 65536
Raw start 36462, raw length 18017; data start 131072, data length 65536
Raw start 54479, raw length 17342; data start 196608, data length 65536
Raw start 71821, raw length 17715; data start 262144, data length 65536
Raw start 89536, raw length 17728; data start 327680, data length 65536
Raw start 107264, raw length 17292; data start 393216, data length 63398
Raw start 124556, raw length 28; data start 456614, data length 0
>>> handle.close()

间接地,我们可以断定这个文件来自旧版本的Samtools,因为所有的块(除了最后一个块和虚拟的空EOF标记挡路)都是65536字节。以后的版本避免在两个块之间拆分读取,并为报头提供自己的挡路(对于加快替换报头很有用)。您可以在使用Samtools 0.1.18创建的ex1_fresh h.bam中看到:

Samtools view-b ex1.bam>ex1_fresh h.bam

>>> handle = open("SamBam/ex1_refresh.bam", "rb")
>>> for values in BgzfBlocks(handle):
...     print("Raw start %i, raw length %i; data start %i, data length %i" % values)
Raw start 0, raw length 53; data start 0, data length 38
Raw start 53, raw length 18195; data start 38, data length 65434
Raw start 18248, raw length 18190; data start 65472, data length 65409
Raw start 36438, raw length 18004; data start 130881, data length 65483
Raw start 54442, raw length 17353; data start 196364, data length 65519
Raw start 71795, raw length 17708; data start 261883, data length 65411
Raw start 89503, raw length 17709; data start 327294, data length 65466
Raw start 107212, raw length 17390; data start 392760, data length 63854
Raw start 124602, raw length 28; data start 456614, data length 0
>>> handle.close()

上面的示例没有嵌入SAM头(因此第一个挡路非常小,只有38字节的解压缩数据),而下一个示例有(更大的103字节的挡路)。请注意,块的睡觉显示相同的大小(它们包含相同的读取数据):

>>> handle = open("SamBam/ex1_header.bam", "rb")
>>> for values in BgzfBlocks(handle):
...     print("Raw start %i, raw length %i; data start %i, data length %i" % values)
Raw start 0, raw length 104; data start 0, data length 103
Raw start 104, raw length 18195; data start 103, data length 65434
Raw start 18299, raw length 18190; data start 65537, data length 65409
Raw start 36489, raw length 18004; data start 130946, data length 65483
Raw start 54493, raw length 17353; data start 196429, data length 65519
Raw start 71846, raw length 17708; data start 261948, data length 65411
Raw start 89554, raw length 17709; data start 327359, data length 65466
Raw start 107263, raw length 17390; data start 392825, data length 63854
Raw start 124653, raw length 28; data start 456679, data length 0
>>> handle.close()
class Bio.bgzf.BgzfReader(filename=None, mode='r', fileobj=None, max_cache=100)

基类:object

BGZF读取器,作用类似于只读句柄,但查找/告知不同。

让我们使用BgzfBlock函数在示例BAM文件中的BGZF块处达到峰值,

>>> from builtins import open
>>> handle = open("SamBam/ex1.bam", "rb")
>>> for values in BgzfBlocks(handle):
...     print("Raw start %i, raw length %i; data start %i, data length %i" % values)
Raw start 0, raw length 18239; data start 0, data length 65536
Raw start 18239, raw length 18223; data start 65536, data length 65536
Raw start 36462, raw length 18017; data start 131072, data length 65536
Raw start 54479, raw length 17342; data start 196608, data length 65536
Raw start 71821, raw length 17715; data start 262144, data length 65536
Raw start 89536, raw length 17728; data start 327680, data length 65536
Raw start 107264, raw length 17292; data start 393216, data length 63398
Raw start 124556, raw length 28; data start 456614, data length 0
>>> handle.close()

现在我们来看看如何使用这个挡路信息跳转到解压后的bam文件的特定部分:

>>> handle = BgzfReader("SamBam/ex1.bam", "rb")
>>> assert 0 == handle.tell()
>>> magic = handle.read(4)
>>> assert 4 == handle.tell()

到目前为止,没有什么奇怪的,我们得到了在一个解压缩的BAM文件的开头使用的魔术标记,并且句柄位置很有意义。不过,现在让我们跳到这个挡路的末尾,通过读取65536个字节跳到下一个挡路的4个字节,

>>> data = handle.read(65536)
>>> len(data)
65536
>>> assert 1195311108 == handle.tell()

期望4+65536=65540,是吗?这是一个BGZF 64位虚拟偏移量,这意味着:

>>> split_virtual_offset(1195311108)
(18239, 4)

你应该会点18239作为第二届博鳌亚洲论坛挡路的起点,而4则是对这一挡路的补偿。另请参见make_virtual_Offset,

>>> make_virtual_offset(18239, 4)
1195311108

让我们回到几乎是文件开头的地方,

>>> make_virtual_offset(0, 2)
2
>>> handle.seek(2)
2
>>> handle.close()

请注意,您可以使用max_cache参数来限制在内存中缓存的BGZF块的数量。默认值为100KB,由于每个挡路最大可达64KB,因此默认缓存可能最多占用6MB内存。高速缓存对于一次性读取文件并不重要,但对于提高随机访问的性能很重要。

__init__(filename=None, mode='r', fileobj=None, max_cache=100)

初始化类。

tell()

返回64位无符号BGZF虚拟偏移量。

seek(virtual_offset)

查找64位无符号BGZF虚拟偏移量。

read(size=-1)

BGZF模块的读取方法。

readline()

读取BGZF文件的一行。

__next__()

返回下一行。

__iter__()

迭代BGZF文件中的各行。

close()

关闭BGZF文件。

seekable()

返回True,表示BGZF支持随机访问。

isatty()

如果连接到TTY设备,则返回True。

fileno()

返回整数文件描述符。

__enter__()

打开可使用WITH语句操作的文件。

__exit__(type, value, traceback)

使用WITH语句关闭文件。

class Bio.bgzf.BgzfWriter(filename=None, mode='w', fileobj=None, compresslevel=6)

基类:object

定义BGZFWriter对象。

__init__(filename=None, mode='w', fileobj=None, compresslevel=6)

开始上课。

write(data)

为类编写方法。

flush()

明确地刷新数据。

close()

刷新数据,写入28字节的BGZF EOF标记,关闭BGZF文件。

Samtools将查找一个神奇的EOF标记,它只是一个28字节的空BGZF挡路,如果它丢失了,就会警告bam文件可能被截断。除了Samtools编写这个挡路之外,bgzip也是如此-这个实现也是如此。

tell()

返回BGZF 64位虚拟偏移量。

seekable()

返回True,表示BGZF支持随机访问。

isatty()

如果连接到TTY设备,则返回True。

fileno()

返回整数文件描述符。

__enter__()

打开可使用WITH语句操作的文件。

__exit__(type, value, traceback)

使用WITH语句关闭文件。