streampile 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streampile-0.1.0/.gitignore +171 -0
- streampile-0.1.0/CONTRIBUTING.md +66 -0
- streampile-0.1.0/LICENSE +21 -0
- streampile-0.1.0/NOTICE +12 -0
- streampile-0.1.0/PKG-INFO +184 -0
- streampile-0.1.0/README.md +151 -0
- streampile-0.1.0/pyproject.toml +227 -0
- streampile-0.1.0/streampile/__init__.py +33 -0
- streampile-0.1.0/streampile/_builder.py +245 -0
- streampile-0.1.0/streampile/_cli.py +143 -0
- streampile-0.1.0/streampile/_footprint.py +94 -0
- streampile-0.1.0/streampile/_frozen.py +22 -0
- streampile-0.1.0/streampile/_pileup.py +306 -0
- streampile-0.1.0/streampile/_table.py +271 -0
- streampile-0.1.0/streampile/_tabulate.py +658 -0
- streampile-0.1.0/streampile/py.typed +0 -0
- streampile-0.1.0/tests/__init__.py +0 -0
- streampile-0.1.0/tests/data/counts.tsv +13 -0
- streampile-0.1.0/tests/data/reads.bam +0 -0
- streampile-0.1.0/tests/data/reads.bam.bai +0 -0
- streampile-0.1.0/tests/data/reads.sam +17 -0
- streampile-0.1.0/tests/data/reference.fa +4 -0
- streampile-0.1.0/tests/data/reference.fa.fai +2 -0
- streampile-0.1.0/tests/data/territory.bed +1 -0
- streampile-0.1.0/tests/records.py +101 -0
- streampile-0.1.0/tests/test_builder.py +551 -0
- streampile-0.1.0/tests/test_cli.py +207 -0
- streampile-0.1.0/tests/test_footprint.py +80 -0
- streampile-0.1.0/tests/test_package.py +7 -0
- streampile-0.1.0/tests/test_pileup.py +243 -0
- streampile-0.1.0/tests/test_pysam_agreement.py +204 -0
- streampile-0.1.0/tests/test_table.py +206 -0
- streampile-0.1.0/tests/test_tabulate.py +532 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
|
|
3
|
+
# Byte-compiled / optimized / DLL files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py,cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
.python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
#Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# poetry
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
104
|
+
#poetry.lock
|
|
105
|
+
|
|
106
|
+
# pdm
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
108
|
+
#pdm.lock
|
|
109
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
110
|
+
# in version control.
|
|
111
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
112
|
+
.pdm.toml
|
|
113
|
+
|
|
114
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
115
|
+
__pypackages__/
|
|
116
|
+
|
|
117
|
+
# Celery stuff
|
|
118
|
+
celerybeat-schedule
|
|
119
|
+
celerybeat.pid
|
|
120
|
+
|
|
121
|
+
# SageMath parsed files
|
|
122
|
+
*.sage.py
|
|
123
|
+
|
|
124
|
+
# Environments
|
|
125
|
+
.env
|
|
126
|
+
.venv
|
|
127
|
+
env/
|
|
128
|
+
venv/
|
|
129
|
+
ENV/
|
|
130
|
+
env.bak/
|
|
131
|
+
venv.bak/
|
|
132
|
+
|
|
133
|
+
# Spyder project settings
|
|
134
|
+
.spyderproject
|
|
135
|
+
.spyproject
|
|
136
|
+
|
|
137
|
+
# Rope project settings
|
|
138
|
+
.ropeproject
|
|
139
|
+
|
|
140
|
+
# mkdocs documentation
|
|
141
|
+
/site
|
|
142
|
+
|
|
143
|
+
# mypy
|
|
144
|
+
.mypy_cache/
|
|
145
|
+
.dmypy.json
|
|
146
|
+
dmypy.json
|
|
147
|
+
|
|
148
|
+
# Pyre type checker
|
|
149
|
+
.pyre/
|
|
150
|
+
|
|
151
|
+
# pytype static type analyzer
|
|
152
|
+
.pytype/
|
|
153
|
+
|
|
154
|
+
# Cython debug symbols
|
|
155
|
+
cython_debug/
|
|
156
|
+
|
|
157
|
+
# PyCharm
|
|
158
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
159
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
160
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
161
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
162
|
+
.idea/
|
|
163
|
+
|
|
164
|
+
# VS Code
|
|
165
|
+
.vscode/
|
|
166
|
+
|
|
167
|
+
# Jupyter notebook files
|
|
168
|
+
*.ipynb
|
|
169
|
+
|
|
170
|
+
# Benchmark scratch files
|
|
171
|
+
benchmarks/out/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Developer Documentation
|
|
2
|
+
|
|
3
|
+
## Local Setup
|
|
4
|
+
|
|
5
|
+
Install [uv](https://docs.astral.sh/uv/), then install the library and its development tools with:
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
uv sync --locked
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Local Testing
|
|
12
|
+
|
|
13
|
+
To ensure all tests pass, run:
|
|
14
|
+
|
|
15
|
+
```console
|
|
16
|
+
uv run poe check-tests
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The tests run the examples in the README too, so keep them working.
|
|
20
|
+
|
|
21
|
+
## Local Linting and Formatting
|
|
22
|
+
|
|
23
|
+
To check the lockfile, project metadata, format, lint, and types of all the code, and run every test, run:
|
|
24
|
+
|
|
25
|
+
```console
|
|
26
|
+
uv run poe check-all
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To fix what can be fixed automatically, run:
|
|
30
|
+
|
|
31
|
+
```console
|
|
32
|
+
uv run poe fix-all
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Benchmarks
|
|
36
|
+
|
|
37
|
+
See [`benchmarks/README.md`](benchmarks/README.md) for how to run them and recent results.
|
|
38
|
+
|
|
39
|
+
## Locking
|
|
40
|
+
|
|
41
|
+
`[tool.uv]` in `pyproject.toml` ignores releases younger than a week, and `uv.lock` records that setting.
|
|
42
|
+
Lock with the uv version pinned as `UV_VERSION` in [`tests.yml`](.github/workflows/tests.yml), and keep any user-level uv configuration out of the lock, or CI will find the lockfile stale:
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
XDG_CONFIG_HOME="$(mktemp -d)" uvx uv@0.12.20 lock
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Commits
|
|
49
|
+
|
|
50
|
+
Commit titles follow [Conventional Commits](https://www.conventionalcommits.org), which group the release notes that [git-cliff](https://git-cliff.org) generates from `pyproject.toml`.
|
|
51
|
+
|
|
52
|
+
## Releasing
|
|
53
|
+
|
|
54
|
+
Releases are published to PyPI by the [`publish_streampile.yml`](.github/workflows/publish_streampile.yml) workflow with PyPI Trusted Publishing, so no API token is stored in GitHub.
|
|
55
|
+
|
|
56
|
+
To release:
|
|
57
|
+
|
|
58
|
+
1. Merge a pull request titled `chore(release): bump to X.Y.Z` that sets the version in `pyproject.toml`.
|
|
59
|
+
2. Tag the merge commit on `main` and push the tag:
|
|
60
|
+
|
|
61
|
+
```console
|
|
62
|
+
git tag X.Y.Z
|
|
63
|
+
git push origin X.Y.Z
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
3. The workflow checks that the tag is on `main` and matches the version, runs the tests, builds the sdist and wheel, checks that each installs and imports on every supported Python, publishes to PyPI, and creates a GitHub release with notes generated by git-cliff.
|
streampile-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Clint Valentine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
streampile-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
streampile
|
|
2
|
+
Copyright © 2026 Clint Valentine
|
|
3
|
+
|
|
4
|
+
The forward-only, coordinate-advancing design of StreamingPileupBuilder follows
|
|
5
|
+
StreamingPileupBuilder in fgbio (https://github.com/fulcrumgenomics/fgbio), which
|
|
6
|
+
is distributed under the MIT License:
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2015-2026 Fulcrum Genomics LLC
|
|
9
|
+
|
|
10
|
+
Pileup entries follow the conventions of htslib (https://github.com/samtools/htslib)
|
|
11
|
+
and pysam (https://github.com/pysam-developers/pysam), both distributed under the
|
|
12
|
+
MIT License.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: streampile
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Forward-only pileups streamed from coordinate-sorted SAM, BAM, and CRAM records.
|
|
5
|
+
Project-URL: homepage, https://github.com/clintval/streampile
|
|
6
|
+
Project-URL: repository, https://github.com/clintval/streampile
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/clintval/streampile/issues
|
|
8
|
+
Author-email: Clint Valentine <valentine.clint@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: BAM,HTS,NGS,SAM,bioinformatics,genomics,pileup
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: bedspec>=4.0.0
|
|
28
|
+
Requires-Dist: pybgzf>=0.1.2
|
|
29
|
+
Requires-Dist: pysam>=0.23
|
|
30
|
+
Requires-Dist: typeline>=2.4.0
|
|
31
|
+
Requires-Dist: typing-extensions>=4.12
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# streampile
|
|
35
|
+
|
|
36
|
+
[](https://github.com/clintval/streampile/actions/workflows/tests.yml?query=branch%3Amain)
|
|
37
|
+
[](https://github.com/clintval/streampile)
|
|
38
|
+
[](https://github.com/clintval/streampile/blob/main/LICENSE)
|
|
39
|
+
[](https://docs.basedpyright.com/latest/)
|
|
40
|
+
[](https://mypy-lang.org/)
|
|
41
|
+
[](https://docs.astral.sh/uv/)
|
|
42
|
+
[](https://docs.astral.sh/ruff/)
|
|
43
|
+
|
|
44
|
+
Forward-only pileups streamed from coordinate-sorted BAM and CRAM records, and a table of the alleles at every base.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```console
|
|
49
|
+
pip install streampile
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
### Building Pileups
|
|
55
|
+
|
|
56
|
+
A `StreamingPileupBuilder` reads records once and piles them up at the 0-based positions you ask for, moving forward only.
|
|
57
|
+
|
|
58
|
+
```pycon
|
|
59
|
+
>>> from pysam import AlignmentFile
|
|
60
|
+
>>> from streampile import StreamingPileupBuilder
|
|
61
|
+
>>>
|
|
62
|
+
>>> with (
|
|
63
|
+
... AlignmentFile("tests/data/reads.bam") as reads,
|
|
64
|
+
... StreamingPileupBuilder(reads, min_base_quality=30) as builder,
|
|
65
|
+
... ):
|
|
66
|
+
... first = builder.pileup("chr1", 10)
|
|
67
|
+
... second = builder.pileup("chr1", 12)
|
|
68
|
+
>>>
|
|
69
|
+
>>> first.filtered_depth, first.bases
|
|
70
|
+
(4, ['A', 'T', 'G', 'A'])
|
|
71
|
+
>>> second.filtered_depth, second.bases
|
|
72
|
+
(3, ['G', 'G', 'G'])
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Filter reads with `read_filter`, and count each template once with `without_overlaps()`:
|
|
77
|
+
|
|
78
|
+
```pycon
|
|
79
|
+
>>> with (
|
|
80
|
+
... AlignmentFile("tests/data/reads.bam") as reads,
|
|
81
|
+
... StreamingPileupBuilder(reads, read_filter=lambda read: read.is_paired) as builder,
|
|
82
|
+
... ):
|
|
83
|
+
... pileup = builder.pileup("chr1", 50)
|
|
84
|
+
>>>
|
|
85
|
+
>>> pileup.bases, pileup.without_overlaps().bases
|
|
86
|
+
(['T', 'T'], ['T'])
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Pass `tap=writer.write` to receive every record, in input order, once the builder has moved past it.
|
|
91
|
+
|
|
92
|
+
### Sweeping a Territory
|
|
93
|
+
|
|
94
|
+
`columns` yields the pileup at every position of a span.
|
|
95
|
+
|
|
96
|
+
```pycon
|
|
97
|
+
>>> with AlignmentFile("tests/data/reads.bam") as reads, StreamingPileupBuilder(reads) as builder:
|
|
98
|
+
... sum(pileup.unfiltered_depth for pileup in builder.columns("chr1", 0, 20))
|
|
99
|
+
73
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Tabulating Alleles
|
|
104
|
+
|
|
105
|
+
`tabulate` counts the reads of every allele at every base of a [bedspec](https://github.com/clintval/bedspec) `Territory`.
|
|
106
|
+
Alleles are normalized VCF alleles at 1-based positions, so they match a VCF by `CHROM`, `POS`, `REF`, and `ALT`.
|
|
107
|
+
|
|
108
|
+
```pycon
|
|
109
|
+
>>> from bedspec import Bed3
|
|
110
|
+
>>> from bedspec import Territory
|
|
111
|
+
>>> from pysam import FastaFile
|
|
112
|
+
>>> from streampile import tabulate
|
|
113
|
+
>>>
|
|
114
|
+
>>> territory = Territory([Bed3("chr1", start=9, end=12)])
|
|
115
|
+
>>> with (
|
|
116
|
+
... AlignmentFile("tests/data/reads.bam") as reads,
|
|
117
|
+
... FastaFile("tests/data/reference.fa") as reference,
|
|
118
|
+
... ):
|
|
119
|
+
... bases = list(tabulate(reads, reference, territory, min_base_quality=30))
|
|
120
|
+
>>>
|
|
121
|
+
>>> for base in bases:
|
|
122
|
+
... print(base.pos, base.ref, base.depth, base.alts, base.alt_reads)
|
|
123
|
+
10 A 4 () ()
|
|
124
|
+
11 A 4 ('T', 'GG') (1, 1)
|
|
125
|
+
12 C 4 () ()
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Each base also splits its reads by strand and counts no-calls apart from its depth.
|
|
130
|
+
|
|
131
|
+
### Reading a Table
|
|
132
|
+
|
|
133
|
+
```pycon
|
|
134
|
+
>>> from streampile import TabulationReader
|
|
135
|
+
>>>
|
|
136
|
+
>>> for base in TabulationReader.from_path("tests/data/counts.tsv"):
|
|
137
|
+
... print(base.pos, base.depth, base.alts, base.alt_reads)
|
|
138
|
+
21 5 () ()
|
|
139
|
+
22 5 () ()
|
|
140
|
+
23 4 ('C',) (1,)
|
|
141
|
+
24 4 () ()
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Command Line
|
|
146
|
+
|
|
147
|
+
```console
|
|
148
|
+
streampile tabulate \
|
|
149
|
+
--bam tests/data/reads.bam \
|
|
150
|
+
--ref tests/data/reference.fa \
|
|
151
|
+
--intervals tests/data/territory.bed \
|
|
152
|
+
--min-base-quality 30 \
|
|
153
|
+
--min-mapping-quality 20 \
|
|
154
|
+
--out counts.tsv
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
##streampile-tabulation=1
|
|
159
|
+
##streampile-version=0.1.0
|
|
160
|
+
##bam=tests/data/reads.bam
|
|
161
|
+
##reference=tests/data/reference.fa
|
|
162
|
+
##territory=tests/data/territory.bed
|
|
163
|
+
##min_base_quality=30
|
|
164
|
+
##min_mapping_quality=20
|
|
165
|
+
##exclude_flags=0xf00
|
|
166
|
+
#contig pos ref depth no_calls ref_reads ref_fwd ref_rev alt_refs alts alt_reads alt_fwd alt_rev
|
|
167
|
+
chr1 21 T 5 0 5 3 2
|
|
168
|
+
chr1 22 G 5 0 5 3 2
|
|
169
|
+
chr1 23 C 4 0 3 2 1 CA C 1 0 1
|
|
170
|
+
chr1 24 A 4 0 3 2 1
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Write to a `.gz` path with `--index tbi` to compress and index the table, then read a region back:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
for base in TabulationReader.query("counts.tsv.gz", "chr1", 20, 24):
|
|
177
|
+
print(base.pos, base.depth, base.alts)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Development and Testing
|
|
181
|
+
|
|
182
|
+
See the [contributing guide](https://github.com/clintval/streampile/blob/main/CONTRIBUTING.md) for more information.
|
|
183
|
+
|
|
184
|
+
The streaming design follows the `StreamingPileupBuilder` of [fgbio](https://github.com/fulcrumgenomics/fgbio); see [NOTICE](https://github.com/clintval/streampile/blob/main/NOTICE).
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# streampile
|
|
2
|
+
|
|
3
|
+
[](https://github.com/clintval/streampile/actions/workflows/tests.yml?query=branch%3Amain)
|
|
4
|
+
[](https://github.com/clintval/streampile)
|
|
5
|
+
[](https://github.com/clintval/streampile/blob/main/LICENSE)
|
|
6
|
+
[](https://docs.basedpyright.com/latest/)
|
|
7
|
+
[](https://mypy-lang.org/)
|
|
8
|
+
[](https://docs.astral.sh/uv/)
|
|
9
|
+
[](https://docs.astral.sh/ruff/)
|
|
10
|
+
|
|
11
|
+
Forward-only pileups streamed from coordinate-sorted BAM and CRAM records, and a table of the alleles at every base.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```console
|
|
16
|
+
pip install streampile
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
### Building Pileups
|
|
22
|
+
|
|
23
|
+
A `StreamingPileupBuilder` reads records once and piles them up at the 0-based positions you ask for, moving forward only.
|
|
24
|
+
|
|
25
|
+
```pycon
|
|
26
|
+
>>> from pysam import AlignmentFile
|
|
27
|
+
>>> from streampile import StreamingPileupBuilder
|
|
28
|
+
>>>
|
|
29
|
+
>>> with (
|
|
30
|
+
... AlignmentFile("tests/data/reads.bam") as reads,
|
|
31
|
+
... StreamingPileupBuilder(reads, min_base_quality=30) as builder,
|
|
32
|
+
... ):
|
|
33
|
+
... first = builder.pileup("chr1", 10)
|
|
34
|
+
... second = builder.pileup("chr1", 12)
|
|
35
|
+
>>>
|
|
36
|
+
>>> first.filtered_depth, first.bases
|
|
37
|
+
(4, ['A', 'T', 'G', 'A'])
|
|
38
|
+
>>> second.filtered_depth, second.bases
|
|
39
|
+
(3, ['G', 'G', 'G'])
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Filter reads with `read_filter`, and count each template once with `without_overlaps()`:
|
|
44
|
+
|
|
45
|
+
```pycon
|
|
46
|
+
>>> with (
|
|
47
|
+
... AlignmentFile("tests/data/reads.bam") as reads,
|
|
48
|
+
... StreamingPileupBuilder(reads, read_filter=lambda read: read.is_paired) as builder,
|
|
49
|
+
... ):
|
|
50
|
+
... pileup = builder.pileup("chr1", 50)
|
|
51
|
+
>>>
|
|
52
|
+
>>> pileup.bases, pileup.without_overlaps().bases
|
|
53
|
+
(['T', 'T'], ['T'])
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pass `tap=writer.write` to receive every record, in input order, once the builder has moved past it.
|
|
58
|
+
|
|
59
|
+
### Sweeping a Territory
|
|
60
|
+
|
|
61
|
+
`columns` yields the pileup at every position of a span.
|
|
62
|
+
|
|
63
|
+
```pycon
|
|
64
|
+
>>> with AlignmentFile("tests/data/reads.bam") as reads, StreamingPileupBuilder(reads) as builder:
|
|
65
|
+
... sum(pileup.unfiltered_depth for pileup in builder.columns("chr1", 0, 20))
|
|
66
|
+
73
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Tabulating Alleles
|
|
71
|
+
|
|
72
|
+
`tabulate` counts the reads of every allele at every base of a [bedspec](https://github.com/clintval/bedspec) `Territory`.
|
|
73
|
+
Alleles are normalized VCF alleles at 1-based positions, so they match a VCF by `CHROM`, `POS`, `REF`, and `ALT`.
|
|
74
|
+
|
|
75
|
+
```pycon
|
|
76
|
+
>>> from bedspec import Bed3
|
|
77
|
+
>>> from bedspec import Territory
|
|
78
|
+
>>> from pysam import FastaFile
|
|
79
|
+
>>> from streampile import tabulate
|
|
80
|
+
>>>
|
|
81
|
+
>>> territory = Territory([Bed3("chr1", start=9, end=12)])
|
|
82
|
+
>>> with (
|
|
83
|
+
... AlignmentFile("tests/data/reads.bam") as reads,
|
|
84
|
+
... FastaFile("tests/data/reference.fa") as reference,
|
|
85
|
+
... ):
|
|
86
|
+
... bases = list(tabulate(reads, reference, territory, min_base_quality=30))
|
|
87
|
+
>>>
|
|
88
|
+
>>> for base in bases:
|
|
89
|
+
... print(base.pos, base.ref, base.depth, base.alts, base.alt_reads)
|
|
90
|
+
10 A 4 () ()
|
|
91
|
+
11 A 4 ('T', 'GG') (1, 1)
|
|
92
|
+
12 C 4 () ()
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Each base also splits its reads by strand and counts no-calls apart from its depth.
|
|
97
|
+
|
|
98
|
+
### Reading a Table
|
|
99
|
+
|
|
100
|
+
```pycon
|
|
101
|
+
>>> from streampile import TabulationReader
|
|
102
|
+
>>>
|
|
103
|
+
>>> for base in TabulationReader.from_path("tests/data/counts.tsv"):
|
|
104
|
+
... print(base.pos, base.depth, base.alts, base.alt_reads)
|
|
105
|
+
21 5 () ()
|
|
106
|
+
22 5 () ()
|
|
107
|
+
23 4 ('C',) (1,)
|
|
108
|
+
24 4 () ()
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Command Line
|
|
113
|
+
|
|
114
|
+
```console
|
|
115
|
+
streampile tabulate \
|
|
116
|
+
--bam tests/data/reads.bam \
|
|
117
|
+
--ref tests/data/reference.fa \
|
|
118
|
+
--intervals tests/data/territory.bed \
|
|
119
|
+
--min-base-quality 30 \
|
|
120
|
+
--min-mapping-quality 20 \
|
|
121
|
+
--out counts.tsv
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
##streampile-tabulation=1
|
|
126
|
+
##streampile-version=0.1.0
|
|
127
|
+
##bam=tests/data/reads.bam
|
|
128
|
+
##reference=tests/data/reference.fa
|
|
129
|
+
##territory=tests/data/territory.bed
|
|
130
|
+
##min_base_quality=30
|
|
131
|
+
##min_mapping_quality=20
|
|
132
|
+
##exclude_flags=0xf00
|
|
133
|
+
#contig pos ref depth no_calls ref_reads ref_fwd ref_rev alt_refs alts alt_reads alt_fwd alt_rev
|
|
134
|
+
chr1 21 T 5 0 5 3 2
|
|
135
|
+
chr1 22 G 5 0 5 3 2
|
|
136
|
+
chr1 23 C 4 0 3 2 1 CA C 1 0 1
|
|
137
|
+
chr1 24 A 4 0 3 2 1
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Write to a `.gz` path with `--index tbi` to compress and index the table, then read a region back:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
for base in TabulationReader.query("counts.tsv.gz", "chr1", 20, 24):
|
|
144
|
+
print(base.pos, base.depth, base.alts)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development and Testing
|
|
148
|
+
|
|
149
|
+
See the [contributing guide](https://github.com/clintval/streampile/blob/main/CONTRIBUTING.md) for more information.
|
|
150
|
+
|
|
151
|
+
The streaming design follows the `StreamingPileupBuilder` of [fgbio](https://github.com/fulcrumgenomics/fgbio); see [NOTICE](https://github.com/clintval/streampile/blob/main/NOTICE).
|