PFASGroups 3.2.2__tar.gz → 3.3.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.
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/HalogenGroupModel.py +3 -4
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/PFASEmbeddings.py +1 -1
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/cli.py +2 -2
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/Halogen_groups_smarts.json +6 -6
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/getter.py +1 -1
- {pfasgroups-3.2.2 → pfasgroups-3.3.1/PFASGroups.egg-info}/PKG-INFO +32 -3
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/SOURCES.txt +21 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/entry_points.txt +1 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/requires.txt +8 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/top_level.txt +1 -0
- {pfasgroups-3.2.2/PFASGroups.egg-info → pfasgroups-3.3.1}/PKG-INFO +32 -3
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/README.md +24 -2
- pfasgroups-3.3.1/gui/__init__.py +1 -0
- pfasgroups-3.3.1/gui/__main__.py +21 -0
- pfasgroups-3.3.1/gui/app.py +162 -0
- pfasgroups-3.3.1/gui/data/help.html +276 -0
- pfasgroups-3.3.1/gui/style.py +407 -0
- pfasgroups-3.3.1/gui/tabs/__init__.py +1 -0
- pfasgroups-3.3.1/gui/tabs/chemspace_tab.py +250 -0
- pfasgroups-3.3.1/gui/tabs/classify_tab.py +823 -0
- pfasgroups-3.3.1/gui/tabs/definition_tester_tab.py +334 -0
- pfasgroups-3.3.1/gui/tabs/help_tab.py +97 -0
- pfasgroups-3.3.1/gui/tabs/modelling_tab.py +489 -0
- pfasgroups-3.3.1/gui/tabs/prioritise_tab.py +303 -0
- pfasgroups-3.3.1/gui/tabs/results_tab.py +350 -0
- pfasgroups-3.3.1/gui/utils/__init__.py +1 -0
- pfasgroups-3.3.1/gui/utils/chemspace.py +170 -0
- pfasgroups-3.3.1/gui/utils/export_dialog.py +742 -0
- pfasgroups-3.3.1/gui/utils/fingerprints.py +91 -0
- pfasgroups-3.3.1/gui/utils/io_readers.py +198 -0
- pfasgroups-3.3.1/gui/utils/modelling.py +176 -0
- pfasgroups-3.3.1/gui/utils/mol_renderer.py +69 -0
- pfasgroups-3.3.1/gui/workers.py +362 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/pyproject.toml +12 -2
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/setup.cfg +1 -1
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_bde_resistance.py +0 -1
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_prioritise.py +19 -7
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_readme_and_docs_examples.py +1 -1
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/HalogenGroups/__init__.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/ComponentsSolverModel.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/PFASDefinitionModel.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/__init__.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/core.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/PFAS_definitions_smarts.json +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/component_smarts.json +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/component_smarts_halogens.json +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/diatomic_bonds_dict.json +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/draw_mols.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/embeddings.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/fragmentation.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/generate_homologues.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/generate_mol.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/generate_paper_figures.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/homologue_series.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/parser.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/prioritise.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/dependency_links.txt +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_component_fractions.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_component_ratios.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_comprehensive_metrics.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_database_integration.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_definition_comparison.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_fingerprints_vs_txppfas.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_fraction_analysis.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_generate_homologues.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_halogen_groups_smarts.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_linker_smarts.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_metrics.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_metrics_detailed.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_n_spacer_ring_size.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_pfasstructv5.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_results_fingerprint.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_results_model.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_results_sql.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_run_groups_definitions_tests.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_smarts_atom_count.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_smarts_matching.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_smarts_precompute.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_summary.py +0 -0
- {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_telomer_validation.py +0 -0
|
@@ -578,13 +578,12 @@ class HalogenGroup():
|
|
|
578
578
|
if len(augmented_matched_components) == 0:
|
|
579
579
|
return 0, [], 0, []
|
|
580
580
|
|
|
581
|
-
# Get all component sizes from
|
|
582
|
-
|
|
583
|
-
component_sizes = [len(x) for x in all_components]
|
|
581
|
+
# Get all component sizes from matched component dicts
|
|
582
|
+
component_sizes = [comp.get('size', 0) for comp in augmented_matched_components]
|
|
584
583
|
|
|
585
584
|
self.all_matches = [] # Clear matches after use
|
|
586
585
|
self.component_specific_extra_atoms = []
|
|
587
|
-
return max([0] + component_sizes), component_sizes, len(
|
|
586
|
+
return max([0] + component_sizes), component_sizes, len(augmented_matched_components), augmented_matched_components
|
|
588
587
|
|
|
589
588
|
def find_aryl_components(self,mol, component_solver=None, **kwargs):
|
|
590
589
|
"""Find aryl components in a molecule with comprehensive metrics."""
|
|
@@ -311,7 +311,7 @@ def _get_group_info() -> Dict[int, Dict[str, str]]:
|
|
|
311
311
|
|
|
312
312
|
# Groups always excluded from the non-OECD category label (perhalogenated /
|
|
313
313
|
# polyhalogenated alkyl catch-alls that add no structural specificity).
|
|
314
|
-
_CLASSIFY_EXCLUDED_IDS: frozenset = frozenset({
|
|
314
|
+
_CLASSIFY_EXCLUDED_IDS: frozenset = frozenset({34,35}) # "Perhalogenated alkyl" and "Polyhalogenated alkyl"
|
|
315
315
|
|
|
316
316
|
# Name subsumption for non-OECD classification: when a more-specific group
|
|
317
317
|
# name (key) is present, every name in its list is suppressed.
|
|
@@ -47,7 +47,7 @@ Examples:
|
|
|
47
47
|
# List available path types
|
|
48
48
|
PFASGroups list-paths
|
|
49
49
|
|
|
50
|
-
Note: Use get_componentSMARTSs() and
|
|
50
|
+
Note: Use get_componentSMARTSs() and get_HalogenGroups() in Python to extend defaults.
|
|
51
51
|
"""
|
|
52
52
|
)
|
|
53
53
|
|
|
@@ -186,7 +186,7 @@ Note: Use get_componentSMARTSs() and get_PFASGroups() in Python to extend defaul
|
|
|
186
186
|
# List groups command
|
|
187
187
|
list_parser = subparsers.add_parser(
|
|
188
188
|
'list-groups',
|
|
189
|
-
help='List available PFAS groups (use in Python to extend with
|
|
189
|
+
help='List available PFAS groups (use in Python to extend with get_HalogenGroups)'
|
|
190
190
|
)
|
|
191
191
|
list_parser.add_argument(
|
|
192
192
|
'-o', '--output',
|
|
@@ -4258,8 +4258,8 @@
|
|
|
4258
4258
|
},
|
|
4259
4259
|
{
|
|
4260
4260
|
"id": 65,
|
|
4261
|
-
"name": "
|
|
4262
|
-
"alias": "
|
|
4261
|
+
"name": "thioester keto dicarboxylic acid",
|
|
4262
|
+
"alias": "Thioester keto dicarboxylic acid",
|
|
4263
4263
|
"componentSmarts": null,
|
|
4264
4264
|
"linker_smarts": null,
|
|
4265
4265
|
"max_dist_from_comp": 0,
|
|
@@ -4331,7 +4331,7 @@
|
|
|
4331
4331
|
"sorting": {
|
|
4332
4332
|
"category": "generic",
|
|
4333
4333
|
"elements": "SCO",
|
|
4334
|
-
"group": "
|
|
4334
|
+
"group": "thioester keto dicarboxylic acids",
|
|
4335
4335
|
"saturation": null,
|
|
4336
4336
|
"aggregated": false
|
|
4337
4337
|
}
|
|
@@ -7871,8 +7871,8 @@
|
|
|
7871
7871
|
},
|
|
7872
7872
|
{
|
|
7873
7873
|
"id": 106,
|
|
7874
|
-
"name": "telomer
|
|
7875
|
-
"alias": "telomer
|
|
7874
|
+
"name": "telomer thioester keto dicarboxylic acid",
|
|
7875
|
+
"alias": "telomer thioester keto dicarboxylic acid",
|
|
7876
7876
|
"componentSmarts": null,
|
|
7877
7877
|
"componentSaturation": "per",
|
|
7878
7878
|
"linker_smarts": "[CH2X4]",
|
|
@@ -7960,7 +7960,7 @@
|
|
|
7960
7960
|
"sorting": {
|
|
7961
7961
|
"category": "telomer",
|
|
7962
7962
|
"elements": "SCO",
|
|
7963
|
-
"group": "
|
|
7963
|
+
"group": "thioester keto dicarboxylic acids",
|
|
7964
7964
|
"saturation": null,
|
|
7965
7965
|
"aggregated": false
|
|
7966
7966
|
}
|
|
@@ -36,7 +36,7 @@ def get_compiled_HalogenGroups(**kwargs):
|
|
|
36
36
|
|
|
37
37
|
Examples
|
|
38
38
|
--------
|
|
39
|
-
>>> from
|
|
39
|
+
>>> from PFASGroups import get_compiled_HalogenGroups, HalogenGroup, parse_smiles
|
|
40
40
|
>>> groups = get_compiled_HalogenGroups()
|
|
41
41
|
>>> groups.append(HalogenGroup(
|
|
42
42
|
... id=200, name="Perfluoroalkyl nitrates",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PFASGroups
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.1
|
|
4
4
|
Summary: A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows.
|
|
5
5
|
Author: Luc T. Miaz
|
|
6
6
|
Author-email: "Luc T. Miaz" <luc@miaz.ch>
|
|
@@ -38,6 +38,13 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
38
38
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
39
39
|
Provides-Extra: database
|
|
40
40
|
Requires-Dist: sqlalchemy>=1.4.0; extra == "database"
|
|
41
|
+
Provides-Extra: gui
|
|
42
|
+
Requires-Dist: PySide6>=6.4; extra == "gui"
|
|
43
|
+
Requires-Dist: plotly>=5.0; extra == "gui"
|
|
44
|
+
Requires-Dist: umap-learn>=0.5; extra == "gui"
|
|
45
|
+
Requires-Dist: openpyxl>=3.0; extra == "gui"
|
|
46
|
+
Requires-Dist: sqlalchemy>=1.4.0; extra == "gui"
|
|
47
|
+
Requires-Dist: matplotlib>=3.5; extra == "gui"
|
|
41
48
|
|
|
42
49
|
# PFASGroups
|
|
43
50
|
|
|
@@ -71,7 +78,15 @@ PFASGroups combines SMARTS pattern matching, molecular formula constraints, and
|
|
|
71
78
|
|
|
72
79
|
## Installation
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
The recommended installation method is via pip. RDKit must already be available in the target environment. It is recommended to use an environment manager (like Conda/Mamba, e.g [Miniforge](https://github.com/conda-forge/miniforge)) and install RDKit via
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
mamba install -y -c rdkit rdkit
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### From PyPI
|
|
88
|
+
|
|
89
|
+
PFASGroups is available on PyPI:
|
|
75
90
|
|
|
76
91
|
```sh
|
|
77
92
|
pip install PFASGroups
|
|
@@ -106,6 +121,20 @@ print(results) # prints PFASEmbeddingSet summary (molecule count, matched
|
|
|
106
121
|
print(results[0]) # prints PFASEmbedding summary for the first molecule
|
|
107
122
|
```
|
|
108
123
|
|
|
124
|
+
## Graphical User Interface (GUI)
|
|
125
|
+
|
|
126
|
+
A GUI is available to run the main commands from the module. To launch the GUI, call the following command from the PFASGroups folder:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
python -m gui
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Note that pyQt6 must be installed as well as pyCSRML to benefit from all features.
|
|
133
|
+
|
|
134
|
+
## Binary release
|
|
135
|
+
|
|
136
|
+
To be done...
|
|
137
|
+
|
|
109
138
|
## Repository Structure
|
|
110
139
|
|
|
111
140
|
```
|
|
@@ -465,7 +494,7 @@ print(arr.shape)
|
|
|
465
494
|
### Option B – import `PFASgroups` and specify `halogens` explicitly
|
|
466
495
|
|
|
467
496
|
```python
|
|
468
|
-
from
|
|
497
|
+
from PFASGroups import parse_smiles
|
|
469
498
|
|
|
470
499
|
smiles_list = [
|
|
471
500
|
"C(C(F)(F)F)F",
|
|
@@ -30,6 +30,27 @@ PFASGroups/data/PFAS_definitions_smarts.json
|
|
|
30
30
|
PFASGroups/data/component_smarts.json
|
|
31
31
|
PFASGroups/data/component_smarts_halogens.json
|
|
32
32
|
PFASGroups/data/diatomic_bonds_dict.json
|
|
33
|
+
gui/__init__.py
|
|
34
|
+
gui/__main__.py
|
|
35
|
+
gui/app.py
|
|
36
|
+
gui/style.py
|
|
37
|
+
gui/workers.py
|
|
38
|
+
gui/data/help.html
|
|
39
|
+
gui/tabs/__init__.py
|
|
40
|
+
gui/tabs/chemspace_tab.py
|
|
41
|
+
gui/tabs/classify_tab.py
|
|
42
|
+
gui/tabs/definition_tester_tab.py
|
|
43
|
+
gui/tabs/help_tab.py
|
|
44
|
+
gui/tabs/modelling_tab.py
|
|
45
|
+
gui/tabs/prioritise_tab.py
|
|
46
|
+
gui/tabs/results_tab.py
|
|
47
|
+
gui/utils/__init__.py
|
|
48
|
+
gui/utils/chemspace.py
|
|
49
|
+
gui/utils/export_dialog.py
|
|
50
|
+
gui/utils/fingerprints.py
|
|
51
|
+
gui/utils/io_readers.py
|
|
52
|
+
gui/utils/modelling.py
|
|
53
|
+
gui/utils/mol_renderer.py
|
|
33
54
|
tests/test_bde_resistance.py
|
|
34
55
|
tests/test_component_fractions.py
|
|
35
56
|
tests/test_component_ratios.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PFASGroups
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.1
|
|
4
4
|
Summary: A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows.
|
|
5
5
|
Author: Luc T. Miaz
|
|
6
6
|
Author-email: "Luc T. Miaz" <luc@miaz.ch>
|
|
@@ -38,6 +38,13 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
38
38
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
39
39
|
Provides-Extra: database
|
|
40
40
|
Requires-Dist: sqlalchemy>=1.4.0; extra == "database"
|
|
41
|
+
Provides-Extra: gui
|
|
42
|
+
Requires-Dist: PySide6>=6.4; extra == "gui"
|
|
43
|
+
Requires-Dist: plotly>=5.0; extra == "gui"
|
|
44
|
+
Requires-Dist: umap-learn>=0.5; extra == "gui"
|
|
45
|
+
Requires-Dist: openpyxl>=3.0; extra == "gui"
|
|
46
|
+
Requires-Dist: sqlalchemy>=1.4.0; extra == "gui"
|
|
47
|
+
Requires-Dist: matplotlib>=3.5; extra == "gui"
|
|
41
48
|
|
|
42
49
|
# PFASGroups
|
|
43
50
|
|
|
@@ -71,7 +78,15 @@ PFASGroups combines SMARTS pattern matching, molecular formula constraints, and
|
|
|
71
78
|
|
|
72
79
|
## Installation
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
The recommended installation method is via pip. RDKit must already be available in the target environment. It is recommended to use an environment manager (like Conda/Mamba, e.g [Miniforge](https://github.com/conda-forge/miniforge)) and install RDKit via
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
mamba install -y -c rdkit rdkit
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### From PyPI
|
|
88
|
+
|
|
89
|
+
PFASGroups is available on PyPI:
|
|
75
90
|
|
|
76
91
|
```sh
|
|
77
92
|
pip install PFASGroups
|
|
@@ -106,6 +121,20 @@ print(results) # prints PFASEmbeddingSet summary (molecule count, matched
|
|
|
106
121
|
print(results[0]) # prints PFASEmbedding summary for the first molecule
|
|
107
122
|
```
|
|
108
123
|
|
|
124
|
+
## Graphical User Interface (GUI)
|
|
125
|
+
|
|
126
|
+
A GUI is available to run the main commands from the module. To launch the GUI, call the following command from the PFASGroups folder:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
python -m gui
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Note that pyQt6 must be installed as well as pyCSRML to benefit from all features.
|
|
133
|
+
|
|
134
|
+
## Binary release
|
|
135
|
+
|
|
136
|
+
To be done...
|
|
137
|
+
|
|
109
138
|
## Repository Structure
|
|
110
139
|
|
|
111
140
|
```
|
|
@@ -465,7 +494,7 @@ print(arr.shape)
|
|
|
465
494
|
### Option B – import `PFASgroups` and specify `halogens` explicitly
|
|
466
495
|
|
|
467
496
|
```python
|
|
468
|
-
from
|
|
497
|
+
from PFASGroups import parse_smiles
|
|
469
498
|
|
|
470
499
|
smiles_list = [
|
|
471
500
|
"C(C(F)(F)F)F",
|
|
@@ -30,7 +30,15 @@ PFASGroups combines SMARTS pattern matching, molecular formula constraints, and
|
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
The recommended installation method is via pip. RDKit must already be available in the target environment. It is recommended to use an environment manager (like Conda/Mamba, e.g [Miniforge](https://github.com/conda-forge/miniforge)) and install RDKit via
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
mamba install -y -c rdkit rdkit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### From PyPI
|
|
40
|
+
|
|
41
|
+
PFASGroups is available on PyPI:
|
|
34
42
|
|
|
35
43
|
```sh
|
|
36
44
|
pip install PFASGroups
|
|
@@ -65,6 +73,20 @@ print(results) # prints PFASEmbeddingSet summary (molecule count, matched
|
|
|
65
73
|
print(results[0]) # prints PFASEmbedding summary for the first molecule
|
|
66
74
|
```
|
|
67
75
|
|
|
76
|
+
## Graphical User Interface (GUI)
|
|
77
|
+
|
|
78
|
+
A GUI is available to run the main commands from the module. To launch the GUI, call the following command from the PFASGroups folder:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
python -m gui
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Note that pyQt6 must be installed as well as pyCSRML to benefit from all features.
|
|
85
|
+
|
|
86
|
+
## Binary release
|
|
87
|
+
|
|
88
|
+
To be done...
|
|
89
|
+
|
|
68
90
|
## Repository Structure
|
|
69
91
|
|
|
70
92
|
```
|
|
@@ -424,7 +446,7 @@ print(arr.shape)
|
|
|
424
446
|
### Option B – import `PFASgroups` and specify `halogens` explicitly
|
|
425
447
|
|
|
426
448
|
```python
|
|
427
|
-
from
|
|
449
|
+
from PFASGroups import parse_smiles
|
|
428
450
|
|
|
429
451
|
smiles_list = [
|
|
430
452
|
"C(C(F)(F)F)F",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""PFASGroups desktop GUI (PyQt6)."""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Entry point — run with: python -m gui or pfasgroups-gui"""
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# Ensure the PFASGroups package root is on the path when run from the
|
|
6
|
+
# repository root (e.g. python -m gui).
|
|
7
|
+
_repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
8
|
+
if _repo_root not in sys.path:
|
|
9
|
+
sys.path.insert(0, _repo_root)
|
|
10
|
+
|
|
11
|
+
from gui.app import create_app
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
app, window = create_app(sys.argv)
|
|
16
|
+
window.show()
|
|
17
|
+
sys.exit(app.exec())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PFASGroups GUI — Main window.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from PySide6.QtCore import Qt
|
|
10
|
+
from PySide6.QtGui import QIcon, QPixmap, QFont
|
|
11
|
+
from PySide6.QtWidgets import (
|
|
12
|
+
QApplication, QMainWindow, QTabWidget, QWidget,
|
|
13
|
+
QHBoxLayout, QVBoxLayout, QLabel, QStatusBar,
|
|
14
|
+
QSizePolicy,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from gui import style
|
|
18
|
+
|
|
19
|
+
_LOGO_PATH = Path(__file__).parent.parent / "logo" / "PFASGroups_logo.png"
|
|
20
|
+
_TASKBAR_LOGO_PATH = Path(__file__).parent.parent / "logo" / "taskbar_logo.svg"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HeaderWidget(QWidget):
|
|
24
|
+
"""Dark purple header bar with logo and app title."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, parent=None):
|
|
27
|
+
super().__init__(parent)
|
|
28
|
+
self.setObjectName("header_widget")
|
|
29
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
30
|
+
self.setFixedHeight(70)
|
|
31
|
+
|
|
32
|
+
layout = QHBoxLayout(self)
|
|
33
|
+
layout.setContentsMargins(16, 8, 16, 8)
|
|
34
|
+
layout.setSpacing(14)
|
|
35
|
+
|
|
36
|
+
# Logo
|
|
37
|
+
if _LOGO_PATH.exists():
|
|
38
|
+
pix = QPixmap(str(_LOGO_PATH)).scaledToHeight(
|
|
39
|
+
48, Qt.TransformationMode.SmoothTransformation
|
|
40
|
+
)
|
|
41
|
+
logo_lbl = QLabel()
|
|
42
|
+
logo_lbl.setPixmap(pix)
|
|
43
|
+
logo_lbl.setFixedSize(pix.size())
|
|
44
|
+
layout.addWidget(logo_lbl)
|
|
45
|
+
else:
|
|
46
|
+
placeholder = QLabel("PFASGroups")
|
|
47
|
+
placeholder.setStyleSheet(
|
|
48
|
+
f"color: {style.C_ORANGE}; font-size: 22px; font-weight: 800;"
|
|
49
|
+
)
|
|
50
|
+
layout.addWidget(placeholder)
|
|
51
|
+
|
|
52
|
+
# Title block
|
|
53
|
+
title_block = QVBoxLayout()
|
|
54
|
+
title_block.setSpacing(2)
|
|
55
|
+
|
|
56
|
+
title_lbl = QLabel("PFASGroups")
|
|
57
|
+
title_lbl.setObjectName("header_title")
|
|
58
|
+
title_block.addWidget(title_lbl)
|
|
59
|
+
|
|
60
|
+
sub_lbl = QLabel(
|
|
61
|
+
"Classification · Screening · Chemical Space · Modelling"
|
|
62
|
+
)
|
|
63
|
+
sub_lbl.setObjectName("header_subtitle")
|
|
64
|
+
title_block.addWidget(sub_lbl)
|
|
65
|
+
|
|
66
|
+
layout.addLayout(title_block)
|
|
67
|
+
layout.addStretch()
|
|
68
|
+
|
|
69
|
+
version_lbl = QLabel("v3.2")
|
|
70
|
+
version_lbl.setStyleSheet(
|
|
71
|
+
f"color: {style.C_PURPLE_LIGHT}; font-size: 11px;"
|
|
72
|
+
)
|
|
73
|
+
layout.addWidget(version_lbl)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MainWindow(QMainWindow):
|
|
77
|
+
"""Application main window."""
|
|
78
|
+
|
|
79
|
+
def __init__(self):
|
|
80
|
+
super().__init__()
|
|
81
|
+
self.setWindowTitle("PFASGroups")
|
|
82
|
+
self.resize(1280, 900)
|
|
83
|
+
self.setMinimumSize(900, 650)
|
|
84
|
+
|
|
85
|
+
if _TASKBAR_LOGO_PATH.exists():
|
|
86
|
+
self.setWindowIcon(QIcon(str(_TASKBAR_LOGO_PATH)))
|
|
87
|
+
elif _LOGO_PATH.exists():
|
|
88
|
+
self.setWindowIcon(QIcon(str(_LOGO_PATH)))
|
|
89
|
+
|
|
90
|
+
# ── Central widget ─────────────────────────────────────────────────
|
|
91
|
+
central = QWidget()
|
|
92
|
+
self.setCentralWidget(central)
|
|
93
|
+
root_layout = QVBoxLayout(central)
|
|
94
|
+
root_layout.setContentsMargins(0, 0, 0, 0)
|
|
95
|
+
root_layout.setSpacing(0)
|
|
96
|
+
|
|
97
|
+
# Header
|
|
98
|
+
root_layout.addWidget(HeaderWidget())
|
|
99
|
+
|
|
100
|
+
# Tab widget
|
|
101
|
+
self._tabs = QTabWidget()
|
|
102
|
+
self._tabs.setDocumentMode(True)
|
|
103
|
+
root_layout.addWidget(self._tabs)
|
|
104
|
+
|
|
105
|
+
# ── Status bar ─────────────────────────────────────────────────────
|
|
106
|
+
self._status_bar = QStatusBar()
|
|
107
|
+
self.setStatusBar(self._status_bar)
|
|
108
|
+
self._status_bar.showMessage("Ready.")
|
|
109
|
+
|
|
110
|
+
# ── Build tabs ─────────────────────────────────────────────────────
|
|
111
|
+
self._build_tabs()
|
|
112
|
+
|
|
113
|
+
# ------------------------------------------------------------------
|
|
114
|
+
def _build_tabs(self):
|
|
115
|
+
from gui.tabs.classify_tab import ClassifyTab
|
|
116
|
+
from gui.tabs.results_tab import ResultsTab
|
|
117
|
+
from gui.tabs.definition_tester_tab import DefinitionTesterTab
|
|
118
|
+
from gui.tabs.prioritise_tab import PrioritiseTab
|
|
119
|
+
from gui.tabs.chemspace_tab import ChemSpaceTab
|
|
120
|
+
from gui.tabs.modelling_tab import ModellingTab
|
|
121
|
+
from gui.tabs.help_tab import HelpTab
|
|
122
|
+
|
|
123
|
+
self._classify_tab = ClassifyTab()
|
|
124
|
+
self._results_tab = ResultsTab()
|
|
125
|
+
self._def_tab = DefinitionTesterTab()
|
|
126
|
+
self._prior_tab = PrioritiseTab()
|
|
127
|
+
self._chem_tab = ChemSpaceTab()
|
|
128
|
+
self._model_tab = ModellingTab()
|
|
129
|
+
self._help_tab = HelpTab()
|
|
130
|
+
|
|
131
|
+
# Wire classification → results
|
|
132
|
+
self._classify_tab.classification_done.connect(self._on_classification_done)
|
|
133
|
+
|
|
134
|
+
self._tabs.addTab(self._classify_tab, "1 · Classification")
|
|
135
|
+
self._tabs.addTab(self._results_tab, "2 · Results")
|
|
136
|
+
self._tabs.addTab(self._def_tab, "3 · Definition Tester")
|
|
137
|
+
self._tabs.addTab(self._prior_tab, "4 · Prioritisation")
|
|
138
|
+
self._tabs.addTab(self._chem_tab, "5 · Chemical Space")
|
|
139
|
+
self._tabs.addTab(self._model_tab, "6 · Modelling")
|
|
140
|
+
self._tabs.addTab(self._help_tab, "7 · Help")
|
|
141
|
+
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
def _on_classification_done(self, embedding_set):
|
|
144
|
+
"""Propagate classification results to all dependent tabs."""
|
|
145
|
+
self._results_tab.set_results(embedding_set)
|
|
146
|
+
self._prior_tab.set_results(embedding_set)
|
|
147
|
+
self._chem_tab.set_results(embedding_set)
|
|
148
|
+
self._model_tab.set_results(embedding_set)
|
|
149
|
+
self._tabs.setCurrentIndex(1) # jump to Results tab
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
def show_status(self, msg: str):
|
|
153
|
+
self._status_bar.showMessage(msg)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def create_app(argv=None) -> tuple[QApplication, MainWindow]:
|
|
157
|
+
app = QApplication(argv or sys.argv)
|
|
158
|
+
app.setApplicationName("PFASGroups")
|
|
159
|
+
app.setOrganizationName("Stockholm University")
|
|
160
|
+
style.apply(app)
|
|
161
|
+
window = MainWindow()
|
|
162
|
+
return app, window
|