plasmidhub 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.
Potentially problematic release.
This version of plasmidhub might be problematic. Click here for more details.
- plasmidhub-1.0.0/LICENSE +24 -0
- plasmidhub-1.0.0/PKG-INFO +193 -0
- plasmidhub-1.0.0/README.md +145 -0
- plasmidhub-1.0.0/plasmidhub/__init__.py +1 -0
- plasmidhub-1.0.0/plasmidhub/abricate.py +46 -0
- plasmidhub-1.0.0/plasmidhub/ani.py +29 -0
- plasmidhub-1.0.0/plasmidhub/cluster_color.py +48 -0
- plasmidhub-1.0.0/plasmidhub/clustering.py +143 -0
- plasmidhub-1.0.0/plasmidhub/filtering.py +54 -0
- plasmidhub-1.0.0/plasmidhub/main.py +381 -0
- plasmidhub-1.0.0/plasmidhub/network_builder.py +202 -0
- plasmidhub-1.0.0/plasmidhub/node_stats.py +69 -0
- plasmidhub-1.0.0/plasmidhub/plot.py +169 -0
- plasmidhub-1.0.0/plasmidhub/plot_only.py +153 -0
- plasmidhub-1.0.0/plasmidhub/preprocessing.py +48 -0
- plasmidhub-1.0.0/plasmidhub.egg-info/PKG-INFO +193 -0
- plasmidhub-1.0.0/plasmidhub.egg-info/SOURCES.txt +22 -0
- plasmidhub-1.0.0/plasmidhub.egg-info/dependency_links.txt +1 -0
- plasmidhub-1.0.0/plasmidhub.egg-info/entry_points.txt +2 -0
- plasmidhub-1.0.0/plasmidhub.egg-info/requires.txt +7 -0
- plasmidhub-1.0.0/plasmidhub.egg-info/top_level.txt +1 -0
- plasmidhub-1.0.0/pyproject.toml +36 -0
- plasmidhub-1.0.0/setup.cfg +4 -0
- plasmidhub-1.0.0/setup.py +34 -0
plasmidhub-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dr. Balint Timmer
|
|
4
|
+
|
|
5
|
+
Institute of Metagenomics, University of Debrecen, Debrecen, Hungary
|
|
6
|
+
Institute of Medical Microbiology, Faculty of Medicine, University of Pecs, Pecs, Hungary
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense,
|
|
12
|
+
and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: plasmidhub
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A command-line tool for plasmid clustering, analysis, and visualization.
|
|
5
|
+
Home-page: https://github.com/YOUR_USERNAME/Plasmidhub
|
|
6
|
+
Author: Dr. Balint Timmer
|
|
7
|
+
Author-email: "Dr. Balint Timmer" <timmer.balint@med.unideb.hu>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Dr. Balint Timmer
|
|
11
|
+
|
|
12
|
+
Institute of Metagenomics, University of Debrecen, Debrecen, Hungary
|
|
13
|
+
Institute of Medical Microbiology, Faculty of Medicine, University of Pecs, Pecs, Hungary
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense,
|
|
19
|
+
and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
Keywords: plasmid,bioinformatics,network,clustering,AMR,virulence,plasmid network
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Intended Audience :: Science/Research
|
|
37
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
38
|
+
Requires-Python: >=3.8
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: biopython>=1.83
|
|
42
|
+
Requires-Dist: pandas>=2.0
|
|
43
|
+
Requires-Dist: networkx>=3.1
|
|
44
|
+
Requires-Dist: matplotlib>=3.7
|
|
45
|
+
Requires-Dist: python-louvain>=0.16
|
|
46
|
+
Requires-Dist: numpy>=1.24
|
|
47
|
+
Requires-Dist: scipy>=1.8
|
|
48
|
+
|
|
49
|
+
<img src="https://img.shields.io/github/license/BALINTESBL/plasmidhub" alt="License"> <img src="https://img.shields.io/pypi/v/plasmidhub" alt="PyPI"> 
|
|
50
|
+
|
|
51
|
+
# Plasmidhub
|
|
52
|
+
Plasmidhub is a free and open-source command-line tool for comprehensive plasmid network analysis based on nucleotide sequence similarity. It enables researchers to cluster plasmids and identify genetically related groups using a dynamic, database-independent approach. Plasmidhub's approach:
|
|
53
|
+
* Is applicable to any plasmid
|
|
54
|
+
* Provides an unambiguous classification
|
|
55
|
+
* Considers the whole sequence of the plasmids
|
|
56
|
+
|
|
57
|
+
Network visualizations, stats and data are provided for further analysis.
|
|
58
|
+
|
|
59
|
+
## Download and Installation
|
|
60
|
+
PlasmidHub can be installed easily via PyPI, Bioconda, or directly from GitHub.
|
|
61
|
+
|
|
62
|
+
### Pip
|
|
63
|
+
```
|
|
64
|
+
pip install plasmidhub
|
|
65
|
+
```
|
|
66
|
+
**Note:** It's highly recommended to use a virtual environment or conda environment.
|
|
67
|
+
Recommended environment setup:
|
|
68
|
+
```
|
|
69
|
+
conda create -n plasmidhub python=3.8
|
|
70
|
+
conda activate plasmidhub
|
|
71
|
+
```
|
|
72
|
+
### Bioconda
|
|
73
|
+
|
|
74
|
+
If you use Conda for environment management:
|
|
75
|
+
```
|
|
76
|
+
conda install -c bioconda plasmidhub
|
|
77
|
+
```
|
|
78
|
+
Make sure you have the bioconda channel configured. If not, configure them with:
|
|
79
|
+
```
|
|
80
|
+
conda config --add channels defaults
|
|
81
|
+
conda config --add channels bioconda
|
|
82
|
+
conda config --add channels conda-forge
|
|
83
|
+
```
|
|
84
|
+
### GitHub
|
|
85
|
+
To get the latest version:
|
|
86
|
+
```
|
|
87
|
+
git clone https://github.com/BALINTESBL/plasmidhub.git
|
|
88
|
+
cd plasmidhub
|
|
89
|
+
pip install .
|
|
90
|
+
```
|
|
91
|
+
### Dependencies
|
|
92
|
+
This tool requires the following external software to be installed:
|
|
93
|
+
- [FastANI](https://github.com/ParBLiSS/FastANI)
|
|
94
|
+
- [ABRicate](https://github.com/tseemann/abricate)
|
|
95
|
+
|
|
96
|
+
## Inputs
|
|
97
|
+
Plasmidhub requires plasmid FASTA files (.fna or .fa or .fasta). Your FASTA files need to be placed in one directory. Ideally, there are no other files in the directory.
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
Perform plasmid network analysis with default settings by defining only the directory path of your plasmid FASTA files! Or, you can also adjust parameters.
|
|
101
|
+
Example usage:
|
|
102
|
+
```
|
|
103
|
+
% plasmidhub path/to/my/plasmid/FASTA/files --fragLen 1000 --kmer 14 --coverage_threshold 0.5 --ani_threshold 95 --min_cluster_size 4 --plot_k 2.0 3.0 -t 32
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
This command will:
|
|
107
|
+
* Compute pairwise ANI using FastANI
|
|
108
|
+
* Build a plasmid similarity network
|
|
109
|
+
* Save network metrics and statistics (results/statistics)
|
|
110
|
+
* Cluster plasmids
|
|
111
|
+
* Annotate resistance and virulence genes with ABRicate (results/abricate_results)
|
|
112
|
+
* Generate network visualizations (results/plots)
|
|
113
|
+
### Key Options
|
|
114
|
+
|
|
115
|
+
| Category | Flag | Description | Default |
|
|
116
|
+
| -------------- | ---------------------- | --------------------------------------- | ------------------------- |
|
|
117
|
+
| **Input** | ` | Path to folder with plasmid FASTA files | – |
|
|
118
|
+
| **FastANI** | `--fragLen` | Fragment length | `1000` |
|
|
119
|
+
| | `--kmer` | K-mer size | `14` |
|
|
120
|
+
| | `--coverage_threshold` | Minimum proportion of the plasmid lenghts| `0.5` |
|
|
121
|
+
| | | covered by the matching fragments | |
|
|
122
|
+
| | `--ani_threshold` | Minimum ANI score (after applying | `95.0` |
|
|
123
|
+
| | | coverage threshold) | |
|
|
124
|
+
| **Clustering** | `--cluster_off` | Disable clustering | – |
|
|
125
|
+
| | `--min_cluster_size` | Minimum cluster size (plasmids) | `3` |
|
|
126
|
+
| **ABRicate** | `--skip_abricate` | Skip annotation step | – |
|
|
127
|
+
| | `--abricate_dbs` | Databases to use e.g.: | `plasmidfinder card vfdb` |
|
|
128
|
+
| | | --abricate_dbs ncbi ecoli_vf | |
|
|
129
|
+
| **Plotting** | `--plot_k` | Range of k values |`3` `3` |
|
|
130
|
+
| | `--plot_skip` | Skips plotting | |
|
|
131
|
+
| **Threads** | `-t` or `--threads` | Number of threads | `4` |
|
|
132
|
+
### Plot-only mode
|
|
133
|
+
In plot-only mode, network visualizations can be generated from existing networks directly, by using --plot_only flag and defining the directory path. In this mode, several parameters can be adjusted.
|
|
134
|
+
Example usage:
|
|
135
|
+
```
|
|
136
|
+
% plasmidhub --plot_only path/to/my/results --plot_k 3 5 --plot_node_color blue --plot_node_size 500 --plot_node_shape s --plot_figsize 20 20 -t 32
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
| **Plotting** | Flag | Description | Default |
|
|
140
|
+
| -------------- | ---------------------- | --------------------------------------- | ------------------------- |
|
|
141
|
+
| | `--plot_node_size` | Size of nodes | `900` |
|
|
142
|
+
| | `--plot_node_shape` | Shape of nodes (`o`, `s`, `^`, etc.) | `o` (circle) |
|
|
143
|
+
| | `--plot_edge_width` | Min/max edge width | `0.2 2.0` |
|
|
144
|
+
| | `--plot_figsize` | Figure size in inches | `25 25` |
|
|
145
|
+
| | `--plot_iterations` | Spring layout iterations | `100` |
|
|
146
|
+
|
|
147
|
+
Node shapes:
|
|
148
|
+
| Marker | Description |
|
|
149
|
+
| ------ | -------------------------- |
|
|
150
|
+
| `'o'` | Circle |
|
|
151
|
+
| `'s'` | Square |
|
|
152
|
+
| `'^'` | Upward-pointing triangle |
|
|
153
|
+
| `'v'` | Downward-pointing triangle |
|
|
154
|
+
| `'>'` | Right-pointing triangle |
|
|
155
|
+
| `'<'` | Left-pointing triangle |
|
|
156
|
+
| `'D'` | Diamond |
|
|
157
|
+
| `'d'` | Thin diamond |
|
|
158
|
+
| `'p'` | Pentagon |
|
|
159
|
+
| `'h'` | Hexagon 1 |
|
|
160
|
+
| `'H'` | Hexagon 2 |
|
|
161
|
+
| `'*'` | Star |
|
|
162
|
+
| `'+'` | Plus |
|
|
163
|
+
| `'x'` | Cross |
|
|
164
|
+
| `'X'` | Filled X |
|
|
165
|
+
|
|
166
|
+
Plots generated with Plasmidhub:
|
|
167
|
+
<img width="1668" height="1668" alt="image" src="https://github.com/user-attachments/assets/afed18b8-6dbe-44b8-b539-23aa47b4bfb0" />
|
|
168
|
+
|
|
169
|
+
## Overview
|
|
170
|
+
|
|
171
|
+
Plasmidhub performs an all-vs-all comparison of input plasmid sequences using FastANI. FastANI results ("raw results") are filtered by the coverage (what proportion of the full plasmid sequences are covered by the matching fragments). The remaining pairs are filtered by the minimum ANI score. ANI scores are further weighted by the proportion of matching fragments, and data are sorted into a similarity matrix. The network is build from the similarity matrix, where:
|
|
172
|
+
- **Nodes** represent plasmids
|
|
173
|
+
- **Edges** represent genetic relatedness (weighted ANI)
|
|
174
|
+
|
|
175
|
+
Within the network, communities are detected via Louvain method (subclusters). Plasmid clusters are complete subgraphs (cliques) detected within the whole network. Clusters comprising highly similar or identical plasmids. If relevant and scientifically appropriate, plasmids of the same cluster may be considered as equivalent. This approach is alignment-free, reference-free, database-independent, and uses relative similarity-based system to overcome the limitations of database dependency (untypeable plasmids, multireplicon/multi-MOB plasmids, mosaic, hybrid plasmids ect.)
|
|
176
|
+
Network and node statistics are saved to a distinct directory for downstream analyses (connectance, modularity, nestedness, community partition, degree centrality, node degrees, betweenness, closeness ect.)
|
|
177
|
+
|
|
178
|
+
Resistance and virulence genes can be annotated via [ABRicate](https://github.com/tseemann/abricate). The abricate files are saved to a distinct subdirectory. By default, plasmidfinder, vfdb and card databases are used, but optionally other databases can be specified from the databases available with ABRicate.
|
|
179
|
+
|
|
180
|
+
To generate custom visualizations, feel free to use and modify the *plot.py*.
|
|
181
|
+
|
|
182
|
+
## Troubleshooting
|
|
183
|
+
Users are welcome to report any issue or feedback related to Plasmidhub by posting a [Github issue](https://github.com/BALINTESBL/plasmidhub/issues).
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
Developed by **Dr. Bálint Timmer**
|
|
188
|
+
*Institute of Metagenomics, University of Debrecen, Debrecen, Hungary*
|
|
189
|
+
*Department of Medical Microbiology, University of Pécs Medical School, Pécs, Hungary*
|
|
190
|
+
|
|
191
|
+
<img width="33" height="33" alt="image" src="https://github.com/user-attachments/assets/bd9f17e9-e9ce-4edb-8319-ef0091c45f00" /> <img width="99" height="32.054" alt="image" src="https://github.com/user-attachments/assets/5f3d5b6b-cef6-478a-af66-614b2e2860b2" />
|
|
192
|
+
|
|
193
|
+
Contact: [timmer.balint@med.unideb.hu](mailto:timmer.balint@med.unideb.hu) , [timmer.balint@pte.hu](mailto:timmer.balint@pte.hu)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<img src="https://img.shields.io/github/license/BALINTESBL/plasmidhub" alt="License"> <img src="https://img.shields.io/pypi/v/plasmidhub" alt="PyPI"> 
|
|
2
|
+
|
|
3
|
+
# Plasmidhub
|
|
4
|
+
Plasmidhub is a free and open-source command-line tool for comprehensive plasmid network analysis based on nucleotide sequence similarity. It enables researchers to cluster plasmids and identify genetically related groups using a dynamic, database-independent approach. Plasmidhub's approach:
|
|
5
|
+
* Is applicable to any plasmid
|
|
6
|
+
* Provides an unambiguous classification
|
|
7
|
+
* Considers the whole sequence of the plasmids
|
|
8
|
+
|
|
9
|
+
Network visualizations, stats and data are provided for further analysis.
|
|
10
|
+
|
|
11
|
+
## Download and Installation
|
|
12
|
+
PlasmidHub can be installed easily via PyPI, Bioconda, or directly from GitHub.
|
|
13
|
+
|
|
14
|
+
### Pip
|
|
15
|
+
```
|
|
16
|
+
pip install plasmidhub
|
|
17
|
+
```
|
|
18
|
+
**Note:** It's highly recommended to use a virtual environment or conda environment.
|
|
19
|
+
Recommended environment setup:
|
|
20
|
+
```
|
|
21
|
+
conda create -n plasmidhub python=3.8
|
|
22
|
+
conda activate plasmidhub
|
|
23
|
+
```
|
|
24
|
+
### Bioconda
|
|
25
|
+
|
|
26
|
+
If you use Conda for environment management:
|
|
27
|
+
```
|
|
28
|
+
conda install -c bioconda plasmidhub
|
|
29
|
+
```
|
|
30
|
+
Make sure you have the bioconda channel configured. If not, configure them with:
|
|
31
|
+
```
|
|
32
|
+
conda config --add channels defaults
|
|
33
|
+
conda config --add channels bioconda
|
|
34
|
+
conda config --add channels conda-forge
|
|
35
|
+
```
|
|
36
|
+
### GitHub
|
|
37
|
+
To get the latest version:
|
|
38
|
+
```
|
|
39
|
+
git clone https://github.com/BALINTESBL/plasmidhub.git
|
|
40
|
+
cd plasmidhub
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
### Dependencies
|
|
44
|
+
This tool requires the following external software to be installed:
|
|
45
|
+
- [FastANI](https://github.com/ParBLiSS/FastANI)
|
|
46
|
+
- [ABRicate](https://github.com/tseemann/abricate)
|
|
47
|
+
|
|
48
|
+
## Inputs
|
|
49
|
+
Plasmidhub requires plasmid FASTA files (.fna or .fa or .fasta). Your FASTA files need to be placed in one directory. Ideally, there are no other files in the directory.
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
Perform plasmid network analysis with default settings by defining only the directory path of your plasmid FASTA files! Or, you can also adjust parameters.
|
|
53
|
+
Example usage:
|
|
54
|
+
```
|
|
55
|
+
% plasmidhub path/to/my/plasmid/FASTA/files --fragLen 1000 --kmer 14 --coverage_threshold 0.5 --ani_threshold 95 --min_cluster_size 4 --plot_k 2.0 3.0 -t 32
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
This command will:
|
|
59
|
+
* Compute pairwise ANI using FastANI
|
|
60
|
+
* Build a plasmid similarity network
|
|
61
|
+
* Save network metrics and statistics (results/statistics)
|
|
62
|
+
* Cluster plasmids
|
|
63
|
+
* Annotate resistance and virulence genes with ABRicate (results/abricate_results)
|
|
64
|
+
* Generate network visualizations (results/plots)
|
|
65
|
+
### Key Options
|
|
66
|
+
|
|
67
|
+
| Category | Flag | Description | Default |
|
|
68
|
+
| -------------- | ---------------------- | --------------------------------------- | ------------------------- |
|
|
69
|
+
| **Input** | ` | Path to folder with plasmid FASTA files | – |
|
|
70
|
+
| **FastANI** | `--fragLen` | Fragment length | `1000` |
|
|
71
|
+
| | `--kmer` | K-mer size | `14` |
|
|
72
|
+
| | `--coverage_threshold` | Minimum proportion of the plasmid lenghts| `0.5` |
|
|
73
|
+
| | | covered by the matching fragments | |
|
|
74
|
+
| | `--ani_threshold` | Minimum ANI score (after applying | `95.0` |
|
|
75
|
+
| | | coverage threshold) | |
|
|
76
|
+
| **Clustering** | `--cluster_off` | Disable clustering | – |
|
|
77
|
+
| | `--min_cluster_size` | Minimum cluster size (plasmids) | `3` |
|
|
78
|
+
| **ABRicate** | `--skip_abricate` | Skip annotation step | – |
|
|
79
|
+
| | `--abricate_dbs` | Databases to use e.g.: | `plasmidfinder card vfdb` |
|
|
80
|
+
| | | --abricate_dbs ncbi ecoli_vf | |
|
|
81
|
+
| **Plotting** | `--plot_k` | Range of k values |`3` `3` |
|
|
82
|
+
| | `--plot_skip` | Skips plotting | |
|
|
83
|
+
| **Threads** | `-t` or `--threads` | Number of threads | `4` |
|
|
84
|
+
### Plot-only mode
|
|
85
|
+
In plot-only mode, network visualizations can be generated from existing networks directly, by using --plot_only flag and defining the directory path. In this mode, several parameters can be adjusted.
|
|
86
|
+
Example usage:
|
|
87
|
+
```
|
|
88
|
+
% plasmidhub --plot_only path/to/my/results --plot_k 3 5 --plot_node_color blue --plot_node_size 500 --plot_node_shape s --plot_figsize 20 20 -t 32
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
| **Plotting** | Flag | Description | Default |
|
|
92
|
+
| -------------- | ---------------------- | --------------------------------------- | ------------------------- |
|
|
93
|
+
| | `--plot_node_size` | Size of nodes | `900` |
|
|
94
|
+
| | `--plot_node_shape` | Shape of nodes (`o`, `s`, `^`, etc.) | `o` (circle) |
|
|
95
|
+
| | `--plot_edge_width` | Min/max edge width | `0.2 2.0` |
|
|
96
|
+
| | `--plot_figsize` | Figure size in inches | `25 25` |
|
|
97
|
+
| | `--plot_iterations` | Spring layout iterations | `100` |
|
|
98
|
+
|
|
99
|
+
Node shapes:
|
|
100
|
+
| Marker | Description |
|
|
101
|
+
| ------ | -------------------------- |
|
|
102
|
+
| `'o'` | Circle |
|
|
103
|
+
| `'s'` | Square |
|
|
104
|
+
| `'^'` | Upward-pointing triangle |
|
|
105
|
+
| `'v'` | Downward-pointing triangle |
|
|
106
|
+
| `'>'` | Right-pointing triangle |
|
|
107
|
+
| `'<'` | Left-pointing triangle |
|
|
108
|
+
| `'D'` | Diamond |
|
|
109
|
+
| `'d'` | Thin diamond |
|
|
110
|
+
| `'p'` | Pentagon |
|
|
111
|
+
| `'h'` | Hexagon 1 |
|
|
112
|
+
| `'H'` | Hexagon 2 |
|
|
113
|
+
| `'*'` | Star |
|
|
114
|
+
| `'+'` | Plus |
|
|
115
|
+
| `'x'` | Cross |
|
|
116
|
+
| `'X'` | Filled X |
|
|
117
|
+
|
|
118
|
+
Plots generated with Plasmidhub:
|
|
119
|
+
<img width="1668" height="1668" alt="image" src="https://github.com/user-attachments/assets/afed18b8-6dbe-44b8-b539-23aa47b4bfb0" />
|
|
120
|
+
|
|
121
|
+
## Overview
|
|
122
|
+
|
|
123
|
+
Plasmidhub performs an all-vs-all comparison of input plasmid sequences using FastANI. FastANI results ("raw results") are filtered by the coverage (what proportion of the full plasmid sequences are covered by the matching fragments). The remaining pairs are filtered by the minimum ANI score. ANI scores are further weighted by the proportion of matching fragments, and data are sorted into a similarity matrix. The network is build from the similarity matrix, where:
|
|
124
|
+
- **Nodes** represent plasmids
|
|
125
|
+
- **Edges** represent genetic relatedness (weighted ANI)
|
|
126
|
+
|
|
127
|
+
Within the network, communities are detected via Louvain method (subclusters). Plasmid clusters are complete subgraphs (cliques) detected within the whole network. Clusters comprising highly similar or identical plasmids. If relevant and scientifically appropriate, plasmids of the same cluster may be considered as equivalent. This approach is alignment-free, reference-free, database-independent, and uses relative similarity-based system to overcome the limitations of database dependency (untypeable plasmids, multireplicon/multi-MOB plasmids, mosaic, hybrid plasmids ect.)
|
|
128
|
+
Network and node statistics are saved to a distinct directory for downstream analyses (connectance, modularity, nestedness, community partition, degree centrality, node degrees, betweenness, closeness ect.)
|
|
129
|
+
|
|
130
|
+
Resistance and virulence genes can be annotated via [ABRicate](https://github.com/tseemann/abricate). The abricate files are saved to a distinct subdirectory. By default, plasmidfinder, vfdb and card databases are used, but optionally other databases can be specified from the databases available with ABRicate.
|
|
131
|
+
|
|
132
|
+
To generate custom visualizations, feel free to use and modify the *plot.py*.
|
|
133
|
+
|
|
134
|
+
## Troubleshooting
|
|
135
|
+
Users are welcome to report any issue or feedback related to Plasmidhub by posting a [Github issue](https://github.com/BALINTESBL/plasmidhub/issues).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
Developed by **Dr. Bálint Timmer**
|
|
140
|
+
*Institute of Metagenomics, University of Debrecen, Debrecen, Hungary*
|
|
141
|
+
*Department of Medical Microbiology, University of Pécs Medical School, Pécs, Hungary*
|
|
142
|
+
|
|
143
|
+
<img width="33" height="33" alt="image" src="https://github.com/user-attachments/assets/bd9f17e9-e9ce-4edb-8319-ef0091c45f00" /> <img width="99" height="32.054" alt="image" src="https://github.com/user-attachments/assets/5f3d5b6b-cef6-478a-af66-614b2e2860b2" />
|
|
144
|
+
|
|
145
|
+
Contact: [timmer.balint@med.unideb.hu](mailto:timmer.balint@med.unideb.hu) , [timmer.balint@pte.hu](mailto:timmer.balint@pte.hu)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# plasmidhub package
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import shutil
|
|
4
|
+
import glob
|
|
5
|
+
import logging
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
def run_abricate_bulk(input_dir, results_dir, db_list, threads=None):
|
|
9
|
+
os.makedirs(results_dir, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
# Use default thread count if not provided
|
|
12
|
+
if threads is None:
|
|
13
|
+
threads = 4
|
|
14
|
+
|
|
15
|
+
# Move into input_dir because wildcard expansion happens here
|
|
16
|
+
original_dir = os.getcwd()
|
|
17
|
+
os.chdir(input_dir)
|
|
18
|
+
|
|
19
|
+
# Collect all fasta-like files
|
|
20
|
+
fasta_files = sorted(
|
|
21
|
+
glob.glob("*.fna") +
|
|
22
|
+
glob.glob("*.fa") +
|
|
23
|
+
glob.glob("*.fasta")
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if not fasta_files:
|
|
27
|
+
raise RuntimeError(f"No input files found in {input_dir} with .fna/.fa/.fasta extensions.")
|
|
28
|
+
|
|
29
|
+
for db in db_list:
|
|
30
|
+
logger.info(f"Running abricate on database: {db}")
|
|
31
|
+
|
|
32
|
+
# Build the shell command with all fasta file names
|
|
33
|
+
cmd = f"abricate {' '.join(fasta_files)} --db {db} -t {threads}"
|
|
34
|
+
|
|
35
|
+
# Output file path (temporary inside input_dir)
|
|
36
|
+
temp_output = f"{db}.abr"
|
|
37
|
+
with open(temp_output, "w") as out_f:
|
|
38
|
+
subprocess.run(cmd, shell=True, stdout=out_f, stderr=subprocess.DEVNULL)
|
|
39
|
+
|
|
40
|
+
# Move the output to results_dir
|
|
41
|
+
final_output_path = os.path.join(results_dir, f"{db}.abr")
|
|
42
|
+
shutil.move(temp_output, final_output_path)
|
|
43
|
+
logger.info(f"Saved: {final_output_path}")
|
|
44
|
+
|
|
45
|
+
# Return to original directory
|
|
46
|
+
os.chdir(original_dir)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import logging
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
def run_fastani(plasmid_list_file, fragLen=1000, minFrag=3, kmer=14, output_dir=".", threads=None):
|
|
7
|
+
if threads is None:
|
|
8
|
+
threads = 4
|
|
9
|
+
|
|
10
|
+
output_file = os.path.join(output_dir, "fastani_raw_results.tsv")
|
|
11
|
+
cmd = [
|
|
12
|
+
"fastANI",
|
|
13
|
+
"--ql", plasmid_list_file,
|
|
14
|
+
"--rl", plasmid_list_file,
|
|
15
|
+
"-o", output_file,
|
|
16
|
+
"--fragLen", str(fragLen),
|
|
17
|
+
"--minFraction", str(minFrag),
|
|
18
|
+
"--kmer", str(kmer),
|
|
19
|
+
"-t", str(threads)
|
|
20
|
+
]
|
|
21
|
+
logger.info("Running FastANI with command:")
|
|
22
|
+
logger.info(" ".join(cmd))
|
|
23
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
24
|
+
if result.returncode != 0:
|
|
25
|
+
logger.error("FastANI failed with error:")
|
|
26
|
+
logger.error(result.stderr)
|
|
27
|
+
exit(1)
|
|
28
|
+
else:
|
|
29
|
+
logger.info("FastANI completed successfully.")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import random
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
def assign_cluster_colors(results_dir, mapping_file):
|
|
9
|
+
cluster_list_path = os.path.join(results_dir, "cluster_list.txt")
|
|
10
|
+
color_file = os.path.join(results_dir, "cluster_colours.txt")
|
|
11
|
+
|
|
12
|
+
clusters = []
|
|
13
|
+
with open(cluster_list_path) as f:
|
|
14
|
+
next(f) # Skip header
|
|
15
|
+
for line in f:
|
|
16
|
+
if line.strip():
|
|
17
|
+
cluster_file, _ = line.strip().split('\t')
|
|
18
|
+
cluster = cluster_file.replace('.txt', '')
|
|
19
|
+
clusters.append(cluster)
|
|
20
|
+
|
|
21
|
+
n_clusters = len(clusters)
|
|
22
|
+
|
|
23
|
+
# Start with base colors from tab20
|
|
24
|
+
cmap = plt.get_cmap('tab20')
|
|
25
|
+
base_colors = [
|
|
26
|
+
'#{:02x}{:02x}{:02x}'.format(int(r * 255), int(g * 255), int(b * 255))
|
|
27
|
+
for r, g, b in cmap.colors
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
used_colors = set(base_colors[:min(n_clusters, len(base_colors))])
|
|
31
|
+
full_color_list = base_colors[:min(n_clusters, len(base_colors))]
|
|
32
|
+
|
|
33
|
+
# Generate additional distinct random colors if needed
|
|
34
|
+
while len(full_color_list) < n_clusters:
|
|
35
|
+
while True:
|
|
36
|
+
color = "#{:06x}".format(random.randint(0, 0xFFFFFF))
|
|
37
|
+
if color not in used_colors:
|
|
38
|
+
used_colors.add(color)
|
|
39
|
+
full_color_list.append(color)
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
color_map = dict(zip(clusters, full_color_list))
|
|
43
|
+
|
|
44
|
+
with open(color_file, 'w') as out:
|
|
45
|
+
for cluster, color in color_map.items():
|
|
46
|
+
out.write(f"{cluster}\t{color}\n")
|
|
47
|
+
|
|
48
|
+
logger.info(f"Cluster colors saved to: {color_file}")
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
import argparse
|
|
5
|
+
import logging
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
def find_valid_subclusters(results_dir):
|
|
9
|
+
valid_subclusters = []
|
|
10
|
+
|
|
11
|
+
for filename in sorted(os.listdir(results_dir)):
|
|
12
|
+
if filename.startswith("subcluster_") and filename.endswith("_plasmids.txt"):
|
|
13
|
+
filepath = os.path.join(results_dir, filename)
|
|
14
|
+
with open(filepath, 'r') as f:
|
|
15
|
+
plasmid_count = sum(1 for _ in f)
|
|
16
|
+
|
|
17
|
+
if plasmid_count >= 3: # Hardcoded rule
|
|
18
|
+
valid_subclusters.append((filename, plasmid_count))
|
|
19
|
+
|
|
20
|
+
valid_subclusters.sort(key=lambda x: x[1], reverse=True)
|
|
21
|
+
return valid_subclusters
|
|
22
|
+
|
|
23
|
+
def write_subcluster_list(valid_subclusters, output_path):
|
|
24
|
+
with open(output_path, "w") as f:
|
|
25
|
+
f.write("Subcluster\tPlasmids\n")
|
|
26
|
+
for subcluster, count in valid_subclusters:
|
|
27
|
+
f.write(f"{subcluster}\t{count}\n")
|
|
28
|
+
|
|
29
|
+
def extract_clusters(valid_subclusters, results_dir, fastani_path, output_dir):
|
|
30
|
+
fastani_df = pd.read_csv(fastani_path, sep="\t")
|
|
31
|
+
|
|
32
|
+
for subcluster_file, _ in valid_subclusters:
|
|
33
|
+
full_path = os.path.join(results_dir, subcluster_file)
|
|
34
|
+
try:
|
|
35
|
+
with open(full_path, "r") as f:
|
|
36
|
+
original_plasmids = set(line.strip() for line in f)
|
|
37
|
+
except FileNotFoundError:
|
|
38
|
+
logger.warning(f"File {subcluster_file} not found. Skipping.")
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
subcluster_plasmids = original_plasmids.copy()
|
|
42
|
+
|
|
43
|
+
connections = defaultdict(set)
|
|
44
|
+
for _, row in fastani_df.iterrows():
|
|
45
|
+
q, r = row["Query"], row["Reference"]
|
|
46
|
+
if q in subcluster_plasmids and r in subcluster_plasmids:
|
|
47
|
+
connections[q].add(r)
|
|
48
|
+
connections[r].add(q)
|
|
49
|
+
|
|
50
|
+
# Iteratively remove nodes with the fewest connections until we get a complete subgraph
|
|
51
|
+
while True:
|
|
52
|
+
current_nodes = set(connections.keys())
|
|
53
|
+
if len(current_nodes) < 3:
|
|
54
|
+
subcluster_plasmids = set()
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
# Check if the current graph is a complete subgraph (clique)
|
|
58
|
+
complete = all(len(connections[node]) == len(current_nodes) - 1 for node in current_nodes)
|
|
59
|
+
if complete:
|
|
60
|
+
subcluster_plasmids = current_nodes
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
# Find the node with the fewest connections (lowest degree)
|
|
64
|
+
min_node = min(current_nodes, key=lambda x: len(connections[x]))
|
|
65
|
+
|
|
66
|
+
# Remove that node from the graph
|
|
67
|
+
del connections[min_node]
|
|
68
|
+
for conn in connections.values():
|
|
69
|
+
conn.discard(min_node)
|
|
70
|
+
|
|
71
|
+
# Step 7: Save the refined subcluster to a new file with the desired naming format
|
|
72
|
+
cluster_number = subcluster_file.split("_")[1] # Extract the number from subcluster_XX_plasmids.txt
|
|
73
|
+
output_file = f"cluster_{cluster_number}.txt"
|
|
74
|
+
|
|
75
|
+
cluster_path = os.path.join(output_dir, output_file)
|
|
76
|
+
with open(cluster_path, "w") as f:
|
|
77
|
+
|
|
78
|
+
for plasmid in subcluster_plasmids:
|
|
79
|
+
f.write(plasmid + "\n")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def filter_clusters_by_size(output_dir, min_cluster_size):
|
|
83
|
+
for filename in os.listdir(output_dir):
|
|
84
|
+
if filename.startswith("cluster_") and filename.endswith(".txt"):
|
|
85
|
+
path = os.path.join(output_dir, filename)
|
|
86
|
+
with open(path, "r") as f:
|
|
87
|
+
lines = f.readlines()
|
|
88
|
+
if len(lines) < min_cluster_size:
|
|
89
|
+
os.remove(path)
|
|
90
|
+
|
|
91
|
+
def write_cluster_list(output_dir, output_path):
|
|
92
|
+
cluster_files = []
|
|
93
|
+
for filename in os.listdir(output_dir):
|
|
94
|
+
if (
|
|
95
|
+
filename.startswith("cluster_")
|
|
96
|
+
and filename.endswith(".txt")
|
|
97
|
+
and filename not in {os.path.basename(output_path), "cluster_colours.txt"} # exclude output file itself and cluster_colours.txt
|
|
98
|
+
):
|
|
99
|
+
path = os.path.join(output_dir, filename)
|
|
100
|
+
with open(path, "r") as f:
|
|
101
|
+
count = sum(1 for _ in f)
|
|
102
|
+
cluster_files.append((filename, count))
|
|
103
|
+
cluster_files.sort(key=lambda x: x[1], reverse=True)
|
|
104
|
+
with open(output_path, "w") as f:
|
|
105
|
+
f.write("Cluster\tPlasmids\n")
|
|
106
|
+
for filename, count in cluster_files:
|
|
107
|
+
f.write(f"{filename}\t{count}\n")
|
|
108
|
+
|
|
109
|
+
def main(results_dir, min_cluster_size):
|
|
110
|
+
fastani_path = os.path.join(results_dir, "ANI_results_final.tsv")
|
|
111
|
+
subcluster_list_output = os.path.join(results_dir, "subcluster_list.txt")
|
|
112
|
+
cluster_list_output = os.path.join(results_dir, "cluster_list.txt")
|
|
113
|
+
|
|
114
|
+
logger.info("Finding valid subclusters (>=3 plasmids)...")
|
|
115
|
+
valid_subclusters = find_valid_subclusters(results_dir)
|
|
116
|
+
|
|
117
|
+
write_subcluster_list(valid_subclusters, subcluster_list_output)
|
|
118
|
+
|
|
119
|
+
logger.info("Identifying clusters...")
|
|
120
|
+
extract_clusters(valid_subclusters, results_dir, fastani_path, results_dir)
|
|
121
|
+
|
|
122
|
+
logging.info(f"Keep only clusters with >={min_cluster_size} plasmids...")
|
|
123
|
+
filter_clusters_by_size(results_dir, min_cluster_size)
|
|
124
|
+
|
|
125
|
+
write_cluster_list(results_dir, cluster_list_output)
|
|
126
|
+
|
|
127
|
+
# Check: warn user if cluster_list.txt is empty
|
|
128
|
+
if os.path.exists(cluster_list_output):
|
|
129
|
+
with open(cluster_list_output, "r") as f:
|
|
130
|
+
lines = f.readlines()
|
|
131
|
+
if len(lines) <= 1:
|
|
132
|
+
logger.warning("No clusters detected with the given parameters!")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# logger.info("Done!")
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
parser = argparse.ArgumentParser(description="Clustering Tool")
|
|
139
|
+
parser.add_argument("results_dir", help="Path to results directory created by main.py")
|
|
140
|
+
parser.add_argument("--min_cluster_size", type=int, default=3, help="Minimum number of plasmids in final cluster (default: 3)")
|
|
141
|
+
args = parser.parse_args()
|
|
142
|
+
|
|
143
|
+
main(args.results_dir, args.min_cluster_size)
|