Skip to main content

streampile

Build Status Python Versions Language License: MIT basedpyright mypy uv Ruff

Forward-only pileups of coordinate-sorted SAM, BAM, and CRAM, and the alleles at every base.

Installation

pip install streampile

Quickstart

Building Pileups

A StreamingPileupBuilder reads records once and piles them up at the 0-based positions you ask for, moving forward only. Give the AlignmentFile threads to decompress the BAM with, which pays at depth; for CRAM, also give it reference_filename.

>>> from pysam import AlignmentFile
>>> from streampile import StreamingPileupBuilder
>>>
>>> with (
...     AlignmentFile("tests/data/reads.bam", threads=4) as reads,
...     StreamingPileupBuilder(reads, min_base_quality=30) as builder,
... ):
...     first = builder.pileup("chr1", 10)
...     second = builder.pileup("chr1", 12)
>>>
>>> first.unfiltered_depth, first.filtered_depth, first.bases
(4, 4, ['A', 'T', 'G', 'A'])
>>> second.unfiltered_depth, second.filtered_depth, second.bases
(4, 3, ['G', 'G', 'G'])

At position 12, the base of lowqual is under the quality floor, so it counts toward unfiltered_depth only.

Filter reads with read_filter, and see each template once with templates(), which calls the bases of overlapping mates into one as fgbio's CallOverlappingConsensusBases does. Overlapping mates read the same bases of their template twice: they agree where they read the same base, and disagree where they read different ones. The mates of pair overlap from position 45 to 54, agree on T at position 50, and disagree at position 52, where the first reads G at Q40 and the second A at Q20:

position  40   45   50   55
pair  99  GATCGGATCCTAGGC
pair 147       GATCCTAAGCTAGCT
                    ^ ^

By default, an agreeing base is called at the sum of the two qualities, and a disagreeing one as the base of the higher quality at their difference:

>>> with (
...     AlignmentFile("tests/data/reads.bam") as reads,
...     StreamingPileupBuilder(reads, read_filter=lambda read: read.is_paired) as builder,
... ):
...     agreeing = builder.pileup("chr1", 50)
...     disagreeing = builder.pileup("chr1", 52)
>>>
>>> for pileup in (agreeing, disagreeing):
...     template = pileup.templates()[0]
...     print(pileup.bases, pileup.qualities, template.base, template.qual)
['T', 'T'] [40, 40] T 80
['G', 'A'] [40, 20] G 20

Other strategies mask a disagreement, or keep the base of the higher quality as it is:

>>> from streampile import DisagreementStrategy
>>>
>>> for strategy in DisagreementStrategy:
...     template = disagreeing.templates(disagreement=strategy)[0]
...     print(strategy, template.base, template.qual)
consensus G 20
mask_both N 2
mask_lower_qual G 40

A mate whose base is under the quality floor does not vote, as it is left out of bases, so it neither masks nor lowers the other mate's base:

>>> with (
...     AlignmentFile("tests/data/reads.bam") as reads,
...     StreamingPileupBuilder(reads, min_base_quality=30) as builder,
... ):
...     floored = builder.pileup("chr1", 52)
>>>
>>> template = floored.templates(disagreement=DisagreementStrategy.mask_both)[0]
>>> floored.bases, template.base, template.qual
(['G'], 'G', 40)

A template also holds its reads, its strand, and its distances to both of its ends:

>>> template = disagreeing.templates()[0]
>>> template.is_reverse, template.five_prime_distance, template.template_end_distance
(False, 12, 7)

Each entry also counts the template's bases from its base to the read's 5′ end and to the template's other end, the 5′ end of its mate in an FR pair, whose CIGAR comes from the MC tag:

>>> with (
...     AlignmentFile("tests/data/reads.bam") as reads,
...     StreamingPileupBuilder(reads) as builder,
... ):
...     pileup = builder.pileup("chr1", 45)
>>>
>>> for entry in pileup.pileups:
...     flag = entry.alignment.flag
...     print(flag, entry.five_prime_distance, entry.template_end_distance)
0 5 None
99 5 14
147 14 5

The unpaired read has no template end, and the two mates of the pair mirror each other. A read has one only when is_fr_pair calls its pair FR, as htsjdk 5.0.0 does: the forward read's aligned 5′ position is at or before the reverse read's.

Tapping Every Record

The function given as tap receives every record, in input order, once the builder has moved past it, so a record can be changed, e.g. tagged, while it is piled up. Each entry's alignment is the very record read, so pass tap=writer.write to write the changed records to an AlignmentFile opened with template=reads. Pileups see each read as it was when the builder read it; changes made after that, including in read_filter, reach tap and alignment but not later pileups. Here every read is written to a new BAM, and a read whose base at position 10 is not the reference A is tagged XV with that base.

>>> from tempfile import mkdtemp
>>>
>>> tagged = f"{mkdtemp()}/tagged.bam"
>>> with (
...     AlignmentFile("tests/data/reads.bam") as reads,
...     AlignmentFile(tagged, "wb", template=reads) as writer,
...     StreamingPileupBuilder(reads, tap=writer.write) as builder,
... ):
...     for entry in builder.pileup("chr1", 10).pileups:
...         if entry.base not in (None, "A"):
...             entry.alignment.set_tag("XV", entry.base)
>>>
>>> with AlignmentFile(tagged) as written:
...     for read in written:
...         if read.has_tag("XV"):
...             print(read.query_name, read.get_tag("XV"))
snv T
mnv G

Sweeping a Territory

The columns method yields the pileup at every position of a span. Its views, such as bases, qualities, and the depths, are computed in Rust, so prefer them to looping over pileups in Python, which is where the time goes at depth.

>>> from collections import Counter
>>>
>>> with (
...     AlignmentFile("tests/data/reads.bam") as reads,
...     StreamingPileupBuilder(reads, min_base_quality=30) as builder,
... ):
...     for pileup in builder.columns("chr1", 9, 13):
...         print(pileup.reference_pos, dict(Counter(pileup.bases)))
9 {'A': 4}
10 {'A': 2, 'T': 1, 'G': 1}
11 {'C': 3, 'G': 1}
12 {'G': 3}

Each pileup is a snapshot that outlives the builder moving on.

Tabulating Alleles

The tabulate function counts the reads of every allele at every base of a bedspec Territory. Alleles are normalized VCF alleles at 1-based positions, so they match a VCF by CHROM, POS, REF, and ALT.

>>> from bedspec import Bed3
>>> from bedspec import Territory
>>> from pysam import FastaFile
>>> from streampile import tabulate
>>>
>>> territory = Territory([Bed3("chr1", start=9, end=12)])
>>> with (
...     AlignmentFile("tests/data/reads.bam") as reads,
...     FastaFile("tests/data/reference.fa") as reference,
... ):
...     bases = list(tabulate(reads, reference, territory, min_base_quality=30))
>>>
>>> for base in bases:
...     print(base.pos, base.ref, base.alts, base.alt_reads, base.alt_fwd, base.alt_rev)
10 A () () () ()
11 A ('T', 'GG') (1, 1) (1, 0) (0, 1)
12 C () () () ()

The alt_fwd and alt_rev fields split each allele's reads by strand, and no_calls counts N bases apart from the depth.

Writing a Table

A path ending in .gz is written with BGZF, indexed as it is written when given an index, and metadata adds ##key=value lines above the header:

>>> from pybgzf import IndexFormat
>>> from streampile import TabulationWriter
>>>
>>> table = f"{mkdtemp()}/counts.tsv.gz"
>>> with TabulationWriter.from_path(
...     table, index=IndexFormat.TBI, metadata={"min_base_quality": 30}
... ) as writer:
...     writer.write_all(bases)

Reading a Table

>>> from streampile import TabulationReader
>>>
>>> for base in TabulationReader.from_path(table):
...     print(base.pos, base.depth, base.alts, base.alt_reads)
10 4 () ()
11 4 ('T', 'GG') (1, 1)
12 4 () ()

With the index, a region can be read alone; it is half-open and 0-based, as in BED:

>>> for base in TabulationReader.query(table, "chr1", 10, 11):
...     print(base.pos, base.depth, base.alts, base.alt_reads)
11 4 ('T', 'GG') (1, 1)

Command Line

streampile tabulate \
    --bam tests/data/reads.bam \
    --ref tests/data/reference.fa \
    --intervals tests/data/territory.bed \
    --min-base-quality 30 \
    --min-mapping-quality 20 \
    --out counts.tsv.gz \
    --index tbi

This writes the table compressed with BGZF and its tabix index, counts.tsv.gz.tbi; decompressed, it reads:

##streampile-tabulation=1
##streampile-version=0.2.0
##bam=tests/data/reads.bam
##reference=tests/data/reference.fa
##territory=tests/data/territory.bed
##min_base_quality=30
##min_mapping_quality=20
##exclude_flags=0xf00
#contig	pos	ref	depth	no_calls	ref_reads	ref_fwd	ref_rev	alt_refs	alts	alt_reads	alt_fwd	alt_rev
chr1	21	T	5	0	5	3	2					
chr1	22	G	5	0	5	3	2					
chr1	23	C	4	0	3	2	1	CA	C	1	0	1
chr1	24	A	4	0	3	2	1					

The --threads option sets the threads decompressing the BAM and compressing a BGZF table: by default, the CPUs available, at most 8.

The examples read tests/data/reads.bam, whose reads are named for what they carry, such as plain, snv, mnv, lowqual, insertion, and deletion.

Development and Testing

See the contributing guide for more information.

The streaming design follows the StreamingPileupBuilder of fgbio of which I was also the author.

See NOTICE for official attribution.

Metadata

Release files for streampile 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for streampile 0.2.0
File Size Uploaded
streampile-0.2.0.tar.gz 173.1 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for streampile 0.2.0
File
streampile-0.2.0-cp311-abi3-musllinux_1_2_x86_64.whl CPython 3.11 abi3 Linux musl 1.2+ x86-64 Details
streampile-0.2.0-cp311-abi3-musllinux_1_2_aarch64.whl CPython 3.11 abi3 Linux musl 1.2+ ARM64 Details
streampile-0.2.0-cp311-abi3-manylinux_2_28_aarch64.whl CPython 3.11 abi3 Linux glibc 2.28+ ARM64 Details
streampile-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
streampile-0.2.0-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
streampile-0.2.0-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 3.6 MB

Release files / streampile-0.2.0.tar.gz

Download URL streampile-0.2.0.tar.gz
Size 173.1 kB
Tags Source
SHA-256 checksum
How to use checksums
a7d06e59d46329b0382bd9895b30cb836760bac7d4600b0196649b532b488204
BLAKE2b-256 checksum
How to use checksums
c7d119b67a5e92725065fdcd48b6630ad9597a299c68d2f06b5156e26802c6ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / streampile-0.2.0-cp311-abi3-musllinux_1_2_x86_64.whl

Download URL streampile-0.2.0-cp311-abi3-musllinux_1_2_x86_64.whl
Size 736.4 kB
Tags CPython 3.11 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
7c88cecce38c95feac06efda8547386bf51e18a575c4b2e98ce0c4dfbaeaf5af
BLAKE2b-256 checksum
How to use checksums
1043c650a96b6127470cc40fec3ab39093b8636cab97df1bc5a4288afff33d05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / streampile-0.2.0-cp311-abi3-musllinux_1_2_aarch64.whl

Download URL streampile-0.2.0-cp311-abi3-musllinux_1_2_aarch64.whl
Size 690.3 kB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
216e967657adfaa4bb9885db629d3b0d17f308cdc7760b612cecc84283e470d1
BLAKE2b-256 checksum
How to use checksums
c66628c6a5cab08812f17c549b90a4dd4d97780451784972b963a23aefdaa94f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / streampile-0.2.0-cp311-abi3-manylinux_2_28_aarch64.whl

Download URL streampile-0.2.0-cp311-abi3-manylinux_2_28_aarch64.whl
Size 511.3 kB
Tags CPython 3.11 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
66d976309c1624ab174ecd9affa69280e4f82b2ea1ccff15c21b84c67ec37e9a
BLAKE2b-256 checksum
How to use checksums
143c9607c269b6d307902a64f1b5415ff5e26da2f0781626d9fdfaa7870123bc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / streampile-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL streampile-0.2.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 521.5 kB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
4f3412136c75e6d15410044c558f3a8c435ac9ac3e54cb6211e54ca46bd8aebe
BLAKE2b-256 checksum
How to use checksums
eed046bb4c1b44e8c4048e90d1daad29140d9b24bd68289bfd2de15ffb684d61
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / streampile-0.2.0-cp311-abi3-macosx_11_0_arm64.whl

Download URL streampile-0.2.0-cp311-abi3-macosx_11_0_arm64.whl
Size 478.0 kB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
cfcebfe6af5a126e1f3a31e75abe30bdc708342f0aeb4f9db8fb081f9f9474a7
BLAKE2b-256 checksum
How to use checksums
7a21f735e6be3ed875acb8c047cf725383798a0af2f2ca833b385f19bcc8b493
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / streampile-0.2.0-cp311-abi3-macosx_10_12_x86_64.whl

Download URL streampile-0.2.0-cp311-abi3-macosx_10_12_x86_64.whl
Size 484.9 kB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
3bf1cef4efbae842ea6c608c3ec5b4457b0eff865b193eabe52c339719959fa9
BLAKE2b-256 checksum
How to use checksums
23fc95fbc402cfe931b7086b424d43f81c45c165a0a2ad5a95c213a920f7dba6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

7 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page