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.
Files changed (110) hide show
  1. dasmixer_gui-0.6.0a2/PKG-INFO +56 -0
  2. dasmixer_gui-0.6.0a2/README.md +35 -0
  3. dasmixer_gui-0.6.0a2/pyproject.toml +37 -0
  4. dasmixer_gui-0.6.0a2/src/dasmixer/gui/__init__.py +3 -0
  5. dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/__init__.py +21 -0
  6. dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/base.py +47 -0
  7. dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/ion_actions.py +343 -0
  8. dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/lfq_action.py +112 -0
  9. dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/protein_ident_action.py +113 -0
  10. dasmixer_gui-0.6.0a2/src/dasmixer/gui/actions/protein_map_action.py +150 -0
  11. dasmixer_gui-0.6.0a2/src/dasmixer/gui/app.py +712 -0
  12. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/__init__.py +16 -0
  13. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/base_plot_view.py +419 -0
  14. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/base_table_and_plot_view.py +58 -0
  15. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/base_table_view.py +738 -0
  16. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/merge_options_dialog.py +148 -0
  17. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/plotly_viewer.py +166 -0
  18. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/progress_dialog.py +68 -0
  19. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/report_form.py +272 -0
  20. dasmixer_gui-0.6.0a2/src/dasmixer/gui/components/sample_select_dialog.py +115 -0
  21. dasmixer_gui-0.6.0a2/src/dasmixer/gui/main.py +151 -0
  22. dasmixer_gui-0.6.0a2/src/dasmixer/gui/reporting/__init__.py +1 -0
  23. dasmixer_gui-0.6.0a2/src/dasmixer/gui/reporting/viewer.py +58 -0
  24. dasmixer_gui-0.6.0a2/src/dasmixer/gui/reports/__init__.py +1 -0
  25. dasmixer_gui-0.6.0a2/src/dasmixer/gui/reports/forms.py +100 -0
  26. dasmixer_gui-0.6.0a2/src/dasmixer/gui/utils.py +77 -0
  27. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/__init__.py +3 -0
  28. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/__init__.py +3 -0
  29. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/data_manager.py +77 -0
  30. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/dialogs/__init__.py +1 -0
  31. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/dialogs/assign_subset_dialog.py +81 -0
  32. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/dialogs/drop_file_dialog.py +223 -0
  33. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/manage_samples_view.py +830 -0
  34. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/mass_operations_row.py +53 -0
  35. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/sample_panel.py +286 -0
  36. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/manage_samples_view/update_row.py +74 -0
  37. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/plugins_view.py +400 -0
  38. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/project_view.py +233 -0
  39. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/settings_view.py +520 -0
  40. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/start_view.py +171 -0
  41. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/__init__.py +1 -0
  42. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/__init__.py +1 -0
  43. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/export_tab.py +52 -0
  44. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/joined_section.py +152 -0
  45. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/mgf_section.py +217 -0
  46. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/mztab_section.py +166 -0
  47. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/export/system_section.py +111 -0
  48. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/README.md +202 -0
  49. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/__init__.py +6 -0
  50. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/actions_section.py +196 -0
  51. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/base_section.py +103 -0
  52. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/base_section_old.py +89 -0
  53. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/dialogs/__init__.py +5 -0
  54. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/dialogs/load_fasta_dialog.py +175 -0
  55. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/dialogs/progress_dialog.py +236 -0
  56. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/fasta_section.py +118 -0
  57. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/ion_calculations.py +236 -0
  58. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/ion_settings_section.py +235 -0
  59. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/matching_section.py +88 -0
  60. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/peptide_ion_plot_view.py +105 -0
  61. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/peptide_ion_table_view.py +314 -0
  62. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/peptides_tab_new.py +200 -0
  63. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/search_section.py +329 -0
  64. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/shared_state.py +68 -0
  65. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides/tool_settings_section.py +571 -0
  66. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/peptides_tab.py +1038 -0
  67. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/plots/__init__.py +5 -0
  68. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/plots/plots_tab.py +558 -0
  69. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/plots/templates/plots_export.html.j2 +50 -0
  70. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/README.md +136 -0
  71. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/__init__.py +5 -0
  72. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/base_section.py +103 -0
  73. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/detection_section.py +136 -0
  74. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/enrichment_section.py +87 -0
  75. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/lfq_section.py +304 -0
  76. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/protein_concentration_plot_view.py +264 -0
  77. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/protein_identifications_table_view.py +217 -0
  78. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/protein_statistics_table_view.py +178 -0
  79. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/proteins_tab.py +221 -0
  80. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/shared_state.py +52 -0
  81. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins/table_section.py +163 -0
  82. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/proteins_tab_old.py +48 -0
  83. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/__init__.py +5 -0
  84. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/report_item.py +441 -0
  85. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/reports_tab.py +160 -0
  86. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/settings_section.py +172 -0
  87. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/reports/shared_state.py +20 -0
  88. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/README.md +147 -0
  89. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/__init__.py +5 -0
  90. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/base_section.py +83 -0
  91. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/constants.py +29 -0
  92. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/__init__.py +17 -0
  93. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/add_tool_dialog.py +154 -0
  94. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/group_dialog.py +192 -0
  95. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_mode_dialog.py +147 -0
  96. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_pattern_dialog.py +511 -0
  97. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_single_dialog.py +307 -0
  98. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/import_stacked_dialog.py +292 -0
  99. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/sample_dialog.py +165 -0
  100. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/dialogs/tool_dialog.py +210 -0
  101. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/groups_section.py +174 -0
  102. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/import_handlers.py +550 -0
  103. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/import_section.py +21 -0
  104. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/samples_section.py +959 -0
  105. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/samples_summary_section.py +144 -0
  106. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/samples_tab.py +262 -0
  107. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/shared_state.py +30 -0
  108. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples/tools_section.py +169 -0
  109. dasmixer_gui-0.6.0a2/src/dasmixer/gui/views/tabs/samples_tab.py +1445 -0
  110. 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,3 @@
1
+ """GUI package for DASMixer."""
2
+
3
+ # GUI implementation will be added in stage 3
@@ -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