|
1 | | -# Deacon Python bindings |
| 1 | +# Deacon for Python |
2 | 2 |
|
3 | | -Enables index reuse between filtering sessions. |
| 3 | +Python bindings for [Deacon](https://github.com/bede/deacon), enabling fast multithreaded DNA sequence filtering for e.g. host pangenome depletion using Python code. These bindings load an index once, allowing subsequent filtering runs with low latency. Deacon's complete functionality is currently only available using the [Rust/CLI version of Deacon](https://github.com/bede/deacon). |
| 4 | + |
| 5 | +## Installation |
| 6 | + |
| 7 | +```bash |
| 8 | +uv install deacon |
| 9 | +``` |
| 10 | + |
| 11 | +## Quickstart |
4 | 12 |
|
5 | 13 | ```python |
6 | 14 | from deacon import Index |
7 | 15 |
|
8 | 16 | index = Index("panhuman-1.k31w15.idx") |
9 | | -stats = [ |
10 | | - index.filter(fq, deplete=True, rename=True, |
11 | | - output=fq.replace(".fastq.gz", ".clean.fastq.gz")) |
12 | | - for fq in input_fastqs |
13 | | -] |
14 | | -print(stats[0]["seqs_in"], stats[0]["seqs_out"]) |
| 17 | +stats = index.filter( |
| 18 | + fastq_path, |
| 19 | + deplete=True, |
| 20 | + rename=True, |
| 21 | + output=fastq_path.replace(".fastq.gz", ".clean.fastq.gz") |
| 22 | +) |
| 23 | +print(stats["seqs_in"], stats["seqs_out"]) |
| 24 | +``` |
| 25 | + |
| 26 | +## `Index()` |
| 27 | + |
| 28 | +Load a minimizer index or probabilistic filter from disk. The resulting object may be reused across many `filter` calls. |
| 29 | + |
| 30 | +## `Index.fetch()` |
| 31 | + |
| 32 | +Download a prebuilt index, then load and return it (a static method, so `Index.fetch(...)` returns an `Index`). `output` is the local path to save to; when omitted it defaults to `"{name}.k{k}w{w}.idx"` in the working directory. The index is downloaded on every call — there is no local cache, so an existing file at that path is overwritten. |
| 33 | + |
| 34 | +```python |
| 35 | +index = Index.fetch("panhuman-1", k=31, w=15, output=None) |
15 | 36 | ``` |
16 | 37 |
|
17 | | -`filter` accepts single or paired input (`fastq2=`, `output2=`), auto-detects `.gz`/`.zst`/ |
18 | | -`.xz` output compression from the extension, and returns a `dict` of summary statistics. |
19 | | -`from deacon import filter` also exposes `filter(index, ...)` as a free function. |
| 38 | +## `Index.info()` |
| 39 | + |
| 40 | +`Index.info(index_path)` Returns a `dict` of index metadata: |
| 41 | + |
| 42 | +| Key | Meaning | |
| 43 | +| --- | --- | |
| 44 | +| `k` | *k*-mer length | |
| 45 | +| `w` | minimizer window size | |
| 46 | +| `format` | `exact-u64`, `exact-u128`, or `bff` (binary fuse filter) | |
| 47 | +| `count` | number of keys in index (fingerprint slot count for `bff`) | |
| 48 | + |
| 49 | +## `Index.filter()` |
| 50 | + |
| 51 | +Filter a FASTA/FASTQ file or file pair against the index and return a `dict` of summary statistics. Auto-detects `.gz`/`.zst`/`.xz` compression on both input and output based on file extension. The Python GIL is released while filtering meaning calls benefit from multithreading. Refer to the [main Deacon readme](https://github.com/bede/deacon) for more detailed usage examples. |
| 52 | + |
| 53 | +```python |
| 54 | +def filter( |
| 55 | + fastq, # input path (FASTA/FASTQ, optionally .gz/.zst/.xz) |
| 56 | + fastq2=None, # second mate for paired reads |
| 57 | + deplete=False, # False = search (keep matches); True = deplete (remove matches) |
| 58 | + rename=False, # replace read names with sequential integers |
| 59 | + rename_random=False, # replace read names with random strings |
| 60 | + output=None, # output path; None writes to stdout |
| 61 | + output2=None, # second output path for paired reads |
| 62 | + abs_threshold=2, # min absolute minimizer hits to call a match |
| 63 | + rel_threshold=0.01, # min proportion of minimizers hitting to call a match |
| 64 | + prefix_length=0, # only use the first N bp of each read (0 = whole read) |
| 65 | + output_fasta=False, # emit FASTA instead of FASTQ |
| 66 | + threads=8, # worker threads for filtering |
| 67 | + compression_level=2, # output compression level |
| 68 | + compression_threads=0, # threads for output compression (0 = auto) |
| 69 | + debug=False, # verbose per-read debug output |
| 70 | + quiet=True, # suppress progress/log output on stderr |
| 71 | +) -> dict |
| 72 | +``` |
| 73 | + |
| 74 | +**Modes.** With `deplete=False` (the default, *search* mode) reads that match the index are kept; with `deplete=True` reads that match are removed (host depletion). A read is a match only when it clears **both** thresholds: at least `abs_threshold` minimizer hits **and** at least `rel_threshold` of its minimizers hitting the index. |
| 75 | + |
| 76 | +**Output.** When `output` is `None` the filtered records are written to stdout. To count without keeping the filtered sequences, pass `output="/dev/null"`. Statistics are returned regardless. |
| 77 | + |
| 78 | +**Return value.** A `dict` including the run configuration (`version`, `index`, `input`/`input2`, `output`/`output2`, `k`, `w`, `abs_threshold`, `rel_threshold`, `prefix_length`, `deplete`, `rename`, `rename_random`) and the results: |
| 79 | + |
| 80 | +| Key | Meaning | |
| 81 | +| --- | --- | |
| 82 | +| `seqs_in`, `seqs_out`, `seqs_removed` | sequence counts | |
| 83 | +| `seqs_out_proportion`, `seqs_removed_proportion` | sequence proportions | |
| 84 | +| `bp_in`, `bp_out`, `bp_removed` | base-pair counts | |
| 85 | +| `bp_out_proportion`, `bp_removed_proportion` | base-pair proportions | |
| 86 | +| `time` | wall-clock seconds | |
| 87 | +| `seqs_per_second`, `bp_per_second` | throughput (filtering only) | |
| 88 | +| `seqs_per_second_total`, `bp_per_second_total` | throughput (including load/IO) | |
0 commit comments