bam-dedup 0.2.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.
- bam_dedup-0.2.0/LICENSE.txt +21 -0
- bam_dedup-0.2.0/MANIFEST.in +21 -0
- bam_dedup-0.2.0/PKG-INFO +40 -0
- bam_dedup-0.2.0/README.md +199 -0
- bam_dedup-0.2.0/bam_dedup.egg-info/SOURCES.txt +25 -0
- bam_dedup-0.2.0/dedup/__init__.py +46 -0
- bam_dedup-0.2.0/dedup/_fast.c +31566 -0
- bam_dedup-0.2.0/dedup/_fast.pyx +440 -0
- bam_dedup-0.2.0/dedup/cli.py +39 -0
- bam_dedup-0.2.0/dedup/fgbiolike.py +783 -0
- bam_dedup-0.2.0/dedup/picardlike.py +747 -0
- bam_dedup-0.2.0/pyproject.toml +3 -0
- bam_dedup-0.2.0/setup.cfg +4 -0
- bam_dedup-0.2.0/setup.py +60 -0
- bam_dedup-0.2.0/tests/__init__.py +0 -0
- bam_dedup-0.2.0/tests/benchmark/README.md +75 -0
- bam_dedup-0.2.0/tests/benchmark/compare_flags.py +67 -0
- bam_dedup-0.2.0/tests/benchmark/make_stress_bam.py +99 -0
- bam_dedup-0.2.0/tests/benchmark/run_benchmark.py +169 -0
- bam_dedup-0.2.0/tests/data/NA12891.bam +0 -0
- bam_dedup-0.2.0/tests/data/NA12892.bam +0 -0
- bam_dedup-0.2.0/tests/data/make_umi_bam.py +98 -0
- bam_dedup-0.2.0/tests/data/synthetic_optical.bam +0 -0
- bam_dedup-0.2.0/tests/data/umi_consensus.bam +0 -0
- bam_dedup-0.2.0/tests/data/umi_consensus.consensus.golden.bam +0 -0
- bam_dedup-0.2.0/tests/data/umi_consensus.grouped.golden.bam +0 -0
- bam_dedup-0.2.0/tests/test_dedup.py +87 -0
- bam_dedup-0.2.0/tests/test_fgbiolike.py +125 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Caleb Lareau
|
|
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.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Ship Cython sources (and generated C, if present) so sdists build cleanly
|
|
2
|
+
recursive-include dedup *.pyx *.pxd *.c
|
|
3
|
+
|
|
4
|
+
# Test + benchmark code and small BAMs used by the suite
|
|
5
|
+
recursive-include tests *.py *.md
|
|
6
|
+
recursive-include tests/data *.bam *.bai
|
|
7
|
+
|
|
8
|
+
# Misc
|
|
9
|
+
include README.md
|
|
10
|
+
include LICENSE.txt
|
|
11
|
+
include pyproject.toml
|
|
12
|
+
|
|
13
|
+
# Prune build cruft
|
|
14
|
+
prune *.egg-info
|
|
15
|
+
global-exclude *.so
|
|
16
|
+
global-exclude *.DS_Store
|
|
17
|
+
global-exclude __pycache__
|
|
18
|
+
|
|
19
|
+
# Keep the 15 MB Picard jar out of the distributable (repo-only, for local
|
|
20
|
+
# benchmarking). See tests/benchmark/README.md.
|
|
21
|
+
global-exclude *.jar
|
bam_dedup-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bam-dedup
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Fast, JVM-free BAM deduplication: PCR/optical (Picard-like) and molecular/UMI consensus (fgbio-like).
|
|
5
|
+
Home-page: https://github.com/caleblareau/bam-dedup
|
|
6
|
+
Author: Caleb Lareau
|
|
7
|
+
Author-email: caleb.lareau@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: POSIX
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Unix
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Cython
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
License-File: LICENSE.txt
|
|
23
|
+
Requires-Dist: pysam
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest; extra == "test"
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: platform
|
|
34
|
+
Dynamic: provides-extra
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
Dynamic: summary
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
bam-dedup: a fast, JVM-free reimplementation of familiar tools.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# bam-dedup
|
|
2
|
+
|
|
3
|
+
A fast, **JVM-free** toolkit for removing duplicate reads from BAM/CRAM. It
|
|
4
|
+
handles the **two major kinds of duplication event** in sequencing data, each a
|
|
5
|
+
faithful, independent port of the standard reference tool, with the
|
|
6
|
+
performance-critical inner loops accelerated in Cython. All BAM/CRAM I/O goes
|
|
7
|
+
through [pysam](https://github.com/pysam-developers/pysam) (htslib) — no Java
|
|
8
|
+
required.
|
|
9
|
+
|
|
10
|
+
| Duplication kind | Module | Reproduces | What it does |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| **PCR / optical duplicates** | `dedup.picardlike` | [Picard MarkDuplicates](https://broadinstitute.github.io/picard/) | flags or removes duplicate *records* by 5′ position + orientation |
|
|
13
|
+
| **Molecular / UMI duplicates** | `dedup.fgbiolike` | [fgbio](https://github.com/fulcrumgenomics/fgbio) GroupReadsByUmi + CallMolecularConsensusReads | collapses reads sharing a UMI into one error-corrected *consensus* read |
|
|
14
|
+
|
|
15
|
+
Both are validated against their reference tool:
|
|
16
|
+
|
|
17
|
+
* `picardlike` is **bit-identical to Picard MarkDuplicates 2.18.21** for the
|
|
18
|
+
duplicate flag *and* the optical/sequencing-duplicate classification.
|
|
19
|
+
* `fgbiolike` is **bit-identical to fgbio 4.1.0** (`GroupReadsByUmi -s Identity`
|
|
20
|
+
and `CallMolecularConsensusReads`) for the grouped `MI` assignment and every
|
|
21
|
+
consensus base, quality, and `cD`/`cM`/`cE`/`cd`/`ce` tag — verified
|
|
22
|
+
record-for-record on both a bundled test BAM and a 176k-read MAESTER file.
|
|
23
|
+
|
|
24
|
+
Both run faster than the corresponding Java tool.
|
|
25
|
+
|
|
26
|
+
> The importable modules `dedup.picardlike` / `dedup.fgbiolike` are independent
|
|
27
|
+
> reimplementations, **not** affiliated with or derived from the Picard or fgbio
|
|
28
|
+
> source code; those names are referenced only to describe the behavior
|
|
29
|
+
> reproduced.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### From PyPI (once published)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install bam-dedup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### From source
|
|
42
|
+
|
|
43
|
+
Requires a C compiler (clang/gcc) and Python ≥ 3.8. Cython is pulled in
|
|
44
|
+
automatically as a build dependency via `pyproject.toml`; the only *runtime*
|
|
45
|
+
dependency is `pysam`.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/caleblareau/bam-dedup.git
|
|
49
|
+
cd bam-dedup
|
|
50
|
+
pip install .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### For development (editable install + tests)
|
|
54
|
+
|
|
55
|
+
An editable install compiles the Cython extension (`dedup._fast`) in place, so
|
|
56
|
+
`pytest` can find it:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install -e ".[test]"
|
|
60
|
+
pytest
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Verify the compiled acceleration is active:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import dedup
|
|
67
|
+
print(dedup.HAVE_CYTHON) # True when the Cython extension is built
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If `HAVE_CYTHON` is `False`, the package still runs correctly using a pure-Python
|
|
71
|
+
fallback that produces bit-identical results (just slower).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
pysam-like: point at an input BAM, call one function, get an output BAM.
|
|
78
|
+
|
|
79
|
+
### PCR / optical duplicates (`picardlike`)
|
|
80
|
+
|
|
81
|
+
Input must be **coordinate-sorted** (e.g. `samtools sort`).
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from dedup import picardlike
|
|
85
|
+
|
|
86
|
+
# Picard-style: flag duplicates in place (records kept, 0x400 flag set)
|
|
87
|
+
picardlike.mark_duplicates("input.sorted.bam", "marked.bam")
|
|
88
|
+
|
|
89
|
+
# Produce a deduplicated BAM (duplicate records removed)
|
|
90
|
+
picardlike.deduplicate("input.sorted.bam", "dedup.bam")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`mark_duplicates(input_bam, output_bam, **options)`:
|
|
94
|
+
|
|
95
|
+
| option | default | meaning |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `remove_duplicates` | `False` | drop duplicate records instead of only flagging them |
|
|
98
|
+
| `remove_sequencing_duplicates` | `False` | drop optical/sequencing duplicates |
|
|
99
|
+
| `read_name_regex_enabled` | `True` | parse tile/x/y from read names for optical detection |
|
|
100
|
+
| `optical_pixel_distance` | `100` | optical duplicate pixel distance |
|
|
101
|
+
|
|
102
|
+
`deduplicate(...)` is a convenience wrapper equal to
|
|
103
|
+
`mark_duplicates(..., remove_duplicates=True)`.
|
|
104
|
+
|
|
105
|
+
### Molecular / UMI duplicates (`fgbiolike`)
|
|
106
|
+
|
|
107
|
+
Reads must carry a UMI tag (default `UB`); a cell-barcode tag (default `CB`) is
|
|
108
|
+
used when present. The one-shot `consensus()` runs the two fgbio steps —
|
|
109
|
+
grouping reads into molecules, then collapsing each molecule into a consensus
|
|
110
|
+
read:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from dedup import fgbiolike
|
|
114
|
+
|
|
115
|
+
# group by UMI (+ cell barcode) and emit error-corrected consensus reads
|
|
116
|
+
fgbiolike.consensus("input.bam", "consensus.bam", umi_tag="UB", min_reads=3)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Or run the two stages explicitly:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
fgbiolike.group_reads_by_umi("input.bam", "grouped.bam", raw_tag="UB")
|
|
123
|
+
fgbiolike.call_molecular_consensus_reads("grouped.bam", "consensus.bam",
|
|
124
|
+
tag="MI", min_reads=3)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`consensus(...)` / `call_molecular_consensus_reads(...)` options:
|
|
128
|
+
|
|
129
|
+
| option | default | meaning |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `min_reads` | `3` | minimum reads in a molecule to emit a consensus |
|
|
132
|
+
| `umi_tag` | `"UB"` | raw UMI tag used for grouping |
|
|
133
|
+
| `cell_tag` | `"CB"` | cell-barcode tag; consensus is called per cell |
|
|
134
|
+
| `consensus_tag` | `"MI"` | tag grouped on in the consensus step (see note) |
|
|
135
|
+
| `on_cell_collision` | `"split"` | if a molecule spans >1 cell: `split`, `merge`, or `error` |
|
|
136
|
+
|
|
137
|
+
> **Grouping key.** The consensus step groups by the assigned molecular id `MI`
|
|
138
|
+
> (per position × cell × UMI). This is the correct, crash-free equivalent of
|
|
139
|
+
> fgbio's `-t UB`: fgbio *aborts* if a raw-UMI group ever spans two cell
|
|
140
|
+
> barcodes, whereas `on_cell_collision="split"` (the default) simply calls one
|
|
141
|
+
> consensus per cell.
|
|
142
|
+
|
|
143
|
+
### Command line
|
|
144
|
+
|
|
145
|
+
Installing the package provides two console commands:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# PCR/optical duplicate marking
|
|
149
|
+
bam-dedup -i input.sorted.bam -o dedup.bam --remove-duplicates
|
|
150
|
+
|
|
151
|
+
# UMI molecular-consensus deduplication (group + call in one step)
|
|
152
|
+
bam-consensus consensus -i input.bam -o consensus.bam -M 3
|
|
153
|
+
# or the individual stages:
|
|
154
|
+
bam-consensus group -i input.bam -o grouped.bam -t UB
|
|
155
|
+
bam-consensus call -i grouped.bam -o consensus.bam -t MI -M 3
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Scope
|
|
161
|
+
|
|
162
|
+
**`picardlike`** — faithful to Picard for coordinate-sorted input, the default
|
|
163
|
+
`SUM_OF_BASE_QUALITIES` scoring strategy, standard Illumina read names, and
|
|
164
|
+
single/multiple libraries. *Not yet handled:* queryname-sorted input, flow-based
|
|
165
|
+
mode, and the non-default scoring strategies (`TOTAL_MAPPED_REFERENCE_LENGTH`,
|
|
166
|
+
`RANDOM`).
|
|
167
|
+
|
|
168
|
+
**`fgbiolike`** — faithful to fgbio for the `Identity` grouping strategy
|
|
169
|
+
(`edits=0`) and the single-end / fragment consensus path (the MAESTER-style
|
|
170
|
+
input), with fgbio's default error model (pre=45, post=40),
|
|
171
|
+
`min-input-base-quality=10`, and per-base tags. *Not yet handled:* the
|
|
172
|
+
Adjacency/Edit/Paired strategies (`edits > 0`) and paired-end / duplex /
|
|
173
|
+
overlapping-base consensus.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Tests
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
pip install -e ".[test]"
|
|
181
|
+
pytest
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`tests/test_dedup.py` validates duplicate and optical-duplicate counts against
|
|
185
|
+
Picard's reference output on the bundled BAMs; `tests/test_fgbiolike.py`
|
|
186
|
+
validates UMI grouping and consensus reads against **fgbio golden outputs**
|
|
187
|
+
(`tests/data/umi_consensus.*.golden.bam`). Both suites also check that the Cython
|
|
188
|
+
and pure-Python paths agree.
|
|
189
|
+
|
|
190
|
+
## Benchmarking against Picard
|
|
191
|
+
|
|
192
|
+
Reproducible head-to-head scripts (and a bundled `picard.jar`) live in
|
|
193
|
+
[`tests/benchmark/`](tests/benchmark/). See
|
|
194
|
+
[`tests/benchmark/README.md`](tests/benchmark/README.md) for the concordance and
|
|
195
|
+
timing harness.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT © Caleb Lareau
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
dedup/__init__.py
|
|
7
|
+
dedup/_fast.c
|
|
8
|
+
dedup/_fast.pyx
|
|
9
|
+
dedup/cli.py
|
|
10
|
+
dedup/fgbiolike.py
|
|
11
|
+
dedup/picardlike.py
|
|
12
|
+
tests/__init__.py
|
|
13
|
+
tests/test_dedup.py
|
|
14
|
+
tests/test_fgbiolike.py
|
|
15
|
+
tests/benchmark/README.md
|
|
16
|
+
tests/benchmark/compare_flags.py
|
|
17
|
+
tests/benchmark/make_stress_bam.py
|
|
18
|
+
tests/benchmark/run_benchmark.py
|
|
19
|
+
tests/data/NA12891.bam
|
|
20
|
+
tests/data/NA12892.bam
|
|
21
|
+
tests/data/make_umi_bam.py
|
|
22
|
+
tests/data/synthetic_optical.bam
|
|
23
|
+
tests/data/umi_consensus.bam
|
|
24
|
+
tests/data/umi_consensus.consensus.golden.bam
|
|
25
|
+
tests/data/umi_consensus.grouped.golden.bam
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
bam-dedup: a fast, JVM-free toolkit for removing duplicate reads from BAM/CRAM.
|
|
3
|
+
|
|
4
|
+
It handles the two major classes of duplicate with faithful, independent ports of
|
|
5
|
+
the reference tools, both accelerated in Cython (:mod:`dedup._fast`) and doing all
|
|
6
|
+
I/O through pysam (htslib) -- no Java dependency:
|
|
7
|
+
|
|
8
|
+
* **PCR / optical duplicates** -- :mod:`dedup.picardlike`, a port of Picard/htsjdk
|
|
9
|
+
MarkDuplicates (coordinate-sorted, Illumina paired-end, default options). Marks
|
|
10
|
+
or removes duplicate *records*.
|
|
11
|
+
|
|
12
|
+
* **Molecular (UMI) duplicates** -- :mod:`dedup.fgbiolike`, a port of fgbio
|
|
13
|
+
GroupReadsByUmi + CallMolecularConsensusReads (Identity strategy, fragment
|
|
14
|
+
path). Collapses reads sharing a UMI into one error-corrected *consensus* read.
|
|
15
|
+
|
|
16
|
+
Typical use::
|
|
17
|
+
|
|
18
|
+
from dedup import picardlike, fgbiolike
|
|
19
|
+
|
|
20
|
+
# PCR/optical: flag duplicates (records kept, 0x400 flag set)
|
|
21
|
+
picardlike.mark_duplicates("input.bam", "marked.bam")
|
|
22
|
+
|
|
23
|
+
# molecular/UMI: group by UMI and call consensus reads
|
|
24
|
+
fgbiolike.consensus("input.bam", "consensus.bam", umi_tag="UB", min_reads=3)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from dedup.picardlike import mark_duplicates, deduplicate
|
|
28
|
+
from dedup.fgbiolike import (
|
|
29
|
+
group_reads_by_umi,
|
|
30
|
+
call_molecular_consensus_reads,
|
|
31
|
+
consensus,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from dedup import _fast # noqa: F401
|
|
36
|
+
HAVE_CYTHON = True
|
|
37
|
+
except ImportError: # pragma: no cover
|
|
38
|
+
HAVE_CYTHON = False
|
|
39
|
+
|
|
40
|
+
__version__ = "0.2.0"
|
|
41
|
+
__all__ = [
|
|
42
|
+
"picardlike", "fgbiolike",
|
|
43
|
+
"mark_duplicates", "deduplicate",
|
|
44
|
+
"group_reads_by_umi", "call_molecular_consensus_reads", "consensus",
|
|
45
|
+
"HAVE_CYTHON", "__version__",
|
|
46
|
+
]
|