PythiaLabelGenerator 1.0.0__tar.gz → 1.1.1__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.
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/workflows/test-label-generator.yml +1 -1
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/PKG-INFO +18 -15
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/README.md +17 -14
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/label.py +7 -5
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/logger.py +17 -1
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/main.py +48 -19
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/pyproject.toml +1 -1
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_label.py +3 -0
- pythialabelgenerator-1.1.1/tests/test_main.py +127 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/setup-iqtree/action.yml +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/setup-raxmlng/action.yml +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/test-label-cli/action.yml +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.gitignore +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.pre-commit-config.yaml +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/LICENSE +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/__init__.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/etc/environment.yml +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/examples/example.phy +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/__init__.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/iqtree.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/iqtree_parser.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/labelgenerator/raxmlng.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/setup.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/__init__.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/conftest.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/AA.phy +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/DNA.phy +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/MORPH.phy +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/AA.iqtree.iqtree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/AA.iqtree.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/DNA.iqtree.iqtree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/DNA.iqtree.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/DNA.raxml.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/MORPH.iqtree.iqtree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/MORPH.iqtree.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.bestModel +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.bestTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.mlTrees +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.rba +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.startTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.rfdist.raxml.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.rfdist.raxml.rfDistances +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.bestModel +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.bestTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.mlTrees +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.rba +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.startTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestModel +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestTreeCollapsed +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.mlTrees +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.rba +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.startTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestModel +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestTreeCollapsed +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.log +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.mlTrees +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.rba +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.startTree +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_config.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_iqtree.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_iqtree_parser.py +0 -0
- {pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/test_raxmlng.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PythiaLabelGenerator
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Command line tool to generate the ground-truth phylogenetic difficulty of MSAs
|
|
5
5
|
Project-URL: Homepage, https://github.com/tschuelia/PythiaLabelGenerator
|
|
6
6
|
Author-email: Julia Haag <info@juliaschmid.com>
|
|
@@ -18,11 +18,11 @@ Provides-Extra: test
|
|
|
18
18
|
Requires-Dist: pytest; extra == 'test'
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
21
|
-
# Pythia Difficulty Label Generator
|
|
21
|
+
# Pythia Difficulty Label Generator (PyDLG)
|
|
22
22
|
|
|
23
23
|

|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
PyDLG generates the ground-truth phylogenetic difficulty label for an MSA and
|
|
26
26
|
corresponds to the prediction target of our difficulty prediction tool [Pythia](https://github.com/tschuelia/PyPythia).
|
|
27
27
|
|
|
28
28
|
> [!CAUTION]
|
|
@@ -70,7 +70,7 @@ comprising [SARS-CoV-2 sequences](https://doi.org/10.1093/molbev/msaa314) (appro
|
|
|
70
70
|
12 hours on a large compute cluster. Using Pythia instead, we can predict the same MSA to be very difficult in about 2.5
|
|
71
71
|
minutes on a standard MacBook.
|
|
72
72
|
|
|
73
|
-
Only use
|
|
73
|
+
Only use PyDLG if you need the ground-truth difficulty for a specific MSA and you are sure that Pythia is unable to
|
|
74
74
|
predict the difficulty accurately.
|
|
75
75
|
The only case where we observed Pythia to fail is for language MSAs, so if you are working with DNA, Protein, or
|
|
76
76
|
biological morphological data, Pythia should work just fine 😉
|
|
@@ -79,7 +79,7 @@ biological morphological data, Pythia should work just fine 😉
|
|
|
79
79
|
|
|
80
80
|
#### Requirements
|
|
81
81
|
|
|
82
|
-
To use
|
|
82
|
+
To use PyDLG, you need to install
|
|
83
83
|
|
|
84
84
|
- RAxML-NG: See [the RAxML-NG GitHub repository](https://github.com/amkozlov/raxml-ng) for installation instructions.
|
|
85
85
|
Please make sure that you install a RAxML-NG version < 2.
|
|
@@ -88,7 +88,11 @@ To use this labelling tool, you need to install
|
|
|
88
88
|
|
|
89
89
|
#### Install via conda (recommended)
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
You can install the package using conda:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
conda install pythialabelgenerator -c conda-forge
|
|
95
|
+
```
|
|
92
96
|
|
|
93
97
|
#### Install using pip
|
|
94
98
|
|
|
@@ -100,7 +104,7 @@ pip install pythialabelgenerator
|
|
|
100
104
|
|
|
101
105
|
## Usage
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
PyDLG is primarily a command line tool. You can call it using the `label` command, for instance, to
|
|
104
108
|
compute the difficulty for the example MSA provided in the `examples` directory run
|
|
105
109
|
|
|
106
110
|
```bash
|
|
@@ -112,7 +116,7 @@ to the console.
|
|
|
112
116
|
The output will look something like this:
|
|
113
117
|
|
|
114
118
|
```text
|
|
115
|
-
|
|
119
|
+
PyDLG version 1.0.1 released by The Exelixis Lab
|
|
116
120
|
Developed by: Julia Haag
|
|
117
121
|
Latest version: https://github.com/tschuelia/LabelGenerator
|
|
118
122
|
Questions/problems/suggestions? Please open an issue on GitHub.
|
|
@@ -147,7 +151,7 @@ Note that this `examply.phy` MSA is not the same exemplary MSA as we provide in
|
|
|
147
151
|
For a full list of command line options, run `label -h`:
|
|
148
152
|
|
|
149
153
|
```text
|
|
150
|
-
|
|
154
|
+
PyDLG version 1.0.1 released by The Exelixis Lab
|
|
151
155
|
Developed by: Julia Haag
|
|
152
156
|
Latest version: https://github.com/tschuelia/LabelGenerator
|
|
153
157
|
Questions/problems/suggestions? Please open an issue on GitHub.
|
|
@@ -183,19 +187,19 @@ difficulties, as the difficulty is based on the average pairwise RF distance bet
|
|
|
183
187
|
|
|
184
188
|
### Result Files
|
|
185
189
|
|
|
186
|
-
Running
|
|
190
|
+
Running PyDLG will result in the following files:
|
|
187
191
|
|
|
188
192
|
- `{prefix}.raxml.*`: RAxML-NG log and result files.
|
|
189
193
|
- `{prefix}.iqtree.*`: IQ-TREE log and result files.
|
|
190
194
|
- `{prefix}.labelGen.log`: Log file containing the output of the label generator. This is the same output as printed to
|
|
191
195
|
the terminal.
|
|
196
|
+
- `{prefix}.csv`: The computed ground-truth difficulty and all features required for Pythia for the given MSA.
|
|
192
197
|
|
|
193
198
|
You can set the prefix of these files using the `-p` option. By default, the prefix is the name of the MSA file.
|
|
194
199
|
Note that RAxML-NG and IQ-TREE refuse to overwrite existing files. If you want to redo the computations, you can use the
|
|
195
200
|
`--redo` option.
|
|
196
201
|
Please also specify the `--redo` option if you want to change the number of trees to infer using the `--ntrees` option
|
|
197
|
-
for the same prefix. Otherwise,
|
|
198
|
-
the label generator will exit with an error message.
|
|
202
|
+
for the same prefix. Otherwise, PyDLG will exit with an error message.
|
|
199
203
|
|
|
200
204
|
### Input Data
|
|
201
205
|
|
|
@@ -209,7 +213,6 @@ you can do so using the `--model` option.
|
|
|
209
213
|
|
|
210
214
|
|
|
211
215
|
## Citation
|
|
212
|
-
|
|
213
|
-
brief description of this new labelling tool. Please cite this pre-print if you use this tool in your research.
|
|
216
|
+
Please cite the following preprint if you use this tool in your research.
|
|
214
217
|
|
|
215
|
-
|
|
218
|
+
Haag, J. & Stamatakis, A. (2025). **Pythia 2.0: New Data, New Prediction Model, New Features.** *BioRxiv*. [https://doi.org/10.1101/2025.03.25.645182](https://doi.org/10.1101/2025.03.25.645182)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Pythia Difficulty Label Generator
|
|
1
|
+
# Pythia Difficulty Label Generator (PyDLG)
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
PyDLG generates the ground-truth phylogenetic difficulty label for an MSA and
|
|
6
6
|
corresponds to the prediction target of our difficulty prediction tool [Pythia](https://github.com/tschuelia/PyPythia).
|
|
7
7
|
|
|
8
8
|
> [!CAUTION]
|
|
@@ -50,7 +50,7 @@ comprising [SARS-CoV-2 sequences](https://doi.org/10.1093/molbev/msaa314) (appro
|
|
|
50
50
|
12 hours on a large compute cluster. Using Pythia instead, we can predict the same MSA to be very difficult in about 2.5
|
|
51
51
|
minutes on a standard MacBook.
|
|
52
52
|
|
|
53
|
-
Only use
|
|
53
|
+
Only use PyDLG if you need the ground-truth difficulty for a specific MSA and you are sure that Pythia is unable to
|
|
54
54
|
predict the difficulty accurately.
|
|
55
55
|
The only case where we observed Pythia to fail is for language MSAs, so if you are working with DNA, Protein, or
|
|
56
56
|
biological morphological data, Pythia should work just fine 😉
|
|
@@ -59,7 +59,7 @@ biological morphological data, Pythia should work just fine 😉
|
|
|
59
59
|
|
|
60
60
|
#### Requirements
|
|
61
61
|
|
|
62
|
-
To use
|
|
62
|
+
To use PyDLG, you need to install
|
|
63
63
|
|
|
64
64
|
- RAxML-NG: See [the RAxML-NG GitHub repository](https://github.com/amkozlov/raxml-ng) for installation instructions.
|
|
65
65
|
Please make sure that you install a RAxML-NG version < 2.
|
|
@@ -68,7 +68,11 @@ To use this labelling tool, you need to install
|
|
|
68
68
|
|
|
69
69
|
#### Install via conda (recommended)
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
You can install the package using conda:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
conda install pythialabelgenerator -c conda-forge
|
|
75
|
+
```
|
|
72
76
|
|
|
73
77
|
#### Install using pip
|
|
74
78
|
|
|
@@ -80,7 +84,7 @@ pip install pythialabelgenerator
|
|
|
80
84
|
|
|
81
85
|
## Usage
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
PyDLG is primarily a command line tool. You can call it using the `label` command, for instance, to
|
|
84
88
|
compute the difficulty for the example MSA provided in the `examples` directory run
|
|
85
89
|
|
|
86
90
|
```bash
|
|
@@ -92,7 +96,7 @@ to the console.
|
|
|
92
96
|
The output will look something like this:
|
|
93
97
|
|
|
94
98
|
```text
|
|
95
|
-
|
|
99
|
+
PyDLG version 1.0.1 released by The Exelixis Lab
|
|
96
100
|
Developed by: Julia Haag
|
|
97
101
|
Latest version: https://github.com/tschuelia/LabelGenerator
|
|
98
102
|
Questions/problems/suggestions? Please open an issue on GitHub.
|
|
@@ -127,7 +131,7 @@ Note that this `examply.phy` MSA is not the same exemplary MSA as we provide in
|
|
|
127
131
|
For a full list of command line options, run `label -h`:
|
|
128
132
|
|
|
129
133
|
```text
|
|
130
|
-
|
|
134
|
+
PyDLG version 1.0.1 released by The Exelixis Lab
|
|
131
135
|
Developed by: Julia Haag
|
|
132
136
|
Latest version: https://github.com/tschuelia/LabelGenerator
|
|
133
137
|
Questions/problems/suggestions? Please open an issue on GitHub.
|
|
@@ -163,19 +167,19 @@ difficulties, as the difficulty is based on the average pairwise RF distance bet
|
|
|
163
167
|
|
|
164
168
|
### Result Files
|
|
165
169
|
|
|
166
|
-
Running
|
|
170
|
+
Running PyDLG will result in the following files:
|
|
167
171
|
|
|
168
172
|
- `{prefix}.raxml.*`: RAxML-NG log and result files.
|
|
169
173
|
- `{prefix}.iqtree.*`: IQ-TREE log and result files.
|
|
170
174
|
- `{prefix}.labelGen.log`: Log file containing the output of the label generator. This is the same output as printed to
|
|
171
175
|
the terminal.
|
|
176
|
+
- `{prefix}.csv`: The computed ground-truth difficulty and all features required for Pythia for the given MSA.
|
|
172
177
|
|
|
173
178
|
You can set the prefix of these files using the `-p` option. By default, the prefix is the name of the MSA file.
|
|
174
179
|
Note that RAxML-NG and IQ-TREE refuse to overwrite existing files. If you want to redo the computations, you can use the
|
|
175
180
|
`--redo` option.
|
|
176
181
|
Please also specify the `--redo` option if you want to change the number of trees to infer using the `--ntrees` option
|
|
177
|
-
for the same prefix. Otherwise,
|
|
178
|
-
the label generator will exit with an error message.
|
|
182
|
+
for the same prefix. Otherwise, PyDLG will exit with an error message.
|
|
179
183
|
|
|
180
184
|
### Input Data
|
|
181
185
|
|
|
@@ -189,7 +193,6 @@ you can do so using the `--model` option.
|
|
|
189
193
|
|
|
190
194
|
|
|
191
195
|
## Citation
|
|
192
|
-
|
|
193
|
-
brief description of this new labelling tool. Please cite this pre-print if you use this tool in your research.
|
|
196
|
+
Please cite the following preprint if you use this tool in your research.
|
|
194
197
|
|
|
195
|
-
|
|
198
|
+
Haag, J. & Stamatakis, A. (2025). **Pythia 2.0: New Data, New Prediction Model, New Features.** *BioRxiv*. [https://doi.org/10.1101/2025.03.25.645182](https://doi.org/10.1101/2025.03.25.645182)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pathlib
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
from pypythia.msa import
|
|
4
|
+
from pypythia.msa import DataType, MSA
|
|
5
5
|
|
|
6
6
|
from labelgenerator.iqtree import (
|
|
7
7
|
filter_plausible_trees,
|
|
@@ -92,14 +92,15 @@ def get_label(
|
|
|
92
92
|
label = total / 5
|
|
93
93
|
|
|
94
94
|
eps = 1e-9
|
|
95
|
-
assert
|
|
96
|
-
|
|
97
|
-
)
|
|
95
|
+
assert (
|
|
96
|
+
-eps <= label <= 1 + eps
|
|
97
|
+
), f"Label {label} is not between 0 and 1. Check the input values."
|
|
98
98
|
|
|
99
99
|
return label
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
def compute_label(
|
|
103
|
+
msa_obj: MSA,
|
|
103
104
|
msa_file: pathlib.Path,
|
|
104
105
|
raxmlng: pathlib.Path,
|
|
105
106
|
iqtree: pathlib.Path,
|
|
@@ -117,6 +118,7 @@ def compute_label(
|
|
|
117
118
|
|
|
118
119
|
|
|
119
120
|
Args:
|
|
121
|
+
msa_obj (MSA): MSA object containing the input data.
|
|
120
122
|
msa_file (pathlib.Path): Path to the MSA file to compute the label for. Can be either in FASTA or PHYLIP format.
|
|
121
123
|
raxmlng (pathlib.Path): Path to the RAxML-NG executable.
|
|
122
124
|
iqtree (pathlib.Path): Path to the IQ-TREE executable.
|
|
@@ -134,7 +136,6 @@ def compute_label(
|
|
|
134
136
|
float: The ground truth difficulty label for the given MSA.
|
|
135
137
|
|
|
136
138
|
"""
|
|
137
|
-
msa_obj = parse_msa(msa_file)
|
|
138
139
|
model = model or msa_obj.get_raxmlng_model()
|
|
139
140
|
|
|
140
141
|
# 1. Infer 100 ML trees for the given MSA using RAxML-NG
|
|
@@ -182,6 +183,7 @@ def compute_label(
|
|
|
182
183
|
seed=seed,
|
|
183
184
|
threads=threads,
|
|
184
185
|
redo=redo,
|
|
186
|
+
is_morph=msa_obj.data_type == DataType.MORPH,
|
|
185
187
|
)
|
|
186
188
|
|
|
187
189
|
# 4. Filter the plausible trees
|
|
@@ -15,7 +15,7 @@ logger.add(sys.stderr, format="{message}")
|
|
|
15
15
|
|
|
16
16
|
def get_header():
|
|
17
17
|
return textwrap.dedent(
|
|
18
|
-
f"
|
|
18
|
+
f"PyDLG version {__version__} released by The Exelixis Lab\n"
|
|
19
19
|
f"Developed by: Julia Haag\n"
|
|
20
20
|
f"Latest version: https://github.com/tschuelia/LabelGenerator\n"
|
|
21
21
|
f"Questions/problems/suggestions? Please open an issue on GitHub.\n",
|
|
@@ -30,3 +30,19 @@ def log_runtime_information(message, log_runtime=True):
|
|
|
30
30
|
else:
|
|
31
31
|
time_string = ""
|
|
32
32
|
logger.info(f"{time_string}{message}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def log_runtime(total_runtime: int, runtime_name: str):
|
|
36
|
+
hours, remainder = divmod(total_runtime, 3600)
|
|
37
|
+
minutes, seconds = divmod(remainder, 60)
|
|
38
|
+
|
|
39
|
+
if hours > 0:
|
|
40
|
+
logger.info(
|
|
41
|
+
f"{runtime_name}: {int(hours):02d}:{int(minutes):02d}:{seconds:02d} hours ({round(total_runtime)} seconds)."
|
|
42
|
+
)
|
|
43
|
+
elif minutes > 0:
|
|
44
|
+
logger.info(
|
|
45
|
+
f"{runtime_name}: {int(minutes):02d}:{int(seconds):02d} minutes ({round(total_runtime)} seconds)."
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
logger.info(f"{runtime_name}: {seconds:.2f} seconds.")
|
|
@@ -3,15 +3,20 @@ import pathlib
|
|
|
3
3
|
import shutil
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
from labelgenerator import __version__
|
|
8
9
|
from labelgenerator.label import compute_label
|
|
9
10
|
from labelgenerator.logger import (
|
|
10
11
|
SCRIPT_START,
|
|
11
12
|
get_header,
|
|
13
|
+
log_runtime,
|
|
12
14
|
log_runtime_information,
|
|
13
15
|
logger,
|
|
14
16
|
)
|
|
17
|
+
from pypythia.msa import parse_msa
|
|
18
|
+
from pypythia.prediction import collect_features
|
|
19
|
+
from pypythia.raxmlng import RAxMLNG
|
|
15
20
|
|
|
16
21
|
DEFAULT_RAXMLNG_EXE = (
|
|
17
22
|
pathlib.Path(shutil.which("raxml-ng")) if shutil.which("raxml-ng") else None
|
|
@@ -22,7 +27,7 @@ DEFAULT_IQTREE_EXE = (
|
|
|
22
27
|
)
|
|
23
28
|
|
|
24
29
|
|
|
25
|
-
def _parse_cli():
|
|
30
|
+
def _parse_cli(arg_list: Optional[list[str]] = None):
|
|
26
31
|
parser = argparse.ArgumentParser(
|
|
27
32
|
description="Generate the ground truth difficulty for the given MSA."
|
|
28
33
|
)
|
|
@@ -110,29 +115,49 @@ def _parse_cli():
|
|
|
110
115
|
help="Print the version number and exit.",
|
|
111
116
|
)
|
|
112
117
|
|
|
113
|
-
return parser.parse_args()
|
|
118
|
+
return parser.parse_args(arg_list)
|
|
114
119
|
|
|
115
120
|
|
|
116
|
-
def main():
|
|
121
|
+
def main(arg_list: Optional[list[str]] = None):
|
|
117
122
|
logger.info(get_header())
|
|
118
|
-
args = _parse_cli()
|
|
123
|
+
args = _parse_cli(arg_list)
|
|
119
124
|
|
|
120
125
|
msa_file = pathlib.Path(args.msa)
|
|
121
126
|
prefix = pathlib.Path(args.prefix) if args.prefix else msa_file
|
|
122
127
|
|
|
123
128
|
log_file = pathlib.Path(f"{prefix}.labelGen.log")
|
|
129
|
+
features_file = pathlib.Path(f"{prefix}.csv")
|
|
130
|
+
|
|
131
|
+
# If the log file and the features file already exist, remove them if the --redo flag is set
|
|
132
|
+
if args.redo:
|
|
133
|
+
log_file.unlink(missing_ok=True)
|
|
134
|
+
features_file.unlink(missing_ok=True)
|
|
135
|
+
|
|
124
136
|
logger.add(log_file, format="{message}")
|
|
125
137
|
log_file.write_text(get_header() + "\n")
|
|
126
138
|
|
|
127
139
|
logger.info(
|
|
128
|
-
f"
|
|
140
|
+
f"PyDLG was called at {time.strftime('%d-%b-%Y %H:%M:%S')} as follows:\n"
|
|
129
141
|
)
|
|
130
142
|
logger.info(" ".join(sys.argv))
|
|
131
143
|
logger.info("")
|
|
132
144
|
|
|
133
145
|
log_runtime_information("Starting label computation.")
|
|
134
146
|
|
|
147
|
+
msa_obj = parse_msa(msa_file)
|
|
148
|
+
|
|
149
|
+
if msa_obj.contains_duplicate_sequences():
|
|
150
|
+
logger.info(
|
|
151
|
+
"WARNING: The input MSA contains duplicate sequences. Consider deduplicating the MSA before running the label generator."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if msa_obj.contains_full_gap_sequences():
|
|
155
|
+
logger.info(
|
|
156
|
+
"WARNING: The input MSA contains sequences that are only gaps. Consider removing these sequences before running the label generator."
|
|
157
|
+
)
|
|
158
|
+
|
|
135
159
|
difficulty = compute_label(
|
|
160
|
+
msa_obj=msa_obj,
|
|
136
161
|
msa_file=msa_file,
|
|
137
162
|
raxmlng=pathlib.Path(args.raxmlng),
|
|
138
163
|
iqtree=pathlib.Path(args.iqtree),
|
|
@@ -145,6 +170,22 @@ def main():
|
|
|
145
170
|
log_info=True,
|
|
146
171
|
)
|
|
147
172
|
|
|
173
|
+
label_end = time.perf_counter()
|
|
174
|
+
|
|
175
|
+
log_runtime_information("Computing corresponding Pythia features.")
|
|
176
|
+
|
|
177
|
+
features = collect_features(
|
|
178
|
+
msa=msa_obj,
|
|
179
|
+
msa_file=msa_file,
|
|
180
|
+
raxmlng=RAxMLNG(pathlib.Path(args.raxmlng)),
|
|
181
|
+
log_info=False,
|
|
182
|
+
threads=args.threads,
|
|
183
|
+
seed=args.seed,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
features["difficulty"] = difficulty
|
|
187
|
+
features.to_csv(features_file, index=False)
|
|
188
|
+
|
|
148
189
|
script_end = time.perf_counter()
|
|
149
190
|
|
|
150
191
|
logger.info("")
|
|
@@ -156,20 +197,8 @@ def main():
|
|
|
156
197
|
)
|
|
157
198
|
|
|
158
199
|
logger.info("")
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
minutes, seconds = divmod(remainder, 60)
|
|
162
|
-
|
|
163
|
-
if hours > 0:
|
|
164
|
-
logger.info(
|
|
165
|
-
f"Total runtime: {int(hours):02d}:{int(minutes):02d}:{seconds:02d} hours ({round(total_runtime)} seconds)."
|
|
166
|
-
)
|
|
167
|
-
elif minutes > 0:
|
|
168
|
-
logger.info(
|
|
169
|
-
f"Total runtime: {int(minutes):02d}:{int(seconds):02d} minutes ({round(total_runtime)} seconds)."
|
|
170
|
-
)
|
|
171
|
-
else:
|
|
172
|
-
logger.info(f"Total runtime: {seconds:.2f} seconds.")
|
|
200
|
+
log_runtime(label_end - SCRIPT_START, "Label computation runtime")
|
|
201
|
+
log_runtime(script_end - SCRIPT_START, "Total runtime")
|
|
173
202
|
|
|
174
203
|
|
|
175
204
|
if __name__ == "__main__":
|
|
@@ -3,7 +3,7 @@ name = "PythiaLabelGenerator"
|
|
|
3
3
|
description = "Command line tool to generate the ground-truth phylogenetic difficulty of MSAs"
|
|
4
4
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
5
5
|
authors = [{name = "Julia Haag", email = "info@juliaschmid.com"}]
|
|
6
|
-
version = "1.
|
|
6
|
+
version = "1.1.1"
|
|
7
7
|
license = "GPL-3.0-or-later"
|
|
8
8
|
classifiers = [
|
|
9
9
|
"Programming Language :: Python :: 3.9",
|
|
@@ -6,6 +6,7 @@ import pytest
|
|
|
6
6
|
from labelgenerator.label import get_label, compute_label
|
|
7
7
|
from labelgenerator.logger import logger
|
|
8
8
|
from pypythia.custom_types import DataType
|
|
9
|
+
from pypythia.msa import parse_msa
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def test_get_label_fails_for_invalid_input():
|
|
@@ -99,6 +100,7 @@ def test_compute_label(
|
|
|
99
100
|
prefix = pathlib.Path(tmpdir) / "test"
|
|
100
101
|
|
|
101
102
|
label = compute_label(
|
|
103
|
+
msa_obj=parse_msa(msa_file),
|
|
102
104
|
msa_file=msa_file,
|
|
103
105
|
raxmlng=raxmlng_command,
|
|
104
106
|
iqtree=iqtree_command,
|
|
@@ -123,6 +125,7 @@ def test_compute_label_with_logging(raxmlng_command, iqtree_command, data_dir):
|
|
|
123
125
|
logger.add(logfile, format="{message}")
|
|
124
126
|
|
|
125
127
|
label = compute_label(
|
|
128
|
+
msa_obj=parse_msa(msa_file),
|
|
126
129
|
msa_file=msa_file,
|
|
127
130
|
raxmlng=raxmlng_command,
|
|
128
131
|
iqtree=iqtree_command,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from labelgenerator.main import main
|
|
7
|
+
from labelgenerator import __version__
|
|
8
|
+
import pathlib
|
|
9
|
+
|
|
10
|
+
from pypythia.custom_types import DataType
|
|
11
|
+
from pypythia.msa import parse_msa
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize(
|
|
15
|
+
"data_type, expected_label",
|
|
16
|
+
[(DataType.DNA, 0.772), (DataType.AA, 0.04), (DataType.MORPH, 0.173)],
|
|
17
|
+
)
|
|
18
|
+
def test_main(data_type, expected_label, data_dir, raxmlng_command, iqtree_command):
|
|
19
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
20
|
+
# Create a temporary directory for the test
|
|
21
|
+
prefix = pathlib.Path(tmpdir) / "test"
|
|
22
|
+
seed = 42
|
|
23
|
+
n_trees = 10
|
|
24
|
+
|
|
25
|
+
logfile = prefix.with_suffix(".labelGen.log")
|
|
26
|
+
features_file = prefix.with_suffix(".csv")
|
|
27
|
+
|
|
28
|
+
msa_file = data_dir / f"{data_type.name}.phy"
|
|
29
|
+
msa_obj = parse_msa(msa_file)
|
|
30
|
+
|
|
31
|
+
args = [
|
|
32
|
+
"--msa",
|
|
33
|
+
str(msa_file),
|
|
34
|
+
"--raxmlng",
|
|
35
|
+
str(raxmlng_command),
|
|
36
|
+
"--iqtree",
|
|
37
|
+
str(iqtree_command),
|
|
38
|
+
"--seed",
|
|
39
|
+
str(seed),
|
|
40
|
+
"--prefix",
|
|
41
|
+
str(prefix),
|
|
42
|
+
"--ntrees",
|
|
43
|
+
str(n_trees),
|
|
44
|
+
"--redo",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
main(args)
|
|
48
|
+
|
|
49
|
+
# Check if all output files exist
|
|
50
|
+
expected_files = [
|
|
51
|
+
logfile,
|
|
52
|
+
features_file,
|
|
53
|
+
# RAxML-NG output files
|
|
54
|
+
prefix.with_suffix(".raxml.bestTree"),
|
|
55
|
+
prefix.with_suffix(".raxml.log"),
|
|
56
|
+
prefix.with_suffix(".raxml.mlTrees"),
|
|
57
|
+
prefix.with_suffix(".raxml.plausibleTrees"),
|
|
58
|
+
# IQ-TREE output files
|
|
59
|
+
prefix.with_suffix(".iqtree.iqtree"),
|
|
60
|
+
prefix.with_suffix(".iqtree.log"),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for file in expected_files:
|
|
64
|
+
assert file.exists(), f"Expected file {file} does not exist."
|
|
65
|
+
|
|
66
|
+
# Check if the features csv file is correct
|
|
67
|
+
features_content = pd.read_csv(features_file)
|
|
68
|
+
assert features_content.shape[0] == 1, "Expected one row in the features file."
|
|
69
|
+
pd.testing.assert_index_equal(
|
|
70
|
+
features_content.columns,
|
|
71
|
+
pd.Index(
|
|
72
|
+
[
|
|
73
|
+
"num_taxa",
|
|
74
|
+
"num_sites",
|
|
75
|
+
"num_patterns",
|
|
76
|
+
"num_patterns/num_taxa",
|
|
77
|
+
"num_sites/num_taxa",
|
|
78
|
+
"num_patterns/num_sites",
|
|
79
|
+
"proportion_gaps",
|
|
80
|
+
"proportion_invariant",
|
|
81
|
+
"entropy",
|
|
82
|
+
"bollback",
|
|
83
|
+
"pattern_entropy",
|
|
84
|
+
"avg_rfdist_parsimony",
|
|
85
|
+
"proportion_unique_topos_parsimony",
|
|
86
|
+
"difficulty",
|
|
87
|
+
]
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Check if the label is correct
|
|
92
|
+
label = features_content["difficulty"].values[0]
|
|
93
|
+
assert label == pytest.approx(expected_label, abs=0.01)
|
|
94
|
+
|
|
95
|
+
# Check if the log file is correct and contains the expected output
|
|
96
|
+
expected_lines = [
|
|
97
|
+
f"PyDLG version {__version__} released by The Exelixis Lab",
|
|
98
|
+
"PyDLG was called at",
|
|
99
|
+
"Starting label computation.",
|
|
100
|
+
"Computing RF-Distance between ML trees.",
|
|
101
|
+
"Inferring 10 ML trees using RAxML-NG.",
|
|
102
|
+
"RF-Distance ML trees:",
|
|
103
|
+
"Unique topologies ML trees:",
|
|
104
|
+
"Running IQ-TREE statistical tests.",
|
|
105
|
+
"Computing RF-Distance between plausible ML trees.",
|
|
106
|
+
"RF-Distance plausible trees:",
|
|
107
|
+
"Unique topologies plausible trees:",
|
|
108
|
+
"Computing corresponding Pythia features.",
|
|
109
|
+
f"Ground Truth Difficulty for {msa_file}:",
|
|
110
|
+
"WARNING: The number of inferred ML trees is less than 100. The computed label may be less reliable.",
|
|
111
|
+
"Label computation runtime",
|
|
112
|
+
"Total runtime",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
if msa_obj.contains_duplicate_sequences():
|
|
116
|
+
expected_lines.append(
|
|
117
|
+
"WARNING: The input MSA contains duplicate sequences. Consider deduplicating the MSA before running the label generator."
|
|
118
|
+
)
|
|
119
|
+
if msa_obj.contains_full_gap_sequences():
|
|
120
|
+
expected_lines.append(
|
|
121
|
+
"WARNING: The input MSA contains sequences that are only gaps. Consider removing these sequences before running the label generator."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
log_content = logfile.read_text()
|
|
125
|
+
|
|
126
|
+
for line in expected_lines:
|
|
127
|
+
assert line in log_content
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/setup-iqtree/action.yml
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/setup-raxmlng/action.yml
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/.github/actions/test-label-cli/action.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/MORPH.iqtree.iqtree
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.bestModel
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.bestTree
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.mlTrees
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.raxml.startTree
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/logs/test.rfdist.raxml.log
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.bestModel
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.bestTree
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.mlTrees
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/AA.raxml.startTree
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestModel
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.bestTree
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.mlTrees
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/DNA.raxml.startTree
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestModel
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.bestTree
RENAMED
|
File without changes
|
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.log
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.mlTrees
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.rba
RENAMED
|
File without changes
|
{pythialabelgenerator-1.0.0 → pythialabelgenerator-1.1.1}/tests/data/mltrees/MORPH.raxml.startTree
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|