brucellaid 1.0.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.
- brucellaid-1.0.0/LICENSE +21 -0
- brucellaid-1.0.0/MANIFEST.in +2 -0
- brucellaid-1.0.0/PKG-INFO +163 -0
- brucellaid-1.0.0/README.md +129 -0
- brucellaid-1.0.0/brucellaid/__init__.py +3 -0
- brucellaid-1.0.0/brucellaid/__main__.py +6 -0
- brucellaid-1.0.0/brucellaid/cli.py +233 -0
- brucellaid-1.0.0/brucellaid/data/README.md +34 -0
- brucellaid-1.0.0/brucellaid/data/mlst_alleles.fasta +2605 -0
- brucellaid-1.0.0/brucellaid/data/mlst_profiles.tsv +123 -0
- brucellaid-1.0.0/brucellaid/data/references.msh +0 -0
- brucellaid-1.0.0/brucellaid/data/references.tsv +22 -0
- brucellaid-1.0.0/brucellaid/errors.py +5 -0
- brucellaid-1.0.0/brucellaid/mash.py +231 -0
- brucellaid-1.0.0/brucellaid/mlst.py +224 -0
- brucellaid-1.0.0/brucellaid/pipeline.py +148 -0
- brucellaid-1.0.0/brucellaid/references.py +140 -0
- brucellaid-1.0.0/brucellaid/report.py +235 -0
- brucellaid-1.0.0/brucellaid/samples.py +129 -0
- brucellaid-1.0.0/brucellaid/seal.py +293 -0
- brucellaid-1.0.0/brucellaid.egg-info/PKG-INFO +163 -0
- brucellaid-1.0.0/brucellaid.egg-info/SOURCES.txt +38 -0
- brucellaid-1.0.0/brucellaid.egg-info/dependency_links.txt +1 -0
- brucellaid-1.0.0/brucellaid.egg-info/entry_points.txt +2 -0
- brucellaid-1.0.0/brucellaid.egg-info/requires.txt +8 -0
- brucellaid-1.0.0/brucellaid.egg-info/top_level.txt +1 -0
- brucellaid-1.0.0/pyproject.toml +79 -0
- brucellaid-1.0.0/setup.cfg +4 -0
- brucellaid-1.0.0/tests/__init__.py +0 -0
- brucellaid-1.0.0/tests/conftest.py +169 -0
- brucellaid-1.0.0/tests/test_cli.py +154 -0
- brucellaid-1.0.0/tests/test_integration.py +150 -0
- brucellaid-1.0.0/tests/test_main_module.py +32 -0
- brucellaid-1.0.0/tests/test_mash.py +194 -0
- brucellaid-1.0.0/tests/test_mlst.py +160 -0
- brucellaid-1.0.0/tests/test_pipeline.py +149 -0
- brucellaid-1.0.0/tests/test_references.py +93 -0
- brucellaid-1.0.0/tests/test_report.py +158 -0
- brucellaid-1.0.0/tests/test_samples.py +118 -0
- brucellaid-1.0.0/tests/test_seal.py +270 -0
brucellaid-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Marc-Olivier Duceppe
|
|
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,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brucellaid
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Identify the Brucella species of a read set with MASH, and type it with MLST
|
|
5
|
+
Author-email: Marc-Olivier Duceppe <marc-olivier.duceppe@inspection.gc.ca>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/duceppemo/BrucellaID
|
|
8
|
+
Project-URL: Documentation, https://github.com/duceppemo/BrucellaID/wiki
|
|
9
|
+
Project-URL: Changelog, https://github.com/duceppemo/BrucellaID/wiki/Changelog
|
|
10
|
+
Project-URL: Issues, https://github.com/duceppemo/BrucellaID/issues
|
|
11
|
+
Keywords: bioinformatics,brucella,species identification,MLST,MASH,BBTools,Seal,fastq,wgs
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
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
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: xlsx
|
|
28
|
+
Requires-Dist: xlsxwriter>=3; extra == "xlsx"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=5; extra == "test"
|
|
32
|
+
Requires-Dist: xlsxwriter>=3; extra == "test"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<picture>
|
|
37
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/duceppemo/BrucellaID/main/assets/logo_dark.svg">
|
|
38
|
+
<img src="https://raw.githubusercontent.com/duceppemo/BrucellaID/main/assets/logo.svg" width="520" alt="BrucellaID: Brucella species identification and MLST from reads">
|
|
39
|
+
</picture>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://github.com/duceppemo/BrucellaID/actions/workflows/tests.yml"><img src="https://github.com/duceppemo/BrucellaID/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
|
44
|
+
<a href="https://codecov.io/gh/duceppemo/BrucellaID"><img src="https://codecov.io/gh/duceppemo/BrucellaID/graph/badge.svg" alt="Coverage"></a>
|
|
45
|
+
<a href="https://github.com/duceppemo/BrucellaID/releases/latest"><img src="https://img.shields.io/github/v/release/duceppemo/BrucellaID?cacheSeconds=3600" alt="Release"></a>
|
|
46
|
+
<a href="https://pypi.org/project/brucellaid/"><img src="https://img.shields.io/pypi/v/brucellaid?cacheSeconds=3600" alt="PyPI"></a>
|
|
47
|
+
<img src="https://img.shields.io/badge/python-3.10%E2%80%933.14-blue" alt="Python 3.10–3.14">
|
|
48
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/duceppemo/BrucellaID" alt="License"></a>
|
|
49
|
+
<a href="https://github.com/duceppemo/BrucellaID/wiki"><img src="https://img.shields.io/badge/docs-wiki-informational" alt="Documentation"></a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
Identify the *Brucella* species of a fastq sample by its [MASH](https://github.com/marbl/mash) distance to 16
|
|
53
|
+
reference genomes, and type it with the nine-locus
|
|
54
|
+
[Whatmore MLST scheme](https://doi.org/10.1186/1471-2180-7-34), whose alleles are called from the reads with
|
|
55
|
+
[Seal](https://sourceforge.net/projects/bbmap/) from BBTools. Point it at a sample, or a folder of samples.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
$ brucellaid -r1 sample_R1.fastq.gz -r2 sample_R2.fastq.gz -o results/
|
|
59
|
+
Sample Species Biovar ClosestReference MashDistance SharedHashes SequenceType AlleleProfile
|
|
60
|
+
sample Brucella abortus 1 NC_006932-NC_006933.fasta 0.000024 999/1000 1 2-1-1-2-1-3-1-1-1
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Both the reference sketch and the MLST database come with the package: nothing to download, index or
|
|
64
|
+
configure.
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
* **Two independent answers**: the species, from the closest of 16 *Brucella* reference genomes, and the
|
|
68
|
+
sequence type, from the nine MLST alleles. Neither call corrects the other, and both come with the
|
|
69
|
+
numbers they rest on.
|
|
70
|
+
* **Nothing is hidden**: the distance to every reference, the reads behind every allele call and the
|
|
71
|
+
runner-up that call had to beat, a note when a second reference was nearly as close, and a tie reported as
|
|
72
|
+
a tie rather than broken by a coin toss.
|
|
73
|
+
* **Batch mode**: point it at a folder; fastq files are found recursively, R1/R2 paired, and several samples
|
|
74
|
+
run at a time. A sample that fails leaves the others readable.
|
|
75
|
+
* **Current reference data**: the MLST database is PubMLST's, 309 alleles and 122 sequence types, rebuilt by
|
|
76
|
+
a script in the repository. The sketch is vSNP's 16 *Brucella* genomes, with the species, biovar, strain
|
|
77
|
+
and accession of each.
|
|
78
|
+
* **Backwards compatible where it counts**: the species identification is vSNP's, down to the threshold of
|
|
79
|
+
150 shared hashes, and `--xlsx` still writes the `stat_alignment_summary_<date>.xlsx` of the first
|
|
80
|
+
version.
|
|
81
|
+
* **Nothing to configure**: only the Python standard library, plus MASH and BBTools.
|
|
82
|
+
* **Tested**: 194 tests, 100% coverage, end to end on a synthetic genome and on reads tiled over the real
|
|
83
|
+
MLST alleles.
|
|
84
|
+
* **Validated** on 16 genomes with a known answer: every species, biovar and closest reference right, and
|
|
85
|
+
all 14 sequence types the one the genome carries — read off the assembly by exact match, which shares no
|
|
86
|
+
code with the read-based caller. The nearest neighbouring species shares 4 of 1000 hashes and comes back
|
|
87
|
+
as not identified ([Validation](https://github.com/duceppemo/BrucellaID/wiki/Validation)).
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
```
|
|
91
|
+
conda create -n brucellaid -c conda-forge -c bioconda brucellaid
|
|
92
|
+
conda activate brucellaid
|
|
93
|
+
```
|
|
94
|
+
MASH and BBTools (which provides `seal.sh`) are not on PyPI, so they come from bioconda; BrucellaID itself
|
|
95
|
+
needs nothing else. See [Installation](https://github.com/duceppemo/BrucellaID/wiki/Installation) for pip
|
|
96
|
+
and for running it from a clone.
|
|
97
|
+
|
|
98
|
+
## Quick start
|
|
99
|
+
```
|
|
100
|
+
brucellaid -r1 sample_R1.fastq.gz -r2 sample_R2.fastq.gz -o results/ # Paired-end reads
|
|
101
|
+
brucellaid -r1 sample.fastq.gz -o results/ # Single-end reads
|
|
102
|
+
brucellaid -i reads/ -o results/ -p 4 # A folder, 4 samples at a time
|
|
103
|
+
brucellaid -i reads/ -o results/ --no-mlst # Species only
|
|
104
|
+
```
|
|
105
|
+
The results are printed and saved in `results/`: one row per sample, one row per locus, every MASH distance,
|
|
106
|
+
and a JSON report. To see it work on data with a known answer, run `bash examples/run_example.sh`, which
|
|
107
|
+
builds its own reads from the alleles of a sequence type.
|
|
108
|
+
|
|
109
|
+
**What the two answers mean**: the species is the species of the closest reference genome, and the biovar is
|
|
110
|
+
that reference's biovar, which is not the same as a biovar call — the *Brucella* biovar references differ by
|
|
111
|
+
very little. The sequence type is `new` when the nine alleles are called but the combination is not in the
|
|
112
|
+
table, and `ND` when a locus could not be called. See
|
|
113
|
+
[How it works](https://github.com/duceppemo/BrucellaID/wiki/How-it-works).
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
The **[wiki](https://github.com/duceppemo/BrucellaID/wiki)** covers
|
|
117
|
+
[installation](https://github.com/duceppemo/BrucellaID/wiki/Installation), a
|
|
118
|
+
[tutorial](https://github.com/duceppemo/BrucellaID/wiki/Tutorial) on public read sets,
|
|
119
|
+
[usage and options](https://github.com/duceppemo/BrucellaID/wiki/Usage),
|
|
120
|
+
[output files](https://github.com/duceppemo/BrucellaID/wiki/Output-files),
|
|
121
|
+
[how it works](https://github.com/duceppemo/BrucellaID/wiki/How-it-works),
|
|
122
|
+
[validation](https://github.com/duceppemo/BrucellaID/wiki/Validation),
|
|
123
|
+
[troubleshooting](https://github.com/duceppemo/BrucellaID/wiki/Troubleshooting) and the
|
|
124
|
+
[FAQ](https://github.com/duceppemo/BrucellaID/wiki/FAQ).
|
|
125
|
+
|
|
126
|
+
## Citing
|
|
127
|
+
If you use BrucellaID, please cite it together with the tools and the data it rests on:
|
|
128
|
+
|
|
129
|
+
> Duceppe M-O. BrucellaID: *Brucella* species identification and MLST from sequencing reads.
|
|
130
|
+
> https://github.com/duceppemo/BrucellaID
|
|
131
|
+
|
|
132
|
+
> Ondov BD, Treangen TJ, Melsted P, et al. Mash: fast genome and metagenome distance estimation using
|
|
133
|
+
> MinHash. *Genome Biology* 2016;17:132. https://doi.org/10.1186/s13059-016-0997-x
|
|
134
|
+
|
|
135
|
+
> Bushnell B. BBTools. https://sourceforge.net/projects/bbmap/
|
|
136
|
+
|
|
137
|
+
> Whatmore AM, Perrett LL, MacMillan AP. Characterisation of the genetic diversity of *Brucella* by
|
|
138
|
+
> multilocus sequencing. *BMC Microbiology* 2007;7:34. https://doi.org/10.1186/1471-2180-7-34
|
|
139
|
+
|
|
140
|
+
> Jolley KA, Bray JE, Maiden MCJ. Open-access bacterial population genomics: BIGSdb software, the
|
|
141
|
+
> PubMLST.org website and their applications. *Wellcome Open Research* 2018;3:124.
|
|
142
|
+
> https://doi.org/10.12688/wellcomeopenres.14826.1
|
|
143
|
+
|
|
144
|
+
GitHub's **"Cite this repository"** button gives the citation in APA and BibTeX formats, from
|
|
145
|
+
[`CITATION.cff`](CITATION.cff).
|
|
146
|
+
|
|
147
|
+
## Related
|
|
148
|
+
* [brucetyper](https://github.com/duceppemo/brucetyper) — *in silico* Bruce-ladder, Suis-ladder, AMOS and
|
|
149
|
+
RB51 junction PCR for *Brucella*, from assemblies. Where BrucellaID gives the species from a distance and
|
|
150
|
+
the sequence type from the alleles, brucetyper reads the assays a laboratory would run on a gel.
|
|
151
|
+
* [seqcounter](https://github.com/duceppemo/seqcounter) — count the occurrences of any reference sequences
|
|
152
|
+
in reads or assemblies.
|
|
153
|
+
* [spoligotyper](https://github.com/duceppemo/spoligotyper) — *in silico* spoligotyping of the
|
|
154
|
+
*M. tuberculosis* complex.
|
|
155
|
+
|
|
156
|
+
## Contributing
|
|
157
|
+
Bug reports, questions and pull requests are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
158
|
+
|
|
159
|
+
## Author
|
|
160
|
+
Marc-Olivier Duceppe, Canadian Food Inspection Agency (CFIA): marc-olivier.duceppe@inspection.gc.ca
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/duceppemo/BrucellaID/main/assets/logo_dark.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/duceppemo/BrucellaID/main/assets/logo.svg" width="520" alt="BrucellaID: Brucella species identification and MLST from reads">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://github.com/duceppemo/BrucellaID/actions/workflows/tests.yml"><img src="https://github.com/duceppemo/BrucellaID/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
|
10
|
+
<a href="https://codecov.io/gh/duceppemo/BrucellaID"><img src="https://codecov.io/gh/duceppemo/BrucellaID/graph/badge.svg" alt="Coverage"></a>
|
|
11
|
+
<a href="https://github.com/duceppemo/BrucellaID/releases/latest"><img src="https://img.shields.io/github/v/release/duceppemo/BrucellaID?cacheSeconds=3600" alt="Release"></a>
|
|
12
|
+
<a href="https://pypi.org/project/brucellaid/"><img src="https://img.shields.io/pypi/v/brucellaid?cacheSeconds=3600" alt="PyPI"></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/python-3.10%E2%80%933.14-blue" alt="Python 3.10–3.14">
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/duceppemo/BrucellaID" alt="License"></a>
|
|
15
|
+
<a href="https://github.com/duceppemo/BrucellaID/wiki"><img src="https://img.shields.io/badge/docs-wiki-informational" alt="Documentation"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
Identify the *Brucella* species of a fastq sample by its [MASH](https://github.com/marbl/mash) distance to 16
|
|
19
|
+
reference genomes, and type it with the nine-locus
|
|
20
|
+
[Whatmore MLST scheme](https://doi.org/10.1186/1471-2180-7-34), whose alleles are called from the reads with
|
|
21
|
+
[Seal](https://sourceforge.net/projects/bbmap/) from BBTools. Point it at a sample, or a folder of samples.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
$ brucellaid -r1 sample_R1.fastq.gz -r2 sample_R2.fastq.gz -o results/
|
|
25
|
+
Sample Species Biovar ClosestReference MashDistance SharedHashes SequenceType AlleleProfile
|
|
26
|
+
sample Brucella abortus 1 NC_006932-NC_006933.fasta 0.000024 999/1000 1 2-1-1-2-1-3-1-1-1
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Both the reference sketch and the MLST database come with the package: nothing to download, index or
|
|
30
|
+
configure.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
* **Two independent answers**: the species, from the closest of 16 *Brucella* reference genomes, and the
|
|
34
|
+
sequence type, from the nine MLST alleles. Neither call corrects the other, and both come with the
|
|
35
|
+
numbers they rest on.
|
|
36
|
+
* **Nothing is hidden**: the distance to every reference, the reads behind every allele call and the
|
|
37
|
+
runner-up that call had to beat, a note when a second reference was nearly as close, and a tie reported as
|
|
38
|
+
a tie rather than broken by a coin toss.
|
|
39
|
+
* **Batch mode**: point it at a folder; fastq files are found recursively, R1/R2 paired, and several samples
|
|
40
|
+
run at a time. A sample that fails leaves the others readable.
|
|
41
|
+
* **Current reference data**: the MLST database is PubMLST's, 309 alleles and 122 sequence types, rebuilt by
|
|
42
|
+
a script in the repository. The sketch is vSNP's 16 *Brucella* genomes, with the species, biovar, strain
|
|
43
|
+
and accession of each.
|
|
44
|
+
* **Backwards compatible where it counts**: the species identification is vSNP's, down to the threshold of
|
|
45
|
+
150 shared hashes, and `--xlsx` still writes the `stat_alignment_summary_<date>.xlsx` of the first
|
|
46
|
+
version.
|
|
47
|
+
* **Nothing to configure**: only the Python standard library, plus MASH and BBTools.
|
|
48
|
+
* **Tested**: 194 tests, 100% coverage, end to end on a synthetic genome and on reads tiled over the real
|
|
49
|
+
MLST alleles.
|
|
50
|
+
* **Validated** on 16 genomes with a known answer: every species, biovar and closest reference right, and
|
|
51
|
+
all 14 sequence types the one the genome carries — read off the assembly by exact match, which shares no
|
|
52
|
+
code with the read-based caller. The nearest neighbouring species shares 4 of 1000 hashes and comes back
|
|
53
|
+
as not identified ([Validation](https://github.com/duceppemo/BrucellaID/wiki/Validation)).
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
```
|
|
57
|
+
conda create -n brucellaid -c conda-forge -c bioconda brucellaid
|
|
58
|
+
conda activate brucellaid
|
|
59
|
+
```
|
|
60
|
+
MASH and BBTools (which provides `seal.sh`) are not on PyPI, so they come from bioconda; BrucellaID itself
|
|
61
|
+
needs nothing else. See [Installation](https://github.com/duceppemo/BrucellaID/wiki/Installation) for pip
|
|
62
|
+
and for running it from a clone.
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
```
|
|
66
|
+
brucellaid -r1 sample_R1.fastq.gz -r2 sample_R2.fastq.gz -o results/ # Paired-end reads
|
|
67
|
+
brucellaid -r1 sample.fastq.gz -o results/ # Single-end reads
|
|
68
|
+
brucellaid -i reads/ -o results/ -p 4 # A folder, 4 samples at a time
|
|
69
|
+
brucellaid -i reads/ -o results/ --no-mlst # Species only
|
|
70
|
+
```
|
|
71
|
+
The results are printed and saved in `results/`: one row per sample, one row per locus, every MASH distance,
|
|
72
|
+
and a JSON report. To see it work on data with a known answer, run `bash examples/run_example.sh`, which
|
|
73
|
+
builds its own reads from the alleles of a sequence type.
|
|
74
|
+
|
|
75
|
+
**What the two answers mean**: the species is the species of the closest reference genome, and the biovar is
|
|
76
|
+
that reference's biovar, which is not the same as a biovar call — the *Brucella* biovar references differ by
|
|
77
|
+
very little. The sequence type is `new` when the nine alleles are called but the combination is not in the
|
|
78
|
+
table, and `ND` when a locus could not be called. See
|
|
79
|
+
[How it works](https://github.com/duceppemo/BrucellaID/wiki/How-it-works).
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
The **[wiki](https://github.com/duceppemo/BrucellaID/wiki)** covers
|
|
83
|
+
[installation](https://github.com/duceppemo/BrucellaID/wiki/Installation), a
|
|
84
|
+
[tutorial](https://github.com/duceppemo/BrucellaID/wiki/Tutorial) on public read sets,
|
|
85
|
+
[usage and options](https://github.com/duceppemo/BrucellaID/wiki/Usage),
|
|
86
|
+
[output files](https://github.com/duceppemo/BrucellaID/wiki/Output-files),
|
|
87
|
+
[how it works](https://github.com/duceppemo/BrucellaID/wiki/How-it-works),
|
|
88
|
+
[validation](https://github.com/duceppemo/BrucellaID/wiki/Validation),
|
|
89
|
+
[troubleshooting](https://github.com/duceppemo/BrucellaID/wiki/Troubleshooting) and the
|
|
90
|
+
[FAQ](https://github.com/duceppemo/BrucellaID/wiki/FAQ).
|
|
91
|
+
|
|
92
|
+
## Citing
|
|
93
|
+
If you use BrucellaID, please cite it together with the tools and the data it rests on:
|
|
94
|
+
|
|
95
|
+
> Duceppe M-O. BrucellaID: *Brucella* species identification and MLST from sequencing reads.
|
|
96
|
+
> https://github.com/duceppemo/BrucellaID
|
|
97
|
+
|
|
98
|
+
> Ondov BD, Treangen TJ, Melsted P, et al. Mash: fast genome and metagenome distance estimation using
|
|
99
|
+
> MinHash. *Genome Biology* 2016;17:132. https://doi.org/10.1186/s13059-016-0997-x
|
|
100
|
+
|
|
101
|
+
> Bushnell B. BBTools. https://sourceforge.net/projects/bbmap/
|
|
102
|
+
|
|
103
|
+
> Whatmore AM, Perrett LL, MacMillan AP. Characterisation of the genetic diversity of *Brucella* by
|
|
104
|
+
> multilocus sequencing. *BMC Microbiology* 2007;7:34. https://doi.org/10.1186/1471-2180-7-34
|
|
105
|
+
|
|
106
|
+
> Jolley KA, Bray JE, Maiden MCJ. Open-access bacterial population genomics: BIGSdb software, the
|
|
107
|
+
> PubMLST.org website and their applications. *Wellcome Open Research* 2018;3:124.
|
|
108
|
+
> https://doi.org/10.12688/wellcomeopenres.14826.1
|
|
109
|
+
|
|
110
|
+
GitHub's **"Cite this repository"** button gives the citation in APA and BibTeX formats, from
|
|
111
|
+
[`CITATION.cff`](CITATION.cff).
|
|
112
|
+
|
|
113
|
+
## Related
|
|
114
|
+
* [brucetyper](https://github.com/duceppemo/brucetyper) — *in silico* Bruce-ladder, Suis-ladder, AMOS and
|
|
115
|
+
RB51 junction PCR for *Brucella*, from assemblies. Where BrucellaID gives the species from a distance and
|
|
116
|
+
the sequence type from the alleles, brucetyper reads the assays a laboratory would run on a gel.
|
|
117
|
+
* [seqcounter](https://github.com/duceppemo/seqcounter) — count the occurrences of any reference sequences
|
|
118
|
+
in reads or assemblies.
|
|
119
|
+
* [spoligotyper](https://github.com/duceppemo/spoligotyper) — *in silico* spoligotyping of the
|
|
120
|
+
*M. tuberculosis* complex.
|
|
121
|
+
|
|
122
|
+
## Contributing
|
|
123
|
+
Bug reports, questions and pull requests are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
124
|
+
|
|
125
|
+
## Author
|
|
126
|
+
Marc-Olivier Duceppe, Canadian Food Inspection Agency (CFIA): marc-olivier.duceppe@inspection.gc.ca
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Command line entry point."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shlex
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from argparse import ArgumentParser, ArgumentTypeError, RawDescriptionHelpFormatter
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from . import __version__
|
|
14
|
+
from .errors import BrucellaIDError
|
|
15
|
+
from .mash import DEFAULT_MIN_HASHES, check_mash, sketch_references
|
|
16
|
+
from .mlst import DEFAULT_MIN_READS, alleles_fasta, load_profiles
|
|
17
|
+
from .pipeline import analyse_samples
|
|
18
|
+
from .references import compare_with_sketch, load_references, reference_sketch
|
|
19
|
+
from .report import print_table, run_info, write_reports
|
|
20
|
+
from .samples import find_samples, single_sample
|
|
21
|
+
from .seal import DEFAULT_KMER_SIZE, DEFAULT_MISMATCHES, auto_memory, check_seal
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger('brucellaid')
|
|
24
|
+
|
|
25
|
+
# Expected errors are reported without a traceback
|
|
26
|
+
USER_ERRORS = (BrucellaIDError, OSError)
|
|
27
|
+
|
|
28
|
+
EPILOG = '''examples:
|
|
29
|
+
brucellaid -r1 sample_R1.fastq.gz -r2 sample_R2.fastq.gz -o results/
|
|
30
|
+
brucellaid -r1 sample.fastq.gz -o results/
|
|
31
|
+
brucellaid -i folder_of_fastq/ -o results/
|
|
32
|
+
brucellaid -i folder_of_fastq/ -o results/ -p 4 -t 16 --xlsx
|
|
33
|
+
|
|
34
|
+
documentation: https://github.com/duceppemo/BrucellaID/wiki'''
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def setup_logging(verbose=False):
|
|
38
|
+
"""Configure the package logger only, so importing brucellaid never alters the root logger."""
|
|
39
|
+
for handler in list(log.handlers):
|
|
40
|
+
log.removeHandler(handler)
|
|
41
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
42
|
+
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-7s %(message)s', datefmt='%H:%M:%S'))
|
|
43
|
+
log.addHandler(handler)
|
|
44
|
+
log.setLevel(logging.DEBUG if verbose else logging.INFO)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def positive_int(value):
|
|
48
|
+
try:
|
|
49
|
+
value = int(value)
|
|
50
|
+
except ValueError:
|
|
51
|
+
raise ArgumentTypeError('"{}" is not an integer'.format(value)) from None
|
|
52
|
+
if value < 1:
|
|
53
|
+
raise ArgumentTypeError('must be >= 1')
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def java_memory(value):
|
|
58
|
+
if not value[:-1].isdigit() or value[-1].lower() not in 'mg':
|
|
59
|
+
raise ArgumentTypeError('"{}" is not a Java memory size such as 500m or 2g'.format(value))
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def available_cpus():
|
|
64
|
+
try:
|
|
65
|
+
return len(os.sched_getaffinity(0)) # Respects cgroup/taskset limits on Linux (e.g. SLURM jobs)
|
|
66
|
+
except AttributeError:
|
|
67
|
+
return os.cpu_count() or 1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def format_elapsed(seconds):
|
|
71
|
+
"""A readable duration: "1h2m3s"."""
|
|
72
|
+
minutes, seconds = divmod(int(round(seconds)), 60)
|
|
73
|
+
hours, minutes = divmod(minutes, 60)
|
|
74
|
+
days, hours = divmod(hours, 24)
|
|
75
|
+
periods = [('d', days), ('h', hours), ('m', minutes), ('s', seconds)]
|
|
76
|
+
return ''.join('{}{}'.format(value, name) for name, value in periods if value) or '0s'
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def build_parser():
|
|
80
|
+
max_cpu = available_cpus()
|
|
81
|
+
parser = ArgumentParser(prog='brucellaid', formatter_class=RawDescriptionHelpFormatter, epilog=EPILOG,
|
|
82
|
+
description='Identify the Brucella species of sequencing reads by their MASH distance\n'
|
|
83
|
+
'to 16 reference genomes, and type them with the nine-locus MLST scheme.')
|
|
84
|
+
inputs = parser.add_argument_group('input (one sample with -r1, or a folder of samples with -i)')
|
|
85
|
+
source = inputs.add_mutually_exclusive_group(required=True)
|
|
86
|
+
source.add_argument('-r1', '--r1', metavar='FILE',
|
|
87
|
+
help='Reads: single-end, or R1 of paired-end. fastq, gzipped or not.')
|
|
88
|
+
source.add_argument('-i', '--input', metavar='FOLDER',
|
|
89
|
+
help='Folder of fastq files, searched recursively. Each single-end fastq file or pair of '
|
|
90
|
+
'R1/R2 fastq files is a sample.')
|
|
91
|
+
inputs.add_argument('-r2', '--r2', metavar='FILE',
|
|
92
|
+
help='R2 reads, for paired-end fastq files.')
|
|
93
|
+
inputs.add_argument('-s', '--sample', metavar='NAME',
|
|
94
|
+
help='With -r1: sample name used in the reports. Default: the input file name, without '
|
|
95
|
+
'extension and read suffix (_R1, _1, ...).')
|
|
96
|
+
|
|
97
|
+
output = parser.add_argument_group('output')
|
|
98
|
+
output.add_argument('-o', '--output', metavar='FOLDER', required=True,
|
|
99
|
+
help='Folder to hold the reports. Created if needed.')
|
|
100
|
+
output.add_argument('--xlsx', action='store_true',
|
|
101
|
+
help='Also write the legacy vSNP-style report, stat_alignment_summary_<date>.xlsx. Needs '
|
|
102
|
+
'the xlsxwriter package.')
|
|
103
|
+
|
|
104
|
+
calling = parser.add_argument_group('identification and typing')
|
|
105
|
+
calling.add_argument('--min-hashes', metavar='N', type=int, default=DEFAULT_MIN_HASHES,
|
|
106
|
+
help='Minimum number of the reference\'s 1000 min-hashes the reads must share for a '
|
|
107
|
+
'species to be reported. Default: {}.'.format(DEFAULT_MIN_HASHES))
|
|
108
|
+
calling.add_argument('--min-reads', metavar='N', type=int, default=DEFAULT_MIN_READS,
|
|
109
|
+
help='Minimum number of reads assigned to an MLST allele for the allele to be called. '
|
|
110
|
+
'Default: {}.'.format(DEFAULT_MIN_READS))
|
|
111
|
+
calling.add_argument('--no-mlst', action='store_true',
|
|
112
|
+
help='Identify the species only, and skip the MLST typing.')
|
|
113
|
+
calling.add_argument('-k', '--kmer-size', metavar='N', type=positive_int, default=DEFAULT_KMER_SIZE,
|
|
114
|
+
help='K-mer size used to assign the reads to the MLST alleles. '
|
|
115
|
+
'Default: {}.'.format(DEFAULT_KMER_SIZE))
|
|
116
|
+
calling.add_argument('-m', '--mismatch', metavar='N', type=int, default=DEFAULT_MISMATCHES,
|
|
117
|
+
help='Mismatches allowed between an allele k-mer and a read (maximum Hamming distance). '
|
|
118
|
+
'Default: {}, as the alleles of a locus differ by a few bases.'
|
|
119
|
+
.format(DEFAULT_MISMATCHES))
|
|
120
|
+
|
|
121
|
+
data = parser.add_argument_group('reference data (the package ships all three)')
|
|
122
|
+
data.add_argument('--sketch', metavar='FILE',
|
|
123
|
+
help='MASH sketch of the reference genomes, instead of the one of the package.')
|
|
124
|
+
data.add_argument('--references', metavar='FILE', dest='references',
|
|
125
|
+
help='Table of the reference genomes and their species, instead of the one of the package.')
|
|
126
|
+
data.add_argument('--alleles', metavar='FILE',
|
|
127
|
+
help='Fasta file of the MLST alleles, instead of the one of the package.')
|
|
128
|
+
data.add_argument('--profiles', metavar='FILE',
|
|
129
|
+
help='Table of the MLST profiles, instead of the one of the package.')
|
|
130
|
+
|
|
131
|
+
other = parser.add_argument_group('performance and other options')
|
|
132
|
+
other.add_argument('-t', '--threads', metavar='N', type=positive_int, default=max_cpu,
|
|
133
|
+
help='Number of threads. Default: all available ({}).'.format(max_cpu))
|
|
134
|
+
other.add_argument('-p', '-j', '--parallel', '--jobs', metavar='N', type=positive_int, default=1, dest='parallel',
|
|
135
|
+
help='With -i: number of samples analysed at the same time, sharing the threads. Default: 1.')
|
|
136
|
+
other.add_argument('--memory', metavar='SIZE', type=java_memory,
|
|
137
|
+
help='Memory for Seal (Java heap size), per job, e.g. 2g. Default: from the size of the '
|
|
138
|
+
'allele file, within 85%% of the machine.')
|
|
139
|
+
other.add_argument('--verbose', action='store_true', help='Show debug messages, including the commands run.')
|
|
140
|
+
other.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__)
|
|
141
|
+
return parser
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def check_arguments(parser, args):
|
|
145
|
+
if args.input and (args.r2 or args.sample):
|
|
146
|
+
parser.error('-r2 and --sample are only used with -r1')
|
|
147
|
+
if args.r1 and args.parallel > 1:
|
|
148
|
+
parser.error('--parallel is only used with -i')
|
|
149
|
+
if args.min_hashes < 1:
|
|
150
|
+
parser.error('--min-hashes must be >= 1')
|
|
151
|
+
if args.min_reads < 1:
|
|
152
|
+
parser.error('--min-reads must be >= 1')
|
|
153
|
+
if args.mismatch < 0:
|
|
154
|
+
parser.error('--mismatch cannot be negative')
|
|
155
|
+
if args.mismatch >= args.kmer_size:
|
|
156
|
+
parser.error('--mismatch ({}) must be smaller than --kmer-size ({})'.format(args.mismatch, args.kmer_size))
|
|
157
|
+
if args.sample is not None and (not args.sample.strip() or '/' in args.sample or args.sample in ('.', '..')):
|
|
158
|
+
parser.error('--sample must be a name, not a path: "{}"'.format(args.sample))
|
|
159
|
+
if args.sample is not None and re.search(r'[\t\r\n]', args.sample):
|
|
160
|
+
parser.error('--sample cannot contain tabs or line breaks (they would break the table)')
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def file_bases(path):
|
|
164
|
+
"""Rough number of bases in a fasta file: its size, which is close enough to size the Seal heap."""
|
|
165
|
+
try:
|
|
166
|
+
return Path(path).stat().st_size
|
|
167
|
+
except OSError:
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def main(argv=None):
|
|
172
|
+
parser = build_parser()
|
|
173
|
+
args = parser.parse_args(argv)
|
|
174
|
+
check_arguments(parser, args)
|
|
175
|
+
setup_logging(args.verbose)
|
|
176
|
+
command = shlex.join([parser.prog, *(sys.argv[1:] if argv is None else argv)])
|
|
177
|
+
log.debug('brucellaid %s: %s', __version__, command)
|
|
178
|
+
started = datetime.now().astimezone()
|
|
179
|
+
start = time.monotonic()
|
|
180
|
+
output = Path(args.output).expanduser()
|
|
181
|
+
mlst = not args.no_mlst
|
|
182
|
+
try:
|
|
183
|
+
check_mash()
|
|
184
|
+
if mlst:
|
|
185
|
+
check_seal()
|
|
186
|
+
sketch = reference_sketch(args.sketch)
|
|
187
|
+
references = load_references(args.references)
|
|
188
|
+
alleles = alleles_fasta(args.alleles)
|
|
189
|
+
loci = load_profiles(args.profiles)[0] if mlst else ()
|
|
190
|
+
log.info('%d reference genome(s) in %s', len(sketch_references(sketch)), sketch)
|
|
191
|
+
for message in compare_with_sketch(sketch_references(sketch), references):
|
|
192
|
+
log.warning('%s', message)
|
|
193
|
+
if mlst:
|
|
194
|
+
log.info('%d MLST locus/loci: %s', len(loci), ', '.join(loci))
|
|
195
|
+
if args.input:
|
|
196
|
+
samples = find_samples(args.input, exclude=[output])
|
|
197
|
+
log.info('%d sample(s) found in %s', len(samples), args.input)
|
|
198
|
+
else:
|
|
199
|
+
samples = [single_sample(args.r1, args.r2, name=args.sample)]
|
|
200
|
+
jobs = min(args.parallel, len(samples))
|
|
201
|
+
memory = args.memory or auto_memory(file_bases(alleles), jobs=jobs)
|
|
202
|
+
results = analyse_samples(samples, jobs=jobs, threads=args.threads, sketch=sketch, references=references,
|
|
203
|
+
min_hashes=args.min_hashes, min_reads=args.min_reads, kmer_size=args.kmer_size,
|
|
204
|
+
mismatch=args.mismatch, memory=memory, alleles=args.alleles,
|
|
205
|
+
profiles=args.profiles, mlst=mlst)
|
|
206
|
+
run = run_info(command, {'Input': ' '.join(os.path.abspath(f) for f in (args.input, args.r1, args.r2) if f),
|
|
207
|
+
'Output folder': str(output.resolve()),
|
|
208
|
+
'Reference sketch': os.path.abspath(sketch),
|
|
209
|
+
'MLST alleles': os.path.abspath(alleles) if mlst else None,
|
|
210
|
+
'Minimum shared hashes': args.min_hashes,
|
|
211
|
+
'Minimum reads per allele': args.min_reads if mlst else None,
|
|
212
|
+
'K-mer size': args.kmer_size, 'Mismatches': args.mismatch,
|
|
213
|
+
'MLST': mlst, 'Threads': args.threads, 'Parallel samples': jobs,
|
|
214
|
+
'Seal memory': memory}, started)
|
|
215
|
+
rows, reports = write_reports(results, references, output, started, command=command, run=run,
|
|
216
|
+
xlsx=args.xlsx)
|
|
217
|
+
except USER_ERRORS as e:
|
|
218
|
+
log.error('%s', e)
|
|
219
|
+
log.debug('Traceback:', exc_info=True) # Shown with --verbose
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
except KeyboardInterrupt:
|
|
222
|
+
log.error('Interrupted')
|
|
223
|
+
sys.exit(130)
|
|
224
|
+
|
|
225
|
+
# The results go to stdout, the messages to stderr: "brucellaid ... > results.tsv" works
|
|
226
|
+
print_table(rows)
|
|
227
|
+
for report in reports:
|
|
228
|
+
log.info('Report saved in %s', report)
|
|
229
|
+
log.info('Done in %s', format_elapsed(time.monotonic() - start))
|
|
230
|
+
failed = [r.sample for r in results if r.error]
|
|
231
|
+
if failed:
|
|
232
|
+
log.error('%d of %d sample(s) failed: %s', len(failed), len(results), ', '.join(failed))
|
|
233
|
+
sys.exit(1)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Reference data
|
|
2
|
+
|
|
3
|
+
Four files, about 290 kB. `brucellaid --sketch/--references/--alleles/--profiles` take files of your own
|
|
4
|
+
instead of these.
|
|
5
|
+
|
|
6
|
+
| File | What it is | Where it comes from |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| `references.msh` | MASH sketch of 16 *Brucella* reference genomes (k = 21, 1000 hashes) | The *Brucella* part of [vSNP](https://github.com/USDA-VS/vsnp)'s reference set, as `vsnp_reference.msh` |
|
|
9
|
+
| `references.tsv` | What each genome of the sketch is: vSNP species code, species, biovar, strain, NCBI accessions | Written for BrucellaID, from the sketch and vSNP's `species_accessions.csv` |
|
|
10
|
+
| `mlst_alleles.fasta` | 309 alleles of the nine MLST loci | [PubMLST](https://pubmlst.org/organisms/brucella-spp), "MLST (9 locus)", downloaded 2026-10-31 |
|
|
11
|
+
| `mlst_profiles.tsv` | 122 sequence types | The same scheme, the same day |
|
|
12
|
+
|
|
13
|
+
The sketch ID of a genome is the name of the file it was built from, which is what `mash dist` reports and
|
|
14
|
+
what `references.tsv` keys on.
|
|
15
|
+
|
|
16
|
+
Two genomes of the sketch, `Bceti1Cudo.fasta` (*B. ceti* strain Cudo) and `B-REF-BS4-40.fasta` (*B. suis*
|
|
17
|
+
bv. 4 strain 40), are distributed by vSNP under its own names rather than as accessions, so their
|
|
18
|
+
`accessions` column is empty and `scripts/make_reference_data.py` cannot download them.
|
|
19
|
+
|
|
20
|
+
## Rebuilding
|
|
21
|
+
```bash
|
|
22
|
+
python3 scripts/make_mlst_data.py # the two MLST files, from PubMLST
|
|
23
|
+
python3 scripts/make_reference_data.py --genomes /tmp/refs --out /tmp/references.msh # the sketch
|
|
24
|
+
```
|
|
25
|
+
PubMLST serves the data submitted up to a cut-off date unless you authenticate; the script prints the date
|
|
26
|
+
it was given. The MLST data here was downloaded on 2026-10-31, with data submitted up to 2024-12-31.
|
|
27
|
+
|
|
28
|
+
The scheme is Whatmore, Perrett and MacMillan (2007),
|
|
29
|
+
[doi:10.1186/1471-2180-7-34](https://doi.org/10.1186/1471-2180-7-34). PubMLST is maintained at the
|
|
30
|
+
University of Oxford; publications using its data should cite Jolley, Bray and Maiden (2018),
|
|
31
|
+
[doi:10.12688/wellcomeopenres.14826.1](https://doi.org/10.12688/wellcomeopenres.14826.1).
|
|
32
|
+
|
|
33
|
+
A sketch must keep MASH's defaults (k = 21, 1000 hashes): the threshold of 150 shared hashes
|
|
34
|
+
(`--min-hashes`) is read against that sketch size.
|