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.
Files changed (80) hide show
  1. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/HalogenGroupModel.py +3 -4
  2. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/PFASEmbeddings.py +1 -1
  3. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/cli.py +2 -2
  4. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/Halogen_groups_smarts.json +6 -6
  5. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/getter.py +1 -1
  6. {pfasgroups-3.2.2 → pfasgroups-3.3.1/PFASGroups.egg-info}/PKG-INFO +32 -3
  7. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/SOURCES.txt +21 -0
  8. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/entry_points.txt +1 -0
  9. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/requires.txt +8 -0
  10. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/top_level.txt +1 -0
  11. {pfasgroups-3.2.2/PFASGroups.egg-info → pfasgroups-3.3.1}/PKG-INFO +32 -3
  12. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/README.md +24 -2
  13. pfasgroups-3.3.1/gui/__init__.py +1 -0
  14. pfasgroups-3.3.1/gui/__main__.py +21 -0
  15. pfasgroups-3.3.1/gui/app.py +162 -0
  16. pfasgroups-3.3.1/gui/data/help.html +276 -0
  17. pfasgroups-3.3.1/gui/style.py +407 -0
  18. pfasgroups-3.3.1/gui/tabs/__init__.py +1 -0
  19. pfasgroups-3.3.1/gui/tabs/chemspace_tab.py +250 -0
  20. pfasgroups-3.3.1/gui/tabs/classify_tab.py +823 -0
  21. pfasgroups-3.3.1/gui/tabs/definition_tester_tab.py +334 -0
  22. pfasgroups-3.3.1/gui/tabs/help_tab.py +97 -0
  23. pfasgroups-3.3.1/gui/tabs/modelling_tab.py +489 -0
  24. pfasgroups-3.3.1/gui/tabs/prioritise_tab.py +303 -0
  25. pfasgroups-3.3.1/gui/tabs/results_tab.py +350 -0
  26. pfasgroups-3.3.1/gui/utils/__init__.py +1 -0
  27. pfasgroups-3.3.1/gui/utils/chemspace.py +170 -0
  28. pfasgroups-3.3.1/gui/utils/export_dialog.py +742 -0
  29. pfasgroups-3.3.1/gui/utils/fingerprints.py +91 -0
  30. pfasgroups-3.3.1/gui/utils/io_readers.py +198 -0
  31. pfasgroups-3.3.1/gui/utils/modelling.py +176 -0
  32. pfasgroups-3.3.1/gui/utils/mol_renderer.py +69 -0
  33. pfasgroups-3.3.1/gui/workers.py +362 -0
  34. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/pyproject.toml +12 -2
  35. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/setup.cfg +1 -1
  36. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_bde_resistance.py +0 -1
  37. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_prioritise.py +19 -7
  38. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_readme_and_docs_examples.py +1 -1
  39. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/HalogenGroups/__init__.py +0 -0
  40. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/ComponentsSolverModel.py +0 -0
  41. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/PFASDefinitionModel.py +0 -0
  42. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/__init__.py +0 -0
  43. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/core.py +0 -0
  44. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/PFAS_definitions_smarts.json +0 -0
  45. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/component_smarts.json +0 -0
  46. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/component_smarts_halogens.json +0 -0
  47. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/data/diatomic_bonds_dict.json +0 -0
  48. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/draw_mols.py +0 -0
  49. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/embeddings.py +0 -0
  50. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/fragmentation.py +0 -0
  51. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/generate_homologues.py +0 -0
  52. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/generate_mol.py +0 -0
  53. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/generate_paper_figures.py +0 -0
  54. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/homologue_series.py +0 -0
  55. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/parser.py +0 -0
  56. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups/prioritise.py +0 -0
  57. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/PFASGroups.egg-info/dependency_links.txt +0 -0
  58. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_component_fractions.py +0 -0
  59. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_component_ratios.py +0 -0
  60. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_comprehensive_metrics.py +0 -0
  61. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_database_integration.py +0 -0
  62. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_definition_comparison.py +0 -0
  63. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_fingerprints_vs_txppfas.py +0 -0
  64. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_fraction_analysis.py +0 -0
  65. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_generate_homologues.py +0 -0
  66. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_halogen_groups_smarts.py +0 -0
  67. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_linker_smarts.py +0 -0
  68. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_metrics.py +0 -0
  69. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_metrics_detailed.py +0 -0
  70. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_n_spacer_ring_size.py +0 -0
  71. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_pfasstructv5.py +0 -0
  72. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_results_fingerprint.py +0 -0
  73. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_results_model.py +0 -0
  74. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_results_sql.py +0 -0
  75. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_run_groups_definitions_tests.py +0 -0
  76. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_smarts_atom_count.py +0 -0
  77. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_smarts_matching.py +0 -0
  78. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_smarts_precompute.py +0 -0
  79. {pfasgroups-3.2.2 → pfasgroups-3.3.1}/tests/test_summary.py +0 -0
  80. {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 all path types
582
- all_components = list(set([comp for comps in augmented_matched_components for comp in comps]))
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(all_components), augmented_matched_components
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({51, 52})
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 get_PFASGroups() in Python to extend defaults.
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 get_PFASGroups)'
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": "thia keto propanoic acid",
4262
- "alias": "Thia keto propanoic acid",
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": "thia keto propanoic acids",
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 thia keto propanoic acid",
7875
- "alias": "telomer thia keto propanoic acid",
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": "thia keto propanoic acids",
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 PFASgroups import get_compiled_HalogenGroups, HalogenGroup, parse_smiles
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.2.2
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
- ### From Pypi
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 PFASgroups import parse_smiles
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,3 +1,4 @@
1
1
  [console_scripts]
2
2
  halogengroups = PFASGroups.cli:main_halogen
3
3
  pfasgroups = PFASGroups.cli:main
4
+ pfasgroups-gui = gui.__main__:main
@@ -13,3 +13,11 @@ sqlalchemy>=1.4.0
13
13
  [dev]
14
14
  pytest
15
15
  pytest-cov
16
+
17
+ [gui]
18
+ PySide6>=6.4
19
+ plotly>=5.0
20
+ umap-learn>=0.5
21
+ openpyxl>=3.0
22
+ sqlalchemy>=1.4.0
23
+ matplotlib>=3.5
@@ -1,2 +1,3 @@
1
1
  HalogenGroups
2
2
  PFASGroups
3
+ gui
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PFASGroups
3
- Version: 3.2.2
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
- ### From Pypi
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 PFASgroups import parse_smiles
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
- ### From Pypi
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 PFASgroups import parse_smiles
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