dasmixer-gui 0.6.0a2__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.
- dasmixer_gui-0.6.0a2/PKG-INFO +56 -0
- dasmixer_gui-0.6.0a2/README.md +35 -0
- dasmixer_gui-0.6.0a2/pyproject.toml +37 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/__init__.py +3 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/__init__.py +21 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/base.py +47 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/ion_actions.py +343 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/lfq_action.py +112 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/protein_ident_action.py +113 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/protein_map_action.py +150 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/app.py +712 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/__init__.py +16 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/base_plot_view.py +419 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/base_table_and_plot_view.py +58 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/base_table_view.py +738 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/merge_options_dialog.py +148 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/plotly_viewer.py +166 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/progress_dialog.py +68 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/report_form.py +272 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/sample_select_dialog.py +115 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/main.py +151 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/reporting/__init__.py +1 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/reporting/viewer.py +58 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/reports/__init__.py +1 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/reports/forms.py +100 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/utils.py +77 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/__init__.py +3 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/__init__.py +3 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/data_manager.py +77 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/dialogs/__init__.py +1 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/dialogs/assign_subset_dialog.py +81 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/dialogs/drop_file_dialog.py +223 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/manage_samples_view.py +830 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/mass_operations_row.py +53 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/sample_panel.py +286 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/update_row.py +74 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/plugins_view.py +400 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/project_view.py +233 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/settings_view.py +520 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/start_view.py +171 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/__init__.py +1 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/__init__.py +1 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/export_tab.py +52 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/joined_section.py +152 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/mgf_section.py +217 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/mztab_section.py +166 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/system_section.py +111 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/README.md +202 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/__init__.py +6 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/actions_section.py +196 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/base_section.py +103 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/base_section_old.py +89 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/dialogs/__init__.py +5 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/dialogs/load_fasta_dialog.py +175 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/dialogs/progress_dialog.py +236 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/fasta_section.py +118 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/ion_calculations.py +236 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/ion_settings_section.py +235 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/matching_section.py +88 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/peptide_ion_plot_view.py +105 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/peptide_ion_table_view.py +314 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/peptides_tab_new.py +200 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/search_section.py +329 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/shared_state.py +68 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/tool_settings_section.py +571 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides_tab.py +1038 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/plots/__init__.py +5 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/plots/plots_tab.py +558 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/plots/templates/plots_export.html.j2 +50 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/README.md +136 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/__init__.py +5 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/base_section.py +103 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/detection_section.py +136 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/enrichment_section.py +87 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/lfq_section.py +304 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/protein_concentration_plot_view.py +264 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/protein_identifications_table_view.py +217 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/protein_statistics_table_view.py +178 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/proteins_tab.py +221 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/shared_state.py +52 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/table_section.py +163 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins_tab_old.py +48 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/__init__.py +5 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/report_item.py +441 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/reports_tab.py +160 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/settings_section.py +172 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/shared_state.py +20 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/README.md +147 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/__init__.py +5 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/base_section.py +83 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/constants.py +29 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/__init__.py +17 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/add_tool_dialog.py +154 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/group_dialog.py +192 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_mode_dialog.py +147 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_pattern_dialog.py +511 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_single_dialog.py +307 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_stacked_dialog.py +292 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/sample_dialog.py +165 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/tool_dialog.py +210 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/groups_section.py +174 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/import_handlers.py +550 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/import_section.py +21 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/samples_section.py +959 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/samples_summary_section.py +144 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/samples_tab.py +262 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/shared_state.py +30 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/tools_section.py +169 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples_tab.py +1445 -0
- dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples_tab_old.py +4 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dasmixer-gui
|
|
3
|
+
Version: 0.6.0a2
|
|
4
|
+
Summary: DASMixer GUI — Flet-based desktop application
|
|
5
|
+
Author: gluck
|
|
6
|
+
Author-email: glucksistemi@gmail.com
|
|
7
|
+
Requires-Python: <4.0, >=3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: dasmixer-core[all] (==0.5.0)
|
|
14
|
+
Requires-Dist: flet[all] (>=0.80.4,<0.81.0)
|
|
15
|
+
Requires-Dist: pythonnet (==3.0.5) ; sys_platform == "win32" and python_version < "3.14"
|
|
16
|
+
Requires-Dist: pythonnet (==3.1.0rc0) ; sys_platform == "win32" and python_version == "3.14"
|
|
17
|
+
Requires-Dist: pywebview (>=6.1,<7) ; sys_platform == "win32"
|
|
18
|
+
Requires-Dist: pywebview[gtk] (>=6.1,<7) ; sys_platform == "linux"
|
|
19
|
+
Project-URL: Homepage, https://github.com/protdb/dasmixer
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# DASMixer GUI
|
|
23
|
+
|
|
24
|
+
Cross-platform desktop graphical interface for comparative proteomics, built with Flet.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Project management** — create, open, configure projects
|
|
29
|
+
- **Sample management** — import spectra (MGF), identifications (PowerNovo2, MaxQuant, PLGS), proteins (FASTA)
|
|
30
|
+
- **Peptide analysis** — ion spectra visualization, PPM/coverage validation, cross-tool comparison
|
|
31
|
+
- **Protein analysis** — detection, quantification (LFQ), UniProt enrichment
|
|
32
|
+
- **Reports** — interactive PCA, Volcano, UpSet, Coverage, Sample Summary with HTML/DOCX/XLSX export
|
|
33
|
+
- **Plugins** — extend parsers and reports via `.py` files
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install dasmixer-gui
|
|
39
|
+
# or the full metapackage:
|
|
40
|
+
pip install dasmixer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Launch
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dasmixer # Open GUI
|
|
47
|
+
dasmixer path/to/project.dasmix # Open specific project
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- Python ≥ 3.11
|
|
53
|
+
- Linux (GTK), Windows
|
|
54
|
+
- For Kaleido export: Chrome (downloaded automatically on first run)
|
|
55
|
+
|
|
56
|
+
Documentation: https://github.com/protdb/dasmixer
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# DASMixer GUI
|
|
2
|
+
|
|
3
|
+
Cross-platform desktop graphical interface for comparative proteomics, built with Flet.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Project management** — create, open, configure projects
|
|
8
|
+
- **Sample management** — import spectra (MGF), identifications (PowerNovo2, MaxQuant, PLGS), proteins (FASTA)
|
|
9
|
+
- **Peptide analysis** — ion spectra visualization, PPM/coverage validation, cross-tool comparison
|
|
10
|
+
- **Protein analysis** — detection, quantification (LFQ), UniProt enrichment
|
|
11
|
+
- **Reports** — interactive PCA, Volcano, UpSet, Coverage, Sample Summary with HTML/DOCX/XLSX export
|
|
12
|
+
- **Plugins** — extend parsers and reports via `.py` files
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install dasmixer-gui
|
|
18
|
+
# or the full metapackage:
|
|
19
|
+
pip install dasmixer
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Launch
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
dasmixer # Open GUI
|
|
26
|
+
dasmixer path/to/project.dasmix # Open specific project
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Python ≥ 3.11
|
|
32
|
+
- Linux (GTK), Windows
|
|
33
|
+
- For Kaleido export: Chrome (downloaded automatically on first run)
|
|
34
|
+
|
|
35
|
+
Documentation: https://github.com/protdb/dasmixer
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dasmixer-gui"
|
|
3
|
+
version = "0.6.0a2"
|
|
4
|
+
description = "DASMixer GUI — Flet-based desktop application"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "gluck", email = "glucksistemi@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = "<4.0, >=3.11"
|
|
10
|
+
urls = {Homepage = "https://github.com/protdb/dasmixer"}
|
|
11
|
+
dependencies = [
|
|
12
|
+
"dasmixer-core[all] ==0.5.0",
|
|
13
|
+
"flet[all] >=0.80.4,<0.81.0",
|
|
14
|
+
"pywebview[gtk] >=6.1,<7 ; sys_platform == 'linux'",
|
|
15
|
+
"pywebview >=6.1,<7 ; sys_platform == 'win32'",
|
|
16
|
+
"pythonnet ==3.1.0rc0 ; sys_platform == 'win32' and python_version >= '3.14' and python_version < '3.15'",
|
|
17
|
+
"pythonnet ==3.0.5 ; sys_platform == 'win32' and python_version < '3.14'",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
dasmixer = "dasmixer.gui.main:app"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
25
|
+
build-backend = "poetry.core.masonry.api"
|
|
26
|
+
|
|
27
|
+
[tool.poetry]
|
|
28
|
+
packages = [
|
|
29
|
+
{include = "dasmixer/gui", from = "src"},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.poetry.dependencies]
|
|
33
|
+
dasmixer-core = {path = "../dasmixer-core", develop = true}
|
|
34
|
+
pythonnet = [
|
|
35
|
+
{version = "==3.1.0rc0", python = ">=3.14,<3.15", markers = "sys_platform == 'win32'", allow-prereleases = true},
|
|
36
|
+
{version = "==3.0.5", python = ">=3.11,<3.14", markers = "sys_platform == 'win32'"},
|
|
37
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GUI action classes for DASMixer calculation pipelines.
|
|
3
|
+
|
|
4
|
+
Each action class wraps a calculation pipeline and provides:
|
|
5
|
+
- Progress dialog management
|
|
6
|
+
- Error/success snackbar messages
|
|
7
|
+
- Optional sample_id filtering for per-sample operations
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .ion_actions import IonCoverageAction, SelectPreferredAction
|
|
11
|
+
from .protein_map_action import MatchProteinsAction
|
|
12
|
+
from .protein_ident_action import ProteinIdentificationsAction
|
|
13
|
+
from .lfq_action import LFQAction
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'IonCoverageAction',
|
|
17
|
+
'SelectPreferredAction',
|
|
18
|
+
'MatchProteinsAction',
|
|
19
|
+
'ProteinIdentificationsAction',
|
|
20
|
+
'LFQAction',
|
|
21
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Base class for all GUI action handlers."""
|
|
2
|
+
|
|
3
|
+
import flet as ft
|
|
4
|
+
|
|
5
|
+
from dasmixer.api.project.project import Project
|
|
6
|
+
from dasmixer.gui.views.tabs.peptides.dialogs.progress_dialog import ProgressDialog
|
|
7
|
+
from dasmixer.gui.utils import show_snack
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseAction:
|
|
11
|
+
"""
|
|
12
|
+
Base class for calculation action handlers.
|
|
13
|
+
|
|
14
|
+
Provides helpers for progress dialogs and snackbar messages.
|
|
15
|
+
All concrete action classes inherit from this.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, project: Project, page: ft.Page):
|
|
19
|
+
self.project = project
|
|
20
|
+
self.page = page
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------
|
|
23
|
+
# Snackbar helpers
|
|
24
|
+
# ------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
def show_error(self, message: str) -> None:
|
|
27
|
+
show_snack(self.page, message, ft.Colors.RED_400)
|
|
28
|
+
self.page.update()
|
|
29
|
+
|
|
30
|
+
def show_success(self, message: str) -> None:
|
|
31
|
+
show_snack(self.page, message, ft.Colors.GREEN_400)
|
|
32
|
+
self.page.update()
|
|
33
|
+
|
|
34
|
+
def show_warning(self, message: str) -> None:
|
|
35
|
+
show_snack(self.page, message, ft.Colors.ORANGE_400)
|
|
36
|
+
self.page.update()
|
|
37
|
+
|
|
38
|
+
def show_info(self, message: str) -> None:
|
|
39
|
+
show_snack(self.page, message, ft.Colors.BLUE_400)
|
|
40
|
+
self.page.update()
|
|
41
|
+
|
|
42
|
+
# ------------------------------------------------------------------
|
|
43
|
+
# Progress dialog helpers
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
def make_progress_dialog(self, title: str, stoppable: bool = False) -> ProgressDialog:
|
|
47
|
+
return ProgressDialog(self.page, title, stoppable=stoppable)
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""Ion coverage and preferred identification selection actions."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
7
|
+
|
|
8
|
+
import flet as ft
|
|
9
|
+
|
|
10
|
+
from dasmixer.utils import logger
|
|
11
|
+
from dasmixer.api.project.project import Project
|
|
12
|
+
from dasmixer.api.config import config as _config
|
|
13
|
+
from dasmixer.api.calculations.spectra.ion_match import IonMatchParameters
|
|
14
|
+
from dasmixer.api.calculations.spectra.coverage_worker import process_peptide_match_batch
|
|
15
|
+
from dasmixer.api.calculations.spectra.identification_processor import process_identificatons_batch
|
|
16
|
+
from dasmixer.api.calculations.peptides.matching import calculate_preferred_identifications_for_file
|
|
17
|
+
from dasmixer.gui.views.tabs.peptides.shared_state import PeptidesTabState
|
|
18
|
+
from .base import BaseAction
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class IonCoverageAction(BaseAction):
|
|
22
|
+
"""
|
|
23
|
+
Calculate ion coverage + PPM + theor_mass + override_charge for identifications.
|
|
24
|
+
|
|
25
|
+
Extracted from IonCalculations.run_coverage_calc().
|
|
26
|
+
Supports optional per-sample filtering via spectra_file_ids.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, project: Project, page: ft.Page):
|
|
30
|
+
super().__init__(project, page)
|
|
31
|
+
|
|
32
|
+
async def run(
|
|
33
|
+
self,
|
|
34
|
+
state: PeptidesTabState,
|
|
35
|
+
recalc_all: bool = False,
|
|
36
|
+
sample_id: int | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Run ion coverage calculation.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
state: PeptidesTabState with ion settings.
|
|
43
|
+
recalc_all: If True, recalculate all; otherwise only missing.
|
|
44
|
+
sample_id: If provided, only process identifications for this sample.
|
|
45
|
+
"""
|
|
46
|
+
# Get batch size and worker count from config
|
|
47
|
+
batch_size = _config.identification_processing_batch_size
|
|
48
|
+
worker_count = _config.max_cpu_threads or max(1, (os.cpu_count() or 2) - 1)
|
|
49
|
+
|
|
50
|
+
# Persist current ion settings
|
|
51
|
+
if self.page and hasattr(self.page, 'peptides_tab'):
|
|
52
|
+
ion_section = self.page.peptides_tab.sections.get('ion_settings')
|
|
53
|
+
if ion_section and hasattr(ion_section, 'save_settings'):
|
|
54
|
+
await ion_section.save_settings()
|
|
55
|
+
|
|
56
|
+
params = IonMatchParameters(
|
|
57
|
+
ions=state.ion_types,
|
|
58
|
+
tolerance=state.ion_ppm_threshold,
|
|
59
|
+
mode='largest',
|
|
60
|
+
water_loss=state.water_loss,
|
|
61
|
+
ammonia_loss=state.nh3_loss,
|
|
62
|
+
charges=state.fragment_charges
|
|
63
|
+
)
|
|
64
|
+
params_dict = {
|
|
65
|
+
'ions': params.ions,
|
|
66
|
+
'tolerance': params.tolerance,
|
|
67
|
+
'mode': params.mode,
|
|
68
|
+
'water_loss': params.water_loss,
|
|
69
|
+
'ammonia_loss': params.ammonia_loss,
|
|
70
|
+
}
|
|
71
|
+
fragment_charges = list(state.fragment_charges)
|
|
72
|
+
target_ppm = state.ion_ppm_threshold
|
|
73
|
+
min_charge = state.min_precursor_charge
|
|
74
|
+
max_charge = state.max_precursor_charge
|
|
75
|
+
force_isotope_offset = state.force_isotope_offset
|
|
76
|
+
max_isotope_offset = state.max_isotope_offset
|
|
77
|
+
seq_criteria = state.seq_criteria
|
|
78
|
+
|
|
79
|
+
tool_ids = list(state.tool_settings_controls.keys())
|
|
80
|
+
if not tool_ids:
|
|
81
|
+
self.show_warning("No tools configured")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Per-tool PTM settings
|
|
85
|
+
max_ptm_sites = state.max_ptm_sites
|
|
86
|
+
|
|
87
|
+
tool_settings_map = {}
|
|
88
|
+
for tid, controls in state.tool_settings_controls.items():
|
|
89
|
+
ptm_selected: list[str] = controls.get('ptm_selected', [])
|
|
90
|
+
from dasmixer.utils.seqfixer_utils import PTMS as _ALL_PTMS
|
|
91
|
+
all_codes = {p.code for p in _ALL_PTMS}
|
|
92
|
+
ptm_list = None if set(ptm_selected) == all_codes else ptm_selected
|
|
93
|
+
max_ptm_ctrl = controls.get('max_ptm')
|
|
94
|
+
try:
|
|
95
|
+
max_ptm = int(max_ptm_ctrl.value) if max_ptm_ctrl else 5
|
|
96
|
+
except (ValueError, AttributeError):
|
|
97
|
+
max_ptm = 5
|
|
98
|
+
tool_settings_map[tid] = {'ptm_list': ptm_list, 'max_ptm': max_ptm}
|
|
99
|
+
|
|
100
|
+
only_missing = not recalc_all
|
|
101
|
+
|
|
102
|
+
# Resolve spectra_file_ids for sample filter
|
|
103
|
+
spectra_file_ids: list[int] | None = None
|
|
104
|
+
if sample_id is not None:
|
|
105
|
+
sf_df = await self.project.get_spectra_files(sample_id=sample_id)
|
|
106
|
+
if sf_df.empty:
|
|
107
|
+
self.show_warning(f"No spectra files for sample id={sample_id}")
|
|
108
|
+
return
|
|
109
|
+
spectra_file_ids = list(sf_df['id'].astype(int))
|
|
110
|
+
|
|
111
|
+
from dasmixer.gui.views.tabs.peptides.dialogs.progress_dialog import ProgressDialog
|
|
112
|
+
dialog = ProgressDialog(self.page, "Calculating Ion Coverage", stoppable=True)
|
|
113
|
+
dialog.show()
|
|
114
|
+
dialog.update_progress(None, "Preparing...", "Counting identifications...")
|
|
115
|
+
|
|
116
|
+
# Count after dialog is visible so the UI doesn't appear frozen
|
|
117
|
+
total_count = 0
|
|
118
|
+
for tool_id in tool_ids:
|
|
119
|
+
total_count += await self.project.get_identifications_count(
|
|
120
|
+
tool_id=tool_id,
|
|
121
|
+
only_missing=only_missing,
|
|
122
|
+
spectra_file_ids=spectra_file_ids,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
total_processed = 0
|
|
126
|
+
chunk_size = max(1, math.ceil(batch_size / worker_count))
|
|
127
|
+
stopped_early = False
|
|
128
|
+
|
|
129
|
+
async def _compute_batch(
|
|
130
|
+
loop, executor, worker_batch, ptm_list, max_ptm
|
|
131
|
+
) -> list:
|
|
132
|
+
"""Submit worker_batch to the process pool and gather results."""
|
|
133
|
+
sub_batches = [
|
|
134
|
+
worker_batch[i:i + chunk_size]
|
|
135
|
+
for i in range(0, len(worker_batch), chunk_size)
|
|
136
|
+
]
|
|
137
|
+
futures = [
|
|
138
|
+
loop.run_in_executor(
|
|
139
|
+
executor,
|
|
140
|
+
process_identificatons_batch,
|
|
141
|
+
sub_batch,
|
|
142
|
+
params_dict,
|
|
143
|
+
fragment_charges,
|
|
144
|
+
target_ppm,
|
|
145
|
+
min_charge,
|
|
146
|
+
max_charge,
|
|
147
|
+
max_isotope_offset,
|
|
148
|
+
force_isotope_offset,
|
|
149
|
+
ptm_list,
|
|
150
|
+
max_ptm,
|
|
151
|
+
seq_criteria,
|
|
152
|
+
max_ptm_sites,
|
|
153
|
+
)
|
|
154
|
+
for sub_batch in sub_batches
|
|
155
|
+
]
|
|
156
|
+
sub_results = await asyncio.gather(*futures)
|
|
157
|
+
return [item for sub in sub_results for item in sub]
|
|
158
|
+
|
|
159
|
+
async def _write_and_commit(results: list) -> None:
|
|
160
|
+
"""Write a computed batch to DB and commit without touching modified_at."""
|
|
161
|
+
await self.project.put_identification_data_batch(results)
|
|
162
|
+
await self.project._commit()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
loop = asyncio.get_event_loop()
|
|
166
|
+
with ProcessPoolExecutor(max_workers=worker_count) as executor:
|
|
167
|
+
for tool_id in tool_ids:
|
|
168
|
+
if stopped_early:
|
|
169
|
+
break
|
|
170
|
+
t_settings = tool_settings_map.get(tool_id, {})
|
|
171
|
+
ptm_list = t_settings.get('ptm_list', None)
|
|
172
|
+
max_ptm = t_settings.get('max_ptm', 5)
|
|
173
|
+
|
|
174
|
+
offset = 0
|
|
175
|
+
|
|
176
|
+
# --- Prime the pipeline: read and compute the first batch ---
|
|
177
|
+
dialog.update_progress(None, "Loading...", "Reading first batch...")
|
|
178
|
+
batch_objects = await self.project.get_identifications_with_spectra_batch(
|
|
179
|
+
tool_id=tool_id,
|
|
180
|
+
offset=offset,
|
|
181
|
+
limit=batch_size,
|
|
182
|
+
only_missing=only_missing,
|
|
183
|
+
spectra_file_ids=spectra_file_ids,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if not batch_objects:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
worker_batch = [obj.to_worker_dict() for obj in batch_objects]
|
|
190
|
+
del batch_objects # free spectrum arrays from memory
|
|
191
|
+
offset += batch_size
|
|
192
|
+
|
|
193
|
+
pending_results = await _compute_batch(
|
|
194
|
+
loop, executor, worker_batch, ptm_list, max_ptm
|
|
195
|
+
)
|
|
196
|
+
del worker_batch
|
|
197
|
+
|
|
198
|
+
while True:
|
|
199
|
+
# --- Overlap: write previous results AND read next batch in parallel ---
|
|
200
|
+
next_read_task = asyncio.create_task(
|
|
201
|
+
self.project.get_identifications_with_spectra_batch(
|
|
202
|
+
tool_id=tool_id,
|
|
203
|
+
offset=offset,
|
|
204
|
+
limit=batch_size,
|
|
205
|
+
only_missing=only_missing,
|
|
206
|
+
spectra_file_ids=spectra_file_ids,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
write_task = asyncio.create_task(
|
|
210
|
+
_write_and_commit(pending_results)
|
|
211
|
+
)
|
|
212
|
+
next_batch_objects, _ = await asyncio.gather(
|
|
213
|
+
next_read_task, write_task
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
total_processed += len(pending_results)
|
|
217
|
+
del pending_results
|
|
218
|
+
offset += batch_size
|
|
219
|
+
|
|
220
|
+
progress_value = (total_processed / total_count) if total_count > 0 else None
|
|
221
|
+
dialog.update_progress(
|
|
222
|
+
progress_value,
|
|
223
|
+
"Calculating...",
|
|
224
|
+
f"Processed {total_processed} / {total_count}",
|
|
225
|
+
processed=total_processed,
|
|
226
|
+
total=total_count,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if dialog.stop_requested:
|
|
230
|
+
stopped_early = True
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
if not next_batch_objects:
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
# --- Compute next batch while loop iterates ---
|
|
237
|
+
next_worker_batch = [obj.to_worker_dict() for obj in next_batch_objects]
|
|
238
|
+
del next_batch_objects # free spectrum arrays from memory
|
|
239
|
+
pending_results = await _compute_batch(
|
|
240
|
+
loop, executor, next_worker_batch, ptm_list, max_ptm
|
|
241
|
+
)
|
|
242
|
+
del next_worker_batch
|
|
243
|
+
|
|
244
|
+
await self.project.save()
|
|
245
|
+
|
|
246
|
+
if stopped_early:
|
|
247
|
+
dialog.complete(f"Stopped: {total_processed} / {total_count} processed")
|
|
248
|
+
else:
|
|
249
|
+
dialog.complete(f"Done: {total_processed} identifications")
|
|
250
|
+
await asyncio.sleep(1)
|
|
251
|
+
dialog.close()
|
|
252
|
+
|
|
253
|
+
self.show_success(f"Ion coverage calculated for {total_processed} identifications")
|
|
254
|
+
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
logger.exception(ex)
|
|
257
|
+
try:
|
|
258
|
+
dialog.close()
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
self.show_error(f"Error: {exc}")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class SelectPreferredAction(BaseAction):
|
|
265
|
+
"""
|
|
266
|
+
Select preferred identifications for spectra files.
|
|
267
|
+
|
|
268
|
+
Extracted from MatchingSection.run_matching_internal().
|
|
269
|
+
Supports optional per-sample filtering.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(self, project: Project, page: ft.Page):
|
|
273
|
+
super().__init__(project, page)
|
|
274
|
+
|
|
275
|
+
async def run(
|
|
276
|
+
self,
|
|
277
|
+
tool_settings: dict,
|
|
278
|
+
criterion: str = 'intensity',
|
|
279
|
+
sample_id: int | None = None,
|
|
280
|
+
) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Run preferred identification selection.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
tool_settings: Tool-specific settings dict (from ToolSettingsSection).
|
|
286
|
+
criterion: 'ppm' or 'intensity'.
|
|
287
|
+
sample_id: If provided, only process files of this sample.
|
|
288
|
+
"""
|
|
289
|
+
if not tool_settings:
|
|
290
|
+
self.show_warning("No tools configured")
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
from dasmixer.gui.views.tabs.peptides.dialogs.progress_dialog import ProgressDialog
|
|
294
|
+
from pathlib import Path
|
|
295
|
+
|
|
296
|
+
dialog = ProgressDialog(self.page, "Running Identification Matching")
|
|
297
|
+
dialog.show()
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
spectre_files = await self.project.get_spectra_files(
|
|
301
|
+
sample_id=sample_id if sample_id is not None else None
|
|
302
|
+
)
|
|
303
|
+
progress = 0.0
|
|
304
|
+
processed_files = 0
|
|
305
|
+
total_files = len(spectre_files)
|
|
306
|
+
progress_step = round(1 / total_files, 3) if total_files > 0 else 1.0
|
|
307
|
+
|
|
308
|
+
for _, spectra_file in spectre_files.iterrows():
|
|
309
|
+
file_name = Path(spectra_file['path']).name
|
|
310
|
+
dialog.update_progress(
|
|
311
|
+
progress,
|
|
312
|
+
f"Processing {file_name} ({processed_files + 1}/{total_files})..."
|
|
313
|
+
)
|
|
314
|
+
idents = await calculate_preferred_identifications_for_file(
|
|
315
|
+
self.project,
|
|
316
|
+
spectra_file['id'],
|
|
317
|
+
criterion,
|
|
318
|
+
tool_settings,
|
|
319
|
+
)
|
|
320
|
+
dialog.update_progress(
|
|
321
|
+
progress,
|
|
322
|
+
f"Saving {file_name} ({processed_files + 1}/{total_files})..."
|
|
323
|
+
)
|
|
324
|
+
await self.project.set_preferred_identifications_for_file(
|
|
325
|
+
spectra_file['id'],
|
|
326
|
+
idents,
|
|
327
|
+
)
|
|
328
|
+
progress += progress_step
|
|
329
|
+
processed_files += 1
|
|
330
|
+
|
|
331
|
+
dialog.complete(f"Completed {processed_files}/{total_files}!")
|
|
332
|
+
await asyncio.sleep(0.5)
|
|
333
|
+
dialog.close()
|
|
334
|
+
|
|
335
|
+
self.show_success(f"Processed {processed_files} spectra files!")
|
|
336
|
+
|
|
337
|
+
except Exception as ex:
|
|
338
|
+
logger.exception(ex)
|
|
339
|
+
try:
|
|
340
|
+
dialog.close()
|
|
341
|
+
except Exception:
|
|
342
|
+
pass
|
|
343
|
+
self.show_error(f"Error: {str(ex)}")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Label-Free Quantification action."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
import flet as ft
|
|
6
|
+
|
|
7
|
+
from dasmixer.utils import logger
|
|
8
|
+
from dasmixer.api.project.project import Project
|
|
9
|
+
from dasmixer.api.calculations.proteins.lfq import calculate_lfq
|
|
10
|
+
from dasmixer.gui.views.tabs.proteins.shared_state import ProteinsTabState
|
|
11
|
+
from .base import BaseAction
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LFQAction(BaseAction):
|
|
15
|
+
"""
|
|
16
|
+
Calculate Label-Free Quantification for protein identifications.
|
|
17
|
+
|
|
18
|
+
Extracted from LFQSection.calculate_lfq().
|
|
19
|
+
Supports optional per-sample calculation.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, project: Project, page: ft.Page):
|
|
23
|
+
super().__init__(project, page)
|
|
24
|
+
|
|
25
|
+
async def run(
|
|
26
|
+
self,
|
|
27
|
+
state: ProteinsTabState,
|
|
28
|
+
sample_id: int | None = None,
|
|
29
|
+
) -> int:
|
|
30
|
+
"""
|
|
31
|
+
Run LFQ calculation.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
state: ProteinsTabState with LFQ parameters.
|
|
35
|
+
sample_id: If provided, only calculate for this sample.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Total number of quantification records saved.
|
|
39
|
+
"""
|
|
40
|
+
selected_methods = state.get_selected_lfq_methods()
|
|
41
|
+
if not selected_methods:
|
|
42
|
+
self.show_warning("No LFQ methods selected. Configure in Proteins tab.")
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
from dasmixer.gui.views.tabs.peptides.dialogs.progress_dialog import ProgressDialog
|
|
46
|
+
|
|
47
|
+
dialog = ProgressDialog(self.page, "Calculating LFQ")
|
|
48
|
+
dialog.show()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# Clear old results (per sample or globally)
|
|
52
|
+
dialog.update_progress(None, "Clearing old quantifications...")
|
|
53
|
+
if sample_id is not None:
|
|
54
|
+
await self.project.clear_protein_quantifications_for_sample(sample_id)
|
|
55
|
+
else:
|
|
56
|
+
await self.project.clear_protein_quantifications()
|
|
57
|
+
|
|
58
|
+
# Determine which samples to process
|
|
59
|
+
dialog.update_progress(None, "Loading samples...")
|
|
60
|
+
if sample_id is not None:
|
|
61
|
+
samples_df = await self.project.execute_query_df(
|
|
62
|
+
"SELECT id FROM sample WHERE id = ?",
|
|
63
|
+
(int(sample_id),)
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
samples_df = await self.project.execute_query_df(
|
|
67
|
+
"SELECT DISTINCT id FROM sample ORDER BY id"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if len(samples_df) == 0:
|
|
71
|
+
dialog.close()
|
|
72
|
+
self.show_warning("No samples found")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
total_samples = len(samples_df)
|
|
76
|
+
total_saved = 0
|
|
77
|
+
|
|
78
|
+
for idx, row in samples_df.iterrows():
|
|
79
|
+
s_id = row['id']
|
|
80
|
+
dialog.update_progress(
|
|
81
|
+
(idx + 1) / total_samples,
|
|
82
|
+
f"Processing sample {idx + 1} of {total_samples}"
|
|
83
|
+
)
|
|
84
|
+
result_df = await calculate_lfq(
|
|
85
|
+
project=self.project,
|
|
86
|
+
sample_id=s_id,
|
|
87
|
+
methods=selected_methods,
|
|
88
|
+
enzyme=state.enzyme,
|
|
89
|
+
min_length=state.min_peptide_length,
|
|
90
|
+
max_length=state.max_peptide_length,
|
|
91
|
+
max_cleavage_sites=state.max_cleavage_sites,
|
|
92
|
+
empai_base=state.empai_base_value,
|
|
93
|
+
)
|
|
94
|
+
if len(result_df) > 0:
|
|
95
|
+
await self.project.add_protein_quantifications_batch(result_df)
|
|
96
|
+
total_saved += len(result_df)
|
|
97
|
+
|
|
98
|
+
dialog.complete()
|
|
99
|
+
await asyncio.sleep(1)
|
|
100
|
+
dialog.close()
|
|
101
|
+
|
|
102
|
+
self.show_success(f"LFQ calculated: {total_saved} quantifications")
|
|
103
|
+
return total_saved
|
|
104
|
+
|
|
105
|
+
except Exception as ex:
|
|
106
|
+
logger.exception(ex)
|
|
107
|
+
try:
|
|
108
|
+
dialog.close()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
self.show_error(f"Error: {str(ex)}")
|
|
112
|
+
return 0
|