ugbio-consensus 1.34.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.
- ugbio_consensus-1.34.0/PKG-INFO +302 -0
- ugbio_consensus-1.34.0/README.consensus.md +289 -0
- ugbio_consensus-1.34.0/pyproject.toml +40 -0
- ugbio_consensus-1.34.0/setup.cfg +4 -0
- ugbio_consensus-1.34.0/ugbio_consensus/__init__.py +0 -0
- ugbio_consensus-1.34.0/ugbio_consensus/consensus_log.py +77 -0
- ugbio_consensus-1.34.0/ugbio_consensus/consensus_report.py +852 -0
- ugbio_consensus-1.34.0/ugbio_consensus/duplex_metrics.py +523 -0
- ugbio_consensus-1.34.0/ugbio_consensus/mark_duplex_consensus.py +171 -0
- ugbio_consensus-1.34.0/ugbio_consensus/mark_duplicates_mi.py +1276 -0
- ugbio_consensus-1.34.0/ugbio_consensus/on_target.py +102 -0
- ugbio_consensus-1.34.0/ugbio_consensus/pair_consensus_reads.py +189 -0
- ugbio_consensus-1.34.0/ugbio_consensus/set_consensus_strand.py +176 -0
- ugbio_consensus-1.34.0/ugbio_consensus/unpair_rename_by_strand.py +427 -0
- ugbio_consensus-1.34.0/ugbio_consensus.egg-info/PKG-INFO +302 -0
- ugbio_consensus-1.34.0/ugbio_consensus.egg-info/SOURCES.txt +18 -0
- ugbio_consensus-1.34.0/ugbio_consensus.egg-info/dependency_links.txt +1 -0
- ugbio_consensus-1.34.0/ugbio_consensus.egg-info/entry_points.txt +8 -0
- ugbio_consensus-1.34.0/ugbio_consensus.egg-info/requires.txt +4 -0
- ugbio_consensus-1.34.0/ugbio_consensus.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ugbio_consensus
|
|
3
|
+
Version: 1.34.0
|
|
4
|
+
Summary: Ultima Genomics consensus (ReadFuserAlignSort) duplicate marking, performance & duplex reports
|
|
5
|
+
Author-email: Gat Krieger <gat.krieger@ultimagen.com>, Doron Shem-Tov <doron.shemtov@ultimagen.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: ugbio_core[vcfbed]
|
|
10
|
+
Requires-Dist: ugbio_cloud_utils
|
|
11
|
+
Requires-Dist: pysam>=0.22.0
|
|
12
|
+
Requires-Dist: boto3
|
|
13
|
+
|
|
14
|
+
# ugbio_consensus
|
|
15
|
+
|
|
16
|
+
Duplicate marking and performance & duplex reports for the **ReadFuserAlignSort**
|
|
17
|
+
(consensus tool) pipeline at Ultima Genomics.
|
|
18
|
+
|
|
19
|
+
| Entry point | Runs | Role |
|
|
20
|
+
|-------------|------|------|
|
|
21
|
+
| `mark_duplicates_mi` | *before* `read_fuser` | Assign `MI`/`DS` duplicate families, and with `--use-umi` the `CS` cross-strand duplex tag. |
|
|
22
|
+
| `mark_duplex_consensus` | *after* the consensus is sorted | Write `dx=1` on the consensus reads of CS families whose `po` shows both orientations (F1R2 and F2R1). |
|
|
23
|
+
| `pair_consensus_reads` | *after* `read_fuser`, before alignment | Re-pair the two strand-consensus reads of each pair (shared name, R1/R2 flags) so UA 4.x aligns them as a pair. |
|
|
24
|
+
| `consensus_report` | *after* the pipeline | Per-sample HTML report of alignment, duplication, coverage and duplex family metrics. |
|
|
25
|
+
|
|
26
|
+
## mark_duplicates_mi
|
|
27
|
+
|
|
28
|
+
Marks duplicate families on a coordinate-sorted BAM/CRAM and writes, on every
|
|
29
|
+
record of a family:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
MI:Z:<run-id>-<10-digit family id> the duplicate family (molecular identifier)
|
|
33
|
+
DS:i:<family size> number of pairs in that family
|
|
34
|
+
CS:Z:<run-id>-<10-digit family id> --use-umi only: the cross-strand duplex group
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`--use-umi` adds `(R1.u5, R2.u3)` to the dedup key alongside the position, so the
|
|
38
|
+
two strands of one duplex molecule land in **separate** `MI` families — which is
|
|
39
|
+
what we want, since each strand is consensus-called on its own. The cost is that
|
|
40
|
+
the duplex relationship is erased: `read_fuser` groups strictly on `MI`, so
|
|
41
|
+
`duplex_consensus` would be 0 by construction. `CS` restores the link.
|
|
42
|
+
|
|
43
|
+
Two clusters are cross-strand partners iff they share the positional key, their
|
|
44
|
+
UMI pairs are exact mutual reverses, **and** they sit on opposite strands. That
|
|
45
|
+
last term is read off the FLAG (`read.is_reverse`), not off which mate starts
|
|
46
|
+
further left: on a real library the two disagree for 10.5 % of pairs, because
|
|
47
|
+
fragments shorter than the read dovetail.
|
|
48
|
+
|
|
49
|
+
`CS` is **total** — a cluster with no partner gets `CS` equal to its own `MI`
|
|
50
|
+
rather than no tag at all. That matters downstream, because `demux --umi=CS`
|
|
51
|
+
treats a missing tag as the empty value, and every `CS`-less read at a position
|
|
52
|
+
would then collapse into one duplicate family. So `CS == MI` reads as "clustered,
|
|
53
|
+
but no cross-strand partner was found", and `CS != MI` as "duplex partner found".
|
|
54
|
+
|
|
55
|
+
Two opt-in options (both off by default, so the default output is unchanged) merge
|
|
56
|
+
clusters that are the same molecule into one `MI` family:
|
|
57
|
+
|
|
58
|
+
- `--position-tolerance BP` merges same-UMI clusters whose fragment boundaries differ
|
|
59
|
+
by at most `BP` (read1/read2 use asymmetric adapters, so one molecule's copies can map
|
|
60
|
+
1-3 bp apart). Drifts chain transitively.
|
|
61
|
+
- `--cross-strand` also merges the F1R2/F2R1 strands of a duplex molecule into one `MI`,
|
|
62
|
+
using the same orientation-aware rule as `CS`. Boundaries must match within
|
|
63
|
+
`--position-tolerance`.
|
|
64
|
+
- `--orientation-in-key` does the opposite: the pair orientation (F1R2/F2R1/FF/RR) joins the
|
|
65
|
+
dedup key, so the two strands of one molecule never share an `MI`. It matters for a symmetric
|
|
66
|
+
UMI (`u5 == u3`), where both strands otherwise get the same key and so one `MI`. `CS` still links
|
|
67
|
+
the strands. On by default with `--use-umi`; `--no-orientation-in-key` turns it off, and
|
|
68
|
+
`--cross-strand` does so implicitly (forcing both is an error).
|
|
69
|
+
Without `--use-umi` it can still be forced on; `CS` is then the position family: every
|
|
70
|
+
orientation family at one (chrom, left_start, right_end) shares a `CS`, which over-links
|
|
71
|
+
independent molecules at the same place.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# whole-file
|
|
75
|
+
mark_duplicates_mi input.bam output.bam --use-umi
|
|
76
|
+
|
|
77
|
+
# sharded, which is what the WDL task runs (--jobs = cpu, shards = panel intervals)
|
|
78
|
+
mark_duplicates_mi input.cram output.cram --use-umi \
|
|
79
|
+
--reference Homo_sapiens_assembly38.fasta \
|
|
80
|
+
--jobs 16 --regions xgen-pan-cancer-targets.hg38.bed
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Family-size histogram goes to stdout; `--stats-only` skips writing the output file.
|
|
84
|
+
Re-marking is idempotent, and re-marking **without** `--use-umi` strips any `CS`
|
|
85
|
+
the input carried (a stale `CS` claims a duplex link the current key does not
|
|
86
|
+
support, which is worse than no `CS`).
|
|
87
|
+
|
|
88
|
+
`--pair-orientation-tag po` also writes each primary read's pair orientation
|
|
89
|
+
(`F1R2`, `F2R1`, `FF` or `RR`) to that tag. The mates of this library carry no
|
|
90
|
+
mate fields (no `RNEXT`, no `0x20`), so orientation only exists where the two
|
|
91
|
+
are paired by name, which this tool does anyway for `MI`; doing it here costs
|
|
92
|
+
no extra pass. A read whose mate is absent or on another contig, or in sharded
|
|
93
|
+
mode further than `--pad` away, gets no tag, and a stale one is dropped. Like
|
|
94
|
+
`CS`, it reaches the consensus reads only if listed in `read_fuser --umi-tags`.
|
|
95
|
+
|
|
96
|
+
### Downstream contract (BIOIN-3068)
|
|
97
|
+
|
|
98
|
+
`CS` only reaches the consensus reads if every stage is told to carry it:
|
|
99
|
+
|
|
100
|
+
1. `read_fuser --umi-tags u3,u5,CS` — the default list is `u5,u3`, so **without
|
|
101
|
+
this `CS` is silently dropped** and the run still completes green.
|
|
102
|
+
2. `sorter_params`: `umi_tag: "CS"` and `mark_duplicates_ends_read_uncertainty: 50`.
|
|
103
|
+
`umi_tag` must be `CS` **alone** — `read_fuser` copies `u5`/`u3` onto each
|
|
104
|
+
consensus read from its own strand's original read, and the two strands carry
|
|
105
|
+
them swapped, so adding them to the key splits exactly the pair `CS` exists to
|
|
106
|
+
join. The wide 50 bp window is needed because the two strands' consensus reads
|
|
107
|
+
routinely end ~27 bp apart, and it is safe only because `CS` is in the key: no
|
|
108
|
+
`(CS, alignment strand)` cell holds more than 2 reads, so there is nothing to
|
|
109
|
+
over-merge. Measured on 36,591 consensus reads: default recovers 424 of 3,423
|
|
110
|
+
true pairs (12.4 %), `ends_read_uncertainty: 50` recovers 3,307 (96.6 %),
|
|
111
|
+
precision 100 % both ways.
|
|
112
|
+
3. For SRSNV/DeepSRSNV, add `"CS:Z"` to `cram_tags_to_copy`.
|
|
113
|
+
|
|
114
|
+
> ⚠️ This module is verified byte-for-byte against a 12,670,749-record baseline
|
|
115
|
+
> (`MI`/`DS` identical to the pre-`CS` output, 55,884 `CS` links, 0 gained / 0
|
|
116
|
+
> lost). The one deliberate deviation is `--regions`: a read that starts upstream of an
|
|
117
|
+
> interval and reaches into it is now emitted (as `samtools view -L` would), which adds
|
|
118
|
+
> ~30 of 12.67 M records versus that baseline. Do not refactor it — including "just" reordering the `set_tag` calls,
|
|
119
|
+
> which fixes the emitted aux order as `MI, DS, CS` — without re-running
|
|
120
|
+
> `/data/Runs/BIOIN-3068/cmp_stream.py` and `cs_links.py`.
|
|
121
|
+
|
|
122
|
+
## mark_duplex_consensus
|
|
123
|
+
|
|
124
|
+
Writes `dx:i:1` on the consensus reads of duplex families. `DS` cannot say this: the sorter gives
|
|
125
|
+
`DS=2` to two consensus reads of one orientation as readily as to the two strands of a duplex
|
|
126
|
+
molecule. `dx` reads the orientation instead: a `CS` family is duplex when its consensus reads
|
|
127
|
+
include both `po=F1R2` and `po=F2R1`, and then every read of the family gets `dx=1`.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
mark_duplex_consensus consensus.cram consensus.dx.cram -T Homo_sapiens_assembly38.fasta --threads 8
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The input is coordinate-sorted and is streamed with a window of `--pad` bp (default 2000); a family
|
|
134
|
+
whose orientations start further apart than that is not seen as duplex. Reads of other families
|
|
135
|
+
get no `dx`, a stale `dx` is replaced, reads without `CS` and
|
|
136
|
+
secondary or supplementary records are left as they are. The output is indexed.
|
|
137
|
+
|
|
138
|
+
## pair_consensus_reads
|
|
139
|
+
|
|
140
|
+
After `unpair_reads_by_strand` each strand of a pair gets its own single-end consensus read, named
|
|
141
|
+
`<stem>0` (forward) and `<stem>1` (reverse) with the same `CS` and `po`. Aligned alone, one of them can
|
|
142
|
+
land with a low MAPQ in a repeat that its mate would have resolved. This writes the two as one unaligned
|
|
143
|
+
pair: R1 is the forward read for `po=F1R2` and the reverse read for `F2R1` (flags 77/141), both are
|
|
144
|
+
named `<stem>`, and the reverse read is reverse-complemented because a mate is handed to the aligner as
|
|
145
|
+
sequenced. `tp`/`t0` are left alone (the read group declares them reference-oriented).
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pair_consensus_reads consensus.bam consensus.paired.bam --counters pair_counters.txt
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Reads without a partner, with another `po`, or whose partners disagree on `po` or `CS` are written
|
|
152
|
+
unchanged. The input need not be sorted: reads are spread over `--buckets` temporary files by stem.
|
|
153
|
+
|
|
154
|
+
Needs UA 4.x (`--unpaired-cost`, `--max-proper-tlen`); UA 3.x has no paired mode. A 3.x index is
|
|
155
|
+
read as is, with identical alignments. UA sets the strand and pair flags, so do not run the nf/nr strand
|
|
156
|
+
step on paired reads.
|
|
157
|
+
|
|
158
|
+
## unpair_rename_by_strand
|
|
159
|
+
|
|
160
|
+
Turns a paired-end, duplicate-marked BAM/CRAM into a single-end one whose reads carry their strand, so the
|
|
161
|
+
consensus tool builds every family per strand. Each primary read gets `0` (forward) or `1` (reverse) appended
|
|
162
|
+
to its name and to `MI`; the pair flags and mate fields are cleared. `CS` is left as it is, since it is the
|
|
163
|
+
only link between the two strands of a molecule. Supplementary and secondary records are written unchanged.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
unpair_rename_by_strand in.bam out.bam -T ref.fa --threads 8 --pair-orientation-tag po \
|
|
167
|
+
--counters unpair_rename_counters.txt
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
With `--pair-orientation-tag` the orientation of each pair (`F1R2`, `F2R1`, `FF`, `RR`) is written first, by
|
|
171
|
+
pairing the mates on name within `--pair-window` bases of the sorted input; reads without a mate in reach get
|
|
172
|
+
no tag. The file is cut into `--shard-size` windows that run in parallel, and the output does not depend on the
|
|
173
|
+
window size. The input needs an index (built when missing).
|
|
174
|
+
|
|
175
|
+
## set_consensus_strand
|
|
176
|
+
|
|
177
|
+
read_fuser emits every consensus read reference-forward. This sets 0x10 from the `nf`/`nr` majority (`nr > nf`
|
|
178
|
+
is reverse, a tie is forward) and changes nothing else. Supplementary, secondary and paired records and reads
|
|
179
|
+
without `nf`/`nr` are left alone.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
set_consensus_strand consensus.bam consensus.strand.bam --counters strand_flag_counters.txt --require-cs-tag
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Exit status is 1 if a record has no `nf`/`nr`, or, with `--require-cs-tag`, if a consensus read has no `CS`.
|
|
186
|
+
|
|
187
|
+
## consensus_report
|
|
188
|
+
The consensus step (`read_fuser`) fuses the reads of each UMI/MI family into a
|
|
189
|
+
single consensus read and records, on that read:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
rn:Z:<comma-separated list of the fused query names>
|
|
193
|
+
nf:i:<n_forward_strand_reads>
|
|
194
|
+
nr:i:<n_reverse_strand_reads>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
i.e. which reads were fused, and how many of them were on the forward (+) and
|
|
198
|
+
reverse (−) strand. From these tags the report classifies every consensus read
|
|
199
|
+
and measures family size **directly** (no MI re-grouping needed):
|
|
200
|
+
|
|
201
|
+
| Category | Condition | Family size |
|
|
202
|
+
|----------|-----------|-------------|
|
|
203
|
+
| both-strands **duplex** | `nf > 0` and `nr > 0` | `nf + nr` |
|
|
204
|
+
| **single-strand** duplicate | exactly one of `nf`/`nr` is 0 | the non-zero count |
|
|
205
|
+
| **singleton** / pass-through | no `rn` tag | 1 |
|
|
206
|
+
|
|
207
|
+
`nf + nr` equals the number of names in `rn`, so the two encodings agree on
|
|
208
|
+
family size; the report cross-checks this and warns on a mismatch.
|
|
209
|
+
|
|
210
|
+
> **A consensus read is identified by the presence of `rn`, never by a strand
|
|
211
|
+
> tag.** Trimmer already emits `rs:i` ("start position in input of segment ...",
|
|
212
|
+
> see the `@CO` lines in the input header) on the *input* reads, and that tag
|
|
213
|
+
> survives onto the reads the consensus step passes through unchanged. An earlier
|
|
214
|
+
> read_fuser wrote the strand counts as `fs`/`rs`, which collided with it and made
|
|
215
|
+
> every pass-through read look like a consensus read; the counts were renamed to
|
|
216
|
+
> `nf`/`nr` to break the collision. Keying off `rn` is collision-proof and also
|
|
217
|
+
> handles the fact that `nf` is simply *absent* (not zero) on pass-through reads —
|
|
218
|
+
> a parquet reader would fill that absence as `0`.
|
|
219
|
+
|
|
220
|
+
## What the report summarises (per sample)
|
|
221
|
+
|
|
222
|
+
- **Sorter QC** — alignment, duplication and coverage metrics from
|
|
223
|
+
`sorter_stats_csv` (post-consensus).
|
|
224
|
+
- **Duplex family metrics** — average MI-family size *and* covered depth for
|
|
225
|
+
duplex families and for single-strand duplicate families, from the `nf`/`nr` tags.
|
|
226
|
+
These are scanned over one chromosome (`--duplex-chrom`, default `chr20`) — a
|
|
227
|
+
representative sample that avoids reading the whole (very large) CRAM. When a
|
|
228
|
+
targets BED is given, the scan is restricted to the targeted intervals on that
|
|
229
|
+
chromosome.
|
|
230
|
+
- **On-target metrics (optional)** — when a `--targets` BED is supplied
|
|
231
|
+
(e.g. an exome capture BED): on-target rate and on-target mean coverage from
|
|
232
|
+
the `bedgraph_mapq0` coverage track. Omit `--targets` for genome-wide coverage
|
|
233
|
+
only. The report is target-agnostic; the exome case is simply "targets given".
|
|
234
|
+
- **Consensus tool performance** — when a `--consensus-log` is supplied, the
|
|
235
|
+
counters parsed from the consensus tool stdout log.
|
|
236
|
+
|
|
237
|
+
All inputs are **local files** — the report does no S3/DB access.
|
|
238
|
+
|
|
239
|
+
## Usage
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Single sample, with an exome targets BED (Quotient case)
|
|
243
|
+
consensus_report \
|
|
244
|
+
--name Z0315 \
|
|
245
|
+
--cram Z0315.cram --crai Z0315.cram.crai \
|
|
246
|
+
--sorter-stats-csv Z0315.csv --sorter-stats-json Z0315.json \
|
|
247
|
+
--bedgraph Z0315_0.bedGraph.gz --consensus-log Z0315.consensus.stdout.log \
|
|
248
|
+
--reference /data/Runs/genomes/hg38/ref_gen/Homo_sapiens_assembly38.fasta \
|
|
249
|
+
--targets Twist_Alliance_Clinical_Research_Exome_hg38.bed \
|
|
250
|
+
--output report.html
|
|
251
|
+
|
|
252
|
+
# Multiple samples: one repeatable --sample key=value block per sample
|
|
253
|
+
consensus_report \
|
|
254
|
+
--sample name=Z0315 cram=Z0315.cram sorter_stats_csv=Z0315.csv \
|
|
255
|
+
sorter_stats_json=Z0315.json bedgraph=Z0315_0.bedGraph.gz \
|
|
256
|
+
--sample name=Z0316 cram=Z0316.cram sorter_stats_csv=Z0316.csv \
|
|
257
|
+
sorter_stats_json=Z0316.json bedgraph=Z0316_0.bedGraph.gz \
|
|
258
|
+
--reference ref.fasta --targets exome.bed --output run_report.html
|
|
259
|
+
|
|
260
|
+
# ReadFuserAlignSort three-way comparison: input vs singletons vs consensus CRAM.
|
|
261
|
+
# Only the consensus CRAM carries rn/nf/nr, so it is the only one given a cram=
|
|
262
|
+
# (the others contribute alignment metrics alone, and need no localised CRAM).
|
|
263
|
+
# --no-summary drops the median-across-samples table, meaningless across these three.
|
|
264
|
+
consensus_report \
|
|
265
|
+
--sample name=Z0315.input sorter_stats_csv=input.csv sorter_stats_json=input.json \
|
|
266
|
+
--sample name=Z0315.singletons sorter_stats_csv=sing.csv sorter_stats_json=sing.json \
|
|
267
|
+
--sample name=Z0315.consensus cram=cons.cram crai=cons.cram.crai \
|
|
268
|
+
sorter_stats_csv=cons.csv sorter_stats_json=cons.json \
|
|
269
|
+
consensus_log=Z0315.consensus.stdout.log \
|
|
270
|
+
--reference ref.fasta --no-summary --output comparison.html
|
|
271
|
+
|
|
272
|
+
# b37 reference: scan chromosome 20 under its b37 name
|
|
273
|
+
consensus_report ... --duplex-chrom 20 --output report.html
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Sample rows appear in the order the `--sample` blocks are given, so the
|
|
277
|
+
input → singletons → consensus reading order above is preserved in the table.
|
|
278
|
+
|
|
279
|
+
Outputs (alongside `--output`):
|
|
280
|
+
|
|
281
|
+
- `<output>.html` — the self-contained HTML report.
|
|
282
|
+
- `<output>_per_sample.csv` — full per-sample metrics table (provenance).
|
|
283
|
+
- `<output>_manifest.csv` — resolved input paths (provenance).
|
|
284
|
+
|
|
285
|
+
## Modules
|
|
286
|
+
|
|
287
|
+
| Module | Role |
|
|
288
|
+
|--------|------|
|
|
289
|
+
| `mark_duplicates_mi.py` | Duplicate families (`MI`/`DS`) and the `CS` cross-strand duplex tag; whole-file and sharded writers. |
|
|
290
|
+
| `mark_duplex_consensus.py` | Streams a sorted consensus CRAM and writes `dx` on the reads of duplex CS families. |
|
|
291
|
+
| `duplex_metrics.py` | Parse `rn`/`nf`/`nr`, classify families, family size + coverage per category (with an `MI`-tag fallback). |
|
|
292
|
+
| `on_target.py` | Genome-wide and optional on-target coverage from a bedGraph + targets BED. |
|
|
293
|
+
| `consensus_log.py` | Parse the consensus tool stdout log for performance counters. |
|
|
294
|
+
| `pair_consensus_reads.py` | Pairs the strand-consensus reads of a pair into unaligned R1/R2 records for UA. |
|
|
295
|
+
| `unpair_rename_by_strand.py` | Unpairs reads, appends the strand digit to QNAME/`MI`, optionally writes the pair orientation tag. |
|
|
296
|
+
| `set_consensus_strand.py` | Sets the consensus reads' reverse flag from the `nf`/`nr` majority. |
|
|
297
|
+
| `consensus_report.py` | CLI orchestration: read local inputs, compute metrics, write the HTML report + CSVs. |
|
|
298
|
+
|
|
299
|
+
## Requirements
|
|
300
|
+
|
|
301
|
+
- `bedtools` and `samtools` on `PATH` (used for BED handling and CRAM decoding).
|
|
302
|
+
- The reference FASTA matching the run's `reference_genome`.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# ugbio_consensus
|
|
2
|
+
|
|
3
|
+
Duplicate marking and performance & duplex reports for the **ReadFuserAlignSort**
|
|
4
|
+
(consensus tool) pipeline at Ultima Genomics.
|
|
5
|
+
|
|
6
|
+
| Entry point | Runs | Role |
|
|
7
|
+
|-------------|------|------|
|
|
8
|
+
| `mark_duplicates_mi` | *before* `read_fuser` | Assign `MI`/`DS` duplicate families, and with `--use-umi` the `CS` cross-strand duplex tag. |
|
|
9
|
+
| `mark_duplex_consensus` | *after* the consensus is sorted | Write `dx=1` on the consensus reads of CS families whose `po` shows both orientations (F1R2 and F2R1). |
|
|
10
|
+
| `pair_consensus_reads` | *after* `read_fuser`, before alignment | Re-pair the two strand-consensus reads of each pair (shared name, R1/R2 flags) so UA 4.x aligns them as a pair. |
|
|
11
|
+
| `consensus_report` | *after* the pipeline | Per-sample HTML report of alignment, duplication, coverage and duplex family metrics. |
|
|
12
|
+
|
|
13
|
+
## mark_duplicates_mi
|
|
14
|
+
|
|
15
|
+
Marks duplicate families on a coordinate-sorted BAM/CRAM and writes, on every
|
|
16
|
+
record of a family:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
MI:Z:<run-id>-<10-digit family id> the duplicate family (molecular identifier)
|
|
20
|
+
DS:i:<family size> number of pairs in that family
|
|
21
|
+
CS:Z:<run-id>-<10-digit family id> --use-umi only: the cross-strand duplex group
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`--use-umi` adds `(R1.u5, R2.u3)` to the dedup key alongside the position, so the
|
|
25
|
+
two strands of one duplex molecule land in **separate** `MI` families — which is
|
|
26
|
+
what we want, since each strand is consensus-called on its own. The cost is that
|
|
27
|
+
the duplex relationship is erased: `read_fuser` groups strictly on `MI`, so
|
|
28
|
+
`duplex_consensus` would be 0 by construction. `CS` restores the link.
|
|
29
|
+
|
|
30
|
+
Two clusters are cross-strand partners iff they share the positional key, their
|
|
31
|
+
UMI pairs are exact mutual reverses, **and** they sit on opposite strands. That
|
|
32
|
+
last term is read off the FLAG (`read.is_reverse`), not off which mate starts
|
|
33
|
+
further left: on a real library the two disagree for 10.5 % of pairs, because
|
|
34
|
+
fragments shorter than the read dovetail.
|
|
35
|
+
|
|
36
|
+
`CS` is **total** — a cluster with no partner gets `CS` equal to its own `MI`
|
|
37
|
+
rather than no tag at all. That matters downstream, because `demux --umi=CS`
|
|
38
|
+
treats a missing tag as the empty value, and every `CS`-less read at a position
|
|
39
|
+
would then collapse into one duplicate family. So `CS == MI` reads as "clustered,
|
|
40
|
+
but no cross-strand partner was found", and `CS != MI` as "duplex partner found".
|
|
41
|
+
|
|
42
|
+
Two opt-in options (both off by default, so the default output is unchanged) merge
|
|
43
|
+
clusters that are the same molecule into one `MI` family:
|
|
44
|
+
|
|
45
|
+
- `--position-tolerance BP` merges same-UMI clusters whose fragment boundaries differ
|
|
46
|
+
by at most `BP` (read1/read2 use asymmetric adapters, so one molecule's copies can map
|
|
47
|
+
1-3 bp apart). Drifts chain transitively.
|
|
48
|
+
- `--cross-strand` also merges the F1R2/F2R1 strands of a duplex molecule into one `MI`,
|
|
49
|
+
using the same orientation-aware rule as `CS`. Boundaries must match within
|
|
50
|
+
`--position-tolerance`.
|
|
51
|
+
- `--orientation-in-key` does the opposite: the pair orientation (F1R2/F2R1/FF/RR) joins the
|
|
52
|
+
dedup key, so the two strands of one molecule never share an `MI`. It matters for a symmetric
|
|
53
|
+
UMI (`u5 == u3`), where both strands otherwise get the same key and so one `MI`. `CS` still links
|
|
54
|
+
the strands. On by default with `--use-umi`; `--no-orientation-in-key` turns it off, and
|
|
55
|
+
`--cross-strand` does so implicitly (forcing both is an error).
|
|
56
|
+
Without `--use-umi` it can still be forced on; `CS` is then the position family: every
|
|
57
|
+
orientation family at one (chrom, left_start, right_end) shares a `CS`, which over-links
|
|
58
|
+
independent molecules at the same place.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# whole-file
|
|
62
|
+
mark_duplicates_mi input.bam output.bam --use-umi
|
|
63
|
+
|
|
64
|
+
# sharded, which is what the WDL task runs (--jobs = cpu, shards = panel intervals)
|
|
65
|
+
mark_duplicates_mi input.cram output.cram --use-umi \
|
|
66
|
+
--reference Homo_sapiens_assembly38.fasta \
|
|
67
|
+
--jobs 16 --regions xgen-pan-cancer-targets.hg38.bed
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Family-size histogram goes to stdout; `--stats-only` skips writing the output file.
|
|
71
|
+
Re-marking is idempotent, and re-marking **without** `--use-umi` strips any `CS`
|
|
72
|
+
the input carried (a stale `CS` claims a duplex link the current key does not
|
|
73
|
+
support, which is worse than no `CS`).
|
|
74
|
+
|
|
75
|
+
`--pair-orientation-tag po` also writes each primary read's pair orientation
|
|
76
|
+
(`F1R2`, `F2R1`, `FF` or `RR`) to that tag. The mates of this library carry no
|
|
77
|
+
mate fields (no `RNEXT`, no `0x20`), so orientation only exists where the two
|
|
78
|
+
are paired by name, which this tool does anyway for `MI`; doing it here costs
|
|
79
|
+
no extra pass. A read whose mate is absent or on another contig, or in sharded
|
|
80
|
+
mode further than `--pad` away, gets no tag, and a stale one is dropped. Like
|
|
81
|
+
`CS`, it reaches the consensus reads only if listed in `read_fuser --umi-tags`.
|
|
82
|
+
|
|
83
|
+
### Downstream contract (BIOIN-3068)
|
|
84
|
+
|
|
85
|
+
`CS` only reaches the consensus reads if every stage is told to carry it:
|
|
86
|
+
|
|
87
|
+
1. `read_fuser --umi-tags u3,u5,CS` — the default list is `u5,u3`, so **without
|
|
88
|
+
this `CS` is silently dropped** and the run still completes green.
|
|
89
|
+
2. `sorter_params`: `umi_tag: "CS"` and `mark_duplicates_ends_read_uncertainty: 50`.
|
|
90
|
+
`umi_tag` must be `CS` **alone** — `read_fuser` copies `u5`/`u3` onto each
|
|
91
|
+
consensus read from its own strand's original read, and the two strands carry
|
|
92
|
+
them swapped, so adding them to the key splits exactly the pair `CS` exists to
|
|
93
|
+
join. The wide 50 bp window is needed because the two strands' consensus reads
|
|
94
|
+
routinely end ~27 bp apart, and it is safe only because `CS` is in the key: no
|
|
95
|
+
`(CS, alignment strand)` cell holds more than 2 reads, so there is nothing to
|
|
96
|
+
over-merge. Measured on 36,591 consensus reads: default recovers 424 of 3,423
|
|
97
|
+
true pairs (12.4 %), `ends_read_uncertainty: 50` recovers 3,307 (96.6 %),
|
|
98
|
+
precision 100 % both ways.
|
|
99
|
+
3. For SRSNV/DeepSRSNV, add `"CS:Z"` to `cram_tags_to_copy`.
|
|
100
|
+
|
|
101
|
+
> ⚠️ This module is verified byte-for-byte against a 12,670,749-record baseline
|
|
102
|
+
> (`MI`/`DS` identical to the pre-`CS` output, 55,884 `CS` links, 0 gained / 0
|
|
103
|
+
> lost). The one deliberate deviation is `--regions`: a read that starts upstream of an
|
|
104
|
+
> interval and reaches into it is now emitted (as `samtools view -L` would), which adds
|
|
105
|
+
> ~30 of 12.67 M records versus that baseline. Do not refactor it — including "just" reordering the `set_tag` calls,
|
|
106
|
+
> which fixes the emitted aux order as `MI, DS, CS` — without re-running
|
|
107
|
+
> `/data/Runs/BIOIN-3068/cmp_stream.py` and `cs_links.py`.
|
|
108
|
+
|
|
109
|
+
## mark_duplex_consensus
|
|
110
|
+
|
|
111
|
+
Writes `dx:i:1` on the consensus reads of duplex families. `DS` cannot say this: the sorter gives
|
|
112
|
+
`DS=2` to two consensus reads of one orientation as readily as to the two strands of a duplex
|
|
113
|
+
molecule. `dx` reads the orientation instead: a `CS` family is duplex when its consensus reads
|
|
114
|
+
include both `po=F1R2` and `po=F2R1`, and then every read of the family gets `dx=1`.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mark_duplex_consensus consensus.cram consensus.dx.cram -T Homo_sapiens_assembly38.fasta --threads 8
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The input is coordinate-sorted and is streamed with a window of `--pad` bp (default 2000); a family
|
|
121
|
+
whose orientations start further apart than that is not seen as duplex. Reads of other families
|
|
122
|
+
get no `dx`, a stale `dx` is replaced, reads without `CS` and
|
|
123
|
+
secondary or supplementary records are left as they are. The output is indexed.
|
|
124
|
+
|
|
125
|
+
## pair_consensus_reads
|
|
126
|
+
|
|
127
|
+
After `unpair_reads_by_strand` each strand of a pair gets its own single-end consensus read, named
|
|
128
|
+
`<stem>0` (forward) and `<stem>1` (reverse) with the same `CS` and `po`. Aligned alone, one of them can
|
|
129
|
+
land with a low MAPQ in a repeat that its mate would have resolved. This writes the two as one unaligned
|
|
130
|
+
pair: R1 is the forward read for `po=F1R2` and the reverse read for `F2R1` (flags 77/141), both are
|
|
131
|
+
named `<stem>`, and the reverse read is reverse-complemented because a mate is handed to the aligner as
|
|
132
|
+
sequenced. `tp`/`t0` are left alone (the read group declares them reference-oriented).
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pair_consensus_reads consensus.bam consensus.paired.bam --counters pair_counters.txt
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Reads without a partner, with another `po`, or whose partners disagree on `po` or `CS` are written
|
|
139
|
+
unchanged. The input need not be sorted: reads are spread over `--buckets` temporary files by stem.
|
|
140
|
+
|
|
141
|
+
Needs UA 4.x (`--unpaired-cost`, `--max-proper-tlen`); UA 3.x has no paired mode. A 3.x index is
|
|
142
|
+
read as is, with identical alignments. UA sets the strand and pair flags, so do not run the nf/nr strand
|
|
143
|
+
step on paired reads.
|
|
144
|
+
|
|
145
|
+
## unpair_rename_by_strand
|
|
146
|
+
|
|
147
|
+
Turns a paired-end, duplicate-marked BAM/CRAM into a single-end one whose reads carry their strand, so the
|
|
148
|
+
consensus tool builds every family per strand. Each primary read gets `0` (forward) or `1` (reverse) appended
|
|
149
|
+
to its name and to `MI`; the pair flags and mate fields are cleared. `CS` is left as it is, since it is the
|
|
150
|
+
only link between the two strands of a molecule. Supplementary and secondary records are written unchanged.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
unpair_rename_by_strand in.bam out.bam -T ref.fa --threads 8 --pair-orientation-tag po \
|
|
154
|
+
--counters unpair_rename_counters.txt
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
With `--pair-orientation-tag` the orientation of each pair (`F1R2`, `F2R1`, `FF`, `RR`) is written first, by
|
|
158
|
+
pairing the mates on name within `--pair-window` bases of the sorted input; reads without a mate in reach get
|
|
159
|
+
no tag. The file is cut into `--shard-size` windows that run in parallel, and the output does not depend on the
|
|
160
|
+
window size. The input needs an index (built when missing).
|
|
161
|
+
|
|
162
|
+
## set_consensus_strand
|
|
163
|
+
|
|
164
|
+
read_fuser emits every consensus read reference-forward. This sets 0x10 from the `nf`/`nr` majority (`nr > nf`
|
|
165
|
+
is reverse, a tie is forward) and changes nothing else. Supplementary, secondary and paired records and reads
|
|
166
|
+
without `nf`/`nr` are left alone.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
set_consensus_strand consensus.bam consensus.strand.bam --counters strand_flag_counters.txt --require-cs-tag
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Exit status is 1 if a record has no `nf`/`nr`, or, with `--require-cs-tag`, if a consensus read has no `CS`.
|
|
173
|
+
|
|
174
|
+
## consensus_report
|
|
175
|
+
The consensus step (`read_fuser`) fuses the reads of each UMI/MI family into a
|
|
176
|
+
single consensus read and records, on that read:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
rn:Z:<comma-separated list of the fused query names>
|
|
180
|
+
nf:i:<n_forward_strand_reads>
|
|
181
|
+
nr:i:<n_reverse_strand_reads>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
i.e. which reads were fused, and how many of them were on the forward (+) and
|
|
185
|
+
reverse (−) strand. From these tags the report classifies every consensus read
|
|
186
|
+
and measures family size **directly** (no MI re-grouping needed):
|
|
187
|
+
|
|
188
|
+
| Category | Condition | Family size |
|
|
189
|
+
|----------|-----------|-------------|
|
|
190
|
+
| both-strands **duplex** | `nf > 0` and `nr > 0` | `nf + nr` |
|
|
191
|
+
| **single-strand** duplicate | exactly one of `nf`/`nr` is 0 | the non-zero count |
|
|
192
|
+
| **singleton** / pass-through | no `rn` tag | 1 |
|
|
193
|
+
|
|
194
|
+
`nf + nr` equals the number of names in `rn`, so the two encodings agree on
|
|
195
|
+
family size; the report cross-checks this and warns on a mismatch.
|
|
196
|
+
|
|
197
|
+
> **A consensus read is identified by the presence of `rn`, never by a strand
|
|
198
|
+
> tag.** Trimmer already emits `rs:i` ("start position in input of segment ...",
|
|
199
|
+
> see the `@CO` lines in the input header) on the *input* reads, and that tag
|
|
200
|
+
> survives onto the reads the consensus step passes through unchanged. An earlier
|
|
201
|
+
> read_fuser wrote the strand counts as `fs`/`rs`, which collided with it and made
|
|
202
|
+
> every pass-through read look like a consensus read; the counts were renamed to
|
|
203
|
+
> `nf`/`nr` to break the collision. Keying off `rn` is collision-proof and also
|
|
204
|
+
> handles the fact that `nf` is simply *absent* (not zero) on pass-through reads —
|
|
205
|
+
> a parquet reader would fill that absence as `0`.
|
|
206
|
+
|
|
207
|
+
## What the report summarises (per sample)
|
|
208
|
+
|
|
209
|
+
- **Sorter QC** — alignment, duplication and coverage metrics from
|
|
210
|
+
`sorter_stats_csv` (post-consensus).
|
|
211
|
+
- **Duplex family metrics** — average MI-family size *and* covered depth for
|
|
212
|
+
duplex families and for single-strand duplicate families, from the `nf`/`nr` tags.
|
|
213
|
+
These are scanned over one chromosome (`--duplex-chrom`, default `chr20`) — a
|
|
214
|
+
representative sample that avoids reading the whole (very large) CRAM. When a
|
|
215
|
+
targets BED is given, the scan is restricted to the targeted intervals on that
|
|
216
|
+
chromosome.
|
|
217
|
+
- **On-target metrics (optional)** — when a `--targets` BED is supplied
|
|
218
|
+
(e.g. an exome capture BED): on-target rate and on-target mean coverage from
|
|
219
|
+
the `bedgraph_mapq0` coverage track. Omit `--targets` for genome-wide coverage
|
|
220
|
+
only. The report is target-agnostic; the exome case is simply "targets given".
|
|
221
|
+
- **Consensus tool performance** — when a `--consensus-log` is supplied, the
|
|
222
|
+
counters parsed from the consensus tool stdout log.
|
|
223
|
+
|
|
224
|
+
All inputs are **local files** — the report does no S3/DB access.
|
|
225
|
+
|
|
226
|
+
## Usage
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Single sample, with an exome targets BED (Quotient case)
|
|
230
|
+
consensus_report \
|
|
231
|
+
--name Z0315 \
|
|
232
|
+
--cram Z0315.cram --crai Z0315.cram.crai \
|
|
233
|
+
--sorter-stats-csv Z0315.csv --sorter-stats-json Z0315.json \
|
|
234
|
+
--bedgraph Z0315_0.bedGraph.gz --consensus-log Z0315.consensus.stdout.log \
|
|
235
|
+
--reference /data/Runs/genomes/hg38/ref_gen/Homo_sapiens_assembly38.fasta \
|
|
236
|
+
--targets Twist_Alliance_Clinical_Research_Exome_hg38.bed \
|
|
237
|
+
--output report.html
|
|
238
|
+
|
|
239
|
+
# Multiple samples: one repeatable --sample key=value block per sample
|
|
240
|
+
consensus_report \
|
|
241
|
+
--sample name=Z0315 cram=Z0315.cram sorter_stats_csv=Z0315.csv \
|
|
242
|
+
sorter_stats_json=Z0315.json bedgraph=Z0315_0.bedGraph.gz \
|
|
243
|
+
--sample name=Z0316 cram=Z0316.cram sorter_stats_csv=Z0316.csv \
|
|
244
|
+
sorter_stats_json=Z0316.json bedgraph=Z0316_0.bedGraph.gz \
|
|
245
|
+
--reference ref.fasta --targets exome.bed --output run_report.html
|
|
246
|
+
|
|
247
|
+
# ReadFuserAlignSort three-way comparison: input vs singletons vs consensus CRAM.
|
|
248
|
+
# Only the consensus CRAM carries rn/nf/nr, so it is the only one given a cram=
|
|
249
|
+
# (the others contribute alignment metrics alone, and need no localised CRAM).
|
|
250
|
+
# --no-summary drops the median-across-samples table, meaningless across these three.
|
|
251
|
+
consensus_report \
|
|
252
|
+
--sample name=Z0315.input sorter_stats_csv=input.csv sorter_stats_json=input.json \
|
|
253
|
+
--sample name=Z0315.singletons sorter_stats_csv=sing.csv sorter_stats_json=sing.json \
|
|
254
|
+
--sample name=Z0315.consensus cram=cons.cram crai=cons.cram.crai \
|
|
255
|
+
sorter_stats_csv=cons.csv sorter_stats_json=cons.json \
|
|
256
|
+
consensus_log=Z0315.consensus.stdout.log \
|
|
257
|
+
--reference ref.fasta --no-summary --output comparison.html
|
|
258
|
+
|
|
259
|
+
# b37 reference: scan chromosome 20 under its b37 name
|
|
260
|
+
consensus_report ... --duplex-chrom 20 --output report.html
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Sample rows appear in the order the `--sample` blocks are given, so the
|
|
264
|
+
input → singletons → consensus reading order above is preserved in the table.
|
|
265
|
+
|
|
266
|
+
Outputs (alongside `--output`):
|
|
267
|
+
|
|
268
|
+
- `<output>.html` — the self-contained HTML report.
|
|
269
|
+
- `<output>_per_sample.csv` — full per-sample metrics table (provenance).
|
|
270
|
+
- `<output>_manifest.csv` — resolved input paths (provenance).
|
|
271
|
+
|
|
272
|
+
## Modules
|
|
273
|
+
|
|
274
|
+
| Module | Role |
|
|
275
|
+
|--------|------|
|
|
276
|
+
| `mark_duplicates_mi.py` | Duplicate families (`MI`/`DS`) and the `CS` cross-strand duplex tag; whole-file and sharded writers. |
|
|
277
|
+
| `mark_duplex_consensus.py` | Streams a sorted consensus CRAM and writes `dx` on the reads of duplex CS families. |
|
|
278
|
+
| `duplex_metrics.py` | Parse `rn`/`nf`/`nr`, classify families, family size + coverage per category (with an `MI`-tag fallback). |
|
|
279
|
+
| `on_target.py` | Genome-wide and optional on-target coverage from a bedGraph + targets BED. |
|
|
280
|
+
| `consensus_log.py` | Parse the consensus tool stdout log for performance counters. |
|
|
281
|
+
| `pair_consensus_reads.py` | Pairs the strand-consensus reads of a pair into unaligned R1/R2 records for UA. |
|
|
282
|
+
| `unpair_rename_by_strand.py` | Unpairs reads, appends the strand digit to QNAME/`MI`, optionally writes the pair orientation tag. |
|
|
283
|
+
| `set_consensus_strand.py` | Sets the consensus reads' reverse flag from the `nf`/`nr` majority. |
|
|
284
|
+
| `consensus_report.py` | CLI orchestration: read local inputs, compute metrics, write the HTML report + CSVs. |
|
|
285
|
+
|
|
286
|
+
## Requirements
|
|
287
|
+
|
|
288
|
+
- `bedtools` and `samtools` on `PATH` (used for BED handling and CRAM decoding).
|
|
289
|
+
- The reference FASTA matching the run's `reference_genome`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ugbio_consensus"
|
|
3
|
+
version = "1.34.0"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
description = "Ultima Genomics consensus (ReadFuserAlignSort) duplicate marking, performance & duplex reports"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Gat Krieger", email = "gat.krieger@ultimagen.com" },
|
|
8
|
+
{ name = "Doron Shem-Tov", email = "doron.shemtov@ultimagen.com" },
|
|
9
|
+
]
|
|
10
|
+
readme = "README.consensus.md"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"ugbio_core[vcfbed]",
|
|
13
|
+
"ugbio_cloud_utils",
|
|
14
|
+
"pysam>=0.22.0",
|
|
15
|
+
"boto3",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.license]
|
|
19
|
+
text = "Apache-2.0"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
run_tests = "pytest:main"
|
|
23
|
+
consensus_report = "ugbio_consensus.consensus_report:main"
|
|
24
|
+
mark_duplicates_mi = "ugbio_consensus.mark_duplicates_mi:main"
|
|
25
|
+
mark_duplex_consensus = "ugbio_consensus.mark_duplex_consensus:main"
|
|
26
|
+
pair_consensus_reads = "ugbio_consensus.pair_consensus_reads:main"
|
|
27
|
+
set_consensus_strand = "ugbio_consensus.set_consensus_strand:main"
|
|
28
|
+
unpair_rename_by_strand = "ugbio_consensus.unpair_rename_by_strand:main"
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources.ugbio_core]
|
|
31
|
+
workspace = true
|
|
32
|
+
|
|
33
|
+
[tool.uv.sources.ugbio_cloud_utils]
|
|
34
|
+
workspace = true
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = [
|
|
38
|
+
"setuptools>=61.0",
|
|
39
|
+
]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
|
File without changes
|