molass-gui 0.1.0__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.
- molass_gui-0.1.0/.github/workflows/upload_to_pypi.yml +42 -0
- molass_gui-0.1.0/.gitignore +7 -0
- molass_gui-0.1.0/PKG-INFO +54 -0
- molass_gui-0.1.0/README.md +37 -0
- molass_gui-0.1.0/app.py +5 -0
- molass_gui-0.1.0/molass_gui/__init__.py +1 -0
- molass_gui-0.1.0/molass_gui/app.py +82 -0
- molass_gui-0.1.0/molass_gui/naive_view.py +63 -0
- molass_gui-0.1.0/molass_gui/quick_view.py +118 -0
- molass_gui-0.1.0/molass_gui/rigorous_view.py +352 -0
- molass_gui-0.1.0/molass_gui/upgraded_view.py +95 -0
- molass_gui-0.1.0/pyproject.toml +39 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Manual Upload Python Package to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
pypi-publish:
|
|
8
|
+
name: upload release to PyPI
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.x'
|
|
22
|
+
|
|
23
|
+
- name: Install build
|
|
24
|
+
run: pip install build
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: python -m build
|
|
28
|
+
|
|
29
|
+
- name: Publish package distributions to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
31
|
+
|
|
32
|
+
- name: Set up git for pushing
|
|
33
|
+
run: |
|
|
34
|
+
git config --global user.name "github-actions[bot]"
|
|
35
|
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
36
|
+
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
|
37
|
+
|
|
38
|
+
- name: Create and push version tag
|
|
39
|
+
run: |
|
|
40
|
+
VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/version *= *//;s/"//g')
|
|
41
|
+
git tag "v$VERSION"
|
|
42
|
+
git push origin "v$VERSION"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: molass_gui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tkinter GUI for Molass — initial estimate workflow
|
|
5
|
+
Project-URL: Repository, https://github.com/biosaxs-dev/molass-gui
|
|
6
|
+
Author: Molass Community
|
|
7
|
+
Maintainer: Molass Community
|
|
8
|
+
License: GNU General Public License v3.0
|
|
9
|
+
Keywords: SEC-SAXS
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Requires-Python: <3.15,>=3.9
|
|
14
|
+
Requires-Dist: molass-legacy>=1.6.13
|
|
15
|
+
Requires-Dist: molass>=1.0.5
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# molass-gui
|
|
19
|
+
|
|
20
|
+
Tkinter GUI for [Molass](https://github.com/biosaxs-dev/molass-library) — progressive SEC-SAXS analysis workflow.
|
|
21
|
+
|
|
22
|
+
## Five-phase workflow
|
|
23
|
+
|
|
24
|
+
| Phase | Window | What happens |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| 1 Setup | Main window | Enter the data folder path |
|
|
27
|
+
| 2 Naive View | `plot_compact` with baseline | Inspect raw data; enter number of components; click **Decompose** |
|
|
28
|
+
| 3 Quick Optimization View | `plot_components` (EGH) | Review EGH decomposition; select column model; click **Upgrade** or **Skip** |
|
|
29
|
+
| 4 Upgraded View | `plot_components` (column model) | Review upgraded decomposition; select method (BH/DE) and subprocess option; click **Rigorous Optimization…** |
|
|
30
|
+
| 5 Rigorous Optimization View | 4-panel live monitor | Rg curve computed → initial score drawn → optimization starts automatically; **Terminate** button available |
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install molass-gui
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
molass-gui
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or during development (from the repository root):
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
py app.py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- Python 3.9–3.14
|
|
53
|
+
- [molass](https://pypi.org/project/molass/) ≥ 1.0.5
|
|
54
|
+
- [molass_legacy](https://pypi.org/project/molass_legacy/) ≥ 1.6.13
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# molass-gui
|
|
2
|
+
|
|
3
|
+
Tkinter GUI for [Molass](https://github.com/biosaxs-dev/molass-library) — progressive SEC-SAXS analysis workflow.
|
|
4
|
+
|
|
5
|
+
## Five-phase workflow
|
|
6
|
+
|
|
7
|
+
| Phase | Window | What happens |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 1 Setup | Main window | Enter the data folder path |
|
|
10
|
+
| 2 Naive View | `plot_compact` with baseline | Inspect raw data; enter number of components; click **Decompose** |
|
|
11
|
+
| 3 Quick Optimization View | `plot_components` (EGH) | Review EGH decomposition; select column model; click **Upgrade** or **Skip** |
|
|
12
|
+
| 4 Upgraded View | `plot_components` (column model) | Review upgraded decomposition; select method (BH/DE) and subprocess option; click **Rigorous Optimization…** |
|
|
13
|
+
| 5 Rigorous Optimization View | 4-panel live monitor | Rg curve computed → initial score drawn → optimization starts automatically; **Terminate** button available |
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pip install molass-gui
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
molass-gui
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or during development (from the repository root):
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
py app.py
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- Python 3.9–3.14
|
|
36
|
+
- [molass](https://pypi.org/project/molass/) ≥ 1.0.5
|
|
37
|
+
- [molass_legacy](https://pypi.org/project/molass_legacy/) ≥ 1.6.13
|
molass_gui-0.1.0/app.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""molass-gui — Tkinter GUI for Molass initial estimate workflow."""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""molass-gui — Phase 1: data folder input."""
|
|
2
|
+
import threading
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
from tkinter import ttk, filedialog
|
|
5
|
+
|
|
6
|
+
_DEFAULT_FOLDER = r"C:\Users\takahashi\PyTools\Data\20230705"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class App(tk.Tk):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__()
|
|
12
|
+
self.title("Molass")
|
|
13
|
+
self.resizable(False, False)
|
|
14
|
+
self._build_ui()
|
|
15
|
+
self.protocol("WM_DELETE_WINDOW", self._quit)
|
|
16
|
+
|
|
17
|
+
def _quit(self):
|
|
18
|
+
import sys
|
|
19
|
+
self.destroy()
|
|
20
|
+
sys.exit(0)
|
|
21
|
+
|
|
22
|
+
def _build_ui(self):
|
|
23
|
+
f = ttk.Frame(self, padding=16)
|
|
24
|
+
f.pack(fill=tk.BOTH, expand=True)
|
|
25
|
+
|
|
26
|
+
ttk.Label(f, text="Data folder:").grid(row=0, column=0, sticky=tk.W, pady=4)
|
|
27
|
+
self._folder_var = tk.StringVar(value=_DEFAULT_FOLDER)
|
|
28
|
+
ttk.Entry(f, textvariable=self._folder_var, width=52).grid(
|
|
29
|
+
row=0, column=1, padx=6)
|
|
30
|
+
ttk.Button(f, text="Browse…", command=self._browse).grid(row=0, column=2)
|
|
31
|
+
|
|
32
|
+
self._btn = ttk.Button(f, text="Load", command=self._run)
|
|
33
|
+
self._btn.grid(row=1, column=0, columnspan=3, pady=10)
|
|
34
|
+
|
|
35
|
+
self._status_var = tk.StringVar(value="Ready.")
|
|
36
|
+
ttk.Label(f, textvariable=self._status_var, foreground="gray").grid(
|
|
37
|
+
row=2, column=0, columnspan=3, sticky=tk.W)
|
|
38
|
+
|
|
39
|
+
def _browse(self):
|
|
40
|
+
d = filedialog.askdirectory(title="Select data folder")
|
|
41
|
+
if d:
|
|
42
|
+
self._folder_var.set(d)
|
|
43
|
+
|
|
44
|
+
def _run(self):
|
|
45
|
+
folder = self._folder_var.get().strip()
|
|
46
|
+
if not folder:
|
|
47
|
+
self._status_var.set("Please select a data folder first.")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
self._btn.state(["disabled"])
|
|
51
|
+
self._status_var.set("Loading…")
|
|
52
|
+
|
|
53
|
+
def worker():
|
|
54
|
+
try:
|
|
55
|
+
from molass.DataObjects import SecSaxsData as SSD
|
|
56
|
+
ssd = SSD(folder)
|
|
57
|
+
trimmed = ssd.trimmed_copy()
|
|
58
|
+
|
|
59
|
+
def on_main():
|
|
60
|
+
self._btn.state(["!disabled"])
|
|
61
|
+
self._status_var.set("Ready.")
|
|
62
|
+
from molass_gui.naive_view import NaiveView
|
|
63
|
+
NaiveView(ssd, trimmed, parent=self).show()
|
|
64
|
+
|
|
65
|
+
self.after(0, on_main)
|
|
66
|
+
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
msg = str(exc)
|
|
69
|
+
def on_error(m=msg):
|
|
70
|
+
self._status_var.set(f"Error: {m}")
|
|
71
|
+
self._btn.state(["!disabled"])
|
|
72
|
+
self.after(0, on_error)
|
|
73
|
+
|
|
74
|
+
threading.Thread(target=worker, daemon=True).start()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main():
|
|
78
|
+
App().mainloop()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""NaiveView — Phase 2: plot_compact + num_components → Decompose → QuickView."""
|
|
2
|
+
import threading
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
from tkinter import ttk
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NaiveView:
|
|
8
|
+
def __init__(self, ssd, trimmed, parent=None):
|
|
9
|
+
self._ssd = ssd
|
|
10
|
+
self._trimmed = trimmed
|
|
11
|
+
self._parent = parent
|
|
12
|
+
|
|
13
|
+
def show(self):
|
|
14
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
15
|
+
|
|
16
|
+
win = tk.Toplevel(self._parent)
|
|
17
|
+
win.title("Naive View")
|
|
18
|
+
|
|
19
|
+
# Header: num_components + Decompose button + status
|
|
20
|
+
hdr = ttk.Frame(win, padding=(8, 4))
|
|
21
|
+
hdr.pack(fill=tk.X)
|
|
22
|
+
ttk.Label(hdr, text="Components:").pack(side=tk.LEFT)
|
|
23
|
+
self._nc_var = tk.IntVar(value=3)
|
|
24
|
+
ttk.Spinbox(hdr, from_=1, to=6, textvariable=self._nc_var, width=5).pack(
|
|
25
|
+
side=tk.LEFT, padx=6)
|
|
26
|
+
self._decomp_btn = ttk.Button(hdr, text="Decompose", command=self._decompose)
|
|
27
|
+
self._decomp_btn.pack(side=tk.LEFT, padx=12)
|
|
28
|
+
self._status_var = tk.StringVar(value="")
|
|
29
|
+
ttk.Label(hdr, textvariable=self._status_var, foreground="gray").pack(side=tk.LEFT)
|
|
30
|
+
|
|
31
|
+
# Show trimmed compact plot with baseline overlay
|
|
32
|
+
result = self._trimmed.plot_compact(baseline=True)
|
|
33
|
+
canvas = FigureCanvasTkAgg(result.fig, master=win)
|
|
34
|
+
canvas.draw()
|
|
35
|
+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
36
|
+
NavigationToolbar2Tk(canvas, win).update()
|
|
37
|
+
self._win = win
|
|
38
|
+
|
|
39
|
+
def _decompose(self):
|
|
40
|
+
nc = self._nc_var.get()
|
|
41
|
+
self._decomp_btn.state(["disabled"])
|
|
42
|
+
self._status_var.set("Decomposing\u2026")
|
|
43
|
+
|
|
44
|
+
def worker():
|
|
45
|
+
try:
|
|
46
|
+
corrected = self._trimmed.corrected_copy()
|
|
47
|
+
decomp = corrected.quick_decomposition(num_components=nc)
|
|
48
|
+
|
|
49
|
+
def on_main():
|
|
50
|
+
self._decomp_btn.state(["!disabled"])
|
|
51
|
+
self._status_var.set("")
|
|
52
|
+
from molass_gui.quick_view import QuickView
|
|
53
|
+
QuickView(decomp, self._trimmed, nc, parent=self._win).show()
|
|
54
|
+
|
|
55
|
+
self._win.after(0, on_main)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
msg = str(exc)
|
|
58
|
+
def on_error(m=msg):
|
|
59
|
+
self._status_var.set(f"Error: {m}")
|
|
60
|
+
self._decomp_btn.state(["!disabled"])
|
|
61
|
+
self._win.after(0, on_error)
|
|
62
|
+
|
|
63
|
+
threading.Thread(target=worker, daemon=True).start()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""QuickView — Phase 3: plot_components (EGH) + model selection → Upgrade / Skip → UpgradedView."""
|
|
2
|
+
import threading
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
from tkinter import ttk
|
|
5
|
+
|
|
6
|
+
# (label, recipe model key, pore_dist kwarg or None)
|
|
7
|
+
_MODEL_OPTIONS = [
|
|
8
|
+
('EGH', 'egh', None),
|
|
9
|
+
('EGH \u2192 SDM (mono)', 'sdm', None),
|
|
10
|
+
('EGH \u2192 SDM (lognormal)', 'sdm', 'lognormal'),
|
|
11
|
+
('EGH \u2192 EDM', 'cedm', None),
|
|
12
|
+
('EGH \u2192 LKM', 'lkm', None),
|
|
13
|
+
('EGH \u2192 GRM', 'grm', None),
|
|
14
|
+
]
|
|
15
|
+
_MODEL_LABELS = [m[0] for m in _MODEL_OPTIONS]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QuickView:
|
|
19
|
+
def __init__(self, decomp, trimmed, nc, parent=None):
|
|
20
|
+
self._decomp = decomp
|
|
21
|
+
self._trimmed = trimmed
|
|
22
|
+
self._nc = nc
|
|
23
|
+
self._parent = parent
|
|
24
|
+
|
|
25
|
+
def show(self):
|
|
26
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
27
|
+
|
|
28
|
+
win = tk.Toplevel(self._parent)
|
|
29
|
+
win.title("Quick Optimization View")
|
|
30
|
+
|
|
31
|
+
# Header: model selection + PSD σ (conditional) + Upgrade / Skip buttons
|
|
32
|
+
hdr = ttk.Frame(win, padding=(8, 4))
|
|
33
|
+
hdr.pack(fill=tk.X)
|
|
34
|
+
|
|
35
|
+
ttk.Label(hdr, text="Model:").pack(side=tk.LEFT)
|
|
36
|
+
self._model_var = tk.StringVar(value='EGH')
|
|
37
|
+
self._model_cb = ttk.Combobox(hdr, textvariable=self._model_var, values=_MODEL_LABELS,
|
|
38
|
+
state='readonly', width=24)
|
|
39
|
+
self._model_cb.pack(side=tk.LEFT, padx=4)
|
|
40
|
+
|
|
41
|
+
# PSD σ frame — shown only when SDM (lognormal) is selected
|
|
42
|
+
self._psd_frame = ttk.Frame(hdr)
|
|
43
|
+
ttk.Label(self._psd_frame, text="PSD \u03c3:").pack(side=tk.LEFT, padx=(8, 0))
|
|
44
|
+
self._psd_sigma_var = tk.StringVar(value="0.1")
|
|
45
|
+
ttk.Entry(self._psd_frame, textvariable=self._psd_sigma_var, width=6).pack(
|
|
46
|
+
side=tk.LEFT, padx=4)
|
|
47
|
+
|
|
48
|
+
self._action_btn = ttk.Button(hdr, text="Skip", command=self._skip)
|
|
49
|
+
self._action_btn.pack(side=tk.RIGHT, padx=8)
|
|
50
|
+
self._status_var = tk.StringVar(value="")
|
|
51
|
+
ttk.Label(hdr, textvariable=self._status_var, foreground="gray").pack(
|
|
52
|
+
side=tk.LEFT, padx=8)
|
|
53
|
+
|
|
54
|
+
def _on_model_change(*_):
|
|
55
|
+
label = self._model_var.get()
|
|
56
|
+
is_ln = label == 'EGH \u2192 SDM (lognormal)'
|
|
57
|
+
if is_ln:
|
|
58
|
+
self._psd_frame.pack(side=tk.LEFT, after=self._model_cb)
|
|
59
|
+
else:
|
|
60
|
+
self._psd_frame.pack_forget()
|
|
61
|
+
_, key, _ = next(m for m in _MODEL_OPTIONS if m[0] == label)
|
|
62
|
+
if key == 'egh':
|
|
63
|
+
self._action_btn.configure(text='Skip', command=self._skip)
|
|
64
|
+
else:
|
|
65
|
+
self._action_btn.configure(text='Upgrade', command=self._upgrade)
|
|
66
|
+
self._model_var.trace_add('write', _on_model_change)
|
|
67
|
+
|
|
68
|
+
# Show EGH plot_components immediately
|
|
69
|
+
result = self._decomp.plot_components()
|
|
70
|
+
canvas = FigureCanvasTkAgg(result.fig, master=win)
|
|
71
|
+
canvas.draw()
|
|
72
|
+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
73
|
+
NavigationToolbar2Tk(canvas, win).update()
|
|
74
|
+
self._win = win
|
|
75
|
+
|
|
76
|
+
def _skip(self):
|
|
77
|
+
model_info = {'model': 'egh', 'pore_dist': None, 'ln_pore_sigma': None}
|
|
78
|
+
from molass_gui.upgraded_view import UpgradedView
|
|
79
|
+
UpgradedView(self._decomp, self._trimmed, self._nc, model_info,
|
|
80
|
+
parent=self._win).show()
|
|
81
|
+
|
|
82
|
+
def _upgrade(self):
|
|
83
|
+
model_label = self._model_var.get()
|
|
84
|
+
_, model_key, pore_dist = next(m for m in _MODEL_OPTIONS if m[0] == model_label)
|
|
85
|
+
if model_key == 'egh':
|
|
86
|
+
self._skip()
|
|
87
|
+
return
|
|
88
|
+
ln_pore_sigma = float(self._psd_sigma_var.get()) if pore_dist == 'lognormal' else None
|
|
89
|
+
self._action_btn.state(["disabled"])
|
|
90
|
+
self._status_var.set(f"Upgrading to {model_key.upper()}\u2026")
|
|
91
|
+
|
|
92
|
+
def worker():
|
|
93
|
+
try:
|
|
94
|
+
upgrade_kwargs = {}
|
|
95
|
+
if pore_dist is not None:
|
|
96
|
+
upgrade_kwargs['pore_dist'] = pore_dist
|
|
97
|
+
if ln_pore_sigma is not None:
|
|
98
|
+
upgrade_kwargs['model_params'] = {'ln_pore_sigma': ln_pore_sigma}
|
|
99
|
+
upgraded = self._decomp.upgrade(model_key, **upgrade_kwargs)
|
|
100
|
+
model_info = {'model': model_key, 'pore_dist': pore_dist,
|
|
101
|
+
'ln_pore_sigma': ln_pore_sigma}
|
|
102
|
+
|
|
103
|
+
def on_main():
|
|
104
|
+
self._action_btn.state(["!disabled"])
|
|
105
|
+
self._status_var.set("")
|
|
106
|
+
from molass_gui.upgraded_view import UpgradedView
|
|
107
|
+
UpgradedView(upgraded, self._trimmed, self._nc, model_info,
|
|
108
|
+
parent=self._win).show()
|
|
109
|
+
|
|
110
|
+
self._win.after(0, on_main)
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
msg = str(exc)
|
|
113
|
+
def on_error(m=msg):
|
|
114
|
+
self._status_var.set(f"Error: {m}")
|
|
115
|
+
self._action_btn.state(["!disabled"])
|
|
116
|
+
self._win.after(0, on_error)
|
|
117
|
+
|
|
118
|
+
threading.Thread(target=worker, daemon=True).start()
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""RigorousView — Phase 5: Rg computation + auto-start optimization + live 4-panel monitor.
|
|
2
|
+
|
|
3
|
+
Receives an already-upgraded Decomposition from UpgradedView (Phase 4).
|
|
4
|
+
Transitions: computing Rg → scoring → running → done.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import queue
|
|
8
|
+
import traceback
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
import tkinter as tk
|
|
12
|
+
from tkinter import ttk
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RigorousView:
|
|
16
|
+
def __init__(self, decomp, trimmed, est_kwargs, analysis_folder, parent=None):
|
|
17
|
+
"""
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
decomp : Decomposition
|
|
21
|
+
EGH decomposition; Rg curve already cached by QuickView.
|
|
22
|
+
trimmed : SecSaxsData
|
|
23
|
+
Trimmed (uncorrected) SSD, passed to score().
|
|
24
|
+
est_kwargs : dict
|
|
25
|
+
Keys: use_subprocess, pipeline_recipe.
|
|
26
|
+
analysis_folder : str
|
|
27
|
+
Output folder for optimization results.
|
|
28
|
+
parent : tk widget or None
|
|
29
|
+
"""
|
|
30
|
+
self._decomp = decomp
|
|
31
|
+
self._trimmed = trimmed
|
|
32
|
+
self._est_kwargs = est_kwargs
|
|
33
|
+
self._analysis_folder = analysis_folder
|
|
34
|
+
self._parent = parent
|
|
35
|
+
self._decomp_for_opt = None
|
|
36
|
+
self._score = None
|
|
37
|
+
self._run_info = None
|
|
38
|
+
self._stopped = False
|
|
39
|
+
self._niter = 0
|
|
40
|
+
self._last_sv_len = 0
|
|
41
|
+
self._redraw_event = threading.Event()
|
|
42
|
+
|
|
43
|
+
def show(self):
|
|
44
|
+
import matplotlib.pyplot as plt
|
|
45
|
+
import matplotlib.gridspec as gridspec
|
|
46
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
47
|
+
|
|
48
|
+
win = tk.Toplevel(self._parent)
|
|
49
|
+
recipe = (self._est_kwargs.get('pipeline_recipe') or {})
|
|
50
|
+
model = recipe.get('model', 'egh').upper()
|
|
51
|
+
method = recipe.get('method', 'bh').upper()
|
|
52
|
+
proc = 'subprocess' if self._est_kwargs.get('use_subprocess') else 'in-process'
|
|
53
|
+
win.title(f"Rigorous Optimization \u2014 {model} | {method} | {proc}")
|
|
54
|
+
|
|
55
|
+
# Prep phase header (hidden after prep completes)
|
|
56
|
+
self._prep_frame = ttk.Frame(win, padding=(8, 2))
|
|
57
|
+
self._prep_frame.pack(fill=tk.X)
|
|
58
|
+
self._prep_var = tk.StringVar(value="Computing Rg curve\u2026")
|
|
59
|
+
ttk.Label(self._prep_frame, textvariable=self._prep_var).pack(side=tk.LEFT)
|
|
60
|
+
n_frames = len(self._decomp.ssd.xr.jv)
|
|
61
|
+
self._pb = ttk.Progressbar(self._prep_frame, maximum=n_frames, length=300)
|
|
62
|
+
self._pb.pack(side=tk.LEFT, padx=8, fill=tk.X, expand=True)
|
|
63
|
+
|
|
64
|
+
# Optimization header (SV, evals, action button)
|
|
65
|
+
hdr = ttk.Frame(win, padding=(8, 4))
|
|
66
|
+
hdr.pack(fill=tk.X)
|
|
67
|
+
self._status_var = tk.StringVar(value="")
|
|
68
|
+
ttk.Label(hdr, textvariable=self._status_var, width=24).pack(side=tk.LEFT)
|
|
69
|
+
self._sv_var = tk.StringVar(value="SV: \u2014")
|
|
70
|
+
ttk.Label(hdr, textvariable=self._sv_var, font=("", 12, "bold")).pack(
|
|
71
|
+
side=tk.LEFT, padx=16)
|
|
72
|
+
self._n_var = tk.StringVar(value="0 evals")
|
|
73
|
+
ttk.Label(hdr, textvariable=self._n_var).pack(side=tk.LEFT)
|
|
74
|
+
self._iter_var = tk.StringVar(value="Iter: \u2014")
|
|
75
|
+
ttk.Label(hdr, textvariable=self._iter_var).pack(side=tk.LEFT, padx=12)
|
|
76
|
+
self._time_var = tk.StringVar(value="")
|
|
77
|
+
ttk.Label(hdr, textvariable=self._time_var, foreground="#555").pack(side=tk.LEFT, padx=4)
|
|
78
|
+
self._action_btn = ttk.Button(hdr, text="Terminate", command=self._stop,
|
|
79
|
+
state='disabled')
|
|
80
|
+
self._action_btn.pack(side=tk.RIGHT, padx=8)
|
|
81
|
+
|
|
82
|
+
# 4-panel figure: top 3 panels + bottom SV strip
|
|
83
|
+
self._fig = plt.figure(figsize=(18, 6.5))
|
|
84
|
+
gs = gridspec.GridSpec(2, 3, figure=self._fig,
|
|
85
|
+
height_ratios=[4, 1.5], hspace=0.45)
|
|
86
|
+
self._ax_uv = self._fig.add_subplot(gs[0, 0])
|
|
87
|
+
self._ax_xr = self._fig.add_subplot(gs[0, 1])
|
|
88
|
+
self._ax_score = self._fig.add_subplot(gs[0, 2])
|
|
89
|
+
self._ax_xr_twin = self._ax_xr.twinx()
|
|
90
|
+
self._ax_xr_twin.grid(False)
|
|
91
|
+
self._ax_sv = self._fig.add_subplot(gs[1, :])
|
|
92
|
+
self._axis_info = (self._fig,
|
|
93
|
+
(self._ax_uv, self._ax_xr, self._ax_score, self._ax_xr_twin))
|
|
94
|
+
|
|
95
|
+
canvas = FigureCanvasTkAgg(self._fig, master=win)
|
|
96
|
+
canvas.draw()
|
|
97
|
+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
98
|
+
NavigationToolbar2Tk(canvas, win).update()
|
|
99
|
+
self._canvas = canvas
|
|
100
|
+
self._win = win
|
|
101
|
+
|
|
102
|
+
self._prep_q = queue.Queue()
|
|
103
|
+
threading.Thread(target=self._prep_worker, daemon=True).start()
|
|
104
|
+
win.after(100, self._prep_poll)
|
|
105
|
+
|
|
106
|
+
# ------------------------------------------------------------------
|
|
107
|
+
# Prep phase
|
|
108
|
+
|
|
109
|
+
def _prep_worker(self):
|
|
110
|
+
try:
|
|
111
|
+
# Upgrade already done in Phase 3; compute Rg then score
|
|
112
|
+
def _rg_cb(rg_buffer, j):
|
|
113
|
+
self._prep_q.put(('rg', j))
|
|
114
|
+
self._decomp.get_rg_curve(progress_cb=_rg_cb)
|
|
115
|
+
self._decomp_for_opt = self._decomp
|
|
116
|
+
score = self._decomp_for_opt.score(trimmed_ssd=self._trimmed)
|
|
117
|
+
self._prep_q.put(('done', score))
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
short = str(exc).split('\n')[0][:120]
|
|
120
|
+
self._prep_q.put(('error', short))
|
|
121
|
+
|
|
122
|
+
def _prep_poll(self):
|
|
123
|
+
while True:
|
|
124
|
+
try:
|
|
125
|
+
item = self._prep_q.get_nowait()
|
|
126
|
+
except queue.Empty:
|
|
127
|
+
break
|
|
128
|
+
if item[0] == 'rg':
|
|
129
|
+
self._pb['value'] = item[1]
|
|
130
|
+
elif item[0] == 'done':
|
|
131
|
+
self._on_prep_done(item[1])
|
|
132
|
+
return
|
|
133
|
+
elif item[0] == 'error':
|
|
134
|
+
self._prep_var.set(f"Prep error: {item[1]}")
|
|
135
|
+
return
|
|
136
|
+
self._win.after(100, self._prep_poll)
|
|
137
|
+
|
|
138
|
+
def _on_prep_done(self, score):
|
|
139
|
+
self._score = score
|
|
140
|
+
self._pb.stop()
|
|
141
|
+
self._prep_frame.pack_forget()
|
|
142
|
+
try:
|
|
143
|
+
opt = score.optimizer
|
|
144
|
+
opt.objective_func(score.init_params, plot=True, axis_info=self._axis_info)
|
|
145
|
+
_retitle_panels(self._ax_uv, self._ax_xr, self._ax_score, score.sv)
|
|
146
|
+
_draw_sv_history(self._ax_sv, [], 0)
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
self._canvas.draw()
|
|
150
|
+
self._status_var.set(f"Ready \u2014 SV={score.sv:.2f}")
|
|
151
|
+
self._sv_var.set(f"SV: {score.sv:.2f}")
|
|
152
|
+
self._optimize() # auto-start; user can Terminate if needed
|
|
153
|
+
|
|
154
|
+
# ------------------------------------------------------------------
|
|
155
|
+
# Optimization phase
|
|
156
|
+
|
|
157
|
+
def _optimize(self):
|
|
158
|
+
self._status_var.set("Starting\u2026")
|
|
159
|
+
threading.Thread(target=self._launch, daemon=True).start()
|
|
160
|
+
|
|
161
|
+
def _launch(self):
|
|
162
|
+
try:
|
|
163
|
+
use_subprocess = self._est_kwargs.get('use_subprocess', False)
|
|
164
|
+
pipeline_recipe = self._est_kwargs.get('pipeline_recipe', None)
|
|
165
|
+
method = (pipeline_recipe or {}).get('method', 'BH').upper()
|
|
166
|
+
|
|
167
|
+
run_info = self._decomp_for_opt.optimize_rigorously(
|
|
168
|
+
trimmed_ssd=self._trimmed,
|
|
169
|
+
in_process=not use_subprocess,
|
|
170
|
+
async_=True,
|
|
171
|
+
monitor=False,
|
|
172
|
+
method=method,
|
|
173
|
+
analysis_folder=self._analysis_folder,
|
|
174
|
+
pipeline_recipe=pipeline_recipe,
|
|
175
|
+
)
|
|
176
|
+
self._run_info = run_info
|
|
177
|
+
self._win.after(0, self._on_started)
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
tb = traceback.format_exc()
|
|
180
|
+
log_path = os.path.join(self._analysis_folder, 'molass_gui_error.log')
|
|
181
|
+
try:
|
|
182
|
+
os.makedirs(self._analysis_folder, exist_ok=True)
|
|
183
|
+
with open(log_path, 'w') as f:
|
|
184
|
+
f.write(tb)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
short = str(exc).split('\n')[0][:120]
|
|
188
|
+
self._win.after(0, lambda m=short: self._status_var.set(
|
|
189
|
+
f"Error: {m} \u2014 see {log_path}"))
|
|
190
|
+
|
|
191
|
+
def _on_started(self):
|
|
192
|
+
self._action_btn.state(['!disabled'])
|
|
193
|
+
self._status_var.set("Running\u2026")
|
|
194
|
+
threading.Thread(target=self._watch_loop, daemon=True).start()
|
|
195
|
+
self._win.after(500, self._ui_poll)
|
|
196
|
+
|
|
197
|
+
def _watch_loop(self):
|
|
198
|
+
"""Background: redraw UV/XR/score panels every ~3 s."""
|
|
199
|
+
first = True
|
|
200
|
+
while not self._stopped:
|
|
201
|
+
if first:
|
|
202
|
+
first = False
|
|
203
|
+
else:
|
|
204
|
+
time.sleep(3.0)
|
|
205
|
+
try:
|
|
206
|
+
opt = self._run_info.optimizer
|
|
207
|
+
params = self._run_info.best_params
|
|
208
|
+
if params is None:
|
|
209
|
+
params = self._run_info.init_params
|
|
210
|
+
if params is not None:
|
|
211
|
+
lock = getattr(opt, '_objective_lock', None)
|
|
212
|
+
acquired = lock.acquire(timeout=1.5) if lock else True
|
|
213
|
+
if acquired:
|
|
214
|
+
try:
|
|
215
|
+
for ax in (self._ax_uv, self._ax_xr, self._ax_score,
|
|
216
|
+
self._ax_xr_twin):
|
|
217
|
+
ax.cla()
|
|
218
|
+
self._ax_xr_twin.grid(False)
|
|
219
|
+
opt.objective_func(params, plot=True,
|
|
220
|
+
axis_info=self._axis_info)
|
|
221
|
+
sv_hist = self._run_info.sv_history
|
|
222
|
+
sv_now = sv_hist[-1] if sv_hist else None
|
|
223
|
+
_retitle_panels(self._ax_uv, self._ax_xr, self._ax_score, sv_now)
|
|
224
|
+
finally:
|
|
225
|
+
if lock:
|
|
226
|
+
lock.release()
|
|
227
|
+
self._redraw_event.set()
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
def _ui_poll(self):
|
|
232
|
+
"""Main-thread: update labels and flush canvas every 500 ms."""
|
|
233
|
+
if self._stopped:
|
|
234
|
+
return
|
|
235
|
+
if self._run_info is None:
|
|
236
|
+
self._win.after(500, self._ui_poll)
|
|
237
|
+
return
|
|
238
|
+
try:
|
|
239
|
+
status = self._run_info.live_status()
|
|
240
|
+
phase = status.get('phase', '?')
|
|
241
|
+
best_sv = status.get('best_sv')
|
|
242
|
+
n_evals = status.get('n_evals', 0)
|
|
243
|
+
n_callbacks = status.get('n_callbacks') or 0
|
|
244
|
+
elapsed_s = status.get('elapsed_s') or 0.0
|
|
245
|
+
manifest = status.get('manifest') or {}
|
|
246
|
+
niter = manifest.get('niter', 0)
|
|
247
|
+
|
|
248
|
+
self._n_var.set(f"{n_evals} evals")
|
|
249
|
+
if best_sv is not None:
|
|
250
|
+
self._sv_var.set(f"SV: {best_sv:.2f}")
|
|
251
|
+
self._iter_var.set(
|
|
252
|
+
f"Iter: {n_callbacks}/{niter}" if niter else f"Iter: {n_callbacks}")
|
|
253
|
+
self._time_var.set(_fmt_time_info(elapsed_s, n_callbacks, niter))
|
|
254
|
+
if niter:
|
|
255
|
+
self._niter = niter
|
|
256
|
+
|
|
257
|
+
if phase == 'done':
|
|
258
|
+
self._status_var.set("Done.")
|
|
259
|
+
self._action_btn.state(["disabled"])
|
|
260
|
+
self._stopped = True
|
|
261
|
+
else:
|
|
262
|
+
p = getattr(self._run_info, '_subprocess_process', None)
|
|
263
|
+
if p is not None and p.poll() is not None and p.poll() != 0 and not n_callbacks:
|
|
264
|
+
stderr_path = os.path.join(
|
|
265
|
+
self._run_info.work_folder or '', 'optimizer_stderr.txt')
|
|
266
|
+
self._status_var.set(
|
|
267
|
+
f"Subprocess error (exit {p.poll()}) \u2014 see {stderr_path}")
|
|
268
|
+
self._action_btn.state(["disabled"])
|
|
269
|
+
self._stopped = True
|
|
270
|
+
else:
|
|
271
|
+
self._status_var.set(f"Phase: {phase}")
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
sv_hist = self._run_info.sv_history
|
|
275
|
+
sv_len = len(sv_hist) if sv_hist else 0
|
|
276
|
+
sv_changed = sv_len != self._last_sv_len
|
|
277
|
+
if sv_changed:
|
|
278
|
+
_draw_sv_history(self._ax_sv, sv_hist, self._niter)
|
|
279
|
+
self._last_sv_len = sv_len
|
|
280
|
+
except Exception:
|
|
281
|
+
sv_changed = False
|
|
282
|
+
except Exception:
|
|
283
|
+
sv_changed = False
|
|
284
|
+
|
|
285
|
+
if self._redraw_event.is_set() or sv_changed:
|
|
286
|
+
self._redraw_event.clear()
|
|
287
|
+
self._canvas.draw()
|
|
288
|
+
if not self._stopped:
|
|
289
|
+
self._win.after(500, self._ui_poll)
|
|
290
|
+
|
|
291
|
+
def _stop(self):
|
|
292
|
+
self._stopped = True
|
|
293
|
+
if self._run_info is not None:
|
|
294
|
+
try:
|
|
295
|
+
self._run_info.stop()
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
self._status_var.set("Terminating\u2026")
|
|
299
|
+
self._action_btn.state(["disabled"])
|
|
300
|
+
|
|
301
|
+
def _wait_dead():
|
|
302
|
+
ri = self._run_info
|
|
303
|
+
if ri is not None:
|
|
304
|
+
for _ in range(60):
|
|
305
|
+
time.sleep(0.5)
|
|
306
|
+
if not ri.is_alive:
|
|
307
|
+
break
|
|
308
|
+
self._win.after(0, lambda: self._status_var.set("Terminated."))
|
|
309
|
+
threading.Thread(target=_wait_dead, daemon=True).start()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# ------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
def _retitle_panels(ax_uv, ax_xr, ax_score, sv):
|
|
315
|
+
ax_uv.set_title("UV Decomposition", fontsize=16)
|
|
316
|
+
ax_xr.set_title("XR Decomposition", fontsize=16)
|
|
317
|
+
if sv is not None:
|
|
318
|
+
ax_score.set_title(f"Score Breakdown (SV={sv:.1f})", fontsize=16)
|
|
319
|
+
else:
|
|
320
|
+
ax_score.set_title("Score Breakdown", fontsize=16)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _fmt_duration(seconds):
|
|
324
|
+
h, rem = divmod(int(seconds), 3600)
|
|
325
|
+
m, s = divmod(rem, 60)
|
|
326
|
+
return f"{h}:{m:02d}:{s:02d}" if h else f"{m:02d}:{s:02d}"
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _fmt_time_info(elapsed_s, n_done, n_total):
|
|
330
|
+
parts = [f"Elapsed: {_fmt_duration(elapsed_s)}"]
|
|
331
|
+
if n_done > 0 and n_total > 0 and elapsed_s > 0:
|
|
332
|
+
rate = elapsed_s / n_done
|
|
333
|
+
remaining = (n_total - n_done) * rate
|
|
334
|
+
parts.append(f"ETA: ~{_fmt_duration(remaining)}")
|
|
335
|
+
return " ".join(parts)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _draw_sv_history(ax, sv_hist, niter=0):
|
|
339
|
+
ax.cla()
|
|
340
|
+
n = len(sv_hist) if sv_hist else 0
|
|
341
|
+
if n > 0:
|
|
342
|
+
xs = range(n)
|
|
343
|
+
ax.step(xs, sv_hist, where='post', lw=1.2, color='steelblue')
|
|
344
|
+
ax.set_ylim(bottom=max(0, min(sv_hist) - 5), top=105)
|
|
345
|
+
if niter > n:
|
|
346
|
+
ax.axvspan(n - 1, niter, alpha=0.06, color='grey')
|
|
347
|
+
ax.axvline(n - 1, color='steelblue', lw=0.8, ls='--', alpha=0.6)
|
|
348
|
+
if niter > 0:
|
|
349
|
+
ax.set_xlim(0, niter)
|
|
350
|
+
ax.set_xlabel("BH hop")
|
|
351
|
+
ax.set_ylabel("SV")
|
|
352
|
+
ax.set_title("SV history")
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""UpgradedView — Phase 4: upgraded plot_components + method/subprocess → RigorousView."""
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from tkinter import ttk
|
|
4
|
+
|
|
5
|
+
_METHOD_LABELS = ['BH', 'DE']
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UpgradedView:
|
|
9
|
+
def __init__(self, decomp, trimmed, nc, model_info, parent=None):
|
|
10
|
+
"""
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
decomp : Decomposition
|
|
14
|
+
Upgraded (or EGH) decomposition.
|
|
15
|
+
trimmed : SecSaxsData
|
|
16
|
+
Trimmed SSD for score().
|
|
17
|
+
nc : int
|
|
18
|
+
Number of components (for pipeline_recipe).
|
|
19
|
+
model_info : dict
|
|
20
|
+
Keys: model, pore_dist, ln_pore_sigma.
|
|
21
|
+
parent : tk widget or None
|
|
22
|
+
"""
|
|
23
|
+
self._decomp = decomp
|
|
24
|
+
self._trimmed = trimmed
|
|
25
|
+
self._nc = nc
|
|
26
|
+
self._model_info = model_info
|
|
27
|
+
self._parent = parent
|
|
28
|
+
|
|
29
|
+
def show(self):
|
|
30
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
31
|
+
|
|
32
|
+
win = tk.Toplevel(self._parent)
|
|
33
|
+
model = self._model_info['model'].upper()
|
|
34
|
+
win.title(f"Upgraded View \u2014 {model}")
|
|
35
|
+
|
|
36
|
+
# Header: method + subprocess + Rigorous button
|
|
37
|
+
hdr = ttk.Frame(win, padding=(8, 4))
|
|
38
|
+
hdr.pack(fill=tk.X)
|
|
39
|
+
|
|
40
|
+
ttk.Label(hdr, text="Method:").pack(side=tk.LEFT)
|
|
41
|
+
self._method_var = tk.StringVar(value='BH')
|
|
42
|
+
ttk.Combobox(hdr, textvariable=self._method_var, values=_METHOD_LABELS,
|
|
43
|
+
state='readonly', width=8).pack(side=tk.LEFT, padx=4)
|
|
44
|
+
|
|
45
|
+
self._subprocess_var = tk.BooleanVar(value=True)
|
|
46
|
+
ttk.Checkbutton(hdr, text="Use subprocess",
|
|
47
|
+
variable=self._subprocess_var).pack(side=tk.LEFT, padx=12)
|
|
48
|
+
|
|
49
|
+
self._rig_btn = ttk.Button(hdr, text="Rigorous Optimization\u2026",
|
|
50
|
+
command=self._proceed_rigorous)
|
|
51
|
+
self._rig_btn.pack(side=tk.RIGHT, padx=8)
|
|
52
|
+
|
|
53
|
+
# Show plot_components for the current model
|
|
54
|
+
result = self._decomp.plot_components()
|
|
55
|
+
canvas = FigureCanvasTkAgg(result.fig, master=win)
|
|
56
|
+
canvas.draw()
|
|
57
|
+
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
58
|
+
NavigationToolbar2Tk(canvas, win).update()
|
|
59
|
+
self._win = win
|
|
60
|
+
|
|
61
|
+
def _proceed_rigorous(self):
|
|
62
|
+
from tkinter import filedialog
|
|
63
|
+
folder = filedialog.askdirectory(
|
|
64
|
+
title="Select output folder for optimization results",
|
|
65
|
+
parent=self._win)
|
|
66
|
+
if not folder:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
model_key = self._model_info['model']
|
|
70
|
+
pore_dist = self._model_info['pore_dist']
|
|
71
|
+
ln_pore_sigma = self._model_info['ln_pore_sigma']
|
|
72
|
+
method = self._method_var.get().lower()
|
|
73
|
+
use_subprocess = self._subprocess_var.get()
|
|
74
|
+
|
|
75
|
+
pipeline_recipe = {
|
|
76
|
+
'num_components': self._nc,
|
|
77
|
+
'model': model_key,
|
|
78
|
+
'method': method,
|
|
79
|
+
'decomp_params': {},
|
|
80
|
+
'trim_params': {},
|
|
81
|
+
'baseline_params': {},
|
|
82
|
+
}
|
|
83
|
+
if pore_dist is not None:
|
|
84
|
+
pipeline_recipe['pore_dist'] = pore_dist
|
|
85
|
+
if ln_pore_sigma is not None:
|
|
86
|
+
pipeline_recipe['ln_pore_sigma'] = ln_pore_sigma
|
|
87
|
+
|
|
88
|
+
est_kwargs = {
|
|
89
|
+
'use_subprocess': use_subprocess,
|
|
90
|
+
'pipeline_recipe': pipeline_recipe,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
from molass_gui.rigorous_view import RigorousView
|
|
94
|
+
RigorousView(self._decomp, self._trimmed, est_kwargs,
|
|
95
|
+
analysis_folder=folder, parent=self._win).show()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "molass_gui"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
"molass>=1.0.5",
|
|
11
|
+
"molass_legacy>=1.6.13",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
requires-python = ">=3.9,<3.15"
|
|
15
|
+
|
|
16
|
+
authors = [
|
|
17
|
+
{name = "Molass Community"},
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
maintainers = [
|
|
21
|
+
{name = "Molass Community"}
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
description = "Tkinter GUI for Molass — initial estimate workflow"
|
|
25
|
+
readme = "README.md"
|
|
26
|
+
license = { text = "GNU General Public License v3.0" }
|
|
27
|
+
|
|
28
|
+
keywords = ["SEC-SAXS"]
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Development Status :: 3 - Alpha",
|
|
31
|
+
"Programming Language :: Python",
|
|
32
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
molass-gui = "molass_gui.app:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Repository = "https://github.com/biosaxs-dev/molass-gui"
|