262 Commits

Author SHA1 Message Date
9baf1e44d3 now with proper package building 2023-11-15 18:20:32 +01:00
8fb17b22fd fit results save weight; closes #139 2023-11-13 18:46:46 +01:00
f04cadf780 remember header settings in ascii dialog; fixes #140 2023-11-13 18:03:57 +01:00
ae45ff2e55 set name of fit results manually; fix #138 2023-11-13 17:17:50 +01:00
735896e68d background color of legend is not changed; fixes #141 2023-11-13 17:11:38 +01:00
aff9cd31a7 add "index" keyword to ascii reader to use row number; part of #135 2023-11-10 18:56:13 +01:00
8772fa9e7a add __future__ import to fix #136 2023-11-10 10:17:42 +00:00
9c5d91918f added f-omega option to log-fourier transform; closes #134 2023-11-09 17:50:29 +01:00
dbb35cdba4 added f-omega option to bds fit functions; closes #133 2023-11-09 17:26:57 +01:00
070509c691 do not overwrite old backup until new backup is done 2023-11-09 17:05:17 +01:00
5ea1ed6d0a change level of appimage version logger 2023-11-07 17:50:32 +01:00
a1691f11a6 remove leading space in ascii preview, because some dimwit wants to use spaces in the beginning of his files... 2023-11-07 17:07:47 +01:00
f956c111c2 regex for numeric value for data from text files; closes #127 2023-11-05 17:23:51 +01:00
cb2bc78a2a work on edit signals 2023-11-05 15:25:31 +01:00
9e9751cd9d add log to edit signal 2023-11-05 15:21:19 +01:00
5a8ef8c9c7 change wording for delete previous fit in fitresult 2023-11-05 15:17:59 +01:00
45f5eb9bef Update src/gui_qt/data/container.py
phase correction should be done when users wants it; closes #129
2023-11-02 13:05:18 +00:00
5116622e42 fixed fit parameter ignore boundary problems; fixes #128 2023-11-01 18:33:52 +01:00
661ab81d7e fix edit dialog for spectra 2023-10-31 19:12:02 +01:00
185dd160f1 fix import of agr with missing legends 2023-10-30 19:34:18 +01:00
7fe564a61e more interactive edit 2023-10-25 20:04:49 +02:00
6339bdc2cc escape backslashes in fit function equation 2023-10-17 18:16:31 +02:00
37a6d789bb increase precision in log-gaussian integration 2023-10-02 12:31:40 +02:00
cde794cb0d parabola on a log-scale added 2023-09-28 17:55:25 +02:00
2a69147ed4 remove slot reference in parameter.get_state; fixes #125 2023-09-28 15:43:55 +02:00
902a35b71a check if Default.agr knows what a graph is; helps with #124 2023-09-27 18:06:53 +02:00
6f76349932 Merge branch 'fit_bounds' 2023-09-27 15:39:23 +02:00
f9bb466cb3 fix data look-up for multiple fit models 2023-09-27 15:38:55 +02:00
a97c31325f 1/p bounds in ui 2023-09-27 15:32:19 +02:00
207ee5bffd 1/p bounds added 2023-09-27 13:55:37 +02:00
0046d04683 improve performance building data tree 2023-09-19 17:22:59 +02:00
9463ed1e6c improve performance for show/hide graphs 2023-09-19 17:05:06 +02:00
ea62a05bd3 improve performance for show/hide graphs 2023-09-19 16:32:54 +02:00
c5706084bf fixing merge problems 2023-09-19 12:48:12 +02:00
04037d6b4d Merge branch 'fit_constraints'
# Conflicts:
#	src/gui_qt/main/management.py
2023-09-19 12:39:32 +02:00
dedb130163 make final fit parameter values 2023-09-19 12:35:24 +02:00
6ecc4a4126 make final fit parameter values 2023-09-19 12:34:44 +02:00
067857eda2 minor fixes 2023-09-19 12:22:33 +02:00
41d90bb15f minor fixes 2023-09-19 11:33:52 +02:00
8d994bb9b4 fixed preview 2023-09-18 17:39:31 +02:00
2cbc7e8d75 more detailed error message in fit preparation 2023-09-18 16:08:42 +02:00
1d22f22901 Parameter in preview 2023-09-18 15:30:06 +02:00
bd1a227e4c use Parameter when collecting fit values 2023-09-18 13:52:10 +02:00
03d172bade use Parameter when collecting fit values 2023-09-18 11:43:28 +02:00
e51a02d277 improvement of t1 calc 2023-09-16 17:42:59 +02:00
3af5cb0301 add todos 2023-09-16 14:16:45 +02:00
22f317da8d remove wonky path variable from graph export to use last directory instead; closes #121 2023-09-13 17:25:53 +02:00
24b56cbd2a calc_relaxation: create copy of x to avoid unintended overwrite 2023-09-13 17:16:57 +02:00
5fd52a1a44 Update src/nmreval/distributions/loggaussian.py
fix typo; closes #119
2023-09-12 07:07:36 +00:00
d8e5de6b7a T1+T2 calculation now always iterates over all args that can be from data 2023-09-11 19:50:58 +02:00
869901596b Merge branch 'main' into fit_constraints
# Conflicts:
#	src/gui_qt/fit/fit_forms.py
#	src/gui_qt/main/management.py
#	src/nmreval/fit/minimizer.py
2023-09-11 18:18:30 +02:00
e4dbaf2b91 work on ui 2023-09-11 18:09:08 +02:00
dee1271fe1 add action to context menu to replace single set fit parameter with general fit parameter 2023-09-08 18:59:14 +02:00
b8bab2af7b fixed LG+CC spectral density calculation for omega=0; closes #118 2023-09-08 18:10:19 +02:00
a406908a69 catch errors in fit preparation 2023-09-07 19:52:53 +02:00
5e55f06723 add completer to general fit linedit 2023-09-07 19:30:10 +02:00
e2e52cebde fixed overwrite of weight option for deltay, resulted in invalid weights; closes #117 2023-09-07 17:48:49 +02:00
53c58b2bbb disable validators 2023-09-06 17:45:57 +02:00
4e865cd0c6 Update src/gui_qt/nmr/t1_from_tau.py
Add Log-Gaussian to T1 calculation; closes #116
2023-09-06 10:20:22 +00:00
311157a01a fix parameter setting after fitting multiple models 2023-09-03 20:17:07 +02:00
f0ee2073ad fixed wrong order for nested fit functions; swapped exchange rates in Peschier model 2023-08-31 19:34:13 +02:00
d2e63a5ee3 refactor odr 2023-08-29 19:44:09 +02:00
5a153585ee write error for global fits 2023-08-29 19:11:56 +02:00
d17d0f251e work on linked models 2023-08-26 20:08:13 +02:00
0b8f4932b2 seems mostly to be working 2023-08-25 18:46:36 +02:00
5d43ccb05d fit with global parameter 2023-08-24 16:19:09 +02:00
88a32ea7fd multiple single fits working 2023-08-15 18:44:08 +02:00
7febe55929 order of fits correspond order in graph, fit result window has correct order, see #109 2023-08-07 18:42:10 +02:00
783fe505ba better check for valid class and argument names in fit function creation 2023-08-06 16:41:22 +02:00
2fed4bb0bf fit creation dialog uses option name for kwargs 2023-08-05 17:53:24 +02:00
ec8fafcc9c correct type for linenumbers in codeeditor 2023-08-05 15:31:17 +02:00
7762758958 save original expression 2023-08-04 17:58:48 +02:00
258922772e change debian source 2023-08-02 19:58:02 +02:00
e612c607e2 Merge remote-tracking branch 'origin/master' 2023-08-01 19:59:16 +02:00
b27d9b55ff subclass every PlotWidget because logtickvalues 2023-08-01 19:47:55 +02:00
3475650899 signal b/w change in graph was not connected 2023-08-01 18:06:33 +02:00
0eca306ebe assert that log scale is finite 2023-08-01 18:00:23 +02:00
d7eaff5f0b functioning apply button for tau calculation, part of #109 2023-08-01 06:54:35 +00:00
a5216b1eed constrain legend rect to be fully inside viewbox; simplest solution to fix #113 2023-07-31 20:17:31 +02:00
cf1565f7d3 use symbol for length 1 data in shift/scale; fixes #112 2023-07-31 11:48:57 +00:00
c601e77cec fit with expression works with single fit 2023-07-30 19:34:59 +02:00
63f4f82228 remove spurious print 2023-07-30 18:10:14 +02:00
bae7ee2db6 list of active sets uses actual order instead order they were displayed; closes #111 2023-07-30 16:18:18 +02:00
dde7b7006d overwrite logTickValues of AaxisItem to avoid to many minor ticks; maybe closes #110 2023-07-29 21:25:22 +02:00
141e9f810a convert removes agr controls 2023-07-29 21:22:12 +02:00
b6136bc8ce subclass QTableWidget for change multiple checkstates 2023-07-29 21:22:12 +02:00
7762e299e4 manually setting graph limits when log-scaling to reduce memory usages; 2023-07-29 21:22:10 +02:00
ca130eaa14 eval parameter 2023-07-27 18:58:22 +02:00
2b2c6e932d set lower bound of FC and BDS KWW to 0.1 2023-07-25 17:31:42 +02:00
402994e09b remove parameter limit; closes #106 2023-07-25 17:23:25 +02:00
7a50028202 add namespace to parameter 2023-07-24 20:12:55 +02:00
becc1a15a9 move shift/scale to container; closes #102 2023-07-24 17:38:55 +02:00
13e6112c99 overwrite error in interpolation; avoid mismatch in length
part of #103
2023-07-24 11:08:42 +00:00
1ff462c4b1 remove duplicate call of t1 calculate and t1 interpolation 2023-07-21 19:53:08 +02:00
0cf5d7eef9 remove log(y) weight option from fit
close #101
2023-07-20 07:27:33 +00:00
609f135855 correcter preview for text files with mixed delimiters 2023-07-19 19:09:42 +02:00
5db6a9f2c5 add HN model with independent slopes 2023-07-19 18:36:13 +02:00
3149a3f958 setRegion of RegionItem with option to account for log mode 2023-07-17 19:19:38 +02:00
3321d85203 set integration limits via keyboard; closes #50 2023-07-13 19:51:19 +02:00
76cd4acfb0 add flag for partial fits; closes #83 2023-07-12 20:48:28 +02:00
33afc2ca94 active graph is unchanged after pick position is applied; closes #89 2023-07-12 19:05:17 +02:00
ac039c1b6d higher zValues for borders of RegionItem to move overlapping RegionItems 2023-07-12 18:31:29 +02:00
ef21d7c7f2 use time for interpolation pf empty dsc reference to avoid infs 2023-07-11 20:31:21 +02:00
3ed7368bb0 fix even savgol window length manuallz 2023-07-11 18:06:17 +02:00
fbf4246c7e bugfix eval expression; closes #83 2023-07-09 19:45:26 +02:00
91afe8224f numerical values of data as tooltip; closes #90 2023-07-08 19:55:20 +02:00
ead9751541 tg dialog clean-up 2023-07-06 20:05:10 +02:00
1d9b641f0a closing Tg dialog is no longer export 2023-07-05 20:06:56 +02:00
46ca50845b Merge branch 'dsc'
# Conflicts:
#	src/gui_qt/_py/basewindow.py
#	src/gui_qt/_py/tnmh_dialog.py
#	src/gui_qt/dsc/glass_dialog.py
#	src/gui_qt/main/mainwindow.py
#	src/gui_qt/main/management.py
#	src/gui_qt/math/binning.py
#	src/nmreval/data/dsc.py
#	src/nmreval/dsc/hodge.py
#	src/resources/_ui/basewindow.ui
#	src/resources/_ui/tnmh_dialog.ui
2023-07-05 20:02:52 +02:00
a310a78fb9 added tnmh; some bugfixes 2023-07-05 19:51:06 +02:00
9756bf958b close #86; 2023-07-05 18:11:58 +02:00
8de4a0cbd3 interim save 2023-07-05 17:35:31 +02:00
bc946e1027 interim save 2023-07-03 18:21:55 +02:00
1d9bf600ba add hodge fit 2023-06-27 18:23:41 +02:00
4a04502012 correct URL for issues 2023-06-27 17:26:44 +02:00
6bcf42d6a8 check even window length in tg calc; part of #91 2023-06-26 20:34:52 +02:00
8a96e0472d stuff 2023-06-26 19:57:23 +02:00
567fcfe37e „src/nmreval/models/stimecho.py“ ändern 2023-06-26 17:22:52 +00:00
058551cc72 closes #86 2023-06-22 17:42:07 +02:00
6fd3053964 Merge remote-tracking branch 'origin/master' 2023-06-20 19:22:19 +02:00
3ee95a1990 correct description in binning dialog 2023-06-20 19:22:00 +02:00
1a2f178f58 undo move of .desktop file 2023-06-20 19:21:37 +02:00
c39ffb8a51 undo move of .desktop file 2023-06-20 19:19:05 +02:00
a26595695c Binning and Tg (#85)
add binning; determine Tg of DSC;

closes #60; part of #61

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #85
2023-06-20 17:13:13 +00:00
c9ea32629d Tg export finalized 2023-06-20 19:09:31 +02:00
032ffb72b1 Merge branch 'main' into dsc
# Conflicts:
#	src/gui_qt/main/mainwindow.py
2023-06-20 18:07:52 +02:00
354d5cbc99 generalize binning 2023-06-19 20:03:21 +02:00
22f8bc80ed concat mask also; closes #84 2023-06-19 18:15:25 +02:00
84d588cf80 work on export 2023-06-19 17:50:36 +02:00
5b146433f3 binning 2023-06-16 20:25:24 +02:00
1a225b2cd2 multiselection and check in graph and bdsdialog; part of #82 2023-06-15 20:16:42 +02:00
89c640e591 bugfix: remove value display item from graph before deletion 2023-06-15 20:00:52 +02:00
1703b8d514 add binning 2023-06-15 19:57:13 +02:00
7732544f69 start tg saving 2023-06-15 17:24:35 +02:00
988d2ccbda minor cleanup 2023-06-13 20:28:37 +02:00
4268bdc5ae #81 make t1 data real and catch more errors; 2023-06-13 19:08:24 +02:00
775b5e7e8a correct tab behavior after value change (part of #79) 2023-06-13 18:31:39 +02:00
30e148de14 Merge branch '79' 2023-06-12 20:40:28 +02:00
2499aac7a1 check for value changes while tabbing in edit mode, avoid signal; closes #79 2023-06-12 20:40:00 +02:00
f934104587 closes #80; evaluate expression an Datensätzen mit gehiddenen Values 2023-06-12 18:07:51 +02:00
6e976e1404 closes #78 interpolieren, auswahl von sets 2023-06-12 17:27:43 +02:00
92c29bec2a split tg calculation from tnmh model 2023-06-12 17:20:58 +02:00
9f6d4e0d0c split ficitive cp and tnmh 2023-06-05 19:57:42 +02:00
90bd33a680 check also for individual files in config directory; closes #76 2023-06-04 19:39:12 +02:00
26fea8f69f remove temporary elements from graphs before deletion 2023-06-04 15:57:17 +02:00
25f7ff5616 ui adjustments 2023-06-03 20:27:56 +02:00
bec789318d initialize plot at start 2023-06-03 17:04:14 +02:00
edc6af2762 fictive cp and tnmh model 2023-06-01 20:09:11 +02:00
eb1e657dab more ui work 2023-05-31 19:35:03 +02:00
79a424c4bd fix mismatched indexes between data and labels in asciireader 2023-05-31 17:21:56 +02:00
f74dd982ba color to dsc curves in tg 2023-05-31 17:20:51 +02:00
a5a9561b54 Merge remote-tracking branch 'origin/master' 2023-05-30 17:42:38 +02:00
c8287c2dbf fix missing return of comments in asciireader 2023-05-30 17:42:12 +02:00
a8a7e75501 plot DSC data and baselines 2023-05-30 17:36:44 +02:00
30750cc4ed more ui 2023-05-26 18:22:13 +02:00
af14793286 more ui 2023-05-26 17:28:06 +02:00
2906c17472 more ui 2023-05-24 19:34:06 +02:00
5590b5cd16 SelectionWidget has no function show_as_local_parameter; fixes #75 2023-05-24 09:00:31 +00:00
09e415babf begin work on gui 2023-05-23 19:23:13 +02:00
4e2938b2a2 proper handling of sets without label,; closes #73 2023-05-23 06:40:08 +00:00
685b9d2316 HN + Excess wing 2023-05-22 18:50:03 +02:00
b91fbf7621 tg evaluation in dsc 2023-05-22 18:49:49 +02:00
8d55430246 binning added to points 2023-05-22 18:49:27 +02:00
8d06240205 Merge branch 'fit' 2023-05-19 18:14:29 +02:00
65034d4518 sub function show numbering; closes #70 2023-05-19 18:13:57 +02:00
2adf15104f attempt to only show real/imag part of complex fit functions; closes #72 2023-05-19 17:35:32 +02:00
cd9c85c12b add FID as fallback option for dtype in nmrreader 2023-05-19 16:56:01 +02:00
84d136dd4c .nmr files recognize all data types; closes #71 2023-05-19 16:53:54 +02:00
f60e125487 display parameter name with number ind window 2023-05-19 16:41:24 +02:00
1e9a390ae2 attempt to distinguish general and set-specifi parameter 2023-05-19 16:17:49 +02:00
8673e5acdb catch error in f-test 2023-05-19 16:00:04 +02:00
66a0e40a23 display correct start parameter 2023-05-19 15:48:32 +02:00
a1ab6335c5 remove remnants of fitresult redesign; closes #68 2023-05-19 13:25:56 +02:00
09a2b61160 read text files as BDS and DSC 2023-05-19 13:02:58 +02:00
681b49a22f Merge branch 'read_fc' 2023-05-19 11:37:42 +02:00
3dcd44c3ee restore optionality to figure saving 2023-05-19 11:37:24 +02:00
6e9dd4d45f use gnuplot as lightweight plotter 2023-05-19 11:33:02 +02:00
888d86d9fe remove typo in AppImageBuilder.yml 2023-05-18 20:50:39 +02:00
67d60949b5 plot fit and residual together with bigger fit 2023-05-18 20:43:45 +02:00
fcaf43b3eb fixed problem with setting color from context menu 2023-05-18 18:51:02 +02:00
49101565a3 fixed problem with setting color from context menu 2023-05-17 19:41:44 +02:00
255d7c7862 Update 'src/nmreval/io/dsc.py'
remove matplotlib import
2023-05-17 15:39:36 +00:00
12dacb73b3 add QMessageBox to display existing backup files 2023-05-16 20:05:55 +02:00
b127cc15e2 dsc: only use low temperature to baseline correct calibration without enthalpy 2023-05-16 19:22:50 +02:00
bc215ce32b some stuff done 2023-05-16 17:45:39 +02:00
996b0b2ae2 tnm function with numpy arrays 2023-05-14 20:05:10 +02:00
753cd06dd1 pick position remembers selected destination graph; closes #62 2023-05-14 18:16:23 +02:00
45d319834b fitresult window is reused and remembers fitplot range/log; closes #65
add color to sub-functions in fit result window
Fitresult window shows one set at a time, more space for plot; closes #66
2023-05-14 15:24:25 +02:00
11ed3c16eb add tnmh stuff 2023-05-13 19:23:15 +02:00
edf858da29 hacky solution to have correct tuda colors in agr file; closes #63 2023-05-11 20:12:32 +02:00
e10b85b904 more correct lines preview in asciireader 2023-05-11 18:28:03 +02:00
267554b252 check for empty line moved to avoid error 2023-05-11 18:00:29 +02:00
233cdd9f80 add gnuplot to requirements for plots of FC reading 2023-05-10 17:09:11 +02:00
15c959fd71 save fit parameter adds name to file as extra comment; possible solution for #59 2023-05-08 19:54:55 +02:00
1378cf6ac7 Fitresult accepts label argument as set name 2023-05-08 18:48:08 +02:00
a964e02612 correct baseline and slopes for calibration measurements at high DSC rates 2023-05-08 18:20:04 +02:00
a72e4ba833 add logging to fit exception to retain more info; all references to current graph are either None or id 2023-05-03 19:06:35 +02:00
75ec462efd add logging to fit exception to retain more info and find problem with #56; all references to current graph are either None or id, should close #57 2023-05-03 19:04:25 +02:00
adcd98fc31 dsc baseline uses consistent offset for correction; closes #53 2023-05-02 17:35:28 +02:00
d8cc99cea4 dsc (#55)
closes #53

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #55
2023-04-30 18:21:16 +00:00
7671a56b6f merge cfunc -> main 2023-04-30 14:59:58 +02:00
8086dd5276 finalized c functions 2023-04-29 20:40:32 +02:00
9479364a64 delete item after the iteration to find its index; closes #52 2023-04-27 19:08:14 +02:00
def2a99ed8 datawidget.py: raise AttributeError manually if item is graph, was not always raised; maybe problem of #52 2023-04-27 12:15:43 +02:00
ac4a4d3b8e DSC: remove non-finite values due mismatch in beginnings of sample and empty 2023-04-24 19:24:23 +02:00
dd1c26e285 more work on loggaussian 2023-04-23 19:55:53 +02:00
eba7869c4d data list in QEvalDialog uses current names instead of stored 2023-04-19 19:43:04 +02:00
efd5b34b13 create .local/bin folder for AppImage if not existent 2023-04-19 18:33:11 +02:00
2d472bd44e lower epsabs for integration of energy barrier spec dens 2023-04-19 18:24:29 +02:00
2042148d0f C function for energy distribution spectral density 2023-04-17 20:17:54 +02:00
4a9d50e8a4 refactor Log-Gaussian susceptibility 2023-04-17 17:43:18 +02:00
56b588293d add loggausian to c func and move lookup of c library 2023-04-17 17:31:34 +02:00
59625c1581 more_bugs (#51)
closes #46, #47, #7

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #51
2023-04-16 15:40:58 +00:00
bb3d5ac58b remoce matplotlib import 2023-04-16 14:33:05 +02:00
48861c3f80 remove spurious print 2023-04-16 14:20:40 +02:00
8e2ebfc765 Merge branch 'more_bugs' 2023-04-16 14:17:36 +02:00
924b58beda move iteration of deleting sets inside undo, fixes #36 2023-04-15 15:49:08 +02:00
bd8a4f16ea UpperManagement.deleteDate emits list of sets, indexes in datawidget are now only updated once 2023-04-15 15:40:49 +02:00
e41c42d573 move iteration of deleting sets inside undo 2023-04-15 14:55:14 +02:00
c94231f9d9 add block to graphwindow to stop rescale/legend uupdates 2023-04-15 14:54:25 +02:00
99bb196e5c basic FC reading is working for different script version 2023-04-14 19:17:05 +02:00
e5563d55d5 set graphs in fc reader 2023-04-14 17:58:14 +02:00
abe6ca42c7 added build date of appimage as version; closes #48 2023-04-13 20:03:39 +02:00
40961a89df correct path in makefile 2023-04-13 19:38:58 +02:00
29518b9ea0 newer FC scripts are working, now older ones fail 2023-04-12 20:42:33 +02:00
3793f67951 rate and time in exponential function was switched 2023-04-12 20:01:27 +02:00
0251dea563 uncheck fits in data_table 2023-04-12 17:28:12 +02:00
bc034cf4f4 set region flag correctly 2023-04-12 17:27:05 +02:00
3e513a1231 update makefile 2023-04-11 19:06:35 +02:00
ead30d127a add C function for FFHS integration 2023-04-10 19:33:23 +02:00
3b79c571fb eval expression reuses ListWidgetItems of datasets; closes #31 2023-04-09 20:20:37 +02:00
ee8ea4f2c5 remove epsilons from integration, now 50% faster 2023-04-09 19:37:22 +02:00
2c0cdfbd68 add constant to ffhs 2023-04-09 19:37:18 +02:00
767fa5f6fb timer needs to be child of window 2023-04-09 19:37:01 +02:00
02f8a3bb31 fitting (#43)
adjustments to fit to see if fit is at least running, could help with #39

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #43
2023-04-08 18:37:07 +00:00
ffecc9c873 remove duplicates of keys in deletion; may help with #36 2023-04-08 17:16:05 +02:00
0ec0021727 appimage-starter (#42)
create program launcher;
reduced size of appimage;
download of appimages working(?)

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #42
2023-04-08 13:28:13 +00:00
6b71de8265 bug-fixer (#40)
fixes #32, #34, #39

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #40
2023-04-07 13:45:28 +00:00
43285b4bd5 added first version of a backup system (#37)
Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #37
2023-04-05 17:50:06 +00:00
2fbaa94109 catch strange behavior of strptime in get_zsync 2023-04-04 16:43:14 +00:00
ebf3e9635d Update 'src/gui_qt/lib/utils.py' 2023-04-04 15:49:09 +00:00
79befb50f1 Update 'src/gui_qt/lib/utils.py' 2023-04-04 15:41:21 +00:00
0adb530dc2 Update 'src/gui_qt/lib/utils.py' 2023-04-04 15:34:09 +00:00
d7fd8395e5 update zsync location 2023-04-02 19:44:01 +02:00
75f9bc84f8 remove typo; remove unnecessary package 2023-04-02 19:23:04 +02:00
1c98676bee Merge pull request 'show sub-functions in fitresult; closes #26' (#28) from fitting into master
Reviewed-on: #28
2023-04-02 15:42:05 +00:00
8a35a5e6cb show sub-functions in fitresult; closes #26 2023-04-02 17:40:23 +02:00
df9b302f9d Merge pull request 'replace semicolon and commas with spaces in csv; closes #23' (#24) from csv_broken into master
Reviewed-on: #24
2023-03-24 10:05:43 +00:00
4e0ff4eddd replace semicolon and commas with spaces in csv; closes #23 2023-03-24 11:03:50 +01:00
de9464ef9f Update 'src/gui_qt/lib/utils.py' 2023-03-17 13:16:24 +00:00
466a63990d Update 'src/gui_qt/lib/utils.py'
logger added to zsync download
2023-03-17 13:15:44 +00:00
8690cb222e ignore errors due to wrong encoding in agr files (#22)
Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #22
2023-03-16 18:49:44 +00:00
28c15ff565 extrapolate_fit (#21)
New sets with arbitrary x range can be created from fit results

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #21
2023-03-12 19:37:10 +00:00
1601f1455c use same value in widget and multimodel to count parameter 2023-03-01 19:27:03 +01:00
b73d6f08b8 asciireader.py avoid IndexError if file has no header 2023-03-01 19:27:03 +01:00
5577adabec AppImage path was incorrect in upload script.
fixes #17
2023-02-07 07:57:56 +01:00
57ecff478b silence curl in upload script 2023-02-07 01:03:05 +01:00
2229b2905a fixes #16 2023-02-07 00:47:27 +01:00
60c93f6bc9 changed the repository path 2023-02-06 20:38:04 +01:00
626804783e Merge remote-tracking branch 'origin/master' 2023-02-06 20:20:09 +01:00
a182d55b98 Add 'LICE' 2023-02-06 20:19:41 +01:00
ee2a91813c Add 'LICE' 2023-02-06 19:13:45 +00:00
142 changed files with 8057 additions and 21744 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ AppDir
NMReval*.zsync NMReval*.zsync
.idea .idea
*.zs-old *.zs-old
docs

View File

@ -12,7 +12,9 @@ script:
# Copy the python application code into the AppDir # Copy the python application code into the AppDir
- cp bin/evaluate.py $TARGET_APPDIR/usr/bin/ - cp bin/evaluate.py $TARGET_APPDIR/usr/bin/
- cp -r src/* $TARGET_APPDIR/usr/src/ - cp -r src/* $TARGET_APPDIR/usr/src/
- cp src/pkm.vogel.nmreval.desktop $TARGET_APPDIR/usr/share/applications - cp src/resources/pkm.vogel.nmreval.desktop $TARGET_APPDIR/usr/share/applications
# set current date as version info
- sed -i "s/CURRENT_DATE/$(date +'%Y-%m-%d')/" $TARGET_APPDIR/usr/src/nmreval/version.py
AppDir: AppDir:
@ -30,35 +32,50 @@ AppDir:
arch: amd64 arch: amd64
allow_unauthenticated: true allow_unauthenticated: true
sources: sources:
- sourceline: 'deb [arch=amd64] http://mirror.infra.pkm/ bullseye main contrib non-free' - sourceline: 'deb [arch=amd64] http://ftp.uni-mainz.de/debian bullseye main contrib non-free'
include: include:
# for /usr/bin/env # for /usr/bin/env
- coreutils # - coreutils
- dash # - dash
- zsync # - zsync
- hicolor-icon-theme # - hicolor-icon-theme
- libatlas3-base - libatlas3-base
- python3.9-minimal - gnuplot-nox
- python3-minimal
- python3-numpy - python3-numpy
- python3-scipy - python3-scipy
# - python3-matplotlib
# - python-matplotlib-data
- python3-bsddb3 - python3-bsddb3
- python3-h5py - python3-h5py
- python3-pyqt5 - python3-pyqt5
- python3-pyqtgraph - python3-pyqtgraph
- python3-requests
- python3-urllib3
# - python3-tk
exclude: exclude:
# lots of qt stuff we do not use
- libqt5designer5
- libqt5help5
- libqt5network5
- libqt5sql5
- libqt5test5
- libqt5xml5
- qtbase5-dev-tools
- pyqt5-dev-tools
- libavahi-client3 - libavahi-client3
- libavahi-common-data - libavahi-common-data
- libavahi-common3 - libavahi-common3
after_bundle: | - libwacom2
echo "MONSTER SED FOLLOWING...(uncomment if needed for mpl-data)" - libwacom-common
#sed -i s,\'/usr/share/matplotlib/mpl-data\',"f\"\{os.environ.get\('APPDIR'\,'/'\)\}/usr/share/matplotlib/mpl-data\"", ${TARGET_APPDIR}/usr/lib/python3/dist-packages/matplotlib/__init__.py
files:
exclude:
- usr/share/man
- usr/share/doc/*/README.*
- usr/share/doc/*/changelog.*
- usr/share/doc/*/NEWS.*
- usr/share/doc/*/TODO.*
runtime: runtime:
# if needed, apparently replaces hardcoded location with APPDIR location
# path_mappings:
# - /usr/share/matplotlib/mpl-data:$APPDIR/usr/share/matplotlib/mpl-data
version: "continuous" version: "continuous"
env: env:
PATH: '${APPDIR}/usr/bin:${PATH}' PATH: '${APPDIR}/usr/bin:${PATH}'
@ -87,6 +104,6 @@ AppDir:
command: ./AppRun command: ./AppRun
AppImage: AppImage:
update-information: 'zsync|https://gitea.pkm.physik.tu-darmstadt.de/api/packages/IPKM-Public/generic/NMReval/latest/NMReval-latest-x86_64.Appimage.zsync' update-information: 'zsync|https://gitea.pkm.physik.tu-darmstadt.de/api/packages/IPKM/generic/NMReval/latest/NMReval-latest-x86_64.Appimage.zsync'
sign-key: 976AC9D78688B628B00D4944D319B98C2D6CE5D3 sign-key: 976AC9D78688B628B00D4944D319B98C2D6CE5D3
arch: x86_64 arch: x86_64

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
BSD 3-Clause License
Copyright (c) 2023 Dominik Demuth.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -5,31 +5,37 @@ PYUIC = pyuic5
PYRCC = pyrcc5 PYRCC = pyrcc5
#Directory with ui files #Directory with ui files
RESOURCE_DIR = resources/_ui RESOURCE_DIR = src/resources/_ui
#Directory for compiled resources #Directory for compiled resources
COMPILED_DIR = nmreval/gui_qt/_py PYQT_DIR = src/gui_qt/_py
#UI files to compile, uses every *.ui found in RESOURCE_DIR #UI files to compile, uses every *.ui found in RESOURCE_DIR
UI_FILES = $(foreach dir, $(RESOURCE_DIR), $(notdir $(wildcard $(dir)/*.ui))) UI_FILES = $(foreach dir, $(RESOURCE_DIR), $(notdir $(wildcard $(dir)/*.ui)))
COMPILED_UI = $(UI_FILES:%.ui=$(COMPILED_DIR)/%.py) PYQT_UI = $(UI_FILES:%.ui=$(PYQT_DIR)/%.py)
SVG_FILES = $(foreach dir, $(RCC_DIR), $(notdir $(wildcard $(dir)/*.svg))) SVG_FILES = $(foreach dir, $(RCC_DIR), $(notdir $(wildcard $(dir)/*.svg)))
PNG_FILES = $(SVG_FILES:%.svg=$(RCC_DIR)/%.png) PNG_FILES = $(SVG_FILES:%.svg=$(RCC_DIR)/%.png)
all : ui CC = gcc
CFLAGS = -O2 -fPIC
ui : $(COMPILED_UI) LDFLAGS = -shared
rcc: $(PNG_FILES) C_DIR = src/nmreval/clib
all : ui compile
ui : $(PYQT_UI)
rcc : $(PNG_FILES)
# only one C file at the moment
compile : $(C_DIR)/integrate.c
$(CC) $(LDFLAGS) $(CFLAGS) -o $(C_DIR)/integrate.so $<
$(COMPILED_DIR)/%.py : $(RESOURCE_DIR)/%.ui $(COMPILED_DIR)/%.py : $(RESOURCE_DIR)/%.ui
$(PYUIC) $< -o $@ $(PYUIC) $< -o $@
# replace import of ressource to correct path
# @sed -i s/images_rc/nmrevalqt.$(COMPILED_DIR).images_rc/g $@
# @sed -i /images_rc/d $@
$(RCC_DIR)/%.png : $(RCC_DIR)/%.svg $(RCC_DIR)/%.png : $(RCC_DIR)/%.svg
convert -background none $< $@ convert -background none $< $@
$(PYRCC) $(RCC_DIR)/images.qrc -o $(COMPILED_DIR)/images_rc.py $(PYRCC) $(RCC_DIR)/images.qrc -o $(COMPILED_DIR)/images_rc.py

View File

@ -1,27 +0,0 @@
#!/usr/bin/env python3
import sys
import pathlib
sys.path.append(str(pathlib.Path(__file__).absolute().parent.parent / 'src'))
from nmreval.configs import check_for_config
# does a directory for config stuff exist? create it if not
check_for_config()
# pyqtgraph warns on Mac if QApplication is created when it is imported
# import pyqtgraph
from nmreval.lib.logger import handle_exception
sys.excepthook = handle_exception
from gui_qt import App
app = App(['Team Rocket FTW!'])
from gui_qt.main.mainwindow import NMRMainWindow
mplQt = NMRMainWindow()
mplQt.show()
sys.exit(app.exec())

View File

@ -1,54 +1,53 @@
[metadata] [build-system]
name = nmreval requires = ["setuptools", "wheel", "setuptools-scm"]
version = 0.1 build-backend = "setuptools.build_meta"
description = Evaluation of data
long_description = file: README.md [project]
author = Dominik Demuth name = "nmreval"
author_email = dominik.demuth@physik.tu-darmstadt.de version = "0.1"
install_requires = [ description = "Evaluation of NMR and orther data"
'numpy', authors = [
'scipy', { name = "Dominik Demuth", email = "dominik.demuth@pkm.tu-darmstadt.de" },
'matplotlib',
'bsddb3',
'pyqtgraph',
'pyqt',
'h5py',
] ]
keywords = ['nmr', 'physics', 'science'] maintainers = [
{ name = "Dominik Demuth", email = "dominik.demuth@pkm.tu-darmstadt.de" },
]
requires-python = ">=3.7"
classifiers = [ classifiers = [
'Development Status :: 3 - Alpha', "Development Status :: 3 - Alpha",
'Intended Audience :: End Users/Desktop', "Intended Audience :: Science/Research",
'Intended Audience :: Science/Research', "Topic :: Scientific/Engineering :: Physics",
'Topic :: Scientific/Engineering :: Physics', "License :: OSI Approved :: BSD License",
'Topic :: Scientific/Engineering :: Visualization', "Programming Language :: Python :: 3",
'Programming Language :: Python :: 3', "Programming Language :: Python :: 3.7",
'Programming Language :: Python :: 3.7', "Programming Language :: Python :: 3.8",
'Programming Language :: Python :: 3.8', "Programming Language :: Python :: 3.9",
'Programming Language :: Python :: 3.9', "Programming Language :: Python :: 3.10",
'Programming Language :: Python :: 3.10', "Programming Language :: Python :: 3.10",
'Programming Language :: Python :: 3 :: only', "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: only",
"Operating System :: POSIX :: Linux"
]
keywords = ["nmr", "physics", "science"]
license = { text = "BSD 3-Clause License" }
dependencies = [
"numpy",
"scipy",
"h5py",
"PyQt5",
"pyqtgraph",
] ]
license = {text = "BSD 3-Clause License"}
[tool.setuptools] [project.optional-dependencies]
include_package_data = true legacy = ["bsddb3"]
[project.urls]
Repository = "https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval"
Issues = "https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/issues"
[tool.setuptools.packages] [project.gui-scripts]
find = {} nmreval = "gui_qt.cli:main"
scripts = bin/evaluate.py
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include =[ where = ["src"]
'nmreval*',
'gui_qt*',
'resources*',
]
[tool.setuptools.package_data]
* = *.txt, *.npz, *.png, *.json

View File

@ -1,8 +0,0 @@
matplotlib
numpy
scipy
PyQt5
h5py
pyqtgraph
bsddb3
requests

View File

@ -1,4 +0,0 @@
# Always prefer setuptools over distutils
from setuptools import setup
setup()

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '_ui/apod_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/apod_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -13,62 +14,223 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ApodEdit(object): class Ui_ApodEdit(object):
def setupUi(self, ApodEdit): def setupUi(self, ApodEdit):
ApodEdit.setObjectName("ApodEdit") ApodEdit.setObjectName("ApodEdit")
ApodEdit.resize(784, 484) ApodEdit.resize(1144, 655)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(ApodEdit.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(ApodEdit.sizePolicy().hasHeightForWidth())
ApodEdit.setSizePolicy(sizePolicy) ApodEdit.setSizePolicy(sizePolicy)
self.gridLayout = QtWidgets.QGridLayout(ApodEdit) self.gridLayout = QtWidgets.QGridLayout(ApodEdit)
self.gridLayout.setContentsMargins(3, 3, 3, 3) self.gridLayout.setContentsMargins(9, 9, 9, 9)
self.gridLayout.setSpacing(3) self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.graphicsView = PlotWidget(ApodEdit) self.freq_graph = NMRPlotWidget(ApodEdit)
self.freq_graph.setObjectName("freq_graph")
self.gridLayout.addWidget(self.freq_graph, 2, 2, 1, 1)
self.time_graph = NMRPlotWidget(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.time_graph.sizePolicy().hasHeightForWidth())
self.graphicsView.setSizePolicy(sizePolicy) self.time_graph.setSizePolicy(sizePolicy)
self.graphicsView.setObjectName("graphicsView") self.time_graph.setObjectName("time_graph")
self.gridLayout.addWidget(self.graphicsView, 2, 0, 1, 1) self.gridLayout.addWidget(self.time_graph, 2, 1, 1, 1)
self.graphicsView_2 = PlotWidget(ApodEdit) self.widget = QtWidgets.QWidget(ApodEdit)
self.graphicsView_2.setObjectName("graphicsView_2") sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
self.gridLayout.addWidget(self.graphicsView_2, 2, 1, 1, 1)
self.apodcombobox = QtWidgets.QComboBox(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.apodcombobox.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
self.apodcombobox.setSizePolicy(sizePolicy) self.widget.setSizePolicy(sizePolicy)
self.widget.setObjectName("widget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
self.verticalLayout.setObjectName("verticalLayout")
self.baseline_box = QtWidgets.QCheckBox(self.widget)
self.baseline_box.setObjectName("baseline_box")
self.verticalLayout.addWidget(self.baseline_box)
self.shift_box = QtWidgets.QGroupBox(self.widget)
self.shift_box.setFlat(True)
self.shift_box.setCheckable(True)
self.shift_box.setChecked(False)
self.shift_box.setObjectName("shift_box")
self.gridLayout_4 = QtWidgets.QGridLayout(self.shift_box)
self.gridLayout_4.setContentsMargins(3, 3, 3, 3)
self.gridLayout_4.setSpacing(3)
self.gridLayout_4.setObjectName("gridLayout_4")
self.ls_lineedit = QtWidgets.QLineEdit(self.shift_box)
self.ls_lineedit.setObjectName("ls_lineedit")
self.gridLayout_4.addWidget(self.ls_lineedit, 1, 1, 1, 1)
self.ls_spinbox = QtWidgets.QSpinBox(self.shift_box)
self.ls_spinbox.setMaximum(999999)
self.ls_spinbox.setObjectName("ls_spinbox")
self.gridLayout_4.addWidget(self.ls_spinbox, 0, 1, 1, 1)
self.ls_combobox = QtWidgets.QComboBox(self.shift_box)
self.ls_combobox.setObjectName("ls_combobox")
self.ls_combobox.addItem("")
self.ls_combobox.addItem("")
self.gridLayout_4.addWidget(self.ls_combobox, 0, 0, 2, 1)
self.verticalLayout.addWidget(self.shift_box)
self.apod_box = QtWidgets.QGroupBox(self.widget)
self.apod_box.setFlat(True)
self.apod_box.setCheckable(True)
self.apod_box.setChecked(False)
self.apod_box.setObjectName("apod_box")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.apod_box)
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.apodcombobox = QtWidgets.QComboBox(self.apod_box)
self.apodcombobox.setObjectName("apodcombobox") self.apodcombobox.setObjectName("apodcombobox")
self.gridLayout.addWidget(self.apodcombobox, 0, 0, 1, 1) self.verticalLayout_2.addWidget(self.apodcombobox)
self.eqn_label = QtWidgets.QLabel(self.apod_box)
self.eqn_label.setIndent(3)
self.eqn_label.setObjectName("eqn_label")
self.verticalLayout_2.addWidget(self.eqn_label)
self.widget_layout = QtWidgets.QHBoxLayout() self.widget_layout = QtWidgets.QHBoxLayout()
self.widget_layout.setContentsMargins(-1, 6, -1, -1) self.widget_layout.setContentsMargins(-1, 6, -1, -1)
self.widget_layout.setSpacing(20) self.widget_layout.setSpacing(20)
self.widget_layout.setObjectName("widget_layout") self.widget_layout.setObjectName("widget_layout")
self.gridLayout.addLayout(self.widget_layout, 1, 0, 1, 2) self.verticalLayout_2.addLayout(self.widget_layout)
self.verticalLayout.addWidget(self.apod_box)
self.zerofill_box = QtWidgets.QGroupBox(self.widget)
self.zerofill_box.setFlat(True)
self.zerofill_box.setCheckable(True)
self.zerofill_box.setChecked(False)
self.zerofill_box.setObjectName("zerofill_box")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.zerofill_box)
self.horizontalLayout.setContentsMargins(3, 3, 3, 3)
self.horizontalLayout.setSpacing(3)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.zerofill_box)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.zf_spinbox = QtWidgets.QSpinBox(self.zerofill_box)
self.zf_spinbox.setMinimum(1)
self.zf_spinbox.setMaximum(3)
self.zf_spinbox.setObjectName("zf_spinbox")
self.horizontalLayout.addWidget(self.zf_spinbox)
self.verticalLayout.addWidget(self.zerofill_box)
self.phase_box = QtWidgets.QGroupBox(self.widget)
self.phase_box.setFlat(True)
self.phase_box.setCheckable(True)
self.phase_box.setChecked(False)
self.phase_box.setObjectName("phase_box")
self.gridLayout_2 = QtWidgets.QGridLayout(self.phase_box)
self.gridLayout_2.setContentsMargins(3, 3, 3, 3)
self.gridLayout_2.setSpacing(3)
self.gridLayout_2.setObjectName("gridLayout_2")
self.label_3 = QtWidgets.QLabel(self.phase_box)
self.label_3.setObjectName("label_3")
self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(self.phase_box)
self.label_4.setObjectName("label_4")
self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1)
self.ph0_spinbox = QtWidgets.QDoubleSpinBox(self.phase_box)
self.ph0_spinbox.setWrapping(True)
self.ph0_spinbox.setDecimals(1)
self.ph0_spinbox.setMinimum(-180.0)
self.ph0_spinbox.setMaximum(180.0)
self.ph0_spinbox.setSingleStep(0.5)
self.ph0_spinbox.setObjectName("ph0_spinbox")
self.gridLayout_2.addWidget(self.ph0_spinbox, 0, 1, 1, 1)
self.pivot_lineedit = QtWidgets.QLineEdit(self.phase_box)
self.pivot_lineedit.setObjectName("pivot_lineedit")
self.gridLayout_2.addWidget(self.pivot_lineedit, 2, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.phase_box)
self.label_2.setObjectName("label_2")
self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1)
self.ph1_spinbox = QtWidgets.QDoubleSpinBox(self.phase_box)
self.ph1_spinbox.setWrapping(True)
self.ph1_spinbox.setDecimals(2)
self.ph1_spinbox.setMinimum(-720.0)
self.ph1_spinbox.setMaximum(720.0)
self.ph1_spinbox.setSingleStep(0.05)
self.ph1_spinbox.setObjectName("ph1_spinbox")
self.gridLayout_2.addWidget(self.ph1_spinbox, 1, 1, 1, 1)
self.verticalLayout.addWidget(self.phase_box)
self.ft_box = QtWidgets.QGroupBox(self.widget)
self.ft_box.setCheckable(True)
self.ft_box.setChecked(False)
self.ft_box.setObjectName("ft_box")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.ft_box)
self.verticalLayout_3.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_3.setSpacing(3)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.phase_before_button = QtWidgets.QRadioButton(self.ft_box)
self.phase_before_button.setChecked(True)
self.phase_before_button.setObjectName("phase_before_button")
self.buttonGroup = QtWidgets.QButtonGroup(ApodEdit)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.phase_before_button)
self.verticalLayout_3.addWidget(self.phase_before_button)
self.phase_after_button = QtWidgets.QRadioButton(self.ft_box)
self.phase_after_button.setObjectName("phase_after_button")
self.buttonGroup.addButton(self.phase_after_button)
self.verticalLayout_3.addWidget(self.phase_after_button)
self.verticalLayout.addWidget(self.ft_box)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.gridLayout.addWidget(self.widget, 2, 0, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(ApodEdit) self.buttonBox = QtWidgets.QDialogButtonBox(ApodEdit)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox") self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 2) self.gridLayout.addWidget(self.buttonBox, 4, 1, 1, 2)
self.eqn_label = QtWidgets.QLabel(ApodEdit) self.log_freq_widget = QtWidgets.QWidget(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) self.log_freq_widget.setObjectName("log_freq_widget")
sizePolicy.setHorizontalStretch(0) self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.log_freq_widget)
sizePolicy.setVerticalStretch(0) self.horizontalLayout_3.setObjectName("horizontalLayout_3")
sizePolicy.setHeightForWidth(self.eqn_label.sizePolicy().hasHeightForWidth()) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.eqn_label.setSizePolicy(sizePolicy) self.horizontalLayout_3.addItem(spacerItem1)
self.eqn_label.setIndent(3) self.logx_freq = QtWidgets.QCheckBox(self.log_freq_widget)
self.eqn_label.setObjectName("eqn_label") self.logx_freq.setObjectName("logx_freq")
self.gridLayout.addWidget(self.eqn_label, 0, 1, 1, 1) self.horizontalLayout_3.addWidget(self.logx_freq)
self.logy_freq = QtWidgets.QCheckBox(self.log_freq_widget)
self.logy_freq.setObjectName("logy_freq")
self.horizontalLayout_3.addWidget(self.logy_freq)
self.gridLayout.addWidget(self.log_freq_widget, 3, 2, 1, 1)
self.logtime_widget = QtWidgets.QWidget(ApodEdit)
self.logtime_widget.setObjectName("logtime_widget")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.logtime_widget)
self.horizontalLayout_2.setContentsMargins(-1, 1, -1, -1)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.logx_time = QtWidgets.QCheckBox(self.logtime_widget)
self.logx_time.setObjectName("logx_time")
self.horizontalLayout_2.addWidget(self.logx_time)
self.logy_time = QtWidgets.QCheckBox(self.logtime_widget)
self.logy_time.setObjectName("logy_time")
self.horizontalLayout_2.addWidget(self.logy_time)
self.gridLayout.addWidget(self.logtime_widget, 3, 1, 1, 1)
self.retranslateUi(ApodEdit) self.retranslateUi(ApodEdit)
self.buttonBox.accepted.connect(ApodEdit.accept) self.buttonBox.accepted.connect(ApodEdit.accept) # type: ignore
self.buttonBox.rejected.connect(ApodEdit.close) self.buttonBox.rejected.connect(ApodEdit.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(ApodEdit) QtCore.QMetaObject.connectSlotsByName(ApodEdit)
def retranslateUi(self, ApodEdit): def retranslateUi(self, ApodEdit):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
ApodEdit.setWindowTitle(_translate("ApodEdit", "Apodization")) ApodEdit.setWindowTitle(_translate("ApodEdit", "Apodization"))
self.baseline_box.setText(_translate("ApodEdit", "Baseline"))
self.shift_box.setTitle(_translate("ApodEdit", "Shift"))
self.ls_lineedit.setText(_translate("ApodEdit", "0"))
self.ls_combobox.setItemText(0, _translate("ApodEdit", "Points"))
self.ls_combobox.setItemText(1, _translate("ApodEdit", "Seconds"))
self.apod_box.setTitle(_translate("ApodEdit", "Apodization"))
self.eqn_label.setText(_translate("ApodEdit", "TextLabel")) self.eqn_label.setText(_translate("ApodEdit", "TextLabel"))
from pyqtgraph import PlotWidget self.zerofill_box.setTitle(_translate("ApodEdit", "Zero fill"))
self.label.setText(_translate("ApodEdit", "Double length"))
self.zf_spinbox.setSuffix(_translate("ApodEdit", "x"))
self.phase_box.setTitle(_translate("ApodEdit", "Phase correction"))
self.label_3.setText(_translate("ApodEdit", "Phase 1"))
self.label_4.setText(_translate("ApodEdit", "Pivot"))
self.pivot_lineedit.setText(_translate("ApodEdit", "0"))
self.label_2.setText(_translate("ApodEdit", "Phase 0"))
self.ft_box.setTitle(_translate("ApodEdit", "Fourier transform"))
self.phase_before_button.setText(_translate("ApodEdit", "before phase correction"))
self.phase_after_button.setText(_translate("ApodEdit", "after phase correction"))
self.logx_freq.setText(_translate("ApodEdit", "Log X"))
self.logy_freq.setText(_translate("ApodEdit", "Log Y"))
self.logx_time.setText(_translate("ApodEdit", "Log X"))
self.logy_time.setText(_translate("ApodEdit", "Log Y"))
from ..lib.graph_items import NMRPlotWidget

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'src/resources/_ui/asciidialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/asciidialog.ui'
# #
# Created by: PyQt5 UI code generator 5.15.7 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ascii_reader(object): class Ui_ascii_reader(object):
def setupUi(self, ascii_reader): def setupUi(self, ascii_reader):
ascii_reader.setObjectName("ascii_reader") ascii_reader.setObjectName("ascii_reader")
ascii_reader.resize(627, 703) ascii_reader.resize(665, 904)
self.verticalLayout = QtWidgets.QVBoxLayout(ascii_reader) self.verticalLayout = QtWidgets.QVBoxLayout(ascii_reader)
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.tabWidget = QtWidgets.QTabWidget(ascii_reader) self.tabWidget = QtWidgets.QTabWidget(ascii_reader)
@ -28,92 +28,25 @@ class Ui_ascii_reader(object):
self.header_widget.setObjectName("header_widget") self.header_widget.setObjectName("header_widget")
self.verticalLayout_3.addWidget(self.header_widget) self.verticalLayout_3.addWidget(self.header_widget)
self.groupBox = QtWidgets.QGroupBox(self.tabWidgetPage1) self.groupBox = QtWidgets.QGroupBox(self.tabWidgetPage1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
self.groupBox.setSizePolicy(sizePolicy)
self.groupBox.setObjectName("groupBox") self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox) self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox)
self.gridLayout.setContentsMargins(3, 3, 3, 3) self.horizontalLayout_4.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setHorizontalSpacing(9) self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.gridLayout.setObjectName("gridLayout") self.verticalLayout_6 = QtWidgets.QVBoxLayout()
self.FID_radioButton = QtWidgets.QRadioButton(self.groupBox) self.verticalLayout_6.setObjectName("verticalLayout_6")
self.FID_radioButton.setAutoExclusive(True)
self.FID_radioButton.setObjectName("FID_radioButton")
self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.FID_radioButton)
self.gridLayout.addWidget(self.FID_radioButton, 2, 1, 1, 1)
self.x_lineedit = QtWidgets.QLineEdit(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.x_lineedit.sizePolicy().hasHeightForWidth())
self.x_lineedit.setSizePolicy(sizePolicy)
self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
self.x_lineedit.setObjectName("x_lineedit")
self.gridLayout.addWidget(self.x_lineedit, 1, 3, 1, 1)
self.y_label = QtWidgets.QLabel(self.groupBox)
self.y_label.setObjectName("y_label")
self.gridLayout.addWidget(self.y_label, 2, 2, 1, 1)
self.pts_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.pts_radioButton.setChecked(True)
self.pts_radioButton.setAutoExclusive(True)
self.pts_radioButton.setObjectName("pts_radioButton")
self.buttonGroup.addButton(self.pts_radioButton)
self.gridLayout.addWidget(self.pts_radioButton, 1, 1, 1, 1)
self.deltay_lineEdit = QtWidgets.QLineEdit(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.deltay_lineEdit.sizePolicy().hasHeightForWidth())
self.deltay_lineEdit.setSizePolicy(sizePolicy)
self.deltay_lineEdit.setObjectName("deltay_lineEdit")
self.gridLayout.addWidget(self.deltay_lineEdit, 3, 3, 1, 1)
self.label_5 = QtWidgets.QLabel(self.groupBox)
self.label_5.setObjectName("label_5")
self.gridLayout.addWidget(self.label_5, 3, 2, 1, 1)
self.column_checkBox = QtWidgets.QCheckBox(self.groupBox) self.column_checkBox = QtWidgets.QCheckBox(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.column_checkBox.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.column_checkBox.sizePolicy().hasHeightForWidth())
self.column_checkBox.setSizePolicy(sizePolicy) self.column_checkBox.setSizePolicy(sizePolicy)
self.column_checkBox.setObjectName("column_checkBox") self.column_checkBox.setObjectName("column_checkBox")
self.gridLayout.addWidget(self.column_checkBox, 0, 0, 1, 1) self.verticalLayout_6.addWidget(self.column_checkBox)
self.label = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 1, 1, 1)
self.label_7 = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth())
self.label_7.setSizePolicy(sizePolicy)
self.label_7.setObjectName("label_7")
self.gridLayout.addWidget(self.label_7, 0, 2, 1, 2)
self.y_lineedit = QtWidgets.QLineEdit(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.y_lineedit.sizePolicy().hasHeightForWidth())
self.y_lineedit.setSizePolicy(sizePolicy)
self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
self.y_lineedit.setObjectName("y_lineedit")
self.gridLayout.addWidget(self.y_lineedit, 2, 3, 1, 1)
self.spectrum_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.spectrum_radioButton.setObjectName("spectrum_radioButton")
self.buttonGroup.addButton(self.spectrum_radioButton)
self.gridLayout.addWidget(self.spectrum_radioButton, 3, 1, 1, 1)
self.x_label = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.x_label.sizePolicy().hasHeightForWidth())
self.x_label.setSizePolicy(sizePolicy)
self.x_label.setObjectName("x_label")
self.gridLayout.addWidget(self.x_label, 1, 2, 1, 1)
self.line_spinBox = QtWidgets.QSpinBox(self.groupBox) self.line_spinBox = QtWidgets.QSpinBox(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
@ -122,17 +55,158 @@ class Ui_ascii_reader(object):
self.line_spinBox.setSizePolicy(sizePolicy) self.line_spinBox.setSizePolicy(sizePolicy)
self.line_spinBox.setMinimum(1) self.line_spinBox.setMinimum(1)
self.line_spinBox.setObjectName("line_spinBox") self.line_spinBox.setObjectName("line_spinBox")
self.gridLayout.addWidget(self.line_spinBox, 1, 0, 1, 1) self.verticalLayout_6.addWidget(self.line_spinBox)
self.label_6 = QtWidgets.QLabel(self.groupBox)
self.label_6.setObjectName("label_6")
self.verticalLayout_6.addWidget(self.label_6)
self.preview_spinBox = QtWidgets.QSpinBox(self.groupBox) self.preview_spinBox = QtWidgets.QSpinBox(self.groupBox)
self.preview_spinBox.setMinimum(1) self.preview_spinBox.setMinimum(1)
self.preview_spinBox.setProperty("value", 10) self.preview_spinBox.setProperty("value", 10)
self.preview_spinBox.setObjectName("preview_spinBox") self.preview_spinBox.setObjectName("preview_spinBox")
self.gridLayout.addWidget(self.preview_spinBox, 3, 0, 1, 1) self.verticalLayout_6.addWidget(self.preview_spinBox)
self.label_6 = QtWidgets.QLabel(self.groupBox) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.label_6.setObjectName("label_6") self.verticalLayout_6.addItem(spacerItem)
self.gridLayout.addWidget(self.label_6, 2, 0, 1, 1) self.horizontalLayout_4.addLayout(self.verticalLayout_6)
self.verticalLayout_5 = QtWidgets.QVBoxLayout()
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.label = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setObjectName("label")
self.verticalLayout_5.addWidget(self.label)
self.pts_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.pts_radioButton.setChecked(True)
self.pts_radioButton.setAutoExclusive(True)
self.pts_radioButton.setObjectName("pts_radioButton")
self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.pts_radioButton)
self.verticalLayout_5.addWidget(self.pts_radioButton)
self.dsc_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.dsc_radioButton.setObjectName("dsc_radioButton")
self.buttonGroup.addButton(self.dsc_radioButton)
self.verticalLayout_5.addWidget(self.dsc_radioButton)
self.FID_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.FID_radioButton.setAutoExclusive(True)
self.FID_radioButton.setObjectName("FID_radioButton")
self.buttonGroup.addButton(self.FID_radioButton)
self.verticalLayout_5.addWidget(self.FID_radioButton)
self.spectrum_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.spectrum_radioButton.setObjectName("spectrum_radioButton")
self.buttonGroup.addButton(self.spectrum_radioButton)
self.verticalLayout_5.addWidget(self.spectrum_radioButton)
self.bds_radioButton = QtWidgets.QRadioButton(self.groupBox)
self.bds_radioButton.setObjectName("bds_radioButton")
self.buttonGroup.addButton(self.bds_radioButton)
self.verticalLayout_5.addWidget(self.bds_radioButton)
self.horizontalLayout_4.addLayout(self.verticalLayout_5)
self.gridLayout_2 = QtWidgets.QGridLayout()
self.gridLayout_2.setObjectName("gridLayout_2")
self.deltay_lineEdit = QtWidgets.QLineEdit(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.deltay_lineEdit.sizePolicy().hasHeightForWidth())
self.deltay_lineEdit.setSizePolicy(sizePolicy)
self.deltay_lineEdit.setObjectName("deltay_lineEdit")
self.gridLayout_2.addWidget(self.deltay_lineEdit, 3, 1, 1, 1)
self.label_7 = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth())
self.label_7.setSizePolicy(sizePolicy)
self.label_7.setObjectName("label_7")
self.gridLayout_2.addWidget(self.label_7, 0, 0, 1, 2)
self.y_lineedit = QtWidgets.QLineEdit(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.y_lineedit.sizePolicy().hasHeightForWidth())
self.y_lineedit.setSizePolicy(sizePolicy)
self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
self.y_lineedit.setObjectName("y_lineedit")
self.gridLayout_2.addWidget(self.y_lineedit, 2, 1, 1, 1)
self.y_label = QtWidgets.QLabel(self.groupBox)
self.y_label.setObjectName("y_label")
self.gridLayout_2.addWidget(self.y_label, 2, 0, 1, 1)
self.x_lineedit = QtWidgets.QLineEdit(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.x_lineedit.sizePolicy().hasHeightForWidth())
self.x_lineedit.setSizePolicy(sizePolicy)
self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
self.x_lineedit.setObjectName("x_lineedit")
self.gridLayout_2.addWidget(self.x_lineedit, 1, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(self.groupBox)
self.label_5.setObjectName("label_5")
self.gridLayout_2.addWidget(self.label_5, 3, 0, 1, 1)
self.x_label = QtWidgets.QLabel(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.x_label.sizePolicy().hasHeightForWidth())
self.x_label.setSizePolicy(sizePolicy)
self.x_label.setObjectName("x_label")
self.gridLayout_2.addWidget(self.x_label, 1, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem1, 4, 1, 1, 1)
self.horizontalLayout_4.addLayout(self.gridLayout_2)
self.verticalLayout_3.addWidget(self.groupBox) self.verticalLayout_3.addWidget(self.groupBox)
self.dsdfsf = QtWidgets.QLabel(self.tabWidgetPage1)
self.dsdfsf.setObjectName("dsdfsf")
self.verticalLayout_3.addWidget(self.dsdfsf)
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.re_match_index = QtWidgets.QSpinBox(self.tabWidgetPage1)
self.re_match_index.setMinimum(1)
self.re_match_index.setObjectName("re_match_index")
self.gridLayout.addWidget(self.re_match_index, 1, 3, 1, 1)
self.label_9 = QtWidgets.QLabel(self.tabWidgetPage1)
self.label_9.setObjectName("label_9")
self.gridLayout.addWidget(self.label_9, 1, 2, 1, 1)
self.regex_input = QtWidgets.QLineEdit(self.tabWidgetPage1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.regex_input.sizePolicy().hasHeightForWidth())
self.regex_input.setSizePolicy(sizePolicy)
self.regex_input.setObjectName("regex_input")
self.gridLayout.addWidget(self.regex_input, 1, 1, 1, 1)
self.re_button = QtWidgets.QRadioButton(self.tabWidgetPage1)
self.re_button.setChecked(True)
self.re_button.setObjectName("re_button")
self.buttonGroup_2 = QtWidgets.QButtonGroup(ascii_reader)
self.buttonGroup_2.setObjectName("buttonGroup_2")
self.buttonGroup_2.addButton(self.re_button)
self.gridLayout.addWidget(self.re_button, 1, 0, 1, 1)
self.custom_input = QtWidgets.QLineEdit(self.tabWidgetPage1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.custom_input.sizePolicy().hasHeightForWidth())
self.custom_input.setSizePolicy(sizePolicy)
self.custom_input.setObjectName("custom_input")
self.gridLayout.addWidget(self.custom_input, 2, 1, 1, 1)
self.custom_button = QtWidgets.QRadioButton(self.tabWidgetPage1)
self.custom_button.setObjectName("custom_button")
self.buttonGroup_2.addButton(self.custom_button)
self.gridLayout.addWidget(self.custom_button, 2, 0, 1, 1)
self.label_8 = QtWidgets.QLabel(self.tabWidgetPage1)
self.label_8.setWordWrap(True)
self.label_8.setObjectName("label_8")
self.gridLayout.addWidget(self.label_8, 0, 0, 1, 4)
self.verticalLayout_3.addLayout(self.gridLayout)
self.groupBox_2 = QtWidgets.QGroupBox(self.tabWidgetPage1) self.groupBox_2 = QtWidgets.QGroupBox(self.tabWidgetPage1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
self.groupBox_2.setSizePolicy(sizePolicy)
self.groupBox_2.setObjectName("groupBox_2") self.groupBox_2.setObjectName("groupBox_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2)
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
@ -215,24 +289,19 @@ class Ui_ascii_reader(object):
self.pushButton.setSizePolicy(sizePolicy) self.pushButton.setSizePolicy(sizePolicy)
self.pushButton.setObjectName("pushButton") self.pushButton.setObjectName("pushButton")
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.pushButton) self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.pushButton)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem) self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem2)
self.horizontalLayout_3.addLayout(self.formLayout) self.horizontalLayout_3.addLayout(self.formLayout)
self.tabWidget.addTab(self.tabWidgetPage2, "") self.tabWidget.addTab(self.tabWidgetPage2, "")
self.verticalLayout.addWidget(self.tabWidget) self.verticalLayout.addWidget(self.tabWidget)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.verticalLayout.addLayout(self.horizontalLayout)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem3)
self.skippy_checkbox = QtWidgets.QCheckBox(ascii_reader) self.skippy_checkbox = QtWidgets.QCheckBox(ascii_reader)
self.skippy_checkbox.setObjectName("skippy_checkbox") self.skippy_checkbox.setObjectName("skippy_checkbox")
self.horizontalLayout_2.addWidget(self.skippy_checkbox) self.horizontalLayout_2.addWidget(self.skippy_checkbox)
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem2)
self.verticalLayout.addLayout(self.horizontalLayout_2) self.verticalLayout.addLayout(self.horizontalLayout_2)
self.buttonbox = QtWidgets.QDialogButtonBox(ascii_reader) self.buttonbox = QtWidgets.QDialogButtonBox(ascii_reader)
self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
@ -247,14 +316,20 @@ class Ui_ascii_reader(object):
ascii_reader.setTabOrder(self.column_checkBox, self.line_spinBox) ascii_reader.setTabOrder(self.column_checkBox, self.line_spinBox)
ascii_reader.setTabOrder(self.line_spinBox, self.preview_spinBox) ascii_reader.setTabOrder(self.line_spinBox, self.preview_spinBox)
ascii_reader.setTabOrder(self.preview_spinBox, self.pts_radioButton) ascii_reader.setTabOrder(self.preview_spinBox, self.pts_radioButton)
ascii_reader.setTabOrder(self.pts_radioButton, self.FID_radioButton) ascii_reader.setTabOrder(self.pts_radioButton, self.dsc_radioButton)
ascii_reader.setTabOrder(self.dsc_radioButton, self.FID_radioButton)
ascii_reader.setTabOrder(self.FID_radioButton, self.spectrum_radioButton) ascii_reader.setTabOrder(self.FID_radioButton, self.spectrum_radioButton)
ascii_reader.setTabOrder(self.spectrum_radioButton, self.x_lineedit) ascii_reader.setTabOrder(self.spectrum_radioButton, self.bds_radioButton)
ascii_reader.setTabOrder(self.bds_radioButton, self.x_lineedit)
ascii_reader.setTabOrder(self.x_lineedit, self.y_lineedit) ascii_reader.setTabOrder(self.x_lineedit, self.y_lineedit)
ascii_reader.setTabOrder(self.y_lineedit, self.deltay_lineEdit) ascii_reader.setTabOrder(self.y_lineedit, self.deltay_lineEdit)
ascii_reader.setTabOrder(self.deltay_lineEdit, self.ascii_table) ascii_reader.setTabOrder(self.deltay_lineEdit, self.re_button)
ascii_reader.setTabOrder(self.ascii_table, self.skippy_checkbox) ascii_reader.setTabOrder(self.re_button, self.regex_input)
ascii_reader.setTabOrder(self.skippy_checkbox, self.delay_textfield) ascii_reader.setTabOrder(self.regex_input, self.re_match_index)
ascii_reader.setTabOrder(self.re_match_index, self.custom_button)
ascii_reader.setTabOrder(self.custom_button, self.custom_input)
ascii_reader.setTabOrder(self.custom_input, self.ascii_table)
ascii_reader.setTabOrder(self.ascii_table, self.delay_textfield)
ascii_reader.setTabOrder(self.delay_textfield, self.delay_lineedit) ascii_reader.setTabOrder(self.delay_textfield, self.delay_lineedit)
ascii_reader.setTabOrder(self.delay_lineedit, self.start_lineedit) ascii_reader.setTabOrder(self.delay_lineedit, self.start_lineedit)
ascii_reader.setTabOrder(self.start_lineedit, self.end_lineedit) ascii_reader.setTabOrder(self.start_lineedit, self.end_lineedit)
@ -262,24 +337,33 @@ class Ui_ascii_reader(object):
ascii_reader.setTabOrder(self.log_checkBox, self.staggered_checkBox) ascii_reader.setTabOrder(self.log_checkBox, self.staggered_checkBox)
ascii_reader.setTabOrder(self.staggered_checkBox, self.stag_lineEdit) ascii_reader.setTabOrder(self.staggered_checkBox, self.stag_lineEdit)
ascii_reader.setTabOrder(self.stag_lineEdit, self.pushButton) ascii_reader.setTabOrder(self.stag_lineEdit, self.pushButton)
ascii_reader.setTabOrder(self.pushButton, self.skippy_checkbox)
def retranslateUi(self, ascii_reader): def retranslateUi(self, ascii_reader):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
ascii_reader.setWindowTitle(_translate("ascii_reader", "Read text file")) ascii_reader.setWindowTitle(_translate("ascii_reader", "Read text file"))
self.groupBox.setTitle(_translate("ascii_reader", "Options")) self.groupBox.setTitle(_translate("ascii_reader", "Options"))
self.FID_radioButton.setText(_translate("ascii_reader", "FID"))
self.x_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which column is used as x-value.</p></body></html>"))
self.y_label.setText(_translate("ascii_reader", "y"))
self.pts_radioButton.setText(_translate("ascii_reader", "Points"))
self.label_5.setText(_translate("ascii_reader", "<html><head/><body><p>Δy</p></body></html>"))
self.column_checkBox.setText(_translate("ascii_reader", "Column name")) self.column_checkBox.setText(_translate("ascii_reader", "Column name"))
self.label.setText(_translate("ascii_reader", "Import as"))
self.label_7.setText(_translate("ascii_reader", "Use columns as"))
self.y_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which columns are read for y-values. (\'Points\': Every number creates a new data set;\'FID\'/\'Spectrum\': Numbers at even positions are used for real parts, at odd positions for imaginary parts.)</p></body></html>"))
self.spectrum_radioButton.setText(_translate("ascii_reader", "Spectrum"))
self.x_label.setText(_translate("ascii_reader", "x"))
self.line_spinBox.setPrefix(_translate("ascii_reader", "header line ")) self.line_spinBox.setPrefix(_translate("ascii_reader", "header line "))
self.label_6.setText(_translate("ascii_reader", "Preview length")) self.label_6.setText(_translate("ascii_reader", "Preview length"))
self.label.setText(_translate("ascii_reader", "Import as"))
self.pts_radioButton.setText(_translate("ascii_reader", "Points"))
self.dsc_radioButton.setText(_translate("ascii_reader", "DSC"))
self.FID_radioButton.setText(_translate("ascii_reader", "FID"))
self.spectrum_radioButton.setText(_translate("ascii_reader", "Spectrum"))
self.bds_radioButton.setText(_translate("ascii_reader", "BDS"))
self.label_7.setText(_translate("ascii_reader", "Use columns as"))
self.y_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which columns are used for y values.</p><p>- \'Points\': Every number creates a new data set;<br/>- \'FID\'/\'Spectrum\': Numbers at even positions are used for real parts, at odd positions for imaginary parts.</p></body></html>"))
self.y_label.setText(_translate("ascii_reader", "y"))
self.x_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which column is used for x values, write <span style=\" font-style:italic;\">index</span> to use row numbers (starting with 0). </p></body></html>"))
self.label_5.setText(_translate("ascii_reader", "<html><head/><body><p>Δy</p></body></html>"))
self.x_label.setText(_translate("ascii_reader", "x"))
self.dsdfsf.setText(_translate("ascii_reader", "Numerical value"))
self.label_9.setText(_translate("ascii_reader", "Match index"))
self.regex_input.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Token:<br/>[abc]: Matches any of a, b, or c<br/>[a-z]: Matches any digit in the range a-z<br/>\\d: Matches any digit in the range 0-9 (equal to [0-9}</p><p>Quantifiers:<br/>a*: 0 or more of a<br/>a*: 1 or more of a<br/>a?: 0 or 1 of a</p></body></html>"))
self.re_button.setText(_translate("ascii_reader", "Regex"))
self.custom_button.setText(_translate("ascii_reader", "Custom value"))
self.label_8.setText(_translate("ascii_reader", "Filename"))
self.groupBox_2.setTitle(_translate("ascii_reader", "Preview")) self.groupBox_2.setTitle(_translate("ascii_reader", "Preview"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("ascii_reader", "Data")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("ascii_reader", "Data"))
self.label_2.setText(_translate("ascii_reader", "Number of delays")) self.label_2.setText(_translate("ascii_reader", "Number of delays"))

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '_ui/baseline_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/baseline_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -44,7 +45,7 @@ class Ui_SignalEdit(object):
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox") self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 3) self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 3)
self.graphicsView = PlotWidget(SignalEdit) self.graphicsView = NMRPlotWidget(SignalEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -54,11 +55,11 @@ class Ui_SignalEdit(object):
self.gridLayout.addWidget(self.graphicsView, 0, 2, 1, 1) self.gridLayout.addWidget(self.graphicsView, 0, 2, 1, 1)
self.retranslateUi(SignalEdit) self.retranslateUi(SignalEdit)
self.buttonBox.accepted.connect(SignalEdit.accept) self.buttonBox.accepted.connect(SignalEdit.accept) # type: ignore
self.buttonBox.rejected.connect(SignalEdit.close) self.buttonBox.rejected.connect(SignalEdit.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(SignalEdit) QtCore.QMetaObject.connectSlotsByName(SignalEdit)
def retranslateUi(self, SignalEdit): def retranslateUi(self, SignalEdit):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
SignalEdit.setWindowTitle(_translate("SignalEdit", "Dialog")) SignalEdit.setWindowTitle(_translate("SignalEdit", "Dialog"))
from pyqtgraph import PlotWidget from ..lib.graph_items import NMRPlotWidget

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'src/resources/_ui/basewindow.ui' # Form implementation generated from reading ui file 'src/resources/_ui/basewindow.ui'
# #
# Created by: PyQt5 UI code generator 5.15.7 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -70,7 +70,7 @@ class Ui_BaseWindow(object):
self.integralwidget = IntegralWidget() self.integralwidget = IntegralWidget()
self.integralwidget.setObjectName("integralwidget") self.integralwidget.setObjectName("integralwidget")
self.tabWidget.addTab(self.integralwidget, "") self.tabWidget.addTab(self.integralwidget, "")
self.area = QtWidgets.QMdiArea(self.splitter) self.area = MdiAreaTile(self.splitter)
self.area.setObjectName("area") self.area.setObjectName("area")
self.horizontalLayout.addWidget(self.splitter) self.horizontalLayout.addWidget(self.splitter)
BaseWindow.setCentralWidget(self.centralwidget) BaseWindow.setCentralWidget(self.centralwidget)
@ -117,6 +117,8 @@ class Ui_BaseWindow(object):
self.menuStuff = QtWidgets.QMenu(self.menubar) self.menuStuff = QtWidgets.QMenu(self.menubar)
self.menuStuff.setTitle("") self.menuStuff.setTitle("")
self.menuStuff.setObjectName("menuStuff") self.menuStuff.setObjectName("menuStuff")
self.menuDSC = QtWidgets.QMenu(self.menubar)
self.menuDSC.setObjectName("menuDSC")
BaseWindow.setMenuBar(self.menubar) BaseWindow.setMenuBar(self.menubar)
self.toolBar = QtWidgets.QToolBar(BaseWindow) self.toolBar = QtWidgets.QToolBar(BaseWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
@ -261,6 +263,10 @@ class Ui_BaseWindow(object):
self.actionMaximize.setObjectName("actionMaximize") self.actionMaximize.setObjectName("actionMaximize")
self.actionTile = QtWidgets.QAction(BaseWindow) self.actionTile = QtWidgets.QAction(BaseWindow)
self.actionTile.setObjectName("actionTile") self.actionTile.setObjectName("actionTile")
self.actionTileVertical = QtWidgets.QAction(BaseWindow)
self.actionTileVertical.setObjectName("actionTileVertical")
self.actionTileHorizontal = QtWidgets.QAction(BaseWindow)
self.actionTileHorizontal.setObjectName("actionTileHorizontal")
self.actionMinimize = QtWidgets.QAction(BaseWindow) self.actionMinimize = QtWidgets.QAction(BaseWindow)
self.actionMinimize.setCheckable(True) self.actionMinimize.setCheckable(True)
self.actionMinimize.setVisible(False) self.actionMinimize.setVisible(False)
@ -358,6 +364,16 @@ class Ui_BaseWindow(object):
self.actionBugs.setObjectName("actionBugs") self.actionBugs.setObjectName("actionBugs")
self.actionShow_error_log = QtWidgets.QAction(BaseWindow) self.actionShow_error_log = QtWidgets.QAction(BaseWindow)
self.actionShow_error_log.setObjectName("actionShow_error_log") self.actionShow_error_log.setObjectName("actionShow_error_log")
self.actionCreate_starter = QtWidgets.QAction(BaseWindow)
self.actionCreate_starter.setObjectName("actionCreate_starter")
self.actionAbout = QtWidgets.QAction(BaseWindow)
self.actionAbout.setObjectName("actionAbout")
self.actionTNMH_model = QtWidgets.QAction(BaseWindow)
self.actionTNMH_model.setObjectName("actionTNMH_model")
self.actionBinning = QtWidgets.QAction(BaseWindow)
self.actionBinning.setObjectName("actionBinning")
self.actionTNMH = QtWidgets.QAction(BaseWindow)
self.actionTNMH.setObjectName("actionTNMH")
self.menuSave.addAction(self.actionSave) self.menuSave.addAction(self.actionSave)
self.menuSave.addAction(self.actionExportGraphic) self.menuSave.addAction(self.actionExportGraphic)
self.menuSave.addAction(self.action_save_fit_parameter) self.menuSave.addAction(self.action_save_fit_parameter)
@ -383,9 +399,9 @@ class Ui_BaseWindow(object):
self.menuData.addSeparator() self.menuData.addSeparator()
self.menuData.addAction(self.actionChange_datatypes) self.menuData.addAction(self.actionChange_datatypes)
self.menuHelp.addAction(self.actionShow_error_log) self.menuHelp.addAction(self.actionShow_error_log)
self.menuHelp.addAction(self.actionDocumentation)
self.menuHelp.addAction(self.actionUpdate) self.menuHelp.addAction(self.actionUpdate)
self.menuHelp.addAction(self.actionBugs) self.menuHelp.addAction(self.actionBugs)
self.menuHelp.addAction(self.actionAbout)
self.menuNormalize.addAction(self.action_norm_max) self.menuNormalize.addAction(self.action_norm_max)
self.menuNormalize.addAction(self.action_norm_max_abs) self.menuNormalize.addAction(self.action_norm_max_abs)
self.menuNormalize.addSeparator() self.menuNormalize.addSeparator()
@ -401,6 +417,7 @@ class Ui_BaseWindow(object):
self.menuExtra.addSeparator() self.menuExtra.addSeparator()
self.menuExtra.addAction(self.menuNormalize.menuAction()) self.menuExtra.addAction(self.menuNormalize.menuAction())
self.menuExtra.addAction(self.actionInterpolation) self.menuExtra.addAction(self.actionInterpolation)
self.menuExtra.addAction(self.actionBinning)
self.menuExtra.addAction(self.actionRunning_values) self.menuExtra.addAction(self.actionRunning_values)
self.menuExtra.addAction(self.actionShift) self.menuExtra.addAction(self.actionShift)
self.menuExtra.addSeparator() self.menuExtra.addSeparator()
@ -422,7 +439,10 @@ class Ui_BaseWindow(object):
self.menuOptions.addSeparator() self.menuOptions.addSeparator()
self.menuOptions.addAction(self.action_colorcycle) self.menuOptions.addAction(self.action_colorcycle)
self.menuOptions.addAction(self.actionConfiguration) self.menuOptions.addAction(self.actionConfiguration)
self.menuOptions.addAction(self.actionCreate_starter)
self.menuView.addAction(self.actionTile) self.menuView.addAction(self.actionTile)
self.menuView.addAction(self.actionTileVertical)
self.menuView.addAction(self.actionTileHorizontal)
self.menuView.addAction(self.actionCascade_windows) self.menuView.addAction(self.actionCascade_windows)
self.menuWindow.addAction(self.actionNew_window) self.menuWindow.addAction(self.actionNew_window)
self.menuWindow.addAction(self.actionDelete_window) self.menuWindow.addAction(self.actionDelete_window)
@ -451,6 +471,7 @@ class Ui_BaseWindow(object):
self.menuStuff.addAction(self.actionLife) self.menuStuff.addAction(self.actionLife)
self.menuStuff.addAction(self.actionTetris) self.menuStuff.addAction(self.actionTetris)
self.menuStuff.addAction(self.actionMine) self.menuStuff.addAction(self.actionMine)
self.menuDSC.addAction(self.actionTNMH_model)
self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuWindow.menuAction()) self.menubar.addAction(self.menuWindow.menuAction())
self.menubar.addAction(self.menuData.menuAction()) self.menubar.addAction(self.menuData.menuAction())
@ -459,6 +480,7 @@ class Ui_BaseWindow(object):
self.menubar.addAction(self.menuFit.menuAction()) self.menubar.addAction(self.menuFit.menuAction())
self.menubar.addAction(self.menuNMR.menuAction()) self.menubar.addAction(self.menuNMR.menuAction())
self.menubar.addAction(self.menuBDS.menuAction()) self.menubar.addAction(self.menuBDS.menuAction())
self.menubar.addAction(self.menuDSC.menuAction())
self.menubar.addAction(self.menuOptions.menuAction()) self.menubar.addAction(self.menuOptions.menuAction())
self.menubar.addAction(self.menuHelp.menuAction()) self.menubar.addAction(self.menuHelp.menuAction())
self.menubar.addAction(self.menuStuff.menuAction()) self.menubar.addAction(self.menuStuff.menuAction())
@ -511,6 +533,7 @@ class Ui_BaseWindow(object):
self.menuNMR.setTitle(_translate("BaseWindow", "NMR")) self.menuNMR.setTitle(_translate("BaseWindow", "NMR"))
self.menuBDS.setTitle(_translate("BaseWindow", "BDS")) self.menuBDS.setTitle(_translate("BaseWindow", "BDS"))
self.menuSpectrum.setTitle(_translate("BaseWindow", "Spectrum")) self.menuSpectrum.setTitle(_translate("BaseWindow", "Spectrum"))
self.menuDSC.setTitle(_translate("BaseWindow", "DSC"))
self.toolBar.setWindowTitle(_translate("BaseWindow", "Main")) self.toolBar.setWindowTitle(_translate("BaseWindow", "Main"))
self.toolbar_edit.setWindowTitle(_translate("BaseWindow", "Math")) self.toolbar_edit.setWindowTitle(_translate("BaseWindow", "Math"))
self.toolBar_nmr.setWindowTitle(_translate("BaseWindow", "NMR")) self.toolBar_nmr.setWindowTitle(_translate("BaseWindow", "NMR"))
@ -566,6 +589,8 @@ class Ui_BaseWindow(object):
self.actionGuide_lines.setText(_translate("BaseWindow", "Draw lines...")) self.actionGuide_lines.setText(_translate("BaseWindow", "Draw lines..."))
self.actionMaximize.setText(_translate("BaseWindow", "Maximize")) self.actionMaximize.setText(_translate("BaseWindow", "Maximize"))
self.actionTile.setText(_translate("BaseWindow", "Tile windows")) self.actionTile.setText(_translate("BaseWindow", "Tile windows"))
self.actionTileVertical.setText(_translate("BaseWindow", "Tile windows vertically"))
self.actionTileHorizontal.setText(_translate("BaseWindow", "Tile windows horizontally"))
self.actionMinimize.setText(_translate("BaseWindow", "Minimize")) self.actionMinimize.setText(_translate("BaseWindow", "Minimize"))
self.actionNew_window.setText(_translate("BaseWindow", "New graph")) self.actionNew_window.setText(_translate("BaseWindow", "New graph"))
self.actionDelete_window.setText(_translate("BaseWindow", "Delete graph")) self.actionDelete_window.setText(_translate("BaseWindow", "Delete graph"))
@ -612,6 +637,11 @@ class Ui_BaseWindow(object):
self.action_draw_object.setText(_translate("BaseWindow", "Draw objects...")) self.action_draw_object.setText(_translate("BaseWindow", "Draw objects..."))
self.actionBugs.setText(_translate("BaseWindow", "Bugs! Problems! Wishes!")) self.actionBugs.setText(_translate("BaseWindow", "Bugs! Problems! Wishes!"))
self.actionShow_error_log.setText(_translate("BaseWindow", "Show error log")) self.actionShow_error_log.setText(_translate("BaseWindow", "Show error log"))
self.actionCreate_starter.setText(_translate("BaseWindow", "Create starter.."))
self.actionAbout.setText(_translate("BaseWindow", "About..."))
self.actionTNMH_model.setText(_translate("BaseWindow", "Tg , Hodge, TNMH,,,"))
self.actionBinning.setText(_translate("BaseWindow", "Binning..."))
self.actionTNMH.setText(_translate("BaseWindow", "TNMH..."))
from ..data.datawidget.datawidget import DataWidget from ..data.datawidget.datawidget import DataWidget
from ..data.integral_widget import IntegralWidget from ..data.integral_widget import IntegralWidget
from ..data.point_select import PointSelectWidget from ..data.point_select import PointSelectWidget
@ -619,4 +649,5 @@ from ..data.signaledit.editsignalwidget import EditSignalWidget
from ..data.valueeditwidget import ValueEditWidget from ..data.valueeditwidget import ValueEditWidget
from ..fit.fitwindow import QFitDialog from ..fit.fitwindow import QFitDialog
from ..graphs.drawings import DrawingsWidget from ..graphs.drawings import DrawingsWidget
from ..lib.mdiarea import MdiAreaTile
from ..nmr.t1widget import QT1Widget from ..nmr.t1widget import QT1Widget

View File

@ -1,13 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/bdsdialog.ui'
# #
# Created by: PyQt5 UI code generator 5.9.2 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object): class Ui_Dialog(object):
def setupUi(self, Dialog): def setupUi(self, Dialog):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
@ -16,12 +19,13 @@ class Ui_Dialog(object):
self.gridLayout.setContentsMargins(3, 3, 3, 3) self.gridLayout.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setSpacing(3) self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.listWidget = QtWidgets.QListWidget(Dialog) self.listWidget = QListWidgetSelect(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listWidget.setObjectName("listWidget") self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 0, 2, 1) self.gridLayout.addWidget(self.listWidget, 1, 0, 2, 1)
self.groupBox_2 = QtWidgets.QGroupBox(Dialog) self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
@ -93,8 +97,8 @@ class Ui_Dialog(object):
self.gridLayout.addWidget(self.label, 0, 0, 1, 2) self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.freq_button, self.temp_button) Dialog.setTabOrder(self.freq_button, self.temp_button)
Dialog.setTabOrder(self.temp_button, self.eps_checkBox) Dialog.setTabOrder(self.temp_button, self.eps_checkBox)
@ -117,4 +121,4 @@ class Ui_Dialog(object):
self.temp_checkBox.setText(_translate("Dialog", "Meas. temperature")) self.temp_checkBox.setText(_translate("Dialog", "Meas. temperature"))
self.time_checkBox.setText(_translate("Dialog", "Meas. time")) self.time_checkBox.setText(_translate("Dialog", "Meas. time"))
self.label.setText(_translate("Dialog", "Found entries")) self.label.setText(_translate("Dialog", "Found entries"))
from ..lib.listwidget import QListWidgetSelect

View File

@ -1,29 +1,113 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/dscfile_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/dscfile_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.9.2 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object): class Ui_Dialog(object):
def setupUi(self, Dialog): def setupUi(self, Dialog):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
Dialog.resize(962, 662) Dialog.resize(1341, 799)
self.gridLayout_2 = QtWidgets.QGridLayout(Dialog) self.verticalLayout_5 = QtWidgets.QVBoxLayout(Dialog)
self.gridLayout_2.setObjectName("gridLayout_2") self.verticalLayout_5.setObjectName("verticalLayout_5")
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.splitter = QtWidgets.QSplitter(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Save) self.splitter.setObjectName("splitter")
self.buttonBox.setObjectName("buttonBox") self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter)
self.gridLayout_2.addWidget(self.buttonBox, 1, 1, 1, 1) self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.gridLayout_4 = QtWidgets.QGridLayout() self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.gridLayout_4.setContentsMargins(-1, 0, 0, -1) self.verticalLayout_4.setContentsMargins(6, 6, 6, 6)
self.gridLayout_4.setSpacing(3) self.verticalLayout_4.setObjectName("verticalLayout_4")
self.gridLayout_4.setObjectName("gridLayout_4") self.groupBox = QtWidgets.QGroupBox(self.verticalLayoutWidget)
self.cp_checkBox = QtWidgets.QCheckBox(Dialog) self.groupBox.setFlat(False)
self.groupBox.setObjectName("groupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout.setContentsMargins(6, 6, 6, 6)
self.verticalLayout.setObjectName("verticalLayout")
self.step_listWidget = QtWidgets.QListWidget(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.step_listWidget.sizePolicy().hasHeightForWidth())
self.step_listWidget.setSizePolicy(sizePolicy)
self.step_listWidget.setMinimumSize(QtCore.QSize(0, 0))
self.step_listWidget.setObjectName("step_listWidget")
self.verticalLayout.addWidget(self.step_listWidget)
self.verticalLayout_4.addWidget(self.groupBox)
self.groupBox_2 = QtWidgets.QGroupBox(self.verticalLayoutWidget)
self.groupBox_2.setObjectName("groupBox_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2)
self.verticalLayout_2.setContentsMargins(6, 6, 6, 6)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.groupBox_4 = QtWidgets.QGroupBox(self.groupBox_2)
self.groupBox_4.setObjectName("groupBox_4")
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_4)
self.verticalLayout_6.setContentsMargins(6, 6, 6, 6)
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.empty_label = QtWidgets.QLabel(self.groupBox_4)
self.empty_label.setObjectName("empty_label")
self.verticalLayout_6.addWidget(self.empty_label)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(3)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.loadempty_button = QtWidgets.QPushButton(self.groupBox_4)
self.loadempty_button.setObjectName("loadempty_button")
self.horizontalLayout_2.addWidget(self.loadempty_button)
self.delempty_button = QtWidgets.QPushButton(self.groupBox_4)
self.delempty_button.setObjectName("delempty_button")
self.horizontalLayout_2.addWidget(self.delempty_button)
self.verticalLayout_6.addLayout(self.horizontalLayout_2)
self.verticalLayout_2.addWidget(self.groupBox_4)
self.groupBox_5 = QtWidgets.QGroupBox(self.groupBox_2)
self.groupBox_5.setObjectName("groupBox_5")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_5)
self.verticalLayout_7.setContentsMargins(6, 6, 6, 6)
self.verticalLayout_7.setSpacing(3)
self.verticalLayout_7.setObjectName("verticalLayout_7")
self.none_radioButton = QtWidgets.QRadioButton(self.groupBox_5)
self.none_radioButton.setObjectName("none_radioButton")
self.buttonGroup = QtWidgets.QButtonGroup(Dialog)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.none_radioButton)
self.verticalLayout_7.addWidget(self.none_radioButton)
self.isotherm_radioButton = QtWidgets.QRadioButton(self.groupBox_5)
self.isotherm_radioButton.setChecked(True)
self.isotherm_radioButton.setObjectName("isotherm_radioButton")
self.buttonGroup.addButton(self.isotherm_radioButton)
self.verticalLayout_7.addWidget(self.isotherm_radioButton)
self.slope_radioButton = QtWidgets.QRadioButton(self.groupBox_5)
self.slope_radioButton.setObjectName("slope_radioButton")
self.buttonGroup.addButton(self.slope_radioButton)
self.verticalLayout_7.addWidget(self.slope_radioButton)
self.widget = QtWidgets.QWidget(self.groupBox_5)
self.widget.setMinimumSize(QtCore.QSize(0, 33))
self.widget.setObjectName("widget")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget)
self.horizontalLayout_3.setContentsMargins(3, 3, 3, 3)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.limit1_lineedit = QtWidgets.QLineEdit(self.widget)
self.limit1_lineedit.setObjectName("limit1_lineedit")
self.horizontalLayout_3.addWidget(self.limit1_lineedit)
self.limit2_lineedit = QtWidgets.QLineEdit(self.widget)
self.limit2_lineedit.setText("")
self.limit2_lineedit.setObjectName("limit2_lineedit")
self.horizontalLayout_3.addWidget(self.limit2_lineedit)
self.verticalLayout_7.addWidget(self.widget)
self.verticalLayout_2.addWidget(self.groupBox_5)
self.verticalLayout_4.addWidget(self.groupBox_2)
self.groupBox_3 = QtWidgets.QGroupBox(self.verticalLayoutWidget)
self.groupBox_3.setObjectName("groupBox_3")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_3)
self.verticalLayout_3.setContentsMargins(6, 6, 6, 6)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.cp_checkBox = QtWidgets.QCheckBox(self.groupBox_3)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -31,36 +115,8 @@ class Ui_Dialog(object):
self.cp_checkBox.setSizePolicy(sizePolicy) self.cp_checkBox.setSizePolicy(sizePolicy)
self.cp_checkBox.setChecked(True) self.cp_checkBox.setChecked(True)
self.cp_checkBox.setObjectName("cp_checkBox") self.cp_checkBox.setObjectName("cp_checkBox")
self.gridLayout_4.addWidget(self.cp_checkBox, 11, 0, 1, 4) self.verticalLayout_3.addWidget(self.cp_checkBox)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.reference_tableWidget = QtWidgets.QTableWidget(self.groupBox_3)
self.horizontalLayout_2.setSpacing(3)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.loadempty_button = QtWidgets.QPushButton(Dialog)
self.loadempty_button.setObjectName("loadempty_button")
self.horizontalLayout_2.addWidget(self.loadempty_button)
self.delempty_button = QtWidgets.QPushButton(Dialog)
self.delempty_button.setObjectName("delempty_button")
self.horizontalLayout_2.addWidget(self.delempty_button)
self.gridLayout_4.addLayout(self.horizontalLayout_2, 5, 0, 1, 4)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_4.addItem(spacerItem, 12, 0, 1, 1)
self.isotherm_radioButton = QtWidgets.QRadioButton(Dialog)
self.isotherm_radioButton.setChecked(True)
self.isotherm_radioButton.setObjectName("isotherm_radioButton")
self.buttonGroup = QtWidgets.QButtonGroup(Dialog)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.isotherm_radioButton)
self.gridLayout_4.addWidget(self.isotherm_radioButton, 6, 1, 1, 1)
self.label_4 = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth())
self.label_4.setSizePolicy(sizePolicy)
self.label_4.setStyleSheet("font-weight: bold")
self.label_4.setObjectName("label_4")
self.gridLayout_4.addWidget(self.label_4, 0, 0, 1, 4)
self.reference_tableWidget = QtWidgets.QTableWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -75,57 +131,11 @@ class Ui_Dialog(object):
self.reference_tableWidget.horizontalHeader().setVisible(False) self.reference_tableWidget.horizontalHeader().setVisible(False)
self.reference_tableWidget.horizontalHeader().setStretchLastSection(True) self.reference_tableWidget.horizontalHeader().setStretchLastSection(True)
self.reference_tableWidget.verticalHeader().setVisible(False) self.reference_tableWidget.verticalHeader().setVisible(False)
self.gridLayout_4.addWidget(self.reference_tableWidget, 9, 0, 1, 4) self.verticalLayout_3.addWidget(self.reference_tableWidget)
self.step_listWidget = QtWidgets.QListWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.step_listWidget.sizePolicy().hasHeightForWidth())
self.step_listWidget.setSizePolicy(sizePolicy)
self.step_listWidget.setMinimumSize(QtCore.QSize(0, 0))
self.step_listWidget.setObjectName("step_listWidget")
self.gridLayout_4.addWidget(self.step_listWidget, 1, 0, 1, 4)
self.label = QtWidgets.QLabel(Dialog)
self.label.setObjectName("label")
self.gridLayout_4.addWidget(self.label, 6, 0, 1, 1)
self.slope_radioButton = QtWidgets.QRadioButton(Dialog)
self.slope_radioButton.setObjectName("slope_radioButton")
self.buttonGroup.addButton(self.slope_radioButton)
self.gridLayout_4.addWidget(self.slope_radioButton, 6, 2, 1, 1)
self.empty_label = QtWidgets.QLabel(Dialog)
self.empty_label.setObjectName("empty_label")
self.gridLayout_4.addWidget(self.empty_label, 4, 0, 1, 4)
self.label_3 = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth())
self.label_3.setSizePolicy(sizePolicy)
self.label_3.setStyleSheet("font-weight: bold")
self.label_3.setObjectName("label_3")
self.gridLayout_4.addWidget(self.label_3, 8, 0, 1, 4)
self.label_2 = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
self.label_2.setStyleSheet("font-weight: bold")
self.label_2.setObjectName("label_2")
self.gridLayout_4.addWidget(self.label_2, 3, 0, 1, 4)
self.line = QtWidgets.QFrame(Dialog)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.gridLayout_4.addWidget(self.line, 7, 0, 1, 4)
self.none_radioButton = QtWidgets.QRadioButton(Dialog)
self.none_radioButton.setObjectName("none_radioButton")
self.buttonGroup.addButton(self.none_radioButton)
self.gridLayout_4.addWidget(self.none_radioButton, 6, 3, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(3) self.horizontalLayout.setSpacing(3)
self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setObjectName("horizontalLayout")
self.ref_add_pushButton = QtWidgets.QPushButton(Dialog) self.ref_add_pushButton = QtWidgets.QPushButton(self.groupBox_3)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -133,7 +143,7 @@ class Ui_Dialog(object):
self.ref_add_pushButton.setSizePolicy(sizePolicy) self.ref_add_pushButton.setSizePolicy(sizePolicy)
self.ref_add_pushButton.setObjectName("ref_add_pushButton") self.ref_add_pushButton.setObjectName("ref_add_pushButton")
self.horizontalLayout.addWidget(self.ref_add_pushButton) self.horizontalLayout.addWidget(self.ref_add_pushButton)
self.ref_remove_pushButton = QtWidgets.QPushButton(Dialog) self.ref_remove_pushButton = QtWidgets.QPushButton(self.groupBox_3)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -141,16 +151,22 @@ class Ui_Dialog(object):
self.ref_remove_pushButton.setSizePolicy(sizePolicy) self.ref_remove_pushButton.setSizePolicy(sizePolicy)
self.ref_remove_pushButton.setObjectName("ref_remove_pushButton") self.ref_remove_pushButton.setObjectName("ref_remove_pushButton")
self.horizontalLayout.addWidget(self.ref_remove_pushButton) self.horizontalLayout.addWidget(self.ref_remove_pushButton)
self.gridLayout_4.addLayout(self.horizontalLayout, 10, 0, 1, 4) self.verticalLayout_3.addLayout(self.horizontalLayout)
self.line_2 = QtWidgets.QFrame(Dialog) self.verticalLayout_4.addWidget(self.groupBox_3)
self.line_2.setFrameShape(QtWidgets.QFrame.HLine) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) self.verticalLayout_4.addItem(spacerItem)
self.line_2.setObjectName("line_2") self.buttonBox = QtWidgets.QDialogButtonBox(self.verticalLayoutWidget)
self.gridLayout_4.addWidget(self.line_2, 2, 0, 1, 4) self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.gridLayout_2.addLayout(self.gridLayout_4, 0, 0, 1, 1) self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Save)
self.gridLayout = QtWidgets.QGridLayout() self.buttonBox.setCenterButtons(True)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout_4.addWidget(self.buttonBox)
self.layoutWidget = QtWidgets.QWidget(self.splitter)
self.layoutWidget.setObjectName("layoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.raw_graph = PlotWidget(Dialog) self.raw_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -159,7 +175,7 @@ class Ui_Dialog(object):
self.raw_graph.setMinimumSize(QtCore.QSize(300, 200)) self.raw_graph.setMinimumSize(QtCore.QSize(300, 200))
self.raw_graph.setObjectName("raw_graph") self.raw_graph.setObjectName("raw_graph")
self.gridLayout.addWidget(self.raw_graph, 0, 0, 1, 1) self.gridLayout.addWidget(self.raw_graph, 0, 0, 1, 1)
self.calib_graph = PlotWidget(Dialog) self.calib_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -168,7 +184,7 @@ class Ui_Dialog(object):
self.calib_graph.setMinimumSize(QtCore.QSize(300, 200)) self.calib_graph.setMinimumSize(QtCore.QSize(300, 200))
self.calib_graph.setObjectName("calib_graph") self.calib_graph.setObjectName("calib_graph")
self.gridLayout.addWidget(self.calib_graph, 1, 0, 1, 1) self.gridLayout.addWidget(self.calib_graph, 1, 0, 1, 1)
self.baseline_graph = PlotWidget(Dialog) self.baseline_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -177,7 +193,7 @@ class Ui_Dialog(object):
self.baseline_graph.setMinimumSize(QtCore.QSize(300, 200)) self.baseline_graph.setMinimumSize(QtCore.QSize(300, 200))
self.baseline_graph.setObjectName("baseline_graph") self.baseline_graph.setObjectName("baseline_graph")
self.gridLayout.addWidget(self.baseline_graph, 0, 1, 1, 1) self.gridLayout.addWidget(self.baseline_graph, 0, 1, 1, 1)
self.end_graph = PlotWidget(Dialog) self.end_graph = NMRPlotWidget(self.layoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -186,28 +202,30 @@ class Ui_Dialog(object):
self.end_graph.setMinimumSize(QtCore.QSize(0, 0)) self.end_graph.setMinimumSize(QtCore.QSize(0, 0))
self.end_graph.setObjectName("end_graph") self.end_graph.setObjectName("end_graph")
self.gridLayout.addWidget(self.end_graph, 1, 1, 1, 1) self.gridLayout.addWidget(self.end_graph, 1, 1, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 0, 1, 1, 1) self.verticalLayout_5.addWidget(self.splitter)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog): def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Read DSC file")) Dialog.setWindowTitle(_translate("Dialog", "Read DSC file"))
self.cp_checkBox.setText(_translate("Dialog", "Convert to heat capacity")) self.groupBox.setTitle(_translate("Dialog", "Detected steps"))
self.groupBox_2.setTitle(_translate("Dialog", "Baseline corrections"))
self.groupBox_4.setTitle(_translate("Dialog", "Empty measurement"))
self.empty_label.setText(_translate("Dialog", "No emtpy measurement"))
self.loadempty_button.setText(_translate("Dialog", "Load empty")) self.loadempty_button.setText(_translate("Dialog", "Load empty"))
self.delempty_button.setText(_translate("Dialog", "Remove empty")) self.delempty_button.setText(_translate("Dialog", "Remove empty"))
self.isotherm_radioButton.setText(_translate("Dialog", "Isotherms")) self.groupBox_5.setTitle(_translate("Dialog", "Slope correction"))
self.label_4.setText(_translate("Dialog", "Detected steps"))
self.label.setText(_translate("Dialog", "Slope"))
self.slope_radioButton.setText(_translate("Dialog", "Initial slope"))
self.empty_label.setText(_translate("Dialog", "Empty measurement"))
self.label_3.setText(_translate("Dialog", "Calibration"))
self.label_2.setText(_translate("Dialog", "Baseline"))
self.none_radioButton.setText(_translate("Dialog", "None")) self.none_radioButton.setText(_translate("Dialog", "None"))
self.isotherm_radioButton.setText(_translate("Dialog", "Isotherms"))
self.slope_radioButton.setText(_translate("Dialog", "Curve slope"))
self.limit1_lineedit.setPlaceholderText(_translate("Dialog", "start (in min)"))
self.limit2_lineedit.setPlaceholderText(_translate("Dialog", "stop (in min)"))
self.groupBox_3.setTitle(_translate("Dialog", "References"))
self.cp_checkBox.setText(_translate("Dialog", "Use reference to convert to heat capacity"))
self.ref_add_pushButton.setText(_translate("Dialog", "Add reference")) self.ref_add_pushButton.setText(_translate("Dialog", "Add reference"))
self.ref_remove_pushButton.setText(_translate("Dialog", "Remove reference")) self.ref_remove_pushButton.setText(_translate("Dialog", "Remove reference"))
from ..lib.graph_items import NMRPlotWidget
from pyqtgraph import PlotWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/eval_expr_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/eval_expr_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.2
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -36,14 +37,14 @@ class Ui_CalcDialog(object):
self.label_2 = QtWidgets.QLabel(self.page) self.label_2 = QtWidgets.QLabel(self.page)
self.label_2.setObjectName("label_2") self.label_2.setObjectName("label_2")
self.verticalLayout_2.addWidget(self.label_2) self.verticalLayout_2.addWidget(self.label_2)
self.listWidget = QtWidgets.QListWidget(self.page) self.listWidget = QListWidgetSelect(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.listWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listWidget.setObjectName("listWidget") self.listWidget.setObjectName("listWidget")
self.verticalLayout_2.addWidget(self.listWidget) self.verticalLayout_2.addWidget(self.listWidget)
self.overwrite_checkbox = QtWidgets.QCheckBox(self.page) self.overwrite_checkbox = QtWidgets.QCheckBox(self.page)
@ -202,7 +203,7 @@ class Ui_CalcDialog(object):
self.label_8.setBuddy(self.line_doubleSpinBox) self.label_8.setBuddy(self.line_doubleSpinBox)
self.retranslateUi(CalcDialog) self.retranslateUi(CalcDialog)
self.stackedWidget.setCurrentIndex(2) self.stackedWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(CalcDialog) QtCore.QMetaObject.connectSlotsByName(CalcDialog)
CalcDialog.setTabOrder(self.calc_edit, self.listWidget) CalcDialog.setTabOrder(self.calc_edit, self.listWidget)
CalcDialog.setTabOrder(self.listWidget, self.overwrite_checkbox) CalcDialog.setTabOrder(self.listWidget, self.overwrite_checkbox)
@ -237,4 +238,5 @@ class Ui_CalcDialog(object):
self.label_11.setText(_translate("CalcDialog", "Style")) self.label_11.setText(_translate("CalcDialog", "Style"))
self.label.setText(_translate("CalcDialog", "Expressions are evaluated line by line and change previous values")) self.label.setText(_translate("CalcDialog", "Expressions are evaluated line by line and change previous values"))
from ..lib.delegates import ColorListEditor, LineStyleEditor, SymbolStyleEditor from ..lib.delegates import ColorListEditor, LineStyleEditor, SymbolStyleEditor
from ..lib.listwidget import QListWidgetSelect
from ..lib.namespace import QNamespaceWidget from ..lib.namespace import QNamespaceWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/fitdialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/fitdialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.2
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -37,7 +38,6 @@ class Ui_FitDialog(object):
self.weight_combobox.addItem("") self.weight_combobox.addItem("")
self.weight_combobox.addItem("") self.weight_combobox.addItem("")
self.weight_combobox.addItem("") self.weight_combobox.addItem("")
self.weight_combobox.addItem("")
self.gridLayout_2.addWidget(self.weight_combobox, 6, 1, 1, 1) self.gridLayout_2.addWidget(self.weight_combobox, 6, 1, 1, 1)
self.newmodel_button = QtWidgets.QPushButton(self.scrollAreaWidgetContents_2) self.newmodel_button = QtWidgets.QPushButton(self.scrollAreaWidgetContents_2)
self.newmodel_button.setEnabled(False) self.newmodel_button.setEnabled(False)
@ -143,7 +143,6 @@ class Ui_FitDialog(object):
self.weight_combobox.setItemText(1, _translate("FitDialog", "y")) self.weight_combobox.setItemText(1, _translate("FitDialog", "y"))
self.weight_combobox.setItemText(2, _translate("FitDialog", "")) self.weight_combobox.setItemText(2, _translate("FitDialog", ""))
self.weight_combobox.setItemText(3, _translate("FitDialog", "Δy")) self.weight_combobox.setItemText(3, _translate("FitDialog", "Δy"))
self.weight_combobox.setItemText(4, _translate("FitDialog", "log(y)"))
self.newmodel_button.setText(_translate("FitDialog", "New model")) self.newmodel_button.setText(_translate("FitDialog", "New model"))
self.deletemodel_button.setText(_translate("FitDialog", "Delete model")) self.deletemodel_button.setText(_translate("FitDialog", "Delete model"))
self.label_3.setText(_translate("FitDialog", "Weight")) self.label_3.setText(_translate("FitDialog", "Weight"))

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/fitmodelwidget.ui' # Form implementation generated from reading ui file 'src/resources/_ui/fitmodelwidget.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_FitParameter(object): class Ui_FitParameter(object):
def setupUi(self, FitParameter): def setupUi(self, FitParameter):
FitParameter.setObjectName("FitParameter") FitParameter.setObjectName("FitParameter")
FitParameter.resize(365, 78) FitParameter.resize(365, 66)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -36,7 +37,7 @@ class Ui_FitParameter(object):
self.parametername.setObjectName("parametername") self.parametername.setObjectName("parametername")
self.horizontalLayout_2.addWidget(self.parametername) self.horizontalLayout_2.addWidget(self.parametername)
self.parameter_line = LineEdit(FitParameter) self.parameter_line = LineEdit(FitParameter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.parameter_line.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.parameter_line.sizePolicy().hasHeightForWidth())
@ -44,20 +45,12 @@ class Ui_FitParameter(object):
self.parameter_line.setText("") self.parameter_line.setText("")
self.parameter_line.setObjectName("parameter_line") self.parameter_line.setObjectName("parameter_line")
self.horizontalLayout_2.addWidget(self.parameter_line) self.horizontalLayout_2.addWidget(self.parameter_line)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.fixed_check = QtWidgets.QCheckBox(FitParameter) self.fixed_check = QtWidgets.QCheckBox(FitParameter)
self.fixed_check.setObjectName("fixed_check") self.fixed_check.setObjectName("fixed_check")
self.horizontalLayout_2.addWidget(self.fixed_check) self.horizontalLayout_2.addWidget(self.fixed_check)
self.global_checkbox = QtWidgets.QCheckBox(FitParameter) self.global_checkbox = QtWidgets.QCheckBox(FitParameter)
self.global_checkbox.setObjectName("global_checkbox") self.global_checkbox.setObjectName("global_checkbox")
self.horizontalLayout_2.addWidget(self.global_checkbox) self.horizontalLayout_2.addWidget(self.global_checkbox)
self.toolButton = QtWidgets.QToolButton(FitParameter)
self.toolButton.setText("")
self.toolButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.toolButton.setArrowType(QtCore.Qt.RightArrow)
self.toolButton.setObjectName("toolButton")
self.horizontalLayout_2.addWidget(self.toolButton)
self.verticalLayout.addLayout(self.horizontalLayout_2) self.verticalLayout.addLayout(self.horizontalLayout_2)
self.frame = QtWidgets.QFrame(FitParameter) self.frame = QtWidgets.QFrame(FitParameter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './resources/_ui/fitresult.ui' # Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -14,117 +14,41 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object): class Ui_Dialog(object):
def setupUi(self, Dialog): def setupUi(self, Dialog):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
Dialog.resize(817, 584) Dialog.resize(969, 974)
self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.sets_comboBox = ElideComboBox(Dialog) self.stack = QtWidgets.QTabWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth())
self.sets_comboBox.setSizePolicy(sizePolicy)
self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215))
self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0))
self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.sets_comboBox.setObjectName("sets_comboBox")
self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2)
self.param_tableWidget = QtWidgets.QTableWidget(Dialog)
self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.param_tableWidget.setAlternatingRowColors(True)
self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.param_tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectColumns)
self.param_tableWidget.setShowGrid(False)
self.param_tableWidget.setColumnCount(0)
self.param_tableWidget.setObjectName("param_tableWidget")
self.param_tableWidget.setRowCount(0)
self.param_tableWidget.horizontalHeader().setStretchLastSection(False)
self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1)
self.groupBox = QtWidgets.QGroupBox(Dialog)
self.groupBox.setObjectName("groupBox")
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout_2.setContentsMargins(3, 3, 3, 3)
self.gridLayout_2.setSpacing(3)
self.gridLayout_2.setObjectName("gridLayout_2")
self.graph_checkBox = QtWidgets.QCheckBox(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graph_checkBox.sizePolicy().hasHeightForWidth())
self.graph_checkBox.setSizePolicy(sizePolicy)
self.graph_checkBox.setChecked(True)
self.graph_checkBox.setObjectName("graph_checkBox")
self.gridLayout_2.addWidget(self.graph_checkBox, 1, 1, 1, 1)
self.graph_comboBox = QtWidgets.QComboBox(self.groupBox)
self.graph_comboBox.setEnabled(False)
self.graph_comboBox.setObjectName("graph_comboBox")
self.gridLayout_2.addWidget(self.graph_comboBox, 1, 2, 1, 1)
self.curve_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.curve_checkbox.setChecked(True)
self.curve_checkbox.setObjectName("curve_checkbox")
self.gridLayout_2.addWidget(self.curve_checkbox, 0, 0, 1, 1)
self.partial_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.partial_checkBox.setObjectName("partial_checkBox")
self.gridLayout_2.addWidget(self.partial_checkBox, 1, 0, 1, 1)
self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.parameter_checkbox.setChecked(True)
self.parameter_checkbox.setObjectName("parameter_checkbox")
self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 1, 1, 1)
self.gridLayout.addWidget(self.groupBox, 5, 0, 1, 2)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(3)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog)
self.reject_fit_checkBox.setObjectName("reject_fit_checkBox")
self.horizontalLayout_2.addWidget(self.reject_fit_checkBox)
self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog)
self.del_prev_checkBox.setObjectName("del_prev_checkBox")
self.horizontalLayout_2.addWidget(self.del_prev_checkBox)
self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 1)
self.line = QtWidgets.QFrame(Dialog)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.gridLayout.addWidget(self.line, 3, 0, 1, 2)
self.stack = QtWidgets.QToolBox(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth())
self.stack.setSizePolicy(sizePolicy) self.stack.setSizePolicy(sizePolicy)
self.stack.setObjectName("stack") self.stack.setObjectName("stack")
self.page = QtWidgets.QWidget() self.stackPage1 = QtWidgets.QWidget()
self.page.setGeometry(QtCore.QRect(0, 0, 399, 346)) self.stackPage1.setObjectName("stackPage1")
self.page.setObjectName("page") self.gridLayout_3 = QtWidgets.QGridLayout(self.stackPage1)
self.gridLayout_3 = QtWidgets.QGridLayout(self.page)
self.gridLayout_3.setContentsMargins(3, 3, 3, 3) self.gridLayout_3.setContentsMargins(3, 3, 3, 3)
self.gridLayout_3.setSpacing(3) self.gridLayout_3.setSpacing(3)
self.gridLayout_3.setObjectName("gridLayout_3") self.gridLayout_3.setObjectName("gridLayout_3")
self.logy_box = QtWidgets.QCheckBox(self.page) self.logy_box = QtWidgets.QCheckBox(self.stackPage1)
self.logy_box.setLayoutDirection(QtCore.Qt.RightToLeft) self.logy_box.setLayoutDirection(QtCore.Qt.RightToLeft)
self.logy_box.setObjectName("logy_box") self.logy_box.setObjectName("logy_box")
self.gridLayout_3.addWidget(self.logy_box, 2, 1, 1, 1) self.gridLayout_3.addWidget(self.logy_box, 2, 1, 1, 1)
self.logx_box = QtWidgets.QCheckBox(self.page) self.logx_box = QtWidgets.QCheckBox(self.stackPage1)
self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft) self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft)
self.logx_box.setObjectName("logx_box") self.logx_box.setObjectName("logx_box")
self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1) self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1)
self.graphicsView = GraphicsLayoutWidget(self.page) self.graphicsView = GraphicsLayoutWidget(self.stackPage1)
self.graphicsView.setObjectName("graphicsView") self.graphicsView.setObjectName("graphicsView")
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2) self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2)
self.stack.addItem(self.page, "") self.stack.addTab(self.stackPage1, "")
self.page_2 = QtWidgets.QWidget() self.stackPage2 = QtWidgets.QWidget()
self.page_2.setGeometry(QtCore.QRect(0, 0, 399, 346)) self.stackPage2.setObjectName("stackPage2")
self.page_2.setObjectName("page_2") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.stackPage2)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.page_2)
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_2.setSpacing(3) self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2") self.verticalLayout_2.setObjectName("verticalLayout_2")
self.stats_tableWidget = QtWidgets.QTableWidget(self.page_2) self.stats_tableWidget = QtWidgets.QTableWidget(self.stackPage2)
self.stats_tableWidget.setFrameShape(QtWidgets.QFrame.Box) self.stats_tableWidget.setFrameShape(QtWidgets.QFrame.Box)
self.stats_tableWidget.setGridStyle(QtCore.Qt.NoPen) self.stats_tableWidget.setGridStyle(QtCore.Qt.NoPen)
self.stats_tableWidget.setColumnCount(1) self.stats_tableWidget.setColumnCount(1)
@ -133,15 +57,14 @@ class Ui_Dialog(object):
self.stats_tableWidget.horizontalHeader().setVisible(False) self.stats_tableWidget.horizontalHeader().setVisible(False)
self.stats_tableWidget.horizontalHeader().setSortIndicatorShown(True) self.stats_tableWidget.horizontalHeader().setSortIndicatorShown(True)
self.verticalLayout_2.addWidget(self.stats_tableWidget) self.verticalLayout_2.addWidget(self.stats_tableWidget)
self.stack.addItem(self.page_2, "") self.stack.addTab(self.stackPage2, "")
self.page_3 = QtWidgets.QWidget() self.stackPage3 = QtWidgets.QWidget()
self.page_3.setGeometry(QtCore.QRect(0, 0, 399, 346)) self.stackPage3.setObjectName("stackPage3")
self.page_3.setObjectName("page_3") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.stackPage3)
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_3)
self.verticalLayout_3.setContentsMargins(3, 3, 3, 3) self.verticalLayout_3.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_3.setSpacing(3) self.verticalLayout_3.setSpacing(3)
self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setObjectName("verticalLayout_3")
self.corr_tableWidget = QtWidgets.QTableWidget(self.page_3) self.corr_tableWidget = QtWidgets.QTableWidget(self.stackPage3)
self.corr_tableWidget.setFrameShape(QtWidgets.QFrame.Box) self.corr_tableWidget.setFrameShape(QtWidgets.QFrame.Box)
self.corr_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.corr_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.corr_tableWidget.setGridStyle(QtCore.Qt.NoPen) self.corr_tableWidget.setGridStyle(QtCore.Qt.NoPen)
@ -159,28 +82,131 @@ class Ui_Dialog(object):
self.corr_tableWidget.horizontalHeader().setStretchLastSection(True) self.corr_tableWidget.horizontalHeader().setStretchLastSection(True)
self.corr_tableWidget.verticalHeader().setVisible(False) self.corr_tableWidget.verticalHeader().setVisible(False)
self.verticalLayout_3.addWidget(self.corr_tableWidget) self.verticalLayout_3.addWidget(self.corr_tableWidget)
self.stack.addItem(self.page_3, "") self.stack.addTab(self.stackPage3, "")
self.gridLayout.addWidget(self.stack, 0, 1, 3, 1) self.gridLayout.addWidget(self.stack, 0, 1, 5, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 8, 0, 1, 2)
self.param_tableWidget = QtWidgets.QTableWidget(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.param_tableWidget.sizePolicy().hasHeightForWidth())
self.param_tableWidget.setSizePolicy(sizePolicy)
self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.param_tableWidget.setAlternatingRowColors(True)
self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.param_tableWidget.setColumnCount(1)
self.param_tableWidget.setObjectName("param_tableWidget")
self.param_tableWidget.setRowCount(0)
self.param_tableWidget.horizontalHeader().setVisible(False)
self.param_tableWidget.horizontalHeader().setStretchLastSection(True)
self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1)
self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog)
self.del_prev_checkBox.setObjectName("del_prev_checkBox")
self.gridLayout.addWidget(self.del_prev_checkBox, 3, 0, 1, 1)
self.line = QtWidgets.QFrame(Dialog)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.gridLayout.addWidget(self.line, 5, 0, 1, 2)
self.groupBox = QtWidgets.QGroupBox(Dialog)
self.groupBox.setObjectName("groupBox")
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout_2.setContentsMargins(3, 3, 3, 3)
self.gridLayout_2.setSpacing(3)
self.gridLayout_2.setObjectName("gridLayout_2")
self.extrapolate_box = QtWidgets.QCheckBox(self.groupBox)
self.extrapolate_box.setObjectName("extrapolate_box")
self.gridLayout_2.addWidget(self.extrapolate_box, 1, 0, 1, 1)
self.parameter_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.parameter_checkbox.setObjectName("parameter_checkbox")
self.gridLayout_2.addWidget(self.parameter_checkbox, 0, 5, 1, 1)
self.graph_comboBox = QtWidgets.QComboBox(self.groupBox)
self.graph_comboBox.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graph_comboBox.sizePolicy().hasHeightForWidth())
self.graph_comboBox.setSizePolicy(sizePolicy)
self.graph_comboBox.setObjectName("graph_comboBox")
self.gridLayout_2.addWidget(self.graph_comboBox, 1, 6, 1, 1)
self.graph_checkBox = QtWidgets.QCheckBox(self.groupBox)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graph_checkBox.sizePolicy().hasHeightForWidth())
self.graph_checkBox.setSizePolicy(sizePolicy)
self.graph_checkBox.setChecked(True)
self.graph_checkBox.setObjectName("graph_checkBox")
self.gridLayout_2.addWidget(self.graph_checkBox, 1, 5, 1, 1)
self.minx_line = QtWidgets.QLineEdit(self.groupBox)
self.minx_line.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.minx_line.sizePolicy().hasHeightForWidth())
self.minx_line.setSizePolicy(sizePolicy)
self.minx_line.setObjectName("minx_line")
self.gridLayout_2.addWidget(self.minx_line, 1, 1, 1, 1)
self.line_2 = QtWidgets.QFrame(self.groupBox)
self.line_2.setFrameShape(QtWidgets.QFrame.VLine)
self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line_2.setObjectName("line_2")
self.gridLayout_2.addWidget(self.line_2, 0, 4, 2, 1)
self.maxx_line = QtWidgets.QLineEdit(self.groupBox)
self.maxx_line.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.maxx_line.sizePolicy().hasHeightForWidth())
self.maxx_line.setSizePolicy(sizePolicy)
self.maxx_line.setObjectName("maxx_line")
self.gridLayout_2.addWidget(self.maxx_line, 1, 2, 1, 1)
self.numx_line = QtWidgets.QLineEdit(self.groupBox)
self.numx_line.setEnabled(False)
self.numx_line.setObjectName("numx_line")
self.gridLayout_2.addWidget(self.numx_line, 1, 3, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.curve_checkbox = QtWidgets.QCheckBox(self.groupBox)
self.curve_checkbox.setChecked(True)
self.curve_checkbox.setObjectName("curve_checkbox")
self.horizontalLayout.addWidget(self.curve_checkbox)
self.partial_checkBox = QtWidgets.QCheckBox(self.groupBox)
self.partial_checkBox.setObjectName("partial_checkBox")
self.horizontalLayout.addWidget(self.partial_checkBox)
self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 4)
self.gridLayout.addWidget(self.groupBox, 7, 0, 1, 2)
self.sets_comboBox = ElideComboBox(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth())
self.sets_comboBox.setSizePolicy(sizePolicy)
self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215))
self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0))
self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.sets_comboBox.setObjectName("sets_comboBox")
self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1)
self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog)
self.reject_fit_checkBox.setObjectName("reject_fit_checkBox")
self.gridLayout.addWidget(self.reject_fit_checkBox, 2, 0, 1, 1)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.stack.setCurrentIndex(0) self.stack.setCurrentIndex(0)
self.stack.layout().setSpacing(0)
QtCore.QMetaObject.connectSlotsByName(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog): def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Fit results")) Dialog.setWindowTitle(_translate("Dialog", "Fit results"))
self.groupBox.setTitle(_translate("Dialog", "Output"))
self.graph_checkBox.setText(_translate("Dialog", "New graph"))
self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve"))
self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions"))
self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter"))
self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits"))
self.logy_box.setText(_translate("Dialog", "logarithmic y axis")) self.logy_box.setText(_translate("Dialog", "logarithmic y axis"))
self.logx_box.setText(_translate("Dialog", "logarithmic x axis")) self.logx_box.setText(_translate("Dialog", "logarithmic x axis"))
self.stack.setItemText(self.stack.indexOf(self.page), _translate("Dialog", "Plot")) self.stack.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot"))
self.stack.setItemText(self.stack.indexOf(self.page_2), _translate("Dialog", "Statistics")) self.stack.setTabText(self.stack.indexOf(self.stackPage2), _translate("Dialog", "Statistics"))
item = self.corr_tableWidget.horizontalHeaderItem(0) item = self.corr_tableWidget.horizontalHeaderItem(0)
item.setText(_translate("Dialog", "Parameter 1")) item.setText(_translate("Dialog", "Parameter 1"))
item = self.corr_tableWidget.horizontalHeaderItem(1) item = self.corr_tableWidget.horizontalHeaderItem(1)
@ -189,6 +215,20 @@ class Ui_Dialog(object):
item.setText(_translate("Dialog", "Corr.")) item.setText(_translate("Dialog", "Corr."))
item = self.corr_tableWidget.horizontalHeaderItem(3) item = self.corr_tableWidget.horizontalHeaderItem(3)
item.setText(_translate("Dialog", "Partial Corr.")) item.setText(_translate("Dialog", "Partial Corr."))
self.stack.setItemText(self.stack.indexOf(self.page_3), _translate("Dialog", "Correlations")) self.stack.setTabText(self.stack.indexOf(self.stackPage3), _translate("Dialog", "Correlations"))
self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits of this set"))
self.groupBox.setTitle(_translate("Dialog", "Output"))
self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function"))
self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves"))
self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter"))
self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter"))
self.minx_line.setToolTip(_translate("Dialog", "Leave empty to start at lowest point"))
self.minx_line.setPlaceholderText(_translate("Dialog", "min x"))
self.maxx_line.setToolTip(_translate("Dialog", "Leave empty to start at highest point"))
self.maxx_line.setPlaceholderText(_translate("Dialog", "max x"))
self.numx_line.setPlaceholderText(_translate("Dialog", "# pts"))
self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve"))
self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions"))
self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
from ..lib.forms import ElideComboBox from ..lib.forms import ElideComboBox
from pyqtgraph import GraphicsLayoutWidget from pyqtgraph import GraphicsLayoutWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/graph.ui' # Form implementation generated from reading ui file 'src/resources/_ui/graph.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -196,19 +197,20 @@ class Ui_GraphWindow(object):
self.gridLayout.setHorizontalSpacing(3) self.gridLayout.setHorizontalSpacing(3)
self.gridLayout.setVerticalSpacing(0) self.gridLayout.setVerticalSpacing(0)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.listWidget = QtWidgets.QListWidget(GraphWindow) self.listWidget = QListWidgetSelect(GraphWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listWidget.setObjectName("listWidget") self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 1, 1, 1) self.gridLayout.addWidget(self.listWidget, 1, 1, 1, 1)
self.checkBox = QtWidgets.QCheckBox(GraphWindow) self.checkBox = QtWidgets.QCheckBox(GraphWindow)
self.checkBox.setChecked(True) self.checkBox.setChecked(True)
self.checkBox.setObjectName("checkBox") self.checkBox.setObjectName("checkBox")
self.gridLayout.addWidget(self.checkBox, 0, 1, 1, 1) self.gridLayout.addWidget(self.checkBox, 0, 1, 1, 1)
self.graphic = PlotWidget(GraphWindow) self.graphic = NMRPlotWidget(GraphWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -272,4 +274,5 @@ class Ui_GraphWindow(object):
self.label_6.setText(_translate("GraphWindow", "X Axis")) self.label_6.setText(_translate("GraphWindow", "X Axis"))
self.label_7.setText(_translate("GraphWindow", "Y Axis")) self.label_7.setText(_translate("GraphWindow", "Y Axis"))
self.checkBox.setText(_translate("GraphWindow", "Show legend")) self.checkBox.setText(_translate("GraphWindow", "Show legend"))
from pyqtgraph import PlotWidget from ..lib.graph_items import NMRPlotWidget
from ..lib.listwidget import QListWidgetSelect

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '/autohome/dominik/nmreval/src/resources/_ui/integral_widget.ui' # Form implementation generated from reading ui file 'src/resources/_ui/integral_widget.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -26,7 +26,6 @@ class Ui_Form(object):
self.set_combobox.setObjectName("set_combobox") self.set_combobox.setObjectName("set_combobox")
self.verticalLayout.addWidget(self.set_combobox) self.verticalLayout.addWidget(self.set_combobox)
self.treeWidget = QtWidgets.QTreeWidget(Form) self.treeWidget = QtWidgets.QTreeWidget(Form)
self.treeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.treeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.treeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.treeWidget.setHeaderHidden(True) self.treeWidget.setHeaderHidden(True)
self.treeWidget.setObjectName("treeWidget") self.treeWidget.setObjectName("treeWidget")

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/integratederive_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/integratederive_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object): class Ui_Dialog(object):
def setupUi(self, Dialog): def setupUi(self, Dialog):
Dialog.setObjectName("Dialog") Dialog.setObjectName("Dialog")
Dialog.resize(400, 308) Dialog.resize(400, 375)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.listWidget = QtWidgets.QListWidget(Dialog) self.listWidget = QtWidgets.QListWidget(Dialog)
@ -49,6 +50,9 @@ class Ui_Dialog(object):
self.log_checkbox = QtWidgets.QCheckBox(Dialog) self.log_checkbox = QtWidgets.QCheckBox(Dialog)
self.log_checkbox.setObjectName("log_checkbox") self.log_checkbox.setObjectName("log_checkbox")
self.verticalLayout.addWidget(self.log_checkbox) self.verticalLayout.addWidget(self.log_checkbox)
self.freq_box = QtWidgets.QCheckBox(Dialog)
self.freq_box.setObjectName("freq_box")
self.verticalLayout.addWidget(self.freq_box)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
self.horizontalLayout_2.setSpacing(3) self.horizontalLayout_2.setSpacing(3)
@ -67,8 +71,8 @@ class Ui_Dialog(object):
self.verticalLayout.addWidget(self.buttonBox) self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog): def retranslateUi(self, Dialog):
@ -80,4 +84,5 @@ class Ui_Dialog(object):
self.ft_comboBox.setItemText(1, _translate("Dialog", "Imag")) self.ft_comboBox.setItemText(1, _translate("Dialog", "Imag"))
self.ft_comboBox.setItemText(2, _translate("Dialog", "Complex")) self.ft_comboBox.setItemText(2, _translate("Dialog", "Complex"))
self.log_checkbox.setText(_translate("Dialog", "use logarithmic x axis")) self.log_checkbox.setText(_translate("Dialog", "use logarithmic x axis"))
self.freq_box.setText(_translate("Dialog", "return x axis as f, not omega"))
self.newgraph_checkbox.setText(_translate("Dialog", "New graph")) self.newgraph_checkbox.setText(_translate("Dialog", "New graph"))

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/interpol_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/interpol_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -65,12 +66,13 @@ class Ui_Dialog(object):
self.label_2 = QtWidgets.QLabel(Dialog) self.label_2 = QtWidgets.QLabel(Dialog)
self.label_2.setObjectName("label_2") self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1) self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1)
self.listWidget = QtWidgets.QListWidget(Dialog) self.listWidget = QListWidgetSelect(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy) self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
self.listWidget.setObjectName("listWidget") self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 2) self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 2)
self.sampling_widget = QtWidgets.QWidget(Dialog) self.sampling_widget = QtWidgets.QWidget(Dialog)
@ -130,8 +132,8 @@ class Ui_Dialog(object):
self.label_8.setBuddy(self.dest_combobox) self.label_8.setBuddy(self.dest_combobox)
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog)
Dialog.setTabOrder(self.listWidget, self.ylog_checkBox) Dialog.setTabOrder(self.listWidget, self.ylog_checkBox)
Dialog.setTabOrder(self.ylog_checkBox, self.interp_comboBox) Dialog.setTabOrder(self.ylog_checkBox, self.interp_comboBox)
@ -164,3 +166,4 @@ class Ui_Dialog(object):
self.xaxis_comboBox.setItemText(1, _translate("Dialog", "from data")) self.xaxis_comboBox.setItemText(1, _translate("Dialog", "from data"))
self.label_8.setText(_translate("Dialog", "Add interpolated data to")) self.label_8.setText(_translate("Dialog", "Add interpolated data to"))
self.xlog_checkBox.setText(_translate("Dialog", "use log(x)")) self.xlog_checkBox.setText(_translate("Dialog", "use log(x)"))
from ..lib.listwidget import QListWidgetSelect

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/phase_corr_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/phase_corr_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -24,7 +24,7 @@ class Ui_SignalEdit(object):
self.gridLayout.setContentsMargins(6, 6, 6, 6) self.gridLayout.setContentsMargins(6, 6, 6, 6)
self.gridLayout.setSpacing(3) self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.graphicsView = PlotWidget(SignalEdit) self.graphicsView = NMRPlotWidget(SignalEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -83,8 +83,8 @@ class Ui_SignalEdit(object):
self.gridLayout.addItem(spacerItem1, 1, 5, 1, 1) self.gridLayout.addItem(spacerItem1, 1, 5, 1, 1)
self.retranslateUi(SignalEdit) self.retranslateUi(SignalEdit)
self.buttonBox.accepted.connect(SignalEdit.accept) self.buttonBox.accepted.connect(SignalEdit.accept) # type: ignore
self.buttonBox.rejected.connect(SignalEdit.close) self.buttonBox.rejected.connect(SignalEdit.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(SignalEdit) QtCore.QMetaObject.connectSlotsByName(SignalEdit)
def retranslateUi(self, SignalEdit): def retranslateUi(self, SignalEdit):
@ -94,4 +94,4 @@ class Ui_SignalEdit(object):
self.label_8.setText(_translate("SignalEdit", "Pivot")) self.label_8.setText(_translate("SignalEdit", "Pivot"))
self.label_6.setText(_translate("SignalEdit", "Phase 1")) self.label_6.setText(_translate("SignalEdit", "Phase 1"))
self.label.setText(_translate("SignalEdit", "Phase 0")) self.label.setText(_translate("SignalEdit", "Phase 0"))
from pyqtgraph import PlotWidget from ..lib.graph_items import NMRPlotWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/shift_scale_dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/shift_scale_dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -162,7 +163,7 @@ class Ui_shift_dialog(object):
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalFrame_2) self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalFrame_2)
self.verticalLayout_2.setSpacing(3) self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2") self.verticalLayout_2.setObjectName("verticalLayout_2")
self.graphicsView = PlotWidget(self.verticalFrame_2) self.graphicsView = NMRPlotWidget(self.verticalFrame_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
@ -267,8 +268,8 @@ class Ui_shift_dialog(object):
self.retranslateUi(shift_dialog) self.retranslateUi(shift_dialog)
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
self.buttonBox.accepted.connect(shift_dialog.accept) self.buttonBox.accepted.connect(shift_dialog.accept) # type: ignore
self.buttonBox.rejected.connect(shift_dialog.reject) self.buttonBox.rejected.connect(shift_dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(shift_dialog) QtCore.QMetaObject.connectSlotsByName(shift_dialog)
shift_dialog.setTabOrder(self.tabWidget, self.shift_table) shift_dialog.setTabOrder(self.tabWidget, self.shift_table)
shift_dialog.setTabOrder(self.shift_table, self.x_shift_spinbox) shift_dialog.setTabOrder(self.shift_table, self.x_shift_spinbox)
@ -310,5 +311,5 @@ class Ui_shift_dialog(object):
self.overwrite_checkbox.setText(_translate("shift_dialog", "Overwrite data")) self.overwrite_checkbox.setText(_translate("shift_dialog", "Overwrite data"))
self.data_newgraph.setText(_translate("shift_dialog", "New graph")) self.data_newgraph.setText(_translate("shift_dialog", "New graph"))
self.values_newgraph.setText(_translate("shift_dialog", "New graph")) self.values_newgraph.setText(_translate("shift_dialog", "New graph"))
from ..lib.graph_items import NMRPlotWidget
from ..lib.spinboxes import SciSpinBox from ..lib.spinboxes import SciSpinBox
from pyqtgraph import PlotWidget

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/t1dialog.ui' # Form implementation generated from reading ui file 'src/resources/_ui/t1dialog.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -160,6 +161,7 @@ class Ui_t1dialog(object):
self.tau_combox.addItem("") self.tau_combox.addItem("")
self.gridLayout_4.addWidget(self.tau_combox, 1, 0, 1, 2) self.gridLayout_4.addWidget(self.tau_combox, 1, 0, 1, 2)
self.checkBox_interpol = QtWidgets.QCheckBox(self.groupBox_3) self.checkBox_interpol = QtWidgets.QCheckBox(self.groupBox_3)
self.checkBox_interpol.setEnabled(False)
self.checkBox_interpol.setObjectName("checkBox_interpol") self.checkBox_interpol.setObjectName("checkBox_interpol")
self.gridLayout_4.addWidget(self.checkBox_interpol, 2, 0, 1, 2) self.gridLayout_4.addWidget(self.checkBox_interpol, 2, 0, 1, 2)
self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3) self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3)

View File

@ -0,0 +1,253 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'src/resources/_ui/tnmh_dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DSCEvalDialog(object):
def setupUi(self, DSCEvalDialog):
DSCEvalDialog.setObjectName("DSCEvalDialog")
DSCEvalDialog.resize(996, 712)
self.gridLayout = QtWidgets.QGridLayout(DSCEvalDialog)
self.gridLayout.setObjectName("gridLayout")
self.listWidget = QListWidgetSelect(DSCEvalDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
self.listWidget.setSizePolicy(sizePolicy)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.listWidget.setObjectName("listWidget")
self.gridLayout.addWidget(self.listWidget, 0, 0, 1, 1)
self.stackedWidget = QtWidgets.QStackedWidget(DSCEvalDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
self.stackedWidget.setSizePolicy(sizePolicy)
self.stackedWidget.setFrameShape(QtWidgets.QFrame.Box)
self.stackedWidget.setObjectName("stackedWidget")
self.page = QtWidgets.QWidget()
self.page.setObjectName("page")
self.gridLayout_2 = QtWidgets.QGridLayout(self.page)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout_9 = QtWidgets.QGridLayout()
self.gridLayout_9.setObjectName("gridLayout_9")
self.tg_export_check = QtWidgets.QCheckBox(self.page)
self.tg_export_check.setChecked(True)
self.tg_export_check.setObjectName("tg_export_check")
self.gridLayout_9.addWidget(self.tg_export_check, 0, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_9.addItem(spacerItem, 3, 0, 1, 1)
self.tglines_export_check = QtWidgets.QCheckBox(self.page)
self.tglines_export_check.setChecked(True)
self.tglines_export_check.setObjectName("tglines_export_check")
self.gridLayout_9.addWidget(self.tglines_export_check, 0, 1, 1, 1)
self.tg_export_button = QtWidgets.QPushButton(self.page)
self.tg_export_button.setObjectName("tg_export_button")
self.gridLayout_9.addWidget(self.tg_export_button, 2, 0, 1, 2)
self.gridLayout_2.addLayout(self.gridLayout_9, 2, 1, 1, 1)
self.tg_tree = QtWidgets.QTreeWidget(self.page)
self.tg_tree.setObjectName("tg_tree")
self.tg_tree.headerItem().setText(0, "1")
self.tg_tree.header().setVisible(False)
self.gridLayout_2.addWidget(self.tg_tree, 2, 0, 1, 1)
self.dsc_plot = NMRPlotWidget(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.dsc_plot.sizePolicy().hasHeightForWidth())
self.dsc_plot.setSizePolicy(sizePolicy)
self.dsc_plot.setObjectName("dsc_plot")
self.gridLayout_2.addWidget(self.dsc_plot, 1, 0, 1, 2)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.label = QtWidgets.QLabel(self.page)
self.label.setObjectName("label")
self.horizontalLayout_4.addWidget(self.label)
self.calctg_button = QtWidgets.QPushButton(self.page)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.calctg_button.sizePolicy().hasHeightForWidth())
self.calctg_button.setSizePolicy(sizePolicy)
self.calctg_button.setObjectName("calctg_button")
self.horizontalLayout_4.addWidget(self.calctg_button)
self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 2)
self.stackedWidget.addWidget(self.page)
self.page_2 = QtWidgets.QWidget()
self.page_2.setObjectName("page_2")
self.gridLayout_3 = QtWidgets.QGridLayout(self.page_2)
self.gridLayout_3.setObjectName("gridLayout_3")
self.label_4 = QtWidgets.QLabel(self.page_2)
self.label_4.setObjectName("label_4")
self.gridLayout_3.addWidget(self.label_4, 2, 0, 1, 1)
self.tghodge_graph = NMRPlotWidget(self.page_2)
self.tghodge_graph.setObjectName("tghodge_graph")
self.gridLayout_3.addWidget(self.tghodge_graph, 1, 0, 1, 1)
self.tau_plot = NMRPlotWidget(self.page_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tau_plot.sizePolicy().hasHeightForWidth())
self.tau_plot.setSizePolicy(sizePolicy)
self.tau_plot.setObjectName("tau_plot")
self.gridLayout_3.addWidget(self.tau_plot, 1, 1, 1, 1)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.label_2 = QtWidgets.QLabel(self.page_2)
self.label_2.setObjectName("label_2")
self.horizontalLayout_3.addWidget(self.label_2)
self.hodge_button = QtWidgets.QPushButton(self.page_2)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hodge_button.sizePolicy().hasHeightForWidth())
self.hodge_button.setSizePolicy(sizePolicy)
self.hodge_button.setObjectName("hodge_button")
self.horizontalLayout_3.addWidget(self.hodge_button)
self.gridLayout_3.addLayout(self.horizontalLayout_3, 0, 0, 1, 2)
self.export_hodge_button = QtWidgets.QPushButton(self.page_2)
self.export_hodge_button.setObjectName("export_hodge_button")
self.gridLayout_3.addWidget(self.export_hodge_button, 5, 1, 1, 1)
self.hodge_graph_combo = QtWidgets.QComboBox(self.page_2)
self.hodge_graph_combo.setEnabled(False)
self.hodge_graph_combo.setObjectName("hodge_graph_combo")
self.gridLayout_3.addWidget(self.hodge_graph_combo, 4, 1, 1, 1)
self.hodge_graph_check = QtWidgets.QCheckBox(self.page_2)
self.hodge_graph_check.setChecked(True)
self.hodge_graph_check.setObjectName("hodge_graph_check")
self.gridLayout_3.addWidget(self.hodge_graph_check, 4, 0, 1, 1)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.onset_check = QtWidgets.QCheckBox(self.page_2)
self.onset_check.setChecked(True)
self.onset_check.setObjectName("onset_check")
self.horizontalLayout_5.addWidget(self.onset_check)
self.mid_check = QtWidgets.QCheckBox(self.page_2)
self.mid_check.setChecked(True)
self.mid_check.setObjectName("mid_check")
self.horizontalLayout_5.addWidget(self.mid_check)
self.end_check = QtWidgets.QCheckBox(self.page_2)
self.end_check.setChecked(True)
self.end_check.setObjectName("end_check")
self.horizontalLayout_5.addWidget(self.end_check)
self.inflection_check = QtWidgets.QCheckBox(self.page_2)
self.inflection_check.setChecked(True)
self.inflection_check.setObjectName("inflection_check")
self.horizontalLayout_5.addWidget(self.inflection_check)
self.fictive_check = QtWidgets.QCheckBox(self.page_2)
self.fictive_check.setChecked(True)
self.fictive_check.setObjectName("fictive_check")
self.horizontalLayout_5.addWidget(self.fictive_check)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_5.addItem(spacerItem1)
self.gridLayout_3.addLayout(self.horizontalLayout_5, 3, 0, 1, 2)
self.stackedWidget.addWidget(self.page_2)
self.page_3 = QtWidgets.QWidget()
self.page_3.setObjectName("page_3")
self.gridLayout_6 = QtWidgets.QGridLayout(self.page_3)
self.gridLayout_6.setObjectName("gridLayout_6")
self.tnmh_graphics = NMRPlotWidget(self.page_3)
self.tnmh_graphics.setObjectName("tnmh_graphics")
self.gridLayout_6.addWidget(self.tnmh_graphics, 1, 0, 1, 2)
self.tnmh_tree = QtWidgets.QTreeWidget(self.page_3)
self.tnmh_tree.setObjectName("tnmh_tree")
self.tnmh_tree.headerItem().setText(0, "1")
self.tnmh_tree.header().setVisible(False)
self.gridLayout_6.addWidget(self.tnmh_tree, 2, 0, 1, 1)
self.gridLayout_5 = QtWidgets.QGridLayout()
self.gridLayout_5.setObjectName("gridLayout_5")
self.tnmh_graph_check = QtWidgets.QCheckBox(self.page_3)
self.tnmh_graph_check.setChecked(True)
self.tnmh_graph_check.setObjectName("tnmh_graph_check")
self.gridLayout_5.addWidget(self.tnmh_graph_check, 1, 0, 1, 1)
self.tnmhfit_export_check = QtWidgets.QCheckBox(self.page_3)
self.tnmhfit_export_check.setChecked(True)
self.tnmhfit_export_check.setObjectName("tnmhfit_export_check")
self.gridLayout_5.addWidget(self.tnmhfit_export_check, 0, 0, 1, 1)
self.tnmh_export_button = QtWidgets.QPushButton(self.page_3)
self.tnmh_export_button.setObjectName("tnmh_export_button")
self.gridLayout_5.addWidget(self.tnmh_export_button, 2, 0, 1, 2)
self.fictive_export_check = QtWidgets.QCheckBox(self.page_3)
self.fictive_export_check.setChecked(True)
self.fictive_export_check.setObjectName("fictive_export_check")
self.gridLayout_5.addWidget(self.fictive_export_check, 0, 1, 1, 1)
self.tnmh_graph_combo = QtWidgets.QComboBox(self.page_3)
self.tnmh_graph_combo.setEnabled(False)
self.tnmh_graph_combo.setObjectName("tnmh_graph_combo")
self.gridLayout_5.addWidget(self.tnmh_graph_combo, 1, 1, 1, 1)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_5.addItem(spacerItem2, 3, 0, 1, 1)
self.gridLayout_6.addLayout(self.gridLayout_5, 2, 1, 1, 1)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label_3 = QtWidgets.QLabel(self.page_3)
self.label_3.setObjectName("label_3")
self.horizontalLayout_2.addWidget(self.label_3)
self.tnhm_fitbutton = QtWidgets.QPushButton(self.page_3)
self.tnhm_fitbutton.setObjectName("tnhm_fitbutton")
self.horizontalLayout_2.addWidget(self.tnhm_fitbutton)
self.gridLayout_6.addLayout(self.horizontalLayout_2, 0, 0, 1, 2)
self.stackedWidget.addWidget(self.page_3)
self.gridLayout.addWidget(self.stackedWidget, 0, 1, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem3)
self.back_button = QtWidgets.QToolButton(DSCEvalDialog)
self.back_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.back_button.setArrowType(QtCore.Qt.LeftArrow)
self.back_button.setObjectName("back_button")
self.horizontalLayout.addWidget(self.back_button)
self.next_button = QtWidgets.QToolButton(DSCEvalDialog)
self.next_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.next_button.setArrowType(QtCore.Qt.RightArrow)
self.next_button.setObjectName("next_button")
self.horizontalLayout.addWidget(self.next_button)
self.close_button = QtWidgets.QPushButton(DSCEvalDialog)
self.close_button.setObjectName("close_button")
self.horizontalLayout.addWidget(self.close_button)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2)
self.retranslateUi(DSCEvalDialog)
self.stackedWidget.setCurrentIndex(0)
self.close_button.clicked.connect(DSCEvalDialog.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(DSCEvalDialog)
def retranslateUi(self, DSCEvalDialog):
_translate = QtCore.QCoreApplication.translate
DSCEvalDialog.setWindowTitle(_translate("DSCEvalDialog", "To boldly go where no man has gone before"))
self.tg_export_check.setText(_translate("DSCEvalDialog", "Export Tg"))
self.tglines_export_check.setText(_translate("DSCEvalDialog", "Export lines"))
self.tg_export_button.setText(_translate("DSCEvalDialog", "Export"))
self.label.setText(_translate("DSCEvalDialog", "<html><head/><body><p><span style=\" font-weight:600;\">Glass transition</span></p></body></html>"))
self.calctg_button.setText(_translate("DSCEvalDialog", "Calculate Tg"))
self.label_4.setText(_translate("DSCEvalDialog", "Export tau:"))
self.label_2.setText(_translate("DSCEvalDialog", "<html><head/><body><p><span style=\" font-weight:600;\">Hodge tau</span></p></body></html>"))
self.hodge_button.setText(_translate("DSCEvalDialog", "Make it so."))
self.export_hodge_button.setText(_translate("DSCEvalDialog", "Export"))
self.hodge_graph_check.setText(_translate("DSCEvalDialog", "New graph"))
self.onset_check.setText(_translate("DSCEvalDialog", "Onset"))
self.mid_check.setText(_translate("DSCEvalDialog", "Midpoint"))
self.end_check.setText(_translate("DSCEvalDialog", "End"))
self.inflection_check.setText(_translate("DSCEvalDialog", "Inflection"))
self.fictive_check.setText(_translate("DSCEvalDialog", "Fictive"))
self.tnmh_graph_check.setText(_translate("DSCEvalDialog", "New graph"))
self.tnmhfit_export_check.setText(_translate("DSCEvalDialog", "Export fit"))
self.tnmh_export_button.setText(_translate("DSCEvalDialog", "Export"))
self.fictive_export_check.setText(_translate("DSCEvalDialog", "Export dTf / dT"))
self.label_3.setText(_translate("DSCEvalDialog", "<html><head/><body><p><span style=\" font-weight:600;\">dTf/dT and TNMH </span></p></body></html>"))
self.tnhm_fitbutton.setText(_translate("DSCEvalDialog", "Engage!"))
self.back_button.setText(_translate("DSCEvalDialog", "Back"))
self.next_button.setText(_translate("DSCEvalDialog", "Next"))
self.close_button.setText(_translate("DSCEvalDialog", "Close"))
from ..lib.graph_items import NMRPlotWidget
from ..lib.listwidget import QListWidgetSelect

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'resources/_ui/valueeditor.ui' # Form implementation generated from reading ui file 'src/resources/_ui/valueeditor.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.9
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.

28
src/gui_qt/cli.py Normal file
View File

@ -0,0 +1,28 @@
def main():
import sys
from nmreval.configs import check_for_config
# does a directory for config stuff exist? create it if not
check_for_config()
# pyqtgraph warns on Mac if QApplication is created when it is imported
# import pyqtgraph
from nmreval.lib.logger import handle_exception
sys.excepthook = handle_exception
from gui_qt import App
app = App(['Team Rocket FTW!'])
from gui_qt.main.mainwindow import NMRMainWindow
mplQt = NMRMainWindow()
mplQt.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()

View File

@ -8,8 +8,10 @@ from pyqtgraph import mkPen
from nmreval.data.points import Points from nmreval.data.points import Points
from nmreval.data.signals import Signal from nmreval.data.signals import Signal
from nmreval.lib.logger import logger
from nmreval.utils.text import convert from nmreval.utils.text import convert
from nmreval.data.bds import BDS from nmreval.data.bds import BDS
from nmreval.data.dsc import DSC
from nmreval.lib.colors import BaseColor, TUColors from nmreval.lib.colors import BaseColor, TUColors
from nmreval.lib.lines import LineStyle from nmreval.lib.lines import LineStyle
from nmreval.lib.symbols import SymbolStyle, symbolcycle from nmreval.lib.symbols import SymbolStyle, symbolcycle
@ -32,6 +34,7 @@ class ExperimentContainer(QtCore.QObject):
self.id = str(identifier) self.id = str(identifier)
self._fits = [] self._fits = []
self._relations = kwargs.get('relations', {})
self._data = data self._data = data
self._manager = kwargs.get('manager') self._manager = kwargs.get('manager')
self.graph = '' self.graph = ''
@ -44,6 +47,7 @@ class ExperimentContainer(QtCore.QObject):
self.actions = {} self.actions = {}
self._update_actions() self._update_actions()
@plot_update
def _init_plot(self): def _init_plot(self):
raise NotImplementedError raise NotImplementedError
@ -226,6 +230,8 @@ class ExperimentContainer(QtCore.QObject):
return self.plot_real, self.plot_imag, self.plot_error return self.plot_real, self.plot_imag, self.plot_error
def get_state(self): def get_state(self):
# TODO preserve relationships
ret_dic = { ret_dic = {
'id': self.id, 'id': self.id,
'data': self._data.get_state(), 'data': self._data.get_state(),
@ -267,6 +273,32 @@ class ExperimentContainer(QtCore.QObject):
else: else:
self._fits.extend(value) self._fits.extend(value)
def has_relation(self, relation_type):
return relation_type in self._relations
def get_relation(self, relation_type: int):
return self._relations.get(relation_type, [])
def add_relation(self, relation_type: int, value: str):
if relation_type not in self._relations:
self._relations[relation_type] = []
self._relations[relation_type].append(value)
def remove_relation(self, relation_type: int, value: str):
if relation_type not in self._relations:
raise ValueError(f'Relationship {relation_type!r} with id {value!r} doe not exist for {self.name}')
related_id_value = self._relations[relation_type]
idx = related_id_value.index(value)
if idx == -1:
raise ValueError(f'Relationship {relation_type!r} with id {value!r} doe not exist for {self.name}')
related_id_value.pop(idx)
if len(related_id_value) == 0:
self._relations.pop(relation_type)
def _update_actions(self): def _update_actions(self):
self.actions.update({'sort': self._data.sort, self.actions.update({'sort': self._data.sort,
'cut': self._data.cut, 'cut': self._data.cut,
@ -312,12 +344,10 @@ class ExperimentContainer(QtCore.QObject):
err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb())) err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb()))
self.plot_error.setData(pen=err_pen) self.plot_error.setData(pen=err_pen)
elif mode == 'imag' and self.plot_imag is not None: if mode in ['imag', 'all'] and self.plot_imag is not None:
self.plot_imag.set_color(color, symbol=symbol, line=line) self.plot_imag.set_color(color, symbol=symbol, line=line)
else:
print('Updating color failed for ' + str(self.id))
def setSymbol(self, symbol=None, color=None, size=None, mode='real'): def setSymbol(self, *, symbol=None, color=None, size=None, mode='real'):
if mode in ['real', 'all']: if mode in ['real', 'all']:
self.plot_real.set_symbol(symbol=symbol, size=size, color=color) self.plot_real.set_symbol(symbol=symbol, size=size, color=color)
if color is not None and self.plot_error is not None and self.plot_real.symbol != SymbolStyle.No: if color is not None and self.plot_error is not None and self.plot_real.symbol != SymbolStyle.No:
@ -327,9 +357,9 @@ class ExperimentContainer(QtCore.QObject):
elif mode in ['imag', 'all'] and self.plot_imag is not None: elif mode in ['imag', 'all'] and self.plot_imag is not None:
self.plot_imag.set_symbol(symbol=symbol, size=size, color=color) self.plot_imag.set_symbol(symbol=symbol, size=size, color=color)
else: else:
print('Updating symbol failed for ' + str(self.id)) logger.warning(f'Updating symbol failed for {self.id}')
def setLine(self, width=None, style=None, color=None, mode='real'): def setLine(self, *, width=None, style=None, color=None, mode='real'):
if mode in ['real', 'all']: if mode in ['real', 'all']:
self.plot_real.set_line(width=width, style=style, color=color) self.plot_real.set_line(width=width, style=style, color=color)
if color is not None and self.plot_error is not None and self.plot_real.symbol == SymbolStyle.No: if color is not None and self.plot_error is not None and self.plot_real.symbol == SymbolStyle.No:
@ -339,7 +369,7 @@ class ExperimentContainer(QtCore.QObject):
elif mode in ['imag', 'all'] and self.plot_imag is not None: elif mode in ['imag', 'all'] and self.plot_imag is not None:
self.plot_imag.set_line(width=width, style=style, color=color) self.plot_imag.set_line(width=width, style=style, color=color)
else: else:
print('Updating line failed for ' + str(self.id)) logger.warning(f'Updating line failed for {self.id}')
def update_property(self, key1: str, key2: str, value: Any): def update_property(self, key1: str, key2: str, value: Any):
keykey = key2.split() keykey = key2.split()
@ -434,11 +464,21 @@ class ExperimentContainer(QtCore.QObject):
return offset return offset
@plot_update
def shift_scale(self, shift_factor: tuple[float, float], scaling_factor: tuple[float, float]):
scale_x, scale_y = scaling_factor
shift_x, shift_y = shift_factor
self.data.x = self.data.x * scale_x + shift_x
self.data.y = self.data.y * scale_y + shift_y
self.data.y_err = self.data.y_err * scale_y
self.update({'shift': scaling_factor, 'scale': shift_factor})
def get_namespace(self, i: int = None, j: int = None) -> dict: def get_namespace(self, i: int = None, j: int = None) -> dict:
if (i is None) and (j is None): if (i is None) and (j is None):
prefix = '' prefix = ''
else: else:
prefix = 'g[%i].s[%i].' % (i, j) prefix = f'g[{i}].s[{j}].'
namespace = {prefix + 'x': (self.x, 'x values'), namespace = {prefix + 'x': (self.x, 'x values'),
prefix + 'y': [self.y, 'y values'], prefix + 'y': [self.y, 'y values'],
@ -459,27 +499,50 @@ class ExperimentContainer(QtCore.QObject):
return namespace return namespace
def eval_expression(self, cmds, namespace): def eval_expression(self, cmds, namespace, i=None, j=None):
namespace.update({'x': self.x, 'y': self.y, 'y_err': self.y_err, 'value': self.value}) if i is not None:
namespace['i'] = i
if len(self._fits) == 1: if j is not None:
namespace.update({"fit['%s']" % (convert(pname, old='tex', new='str')): pvalue.value namespace['j'] = j
for (pname, pvalue) in self._manager[self._fits[0]].parameter.items()})
namespace.update({'x': self._data.x, 'y': self._data.y, 'y_err': self._data.y_err, 'value': self.value})
namespace['fit'] = {}
if isinstance(self, FitContainer):
namespace['fit'].update({
'%s' % convert(pname, old='latex', new='plain'): pvalue.value
for (pname, pvalue) in self._data.parameter.items()
})
elif len(self._fits) == 1:
namespace['fit'].update({
'%s' % convert(pname, old='tex', new='str'): pvalue.value
for (pname, pvalue) in self._manager[self._fits[0]].parameter.items()
})
else: else:
for k, f in enumerate(self._fits): for k, f in enumerate(self._fits):
namespace.update({"fit['%s_%i']" % (convert(pname, old='tex', new='str'), k): pvalue.value namespace['fit'].update({
for (pname, pvalue) in self._manager[f].parameter.items()}) "%s_%i" % (convert(pname, old='tex', new='str'), k): pvalue.value
for (pname, pvalue) in self._manager[f].parameter.items()
})
new_data = self.copy() new_data = self.copy()
for c in cmds: for c in cmds:
if c: if c:
exec(c, globals(), namespace) exec(c, globals(), namespace)
new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err']) new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err'], replace_mask=False)
new_data.value = namespace['value'] new_data.value = namespace['value']
return new_data return new_data
def binning(self, digits: float):
new_data = self.copy(full=True)
new_data.data = self._data.binning(value=digits)
return new_data
class PointContainer(ExperimentContainer): class PointContainer(ExperimentContainer):
symbols = symbolcycle() symbols = symbolcycle()
@ -490,6 +553,9 @@ class PointContainer(ExperimentContainer):
self.mode = 'pts' self.mode = 'pts'
self._init_plot(**kwargs) self._init_plot(**kwargs)
if isinstance(self._data, DSC):
self.mode = 'dsc'
def _init_plot(self, **kwargs): def _init_plot(self, **kwargs):
self.plot_imag = None self.plot_imag = None
@ -526,17 +592,17 @@ class PointContainer(ExperimentContainer):
line_kwargs['style'] = LineStyle.No line_kwargs['style'] = LineStyle.No
sym_kwargs['symbol'] = next(PointContainer.symbols) sym_kwargs['symbol'] = next(PointContainer.symbols)
self.plot_real = PlotItem(x=self._data.x, y=self._data.y, name=self.name, self.plot_real = PlotItem(x=self.x, y=self.y, name=self.name,
symbol=None, pen=None, connect='finite') symbol=None, pen=None, connect='finite')
self.setSymbol(mode='real', **sym_kwargs) self.setSymbol(mode='real', **sym_kwargs)
self.setLine(mode='real', **line_kwargs) self.setLine(mode='real', **line_kwargs)
if sym_kwargs['symbol'] != SymbolStyle.No: if sym_kwargs['symbol'] != SymbolStyle.No:
self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err, self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err,
pen=mkPen({'color': self.plot_real.symbolcolor.rgb()})) pen=mkPen({'color': self.plot_real.symbolcolor.rgb()}))
else: else:
self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err, self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err,
pen=mkPen({'color': self.plot_real.linecolor.rgb()})) pen=mkPen({'color': self.plot_real.linecolor.rgb()}))
@ -553,16 +619,18 @@ class FitContainer(ExperimentContainer):
setattr(self, n, getattr(data, n)) setattr(self, n, getattr(data, n))
def _init_plot(self, **kwargs): def _init_plot(self, **kwargs):
color = kwargs.get('color', (0, 0, 0)) color = kwargs.get('color')
if color is None:
color = kwargs.get('linecolor', (0, 0, 0))
if isinstance(color, BaseColor): if isinstance(color, BaseColor):
color = color.rgb() color = color.rgb()
self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name, self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name,
pen=mkPen({'color': color}), pen=mkPen({'color': color}),
connect='finite', symbol=None) connect='finite', symbol=None)
if np.iscomplexobj(self._data.y): if np.iscomplexobj(self._data.y):
self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name, self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name,
pen=mkPen({'color': color}), pen=mkPen({'color': color}),
connect='finite', symbol=None) connect='finite', symbol=None)
@ -595,9 +663,9 @@ class SignalContainer(ExperimentContainer):
self._init_plot(symbol=symbol, **kwargs) self._init_plot(symbol=symbol, **kwargs)
def _init_plot(self, **kwargs): def _init_plot(self, **kwargs):
self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name, self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name,
symbol=None, pen=None, connect='finite') symbol=None, pen=None, connect='finite')
self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name, self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name,
symbol=None, pen=None, connect='finite') symbol=None, pen=None, connect='finite')
color = kwargs.get('color', None) color = kwargs.get('color', None)
@ -605,7 +673,7 @@ class SignalContainer(ExperimentContainer):
linecolor = kwargs.get('linecolor', color) linecolor = kwargs.get('linecolor', color)
if symcolor is None and linecolor is None: if symcolor is None and linecolor is None:
color = next(self.colors) color = next(self.colors) if color is None else color
symcolor = color symcolor = color
linecolor = color linecolor = color
elif symcolor is None: elif symcolor is None:
@ -681,3 +749,46 @@ class SignalContainer(ExperimentContainer):
self._update_actions() self._update_actions()
return self return self
@plot_update
def edit_signal(
self: SignalContainer,
baseline: tuple[None | bool],
leftshift: tuple[None | float, str],
zerofill: tuple[None, int],
apod: tuple[None, list[float], type[object]],
phase: tuple[None | float, float, float],
fourier: tuple[None | bool]
):
"""
Function for EditUndoCommand to call if a timesignal or spectra must be worked on.
This avoids to update the plot for every action we do and makes it slightly faster.
"""
if baseline[0] is not None:
self._data.baseline()
if leftshift[0] is not None:
self._data.shift(*leftshift)
if zerofill[0] is not None:
self._data.zerofill(*zerofill)
if apod[0] is not None:
self._data.apod(*apod)
# ft with three options: None, True, False
if fourier[0] is None:
# ft None -> only phase correct
if phase[0] is not None:
self._data.manual_phase(*phase)
elif fourier[0] == True:
# ft True -> first phase correct then fft
if phase[0] is not None:
self._data.manual_phase(*phase)
self.fourier()
else:
# ft False -> first fft then phase correct
self.fourier()
if phase[0] is not None:
self._data.manual_phase(*phase)

View File

@ -5,7 +5,7 @@ from nmreval.lib.colors import available_cycles
from .properties import PropWidget from .properties import PropWidget
from ...Qt import QtWidgets, QtGui, QtCore from ...Qt import QtWidgets, QtGui, QtCore
from ..._py.datawidget import Ui_DataWidget from ..._py.datawidget import Ui_DataWidget
from ...lib import make_action_icons from ...lib.iconloading import make_action_icons
from ...lib.delegates import HeaderDelegate from ...lib.delegates import HeaderDelegate
@ -17,7 +17,9 @@ class DataTree(QtWidgets.QTreeWidget):
moveItem = QtCore.pyqtSignal(list, str, str, int) # items, from, to, new row moveItem = QtCore.pyqtSignal(list, str, str, int) # items, from, to, new row
copyItem = QtCore.pyqtSignal(list, str) copyItem = QtCore.pyqtSignal(list, str)
saveFits = QtCore.pyqtSignal(list) saveFits = QtCore.pyqtSignal(list)
extendFits = QtCore.pyqtSignal(list)
# noinspection PyUnresolvedReferences
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
@ -59,15 +61,16 @@ class DataTree(QtWidgets.QTreeWidget):
self.update_indexes() self.update_indexes()
def add_item(self, items: (tuple | list[tuple]), gid: str): def add_item(self, items: (tuple | list[tuple]), gid: str, update: bool = True):
if isinstance(items, tuple): if isinstance(items, tuple):
items = [items] items = [items]
for row in range(self.invisibleRootItem().childCount()): for row in range(self.invisibleRootItem().childCount()):
graph = self.invisibleRootItem().child(row) graph = self.invisibleRootItem().child(row)
if graph.data(0, QtCore.Qt.UserRole) == gid: if graph.data(0, QtCore.Qt.UserRole) == gid:
for (idd, name) in items: for (idd, name, value) in items:
item = QtWidgets.QTreeWidgetItem([name]) item = QtWidgets.QTreeWidgetItem([name])
item.setToolTip(0, f'Value: {value}')
item.setData(0, QtCore.Qt.UserRole, idd) item.setData(0, QtCore.Qt.UserRole, idd)
item.setCheckState(0, QtCore.Qt.Checked) item.setCheckState(0, QtCore.Qt.Checked)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable | item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable |
@ -77,11 +80,11 @@ class DataTree(QtWidgets.QTreeWidget):
self.resizeColumnToContents(0) self.resizeColumnToContents(0)
break break
if update:
self.update_indexes() self.update_indexes()
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem) @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
def data_change(self, item: QtWidgets.QTreeWidgetItem) -> tuple[set, set]: def data_change(self, item: QtWidgets.QTreeWidgetItem, emit: bool = True) -> tuple[set, set]:
idd = item.data(0, QtCore.Qt.UserRole) idd = item.data(0, QtCore.Qt.UserRole)
is_selected = item.checkState(0) == QtCore.Qt.Checked is_selected = item.checkState(0) == QtCore.Qt.Checked
to_be_hidden = set() to_be_hidden = set()
@ -139,9 +142,10 @@ class DataTree(QtWidgets.QTreeWidget):
pass pass
else: else:
self.keyChanged.emit(idd, item.text(0)) if emit:
self.keyChanged.emit(idd, item.text(0))
if to_be_shown or to_be_hidden: if (to_be_shown or to_be_hidden) and emit:
self.stateChanged.emit(list(to_be_shown), list(to_be_hidden)) self.stateChanged.emit(list(to_be_shown), list(to_be_hidden))
return to_be_shown, to_be_hidden return to_be_shown, to_be_hidden
@ -228,7 +232,7 @@ class DataTree(QtWidgets.QTreeWidget):
def sort(self, graph_item: QtWidgets.QTreeWidgetItem, mode: str = 'value'): def sort(self, graph_item: QtWidgets.QTreeWidgetItem, mode: str = 'value'):
graph_id = graph_item.data(0, QtCore.Qt.UserRole) graph_id = graph_item.data(0, QtCore.Qt.UserRole)
sets = self.management.get_attributes(graph_id, mode) sets = self.management.get_attributes(graph_id, mode)
sets = [el[0] for el in sorted(sets.items(), key=lambda x: x[1])] sets = [el[0] for el in sorted(sets.items(), key=lambda x: x[1])]
self.management.move_sets(sets, graph_id, graph_id, pos=-1) self.management.move_sets(sets, graph_id, graph_id, pos=-1)
@ -246,11 +250,11 @@ class DataTree(QtWidgets.QTreeWidget):
self.blockSignals(False) self.blockSignals(False)
def update_indexes(self): def update_indexes(self):
graph_cnt = -1 graph_cnt = -1
set_cnt = 0 set_cnt = 0
iterator = QtWidgets.QTreeWidgetItemIterator(self) iterator = QtWidgets.QTreeWidgetItemIterator(self)
self.blockSignals(True)
while iterator.value(): while iterator.value():
item = iterator.value() item = iterator.value()
if item is not None: if item is not None:
@ -265,6 +269,7 @@ class DataTree(QtWidgets.QTreeWidget):
iterator += 1 iterator += 1
self.resizeColumnToContents(1) self.resizeColumnToContents(1)
self.blockSignals(False)
def set_name(self, sid, name): def set_name(self, sid, name):
iterator = QtWidgets.QTreeWidgetItemIterator(self) iterator = QtWidgets.QTreeWidgetItemIterator(self)
@ -286,13 +291,20 @@ class DataTree(QtWidgets.QTreeWidget):
for idx in self.selectedIndexes(): for idx in self.selectedIndexes():
if idx.column() == 1: if idx.column() == 1:
continue continue
item = self.itemFromIndex(idx) item = self.itemFromIndex(idx)
if item.parent() is None: if item.parent() is None:
for c_i in range(item.childCount()): for c_i in range(item.childCount()):
rm_sets.append(item.child(c_i).data(0, QtCore.Qt.UserRole)) # add sets inside graph to removal
child_data = item.child(c_i).data(0, QtCore.Qt.UserRole)
if child_data not in rm_sets:
rm_sets.append(child_data)
rm_graphs.append(item.data(0, QtCore.Qt.UserRole)) rm_graphs.append(item.data(0, QtCore.Qt.UserRole))
else: else:
rm_sets.append(item.data(0, QtCore.Qt.UserRole)) item_data = item.data(0, QtCore.Qt.UserRole)
if item_data not in rm_sets:
rm_sets.append(item_data)
# self.deleteItem.emit(rm_sets+rm_graphs) # self.deleteItem.emit(rm_sets+rm_graphs)
self.management.delete_sets(rm_sets+rm_graphs) self.management.delete_sets(rm_sets+rm_graphs)
@ -305,26 +317,24 @@ class DataTree(QtWidgets.QTreeWidget):
if idx.column() != 0: if idx.column() != 0:
continue continue
item = self.itemFromIndex(idx) item = self.itemFromIndex(idx)
if item.parent() is None: if item.parent() is None:
is_selected = item.checkState(0)
self.blockSignals(True)
for i in range(item.childCount()): for i in range(item.childCount()):
child = item.child(i) child = item.child(i)
from_parent.append(child) from_parent.append(child)
self.blockSignals(False) sets.append(item)
if is_selected == QtCore.Qt.Checked:
item.setCheckState(0, QtCore.Qt.Unchecked)
else:
item.setCheckState(0, QtCore.Qt.Checked)
else:
sets.append(item)
to_be_hidden = set()
to_be_shown = set()
self.blockSignals(True)
for it in sets: for it in sets:
if it in from_parent: if it in from_parent:
continue continue
it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked) it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
s1, s2 = self.data_change(it, emit=False)
to_be_hidden |= s2
to_be_shown |= s1
self.blockSignals(False)
self.stateChanged.emit(list(to_be_shown), list(to_be_hidden))
else: else:
super().keyPressEvent(evt) super().keyPressEvent(evt)
@ -334,24 +344,47 @@ class DataTree(QtWidgets.QTreeWidget):
self.setDragEnabled(idx.column() == 0) self.setDragEnabled(idx.column() == 0)
super().mousePressEvent(evt) super().mousePressEvent(evt)
def remove_item(self, ids: list): def remove_item(self, ids: list[str]):
iterator = QtWidgets.QTreeWidgetItemIterator(self) iterator = QtWidgets.QTreeWidgetItemIterator(self)
toberemoved = []
graph_removal = []
# find all items that have to be removed
while iterator.value(): while iterator.value():
item = iterator.value() item = iterator.value()
_id = item.data(0, QtCore.Qt.UserRole) _id = item.data(0, QtCore.Qt.UserRole)
if _id in ids: if _id in ids:
try: try:
idx = item.parent().indexOfChild(item) item_parent = item.parent()
item.parent().takeChild(idx) if item_parent is None:
raise AttributeError
idx = item_parent.indexOfChild(item)
# item.parent().takeChild(idx)
toberemoved.append((item_parent, idx))
if _id in self._checked_sets: if _id in self._checked_sets:
self._checked_sets.remove(_id) self._checked_sets.remove(_id)
except AttributeError: except AttributeError:
idx = self.invisibleRootItem().indexOfChild(item) idx = self.invisibleRootItem().indexOfChild(item)
self.invisibleRootItem().takeChild(idx) # self.invisibleRootItem().takeChild(idx)
self._checked_graphs.remove(_id)
graph_removal.append(idx)
if _id in self._checked_graphs:
self._checked_graphs.remove(_id)
iterator += 1 iterator += 1
for (item, set_idx) in sorted(toberemoved, key=lambda x: x[1], reverse=True):
item.takeChild(set_idx)
for graph_idx in sorted(graph_removal, reverse=True):
self.invisibleRootItem().takeChild(graph_idx)
self.update_indexes() self.update_indexes()
def contextMenuEvent(self, evt): def contextMenuEvent(self, evt):
@ -387,6 +420,10 @@ class DataTree(QtWidgets.QTreeWidget):
for c in available_cycles.keys(): for c in available_cycles.keys():
col_menu.addAction(c) col_menu.addAction(c)
action = menu.exec(evt.globalPos())
if action is None:
return
graphs = [] graphs = []
items = [] items = []
for i in self.selectedIndexes(): for i in self.selectedIndexes():
@ -395,7 +432,6 @@ class DataTree(QtWidgets.QTreeWidget):
items.append(self.itemFromIndex(i)) items.append(self.itemFromIndex(i))
graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.UserRole)) graphs.append(self.itemFromIndex(i).data(0, QtCore.Qt.UserRole))
action = menu.exec(evt.globalPos())
if action == del_action: if action == del_action:
for gid in graphs: for gid in graphs:
self.management.delete_graph(gid) self.management.delete_graph(gid)
@ -414,8 +450,7 @@ class DataTree(QtWidgets.QTreeWidget):
del_action = menu.addAction('Exterminate sets') del_action = menu.addAction('Exterminate sets')
cp_action = menu.addAction('Replicate sets') cp_action = menu.addAction('Replicate sets')
cat_action = menu.addAction('Join us!') cat_action = menu.addAction('Join us!')
plt_action = None plt_action = save_action = extend_action = None
save_action = None
menu.addSeparator() menu.addSeparator()
col_menu = menu.addMenu('Color cycle') col_menu = menu.addMenu('Color cycle')
for c in available_cycles.keys(): for c in available_cycles.keys():
@ -446,6 +481,7 @@ class DataTree(QtWidgets.QTreeWidget):
menu.addSeparator() menu.addSeparator()
plt_action = menu.addAction('Plot fit parameter') plt_action = menu.addAction('Plot fit parameter')
save_action = menu.addAction('Save fit parameter') save_action = menu.addAction('Save fit parameter')
extend_action = menu.addAction('Extrapolate fit')
action = menu.exec(evt.globalPos()) action = menu.exec(evt.globalPos())
@ -469,6 +505,9 @@ class DataTree(QtWidgets.QTreeWidget):
elif action == save_action: elif action == save_action:
self.saveFits.emit(s) self.saveFits.emit(s)
elif action == extend_action:
self.extendFits.emit(s)
elif action.parent() == col_menu: elif action.parent() == col_menu:
self.management.set_cycle(s, action.text()) self.management.set_cycle(s, action.text())
@ -509,6 +548,7 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
self.setupUi(self) self.setupUi(self)
self.tree = DataTree(self) self.tree = DataTree(self)
self.verticalLayout.addWidget(self.tree) self.verticalLayout.addWidget(self.tree)
# noinspection PyUnresolvedReferences
self.tree.selectionModel().selectionChanged.connect(lambda x, y: self.show_property(x)) self.tree.selectionModel().selectionChanged.connect(lambda x, y: self.show_property(x))
self.tree.keyChanged.connect(lambda x, y: self.keyChanged.emit(x, y)) self.tree.keyChanged.connect(lambda x, y: self.keyChanged.emit(x, y))
@ -526,9 +566,9 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
self.tree.add_graph(idd, name) self.tree.add_graph(idd, name)
self.tree.blockSignals(False) self.tree.blockSignals(False)
def add_item(self, idd: str, name: str, gid: str): def add_item(self, idd: str, name: str, value: str, gid: str, update: bool= True):
self.tree.blockSignals(True) self.tree.blockSignals(True)
self.tree.add_item((idd, name), gid) self.tree.add_item((idd, name, value), gid, update=update)
self.tree.blockSignals(False) self.tree.blockSignals(False)
def add_item_list(self, loi: list, gid: str): def add_item_list(self, loi: list, gid: str):
@ -536,7 +576,7 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
self.tree.add_item(loi, gid) self.tree.add_item(loi, gid)
self.tree.blockSignals(False) self.tree.blockSignals(False)
def remove_item(self, key): def remove_item(self, key: list[str]):
self.tree.remove_item(key) self.tree.remove_item(key)
def show_property(self, _: QtCore.QModelIndex = None): def show_property(self, _: QtCore.QModelIndex = None):

View File

@ -42,6 +42,8 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
self.max_y = inf self.max_y = inf
self.min_y = -inf self.min_y = -inf
self.treeWidget.itemChanged.connect(self._update_by_tree)
def __call__(self, graph_name, items): def __call__(self, graph_name, items):
self.label_2.setText(f'Connected to {graph_name}\nChanging tab will remove all integration limits.') self.label_2.setText(f'Connected to {graph_name}\nChanging tab will remove all integration limits.')
@ -77,7 +79,6 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
for idx, rnge in enumerate(self.ranges): for idx, rnge in enumerate(self.ranges):
self._update_values(idx, rnge) self._update_values(idx, rnge)
def add(self, pos): def add(self, pos):
x = pos[0] x = pos[0]
self.ranges.append((x, x*1.1)) self.ranges.append((x, x*1.1))
@ -108,7 +109,10 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
item_list = [] item_list = []
for text, val in [('Start', pts_i[0]), ('Stop', pts_i[1]), ('Areas', 0), ('Ratio', 1.)]: for text, val in [('Start', pts_i[0]), ('Stop', pts_i[1]), ('Areas', 0), ('Ratio', 1.)]:
child = QtWidgets.QTreeWidgetItem() child = QtWidgets.QTreeWidgetItem()
child.setFlags(QtCore.Qt.NoItemFlags) if text.startswith('S'):
child.setFlags(child.flags() | QtCore.Qt.ItemIsEditable)
else:
child.setFlags(QtCore.Qt.NoItemFlags)
child.setText(0, f'{text}: {val:.5g}') child.setText(0, f'{text}: {val:.5g}')
child.setForeground(0, QtGui.QBrush(QtGui.QColor('black'))) child.setForeground(0, QtGui.QBrush(QtGui.QColor('black')))
@ -121,8 +125,27 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
self._update_values(len(self.ranges) - 1, pts_i) self._update_values(len(self.ranges) - 1, pts_i)
def _update_by_tree(self, item: QtWidgets.QTreeWidgetItem) -> None:
parent_item = item.parent()
idx = self.treeWidget.invisibleRootItem().indexOfChild(parent_item)
is_left_border = parent_item.indexOfChild(item) == 0
current_region = self.lines[idx][0]
current_limits = current_region.getRegion()
new_value = item.text(0)
try:
new_value = float(new_value)
if is_left_border:
current_region.setRegion((new_value, current_limits[1]))
else:
current_region.setRegion((current_limits[0], new_value))
except ValueError:
self._update_values(idx, current_limits)
def _update_integral(self): def _update_integral(self):
idx = None idx = None
reg = None
sender = self.sender() sender = self.sender()
for i, (reg, _) in enumerate(self.lines): for i, (reg, _) in enumerate(self.lines):
if sender == reg: if sender == reg:
@ -132,19 +155,20 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
if idx is None: if idx is None:
return return
self._update_values(idx, sender.getRegion()) self._update_values(idx, reg.getRegion())
def _update_values(self, idx, new_range): def _update_values(self, idx, new_range):
self.ranges[idx] = new_range self.ranges[idx] = new_range
area = self.make_integral(idx, *new_range) area = self.make_integral(idx, *new_range)
self.treeWidget.blockSignals(True)
item = self.treeWidget.topLevelItem(idx) item = self.treeWidget.topLevelItem(idx)
item.child(0).setText(0, f'Start: {new_range[0]:.5g}') item.child(0).setText(0, f'Start: {new_range[0]:.5g}')
item.child(1).setText(0, f'Stop: {new_range[1]:.5g}') item.child(1).setText(0, f'Stop: {new_range[1]:.5g}')
if area is not None: if area is not None:
self.areas[idx] = area
item.child(2).setText(0, f'Area: {area:.5g}') item.child(2).setText(0, f'Area: {area:.5g}')
if self.max_area > 0: if self.max_area > 0:
self._set_ratios(idx, self.max_area) self._set_ratios(idx, self.max_area)
@ -157,9 +181,12 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
self._set_ratios(i, curr_max) self._set_ratios(i, curr_max)
self.max_area = curr_max self.max_area = curr_max
self.treeWidget.blockSignals(False)
def _set_ratios(self, idx, max_value): def _set_ratios(self, idx, max_value):
item = self.treeWidget.invisibleRootItem().child(idx) item = self.treeWidget.invisibleRootItem().child(idx)
area_i = self.areas[idx] area_i = self.areas[idx]
item.child(3).setText(0, f'Ratio: {area_i / max_value:.3g}') item.child(3).setText(0, f'Ratio: {area_i / max_value:.3g}')
integral_line = self.lines[idx][1] integral_line = self.lines[idx][1]
@ -176,7 +203,9 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
if integral.size != 0: if integral.size != 0:
area = integral[-1, 1] area = integral[-1, 1]
scale = (self.max_y-self.min_y) / area scale = (self.max_y-self.min_y) / area
self.lines[idx][1].setData(x=integral[:, 0], y=integral[:, 1]*scale + self.min_y) self.lines[idx][1].setData(x=integral[:, 0], y=integral[:, 1]*scale + self.min_y)
self.areas[idx] = area
return area return area

View File

@ -177,7 +177,7 @@ class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
it.setText(f'{pos[0]:.5g} - {pos[1]:.5g}') it.setText(f'{pos[0]:.5g} - {pos[1]:.5g}')
self.peaktable.blockSignals(False) self.peaktable.blockSignals(False)
it_pts.blockSignals(True) it_pts.blockSignals(True)
it_pts.setRegion(pos) it_pts.setRegion(pos, use_log=True)
it_pts.blockSignals(False) it_pts.blockSignals(False)
undo = False undo = False
@ -187,9 +187,15 @@ class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
self.peaktable.blockSignals(False) self.peaktable.blockSignals(False)
def set_graphs(self, graphs: list): def set_graphs(self, graphs: list):
last_graph = self.graph_combobox.currentData()
self.graph_combobox.clear() self.graph_combobox.clear()
for g in graphs: idx = 0
for i, g in enumerate(graphs):
self.graph_combobox.addItem(g[1], userData=g[0]) self.graph_combobox.addItem(g[1], userData=g[0])
if g[0] == last_graph:
idx = i
self.graph_combobox.setCurrentIndex(idx)
@QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged') @QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged')
def changed_state(self, checked): def changed_state(self, checked):

View File

@ -1,7 +1,7 @@
import numpy as np import numpy as np
from itertools import cycle from itertools import cycle
from pyqtgraph import mkColor, mkPen from pyqtgraph import mkColor, mkPen, mkBrush
from nmreval.lib.colors import Tab10 from nmreval.lib.colors import Tab10
@ -42,11 +42,17 @@ class QShift(QtWidgets.QDialog, Ui_shift_dialog):
def add_item(self, idx, name, x, y): def add_item(self, idx, name, x, y):
color = mkColor(next(self._colors).rgb()) color = mkColor(next(self._colors).rgb())
if np.iscomplexobj(y):
pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color)), if len(y) == 1:
PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color))] sym_kwds = {'symbol': 'o', 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
else: else:
pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color))] sym_kwds = {'symbol': None, 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
if np.iscomplexobj(y):
pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color), **sym_kwds),
PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color), **sym_kwds)]
else:
pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color), **sym_kwds)]
self.data[idx] = (pl, x, y) self.data[idx] = (pl, x, y)

View File

@ -1,2 +1,2 @@
from .phase_dialog import QApodDialog, QPhasedialog from .phase_dialog import QPreviewDialog
from .baseline_dialog import QBaselineDialog from .baseline_dialog import QBaselineDialog

View File

@ -1,3 +1,4 @@
from nmreval.lib.logger import logger
from nmreval.math import apodization from nmreval.math import apodization
from nmreval.lib.importer import find_models from nmreval.lib.importer import find_models
from nmreval.utils.text import convert from nmreval.utils.text import convert
@ -46,7 +47,7 @@ class EditSignalWidget(QtWidgets.QWidget, Ui_Form):
stype = 'pts' stype = 'pts'
else: else:
try: try:
_nop = float(self.lineEdit.text()) _nop = float(self.ls_lineEdit.text())
except ValueError: except ValueError:
_nop = 0.0 _nop = 0.0
stype = 'time' stype = 'time'
@ -67,7 +68,7 @@ class EditSignalWidget(QtWidgets.QWidget, Ui_Form):
self.do_something.emit(sender, (ph0, ph1, pvt)) self.do_something.emit(sender, (ph0, ph1, pvt))
else: else:
print('You should never reach this by accident.') logger.warning(f'You should never reach this by accident, invalid sender {sender!r}')
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged') @QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
def change_apodization(self, index): def change_apodization(self, index):

View File

@ -1,119 +1,43 @@
from __future__ import annotations from __future__ import annotations
import numpy as np
from pyqtgraph import mkPen from pyqtgraph import mkPen
from numpy import inf, linspace import numpy as np
from numpy.fft import fft, fftfreq, fftshift from numpy import pi
from numpy.fft import fft, fftshift, fftfreq
from nmreval.data import FID, Spectrum
from ...lib.pg_objects import PlotItem, LogInfiniteLine from ...lib.pg_objects import PlotItem, LogInfiniteLine
from nmreval.lib.importer import find_models from nmreval.lib.importer import find_models
from nmreval.math import apodization as apodization from nmreval.math import apodization as apodization
from nmreval.utils.text import convert from nmreval.utils.text import convert
from ...Qt import QtCore, QtWidgets from ...Qt import QtCore, QtWidgets, QtGui
from ..._py.apod_dialog import Ui_ApodEdit from ..._py.apod_dialog import Ui_ApodEdit
from ..._py.phase_corr_dialog import Ui_SignalEdit
from ...lib.forms import FormWidget from ...lib.forms import FormWidget
class QPreviewDialogs(QtWidgets.QDialog): class QPreviewDialog(QtWidgets.QDialog, Ui_ApodEdit):
finished = QtCore.pyqtSignal(str, tuple) finished = QtCore.pyqtSignal(str, tuple)
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
self.data = []
self.graphs = []
self.mode = ''
def setRange(self, xlim: list, ylim: list, logmode: list[bool]):
self.graphicsView.getPlotItem().setLogMode(x=logmode[0], y=logmode[1])
if logmode[0]:
xlim = [np.log10(x) for x in xlim]
if logmode[1]:
ylim = [np.log10(y) for y in ylim]
self.graphicsView.setRange(xRange=xlim, yRange=ylim, padding=0, disableAutoRange=True)
def add_data(self, x, y):
self.data.append((x, y))
real_plt = PlotItem(x=x, y=y.real, pen=mkPen('b'), )
imag_plt = PlotItem(x=x, y=y.imag, pen=mkPen('r'))
self.graphs.append((real_plt, imag_plt))
self.graphicsView.addItem(real_plt)
self.graphicsView.addItem(imag_plt)
def done(self, val):
self.cleanup()
super().done(val)
def close(self):
self.cleanup()
super().close()
def accept(self):
self.finished.emit(self.mode, self.get_value())
super().accept()
def get_value(self):
raise NotImplementedError
def cleanup(self):
self.blockSignals(True)
for line in self.graphs:
for g in line:
self.graphicsView.removeItem(g)
del g
self.graphicsView.clear()
self.data = []
self.graphs = []
self.blockSignals(False)
class QPhasedialog(QPreviewDialogs, Ui_SignalEdit):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self) self.setupUi(self)
self.mode = 'ph' self.data = []
self.graphs = []
self._tmp_data_bl = []
self._tmp_data_zf = []
self._tmp_data_ls = []
self._tmp_data_ap = []
self._tmp_data_ph = []
self.pvt_line = LogInfiniteLine(pos=0, movable=True) self.pvt_line = LogInfiniteLine(pos=0, movable=True)
self.graphicsView.addItem(self.pvt_line) self.freq_graph.addItem(self.pvt_line)
self.pvt_line.sigPositionChanged.connect(self.move_line) self.pvt_line.sigPositionChanged.connect(self.move_line)
@QtCore.pyqtSlot(float, name='on_ph1slider_valueChanged') self.ls_lineedit.hide()
@QtCore.pyqtSlot(float, name='on_ph0slider_valueChanged')
def _temp_phase(self, *args):
ph0, ph1, pvt = self.get_value()
self.pvt_line.setValue(pvt)
for i, (x, y) in enumerate(self.data):
phasecorr = np.exp(-1j * (ph0 + ph1*(x-pvt)/np.max(x))*np.pi/180.)
_y = y * phasecorr
self.graphs[i][0].setData(x=x, y=_y.real)
self.graphs[i][1].setData(x=x, y=_y.imag)
def get_value(self):
return float(self.ph0slider.text()), float(self.ph1slider.text()), float(self.pivot_lineedit.text())
def move_line(self, evt):
self.pivot_lineedit.setText(f'{evt.value():.5g}')
class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self._limits = (-inf, inf), -inf
self.apods = []
self.apods = find_models(apodization) self.apods = find_models(apodization)
self.apodcombobox.blockSignals(True) self.apodcombobox.blockSignals(True)
@ -122,72 +46,262 @@ class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
self.apodcombobox.blockSignals(False) self.apodcombobox.blockSignals(False)
self.apod_graph = PlotItem(x=[], y=[]) self.apod_graph = PlotItem(x=[], y=[])
self.graphicsView.addItem(self.apod_graph) self.time_graph.addItem(self.apod_graph)
self.mode = 'ap' for g in [self.freq_graph, self.time_graph]:
pl = g.getPlotItem()
pl.hideButtons()
pl.setMenuEnabled(False)
self._all_time = None
self._all_freq = None
self.change_apodization(0) self.change_apodization(0)
def add_data(self, x, y): self.shift_box.clicked.connect(self._update_shift)
real_plt = PlotItem(x=x, y=y.real, pen=mkPen('b')) self.ls_spinbox.valueChanged.connect(self._update_shift)
# imag_plt = (x=x, y=y.imag, pen=pg.mkPen('r')) self.ls_lineedit.setValidator(QtGui.QDoubleValidator())
self.graphicsView.addItem(real_plt) self.ls_lineedit.textChanged.connect(self._update_shift)
# self.graphicsView.addItem(imag_plt)
self.zerofill_box.clicked.connect(self._update_zf)
self.zf_spinbox.valueChanged.connect(self._update_zf)
self.apod_box.clicked.connect(self._update_apod)
self.phase_box.clicked.connect(self._update_phase)
self.ph0_spinbox.valueChanged.connect(self._update_phase)
self.ph1_spinbox.valueChanged.connect(self._update_phase)
self.pivot_lineedit.setValidator(QtGui.QDoubleValidator())
self.pivot_lineedit.textChanged.connect(self._update_phase)
self.pivot_lineedit.textEdited.connect(lambda x: self.pvt_line.setValue(float(x)))
def add_data(self: QPreviewDialog, data: FID | Spectrum) -> bool:
if isinstance(data, FID):
if self._all_freq:
msg = QtWidgets.QMessageBox.warning(self, 'Mixed types',
'Timesignals and spectra cannot be edited at the same time.')
return False
else:
self._all_time = True
self._all_freq = False
elif isinstance(data, Spectrum):
if self._all_time:
msg = QtWidgets.QMessageBox.warning(self, 'Mixed types',
'Timesignals and spectra cannot be edited at the same time.')
return False
else:
self._all_time = False
self._all_freq = True
fid = data.copy()
spec = self._temp_fft_time(fid.x, fid.y, self.baseline_box.isChecked())
x_len = data.x.size
self.zf_spinbox.setMaximum(min(2**17//x_len, 3))
real_plt = PlotItem(x=fid.x, y=fid.y.real, pen=mkPen('b'))
imag_plt = PlotItem(x=fid.x, y=fid.y.imag, pen=mkPen('r'))
self.time_graph.addItem(imag_plt)
self.time_graph.addItem(real_plt)
real_plt_fft = PlotItem(x=spec[0], y=spec[1].real, pen=mkPen('b'))
imag_plt_fft = PlotItem(x=spec[0], y=spec[1].imag, pen=mkPen('r'))
self.freq_graph.addItem(imag_plt_fft)
self.freq_graph.addItem(real_plt_fft)
self.data.append(data)
for p in [self._tmp_data_bl, self._tmp_data_ls]:
p.append(data.y.copy())
for p in [self._tmp_data_zf, self._tmp_data_ap]:
p.append((data.x, data.y.copy()))
self._tmp_data_ph.append((data.x, data.y, spec[0], spec[1]))
self.graphs.append((real_plt, imag_plt, real_plt_fft, imag_plt_fft))
return True
@QtCore.pyqtSlot(name='on_baseline_box_clicked')
def _update_bl(self):
if self.baseline_box.isChecked():
for y in self._tmp_data_bl:
self._temp_baseline(y)
else:
for i, d in enumerate(self.data):
self._tmp_data_bl[i] = d.y.copy()
self._update_shift()
def _update_shift(self):
if self.shift_box.isChecked():
if self.ls_combobox.currentIndex() == 0:
num_points = self.ls_spinbox.value()
is_time = False
else:
num_points = float(self.ls_lineedit.text())
is_time = True
for i, y in enumerate(self._tmp_data_bl):
self._tmp_data_ls[i] = self._temp_leftshift(self.data[i].dx, y, num_points, is_time)
else:
for i, y in enumerate(self._tmp_data_bl):
self._tmp_data_ls[i] = y
self._update_zf()
def _update_zf(self):
zf_padding = self.zf_spinbox.value()
if self.zerofill_box.isChecked():
for i, y in enumerate(self._tmp_data_ls):
self._tmp_data_zf[i] = self._temp_zerofill(self.data[i].x, y, zf_padding)
else:
for i, y in enumerate(self._tmp_data_ls):
self._tmp_data_zf[i] = self.data[i].x, y
self._update_apod()
def _update_apod(self):
if self.apod_box.isChecked():
model = self.apods[self.apodcombobox.currentIndex()]
p = self._get_parameter()
x_limit = np.inf, -np.inf
y_limit = -np.inf
for i, (x, y) in enumerate(self._tmp_data_zf):
self._tmp_data_ap[i] = x, y * model.apod(x, *p)
y_limit = max(y.real.max(), y_limit)
x_limit = min(x_limit[0], x.min()), max(x_limit[1], x.max())
_x_apod = np.linspace(*x_limit, num=150)
_y_apod = model.apod(_x_apod, *p)
self.apod_graph.setData(x=_x_apod, y=y_limit * _y_apod)
self.apod_graph.show()
else:
for i, (x, y) in enumerate(self._tmp_data_zf):
self._tmp_data_ap[i] = x, y
self.apod_graph.hide()
self._update_phase()
def _update_phase(self):
if self.phase_box.isChecked():
pvt = float(self.pivot_lineedit.text())
self.pvt_line.show()
ph0 = self.ph0_spinbox.value()
ph1 = self.ph1_spinbox.value()
for i, (x, y) in enumerate(self._tmp_data_ap):
x_fft, y_fft = self._temp_fft_time(x, y, self.baseline_box.isChecked())
if ph0 != 0:
y = self._temp_phase(x, y, ph0, 0, 0)
y_fft = self._temp_phase(x, y_fft, ph0, ph1, pvt)
elif ph1 != 0:
y_fft = self._temp_phase(x, y_fft, ph0, ph1, pvt)
self._tmp_data_ph[i] = x, y, x_fft, y_fft
else:
self.pvt_line.hide()
for i, (x, y) in enumerate(self._tmp_data_ap):
self._tmp_data_ph[i] = x, y, *self._temp_fft_time(x, y, self.baseline_box.isChecked())
self._update_plots()
def _update_plots(self):
for i, (x, y, xf, yf) in enumerate(self._tmp_data_ph):
self.graphs[i][0].setData(x=x, y=y.real)
self.graphs[i][1].setData(x=x, y=y.imag)
self.graphs[i][2].setData(x=xf, y=yf.real)
self.graphs[i][3].setData(x=xf, y=yf.imag)
@staticmethod
def _temp_baseline_time(y):
y -= y[int(-0.12 * y.size):].mean()
@staticmethod
def _temp_baseline_freq(y):
region = int(0.12 * y.size)
y -= np.mean([y[-region:], y[:region]])
@staticmethod
def _temp_phase(x: np.ndarray, y: np.ndarray, ph0: float, ph1: float, pvt: float) -> np.ndarray:
phase_correction = np.exp(-1j * (ph0 + ph1 * (x - pvt) / x.max()) * pi / 180.)
_y = y * phase_correction
return _y
@staticmethod
def _temp_zerofill(x: np.ndarray, y: np.ndarray, num_padding: int) -> tuple[np.ndarray, np.ndarray]:
length = x.size
factor = 2**num_padding
_y = np.r_[y, np.zeros((factor-1) * length)]
_temp_x = np.arange(1, (factor-1) * length+1) * (x[1]-x[0]) + np.max(x)
_x = np.r_[x, _temp_x]
return _x, _y
@staticmethod
def _temp_leftshift(dx: np.ndarray, y: np.ndarray, points: float | int, is_time: bool) -> np.ndarray:
if is_time:
points = int(points//dx)
_y = np.roll(y, -points)
_y[-points-1:] = 0
return _y
@staticmethod
def _temp_fft_time(x: np.ndarray, y: np.ndarray, baseline: bool = False) -> tuple[np.ndarray, np.ndarray]:
y_fft = fftshift(fft(y)) y_fft = fftshift(fft(y))
x_fft = fftshift(fftfreq(len(x), d=x[1]-x[0])) x_fft = fftshift(fftfreq(len(x), d=x[1]-x[0]))
real_plt_fft = PlotItem(x=x_fft, y=y_fft.real, pen=mkPen('b'))
# imag_plt_fft = pg.PlotDataItem(x=x_fft, y=y_fft.imag, pen=pg.mkPen('b'))
self.graphicsView_2.addItem(real_plt_fft)
# self.graphicsView_2.addItem(imag_plt_fft)
self.graphs.append((real_plt, real_plt_fft)) if baseline:
self.data.append((x, y, x_fft)) QPreviewDialog._temp_baseline_freq(y_fft)
xlimits = (max(x.min(), self._limits[0][0]), min(x.max(), self._limits[0][1])) return x_fft, y_fft
ylimit = max(self._limits[1], y.real.max())
self._limits = xlimits, ylimit @staticmethod
def _temp_fft_freq(x: np.ndarray, y: np.ndarray, _=None):
return x, y
def move_line(self, evt):
self.pivot_lineedit.setText(f'{evt.value():.5g}')
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged') @QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
def change_apodization(self, index): def change_apodization(self, index: int) -> None:
# delete old widgets # delete old widgets
self.eqn_label.setText(convert(self.apods[index].equation)) self.eqn_label.setText(convert(self.apods[index].equation))
while self.widget_layout.count(): while self.widget_layout.count():
item = self.widget_layout.takeAt(0) item = self.widget_layout.takeAt(0)
if isinstance(item, FormWidget):
item.disconnect()
try: try:
item.widget().deleteLater() item.widget().deleteLater()
except AttributeError: except AttributeError:
pass pass
# set up parameter widgets for new model # set up parameter widgets for new model
for k, v in enumerate(self.apods[index]().params): for k, v in enumerate(self.apods[index].params):
widgt = FormWidget(name=v) widget = FormWidget(name=v)
widgt.valueChanged.connect(self._temp_apod) widget.value = 1
self.widget_layout.addWidget(widgt) widget.valueChanged.connect(self._update_apod)
self.widget_layout.addWidget(widget)
self.widget_layout.addStretch() self.widget_layout.addStretch()
self._temp_apod() self._update_apod()
def _temp_apod(self):
apodmodel = self.apods[self.apodcombobox.currentIndex()]
p = self._get_parameter()
if self.data:
for i, (x, y, x_fft) in enumerate(self.data):
y2 = apodmodel.apod(x, *p)
_y = y2 * y
self.graphs[i][0].setData(x=x, y=_y.real)
# self.graphs[i][1].setData(y=_y.imag)
y_fft = fftshift(fft(_y))
self.graphs[i][1].setData(x=x_fft, y=y_fft.real)
# self.graphs[i][3].setData(y=y_fft.imag)
_x_apod = linspace(self._limits[0][0], self._limits[0][1])
try:
_y_apod = apodmodel.apod(_x_apod, *p)
self.apod_graph.setData(x=_x_apod, y=self._limits[1]*_y_apod)
except IndexError:
pass
def _get_parameter(self): def _get_parameter(self):
p = [] p = []
@ -201,8 +315,113 @@ class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
return p return p
def get_value(self): @QtCore.pyqtSlot(int, name='on_ls_combobox_currentIndexChanged')
apodmodel = self.apods[self.apodcombobox.currentIndex()] def change_ls(self, idx: int) -> None:
p = self._get_parameter() self.ls_lineedit.setVisible(bool(idx))
self.ls_spinbox.setVisible(not bool(idx))
@QtCore.pyqtSlot(bool, name='on_ft_checkbox_stateChanged')
def change_ft(self, state: bool):
self.ph1_spinbox.setEnabled(state)
self.pivot_lineedit.setEnabled(state)
def cleanup(self):
self.blockSignals(True)
for line in self.graphs:
for g in line:
self.time_graph.removeItem(g)
self.freq_graph.removeItem(g)
del g
self.time_graph.clear()
self.freq_graph.clear()
self._tmp_data_ap = []
self._tmp_data_bl = []
self._tmp_data_ls = []
self._tmp_data_ph = []
self._tmp_data_zf = []
self.data = []
self.graphs = []
self.freq_graph.removeItem(self.pvt_line)
self.time_graph.removeItem(self.pvt_line)
self.blockSignals(False)
def get_value(self):
edits = [(None,), (None,), (None,), (None,), (None,), (None,)]
if self.baseline_box.isChecked():
edits[0] = (True,)
if self.zerofill_box.isChecked():
edits[2] = (self.zf_spinbox.value(),)
if self.shift_box.isChecked():
if self.ls_combobox.currentIndex() == 0:
edits[1] = (self.ls_spinbox.value(), 'pts')
else:
edits[1] = (float(self.ls_lineedit.text()), 'time')
if self.apod_box.isChecked():
edits[3] = (self._get_parameter(), self.apods[self.apodcombobox.currentIndex()])
if self.phase_box.isChecked():
edits[4] = (self.ph0_spinbox.value(), self.ph1_spinbox.value(), float(self.pivot_lineedit.text()))
if self.ft_box.isChecked():
edits[5] = (self.phase_before_button.isChecked(),)
return edits
def exec(self):
self._prepare_ui()
return super().exec()
def _prepare_ui(self):
"""Stuff we have to do before showing the window but after all the data was added"""
vb = self.freq_graph.getPlotItem().getViewBox()
vb.disableAutoRange(axis=vb.YAxis)
vb = self.time_graph.getPlotItem().getViewBox()
vb.disableAutoRange(axis=vb.YAxis)
self.zerofill_box.setVisible(self._all_time)
self.apod_box.setVisible(self._all_time)
self.shift_box.setVisible(self._all_time)
self.time_graph.setVisible(self._all_time)
self.logtime_widget.setVisible(self._all_time)
@QtCore.pyqtSlot(int, name='on_logx_time_stateChanged')
@QtCore.pyqtSlot(int, name='on_logy_time_stateChanged')
@QtCore.pyqtSlot(int, name='on_logx_freq_stateChanged')
@QtCore.pyqtSlot(int, name='on_logy_freq_stateChanged')
def set_log(self, state: int):
switch = {
self.logx_time: lambda _x: self.time_graph.setLogMode(x=_x),
self.logy_time: lambda _x: self.time_graph.setLogMode(y=_x),
self.logx_freq: lambda _x: self.freq_graph.setLogMode(x=_x),
self.logy_freq: lambda _x: self.freq_graph.setLogMode(y=_x),
}[self.sender()]
switch(state == QtCore.Qt.Checked)
vb = self.freq_graph.getPlotItem().getViewBox()
vb.disableAutoRange(axis=vb.YAxis)
vb = self.time_graph.getPlotItem().getViewBox()
vb.disableAutoRange(axis=vb.YAxis)
self._temp_baseline = self._temp_baseline_time if self._all_time else self._temp_baseline_freq
self._temp_fft = self._temp_fft_time if self._all_time else self._temp_fft_freq
self.freq_graph.setVisible(self._all_time)
if self._all_freq:
self.time_graph.addItem(self.pvt_line)
else:
self.freq_graph.addItem(self.pvt_line)
return p, apodmodel

View File

@ -188,7 +188,15 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
new_value = complex(val) new_value = complex(val)
new_value = new_value.real if new_value.imag == 0 else new_value new_value = new_value.real if new_value.imag == 0 else new_value
# table view loses focus when itemChanged is emitted
# if edit of item is cause of change resume editing at next item
prev_state = self.tableView.state()
idx = self.tableView.currentIndex()
idx = idx.sibling((col+1)//3+row, (col+1) % 3)
self.itemChanged.emit(sid, (col, row), new_value) self.itemChanged.emit(sid, (col, row), new_value)
if prev_state == self.tableView.State.EditingState:
self.tableView.setCurrentIndex(idx)
self.tableView.edit(idx)
@QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection) @QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection)
def show_position(self, *_): def show_position(self, *_):
@ -259,10 +267,7 @@ class ValueModel(QtCore.QAbstractTableModel):
row = idx.row() row = idx.row()
if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]: if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]:
val = self._data[row][idx.column()] val = self._data[row][idx.column()]
if isinstance(val, complex): return self.as_string(val)
return f'{val.real:.8g}{val.imag:+.8g}j'
else:
return f'{val:.8g}'
elif role == QtCore.Qt.BackgroundRole: elif role == QtCore.Qt.BackgroundRole:
pal = QtGui.QGuiApplication.palette() pal = QtGui.QGuiApplication.palette()
@ -295,11 +300,16 @@ class ValueModel(QtCore.QAbstractTableModel):
if value: if value:
if role == QtCore.Qt.EditRole: if role == QtCore.Qt.EditRole:
if value == self.as_string(self._data[row][col]):
return True
try: try:
value = complex(value) value = complex(value)
except ValueError: except ValueError:
# not a number # not a number
return False return False
value = value.real if value.imag == 0 else value
self._data[row][col] = value.real if value.imag == 0 else value self._data[row][col] = value.real if value.imag == 0 else value
self.itemChanged.emit(col, row, str(value)) self.itemChanged.emit(col, row, str(value))
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role]) self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role])
@ -368,3 +378,10 @@ class ValueModel(QtCore.QAbstractTableModel):
def unmask(self): def unmask(self):
self.mask = [True] * self.total_rows self.mask = [True] * self.total_rows
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole]) self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole])
@staticmethod
def as_string(value) -> str:
if isinstance(value, complex):
return f'{value.real:.8g}{value.imag:+.8g}j'
else:
return f'{value:.8g}'

View File

View File

@ -0,0 +1,375 @@
from itertools import cycle
from numpy import array, nan, isnan
from pyqtgraph import mkPen, mkBrush
from nmreval.dsc.hodge import tau_hodge
from nmreval.lib.colors import Tab10
from ..Qt import QtWidgets, QtCore
from .._py.tnmh_dialog import Ui_DSCEvalDialog
from ..lib.pg_objects import PlotItem, RegionItem
from nmreval.data import DSC, Points
class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
newData = QtCore.pyqtSignal(dict, str)
def __init__(self, management, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self._management = management
self._colors = cycle(Tab10)
self._dsc = {}
self._plots = {}
self._tg_value = {}
self._fit = {}
self._hodge = {
'onset': (
PlotItem(x=[], y=[], pen=None, symbol='o', symbolBrush=Tab10.TabBlue.rgb(), name='Onset'),
None,
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabBlue.rgb()})),
PlotItem(x=[], y=[], pen=None, symbol='o', symbolBrush=Tab10.TabBlue.rgb())),
None,
),
'midpoint': (
PlotItem(x=[], y=[], pen=None, symbol='s', symbolBrush=Tab10.TabOrange.rgb(), name='Midpoint'),
None,
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabOrange.rgb()})),
PlotItem(x=[], y=[], pen=None, symbol='s', symbolBrush=Tab10.TabOrange.rgb())),
None,
),
'end': (
PlotItem(x=[], y=[], pen=None, symbol='t', symbolBrush=Tab10.TabGreen.rgb(), name='End'),
None,
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabGreen.rgb()})),
PlotItem(x=[], y=[], pen=None, symbol='t', symbolBrush=Tab10.TabGreen.rgb())),
None,
),
'inflection': (
PlotItem(x=[], y=[], pen=None, symbol='d', symbolBrush=Tab10.TabRed.rgb(), name='Inflection'),
None,
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabRed.rgb()})),
PlotItem(x=[], y=[], pen=None, symbol='d', symbolBrush=Tab10.TabRed.rgb())),
None,
),
'fictive': (
PlotItem(x=[], y=[], pen=None, symbol='t1', symbolBrush=Tab10.TabPurple.rgb(), name='Fictive'),
None,
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabPurple.rgb()})),
PlotItem(x=[], y=[], pen=None, symbol='t1', symbolBrush=Tab10.TabPurple.rgb())),
None,
),
}
self._lines = {}
self.tau_plot.getPlotItem().addLegend()
for plt, _, fitplt, _ in self._hodge.values():
self.tau_plot.addItem(plt)
self.tghodge_graph.addItem(fitplt[0])
self.tghodge_graph.addItem(fitplt[1])
self.tau_plot.setLogMode(y=True)
self.tghodge_graph.setLogMode(y=True)
self.limits = RegionItem(), RegionItem()
for lim in self.limits:
self.dsc_plot.addItem(lim)
self._limitless = True
self.add_sets()
self.tnmh_graph_check.stateChanged.connect(lambda state: self.tnmh_graph_combo.setEnabled(not bool(state)))
self.hodge_graph_check.stateChanged.connect(lambda state: self.hodge_graph_combo.setEnabled(not bool(state)))
self.next_button.clicked.connect(lambda: self.stackedWidget.setCurrentIndex((self.stackedWidget.currentIndex() + 1) % 3))
self.back_button.clicked.connect(lambda: self.stackedWidget.setCurrentIndex((self.stackedWidget.currentIndex() + 2) % 3))
self.listWidget.itemChanged.connect(self.change_visibility)
def __call__(self):
self.clear()
self._colors = cycle(Tab10)
self.add_sets()
return self
def clear(self):
self.listWidget.clear()
self.tg_tree.clear()
self.tnmh_tree.clear()
for plots in self._plots.values():
for val in plots:
self.dsc_plot.removeItem(val)
self.tnmh_graphics.removeItem(val)
for key, plt in self._hodge.items():
plt[0].setData(x=[], y=[])
plt[2][0].setData(x=[], y=[])
plt[2][1].setData(x=[], y=[])
self._hodge[key] = (plt[0], None, plt[2], None)
self._dsc = {}
self._plots = {}
self._tg_value = {}
self._lines = {}
self._fit = {}
self.stackedWidget.setCurrentIndex(0)
def add_sets(self):
for w in (self.tnmh_graph_combo, self.hodge_graph_combo):
w.clear()
for graphs in self._management.graphs.list():
w.addItem(graphs[1], userData=graphs[0])
min_x = 10_000_000
max_x = -10_000_000
for (key, name), c in zip(self._management.active_sets, self._colors):
data = self._management[key].data
if not isinstance(data, DSC):
continue
min_x = min(min_x, data.x.min())
max_x = max(max_x, data.x.max())
item = QtWidgets.QListWidgetItem(name)
item.setCheckState(QtCore.Qt.Checked)
item.setData(QtCore.Qt.UserRole, key)
item.setForeground(mkBrush(c.rgb()))
self.listWidget.addItem(item)
self._dsc[key] = (data, None)
data_plot = PlotItem(x=data.x, y=data.y, pen=mkPen(c.rgb()))
self.dsc_plot.addItem(data_plot)
glass = PlotItem()
glass.set_line(style=2, color=c)
self.dsc_plot.addItem(glass)
liquid = PlotItem()
liquid.set_line(style=2, color=c)
self.dsc_plot.addItem(liquid)
tangent = PlotItem()
tangent.set_line(style=2, color=c)
self.dsc_plot.addItem(tangent)
tg_plot = PlotItem(pen=None, symbolBrush=c.rgb(), symbol='o')
self.dsc_plot.addItem(tg_plot)
fictive_cp = PlotItem(pen=mkPen(c.rgb()))
self.tnmh_graphics.addItem(fictive_cp)
tnmh_fit = PlotItem()
tnmh_fit.set_line(style=2, color=c)
self.tnmh_graphics.addItem(tnmh_fit)
self._plots[key] = (data_plot, tg_plot, glass, liquid, tangent, fictive_cp, tnmh_fit)
self._tg_value[key] = {
'onset': (nan, nan),
'midpoint': (nan, nan),
'end': (nan, nan),
'inflection': (nan, nan),
# 'fictive': (nan, nan),
}
if self._limitless and max_x != -10000000 and min_x != 10000000 :
dist = max_x - min_x
self.limits[0].setRegion((min_x, min_x+min(0.1*dist, 5)))
self.limits[1].setRegion((max_x-min(5, 0.1*dist), max_x))
self._limitless = False
@QtCore.pyqtSlot(name='on_calctg_button_clicked')
def calc_tg(self):
baselines = tuple(lim.getRegion() for lim in self.limits)
if baselines[0][0] > baselines[1][0]:
baselines = baselines[1], baselines[0]
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
if item.checkState() == QtCore.Qt.Unchecked:
continue
key = item.data(QtCore.Qt.UserRole)
plot = self._plots[key]
data, _ = self._dsc[key]
tg_results, glass, liquid, tangent = data.glass_transition(*baselines)
self._lines[key] = (glass, liquid, tangent)
for i, line in enumerate((glass, liquid, tangent)):
plot[i+2].setData(x=line.x, y=line.y)
self._tg_value[key].update(tg_results)
self._update_tg_plots()
def _update_tg_plots(self):
self.tg_tree.clear()
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
tree_item = QtWidgets.QTreeWidgetItem([item.text()])
values = self._tg_value.get(item.data(QtCore.Qt.UserRole))
if values is not None:
for name, pos in values.items():
child_item = QtWidgets.QTreeWidgetItem([f'{name.capitalize()}: {pos[0]:.2f} K'])
tree_item.addChild(child_item)
self.tg_tree.addTopLevelItem(tree_item)
key = item.data(QtCore.Qt.UserRole)
plot = self._plots[key]
data, _ = self._dsc[key]
plot[1].setData(array(list(self._tg_value[key].values())))
@QtCore.pyqtSlot(name='on_tg_export_button_clicked')
def export_tg(self):
ret_dic = {}
for key, tg in self._tg_value.items():
tgx = [x for x, y in tg.values()]
tgy = [y for x, y in tg.values()]
if self.tg_export_check.isChecked():
tg_pts = Points(x=tgx, y=tgy, name=self._management[key].name + ' (Tg)', value=self._management[key].value)
else:
tg_pts = None
line = []
if self.tglines_export_check.isChecked():
line = self._lines.get(key, [])
ret_dic[key] = (tg_pts, line)
self.newData.emit(ret_dic, 'tg')
@QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
def change_visibility(self, item: QtWidgets.QListWidgetItem):
is_checked = bool(item.checkState())
plot = self._plots[item.data(QtCore.Qt.UserRole)]
for val in plot:
val.setVisible(is_checked)
def get_fictive(self, key, baselines):
plot = self._plots[key]
data, _ = self._dsc[key]
cp, tg = data.get_fictive_cp(*baselines)
plot[5].setData(cp.x, cp.y)
self._dsc[key] = (data, cp)
return cp
@QtCore.pyqtSlot(name='on_tnhm_fitbutton_clicked')
def make_tnmh(self):
baselines = tuple(lim.getRegion() for lim in self.limits)
if baselines[0][0] > baselines[1][0]:
baselines = baselines[1], baselines[0]
self.tnmh_tree.clear()
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
if item.checkState() == QtCore.Qt.Unchecked:
continue
key = item.data(QtCore.Qt.UserRole)
data = self.get_fictive(key, baselines)
res = data.calculate_tnmh([60, 0.5, 1, 2e5], *baselines, return_fictive=False)
self._fit[key] = res
plot = self._plots[key]
plot[-1].setData(res.x, res.y)
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
tree_item = QtWidgets.QTreeWidgetItem([item.text()])
values = self._fit.get(item.data(QtCore.Qt.UserRole))
if values is not None:
child_item = QtWidgets.QTreeWidgetItem([values.parameter_string()])
tree_item.addChild(child_item)
self.tnmh_tree.addTopLevelItem(tree_item)
@QtCore.pyqtSlot(name='on_tnmh_export_button_clicked')
def export_tnmh(self):
ret_dic = {}
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
if item.checkState() == QtCore.Qt.Unchecked:
continue
key = item.data(QtCore.Qt.UserRole)
cp = None
if self.fictive_export_check.isChecked():
_, cp = self._dsc[key]
line = None
if self.tnmhfit_export_check.isChecked():
line = self._fit.get(key)
ret_dic[key] = (cp, line)
ret_dic['graph'] = '' if self.tnmh_graph_check.isChecked() else self.tnmh_graph_combo.currentData()
self.newData.emit(ret_dic, 'tnmh')
@QtCore.pyqtSlot(name='on_hodge_button_clicked')
def hodge(self):
for tg_type, (plot, data, fitplots, fit) in self._hodge.items():
m = []
for idx in range(self.listWidget.count()):
item = self.listWidget.item(idx)
if item.checkState() == QtCore.Qt.Unchecked:
continue
key = item.data(QtCore.Qt.UserRole)
data, _ = self._dsc[key]
try:
tg_value = self._tg_value[key][tg_type][0]
if isnan(tg_value):
continue
except KeyError:
continue
m.append([tg_value, data.value])
if len(m) > 1:
data, fit = tau_hodge(*array(m).T)
data.name = f'{data.name} ({tg_type.capitalize()})'
plot.setData(data.x, data.y)
fitplots[0].setData(fit.x, fit.y)
fitplots[1].setData(fit.x_data, fit.y_data)
self._hodge[tg_type] = (plot, data, fitplots, fit)
@QtCore.pyqtSlot(name='on_export_hodge_button_clicked')
def export_hodge(self):
ret_dic = {}
for cb in (self.onset_check, self.mid_check, self.end_check, self.inflection_check, self.fictive_check):
if cb.isChecked():
item = cb.text().lower()
data = self._hodge.get(item)
if data[1] is not None:
ret_dic[item] = data[1]
ret_dic['graph'] = '' if self.hodge_graph_check.isChecked() else self.hodge_graph_combo.currentData()
self.newData.emit(ret_dic, 'hodge')
def close(self) -> bool:
self.clear()
return super().close()

View File

@ -5,51 +5,48 @@ from nmreval.utils.text import convert
from ..Qt import QtCore, QtWidgets, QtGui from ..Qt import QtCore, QtWidgets, QtGui
from .._py.fitmodelwidget import Ui_FitParameter from .._py.fitmodelwidget import Ui_FitParameter
from .._py.save_fitmodel_dialog import Ui_SaveDialog from .._py.save_fitmodel_dialog import Ui_SaveDialog
from ..lib import get_icon from ..lib.iconloading import get_icon
from ..lib.tables import TableWidget
class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter): class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
value_requested = QtCore.pyqtSignal(object) value_requested = QtCore.pyqtSignal(object)
value_changed = QtCore.pyqtSignal(str) value_changed = QtCore.pyqtSignal(str)
state_changed = QtCore.pyqtSignal() state_changed = QtCore.pyqtSignal()
replace_single_value = QtCore.pyqtSignal(object)
def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False): def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.parametername.setText(label + ' ') self.name = label
self.parametername.setText(convert(label) + ' ')
validator = QtGui.QDoubleValidator()
validator.setDecimals(9)
self.parameter_line.setValidator(validator)
self.parameter_line.setText('1') self.parameter_line.setText('1')
self.parameter_line.setMaximumWidth(60) self.parameter_line.setMaximumWidth(160)
self.lineEdit.setMaximumWidth(60) self.lineEdit.setMaximumWidth(100)
self.lineEdit_2.setMaximumWidth(60) self.lineEdit_2.setMaximumWidth(100)
self.label_3.setText(f'&lt; {label} &lt;') self.label_3.setText(f'&lt; {convert(label)} &lt;')
self.checkBox.stateChanged.connect(self.enableBounds) self.checkBox.stateChanged.connect(self.enableBounds)
self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit()) self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit())
self.parameter_line.editingFinished.connect(self.update_parameter)
self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self)) self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self))
self.parameter_line.replace_single_values.connect(lambda: self.replace_single_value.emit(None))
self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text())) self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text()))
self.fixed_check.toggled.connect(self.set_fixed) self.fixed_check.toggled.connect(self.set_fixed)
if fixed: if fixed:
self.fixed_check.hide() self.fixed_check.hide()
self.menu = QtWidgets.QMenu(self)
self.add_links()
self.is_linked = None
self.parameter_pos = None self.parameter_pos = None
self.func_idx = None self.func_idx = None
self._linetext = '1' self._linetext = '1'
@property self.menu = QtWidgets.QMenu(self)
def name(self):
return convert(self.parametername.text().strip(), old='html', new='str')
def set_parameter_string(self, p: str): def set_parameter_string(self, p: str):
self.parameter_line.setText(p) self.parameter_line.setText(p)
@ -69,123 +66,67 @@ class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
def set_parameter(self, p: float | None, bds: tuple[float, float, bool] = None, def set_parameter(self, p: float | None, bds: tuple[float, float, bool] = None,
fixed: bool = None, glob: bool = None): fixed: bool = None, glob: bool = None):
if p is None: ptext = f'{p:.4g}'
# bad hack: linked parameter return (None, linked parameter)
# if p is None -> parameter is linked to argument given by bds
self.link_parameter(linkto=bds)
else:
ptext = f'{p:.4g}'
self.set_parameter_string(ptext) self.set_parameter_string(ptext)
if bds is not None: if bds is not None:
self.set_bounds(*bds) self.set_bounds(*bds)
if fixed is not None: if fixed is not None:
self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked) self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked)
if glob is not None: if glob is not None:
self.global_checkbox.setCheckState(QtCore.Qt.Checked if glob else QtCore.Qt.Unchecked) self.global_checkbox.setCheckState(QtCore.Qt.Checked if glob else QtCore.Qt.Unchecked)
def get_parameter(self): def get_parameter(self):
if self.is_linked: try:
try: p = float(self.parameter_line.text().replace(',', '.'))
p = float(self._linetext) except ValueError:
except ValueError: p = self.parameter_line.text().replace(',', '.')
p = 1.0
else:
try:
p = float(self.parameter_line.text().replace(',', '.'))
except ValueError:
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value',
f'{self.parametername.text()} contains invalid values',
QtWidgets.QMessageBox.Cancel)
return None
if self.checkBox.isChecked(): if self.checkBox.isChecked():
try: lb_text = self.lineEdit.text()
lb = float(self.lineEdit.text().replace(',', '.')) lb = None
except ValueError: if lb_text:
lb = None try:
lb = float(lb_text.replace(',', '.'))
except ValueError:
lb = lb_text
try: ub_text = self.lineEdit_2.text()
rb = float(self.lineEdit_2.text().replace(',', '.')) rb = None
except ValueError: if ub_text:
rb = None try:
rb = float(ub_text.replace(',', '.'))
except ValueError:
rb = ub_text
else: else:
lb = rb = None lb = rb = None
bounds = (lb, rb) bounds = (lb, rb)
return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked(), self.is_linked return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked()
@QtCore.pyqtSlot(bool) @QtCore.pyqtSlot(bool)
def set_fixed(self, state: bool): def set_fixed(self, state: bool):
# self.global_checkbox.setVisible(not state) # self.global_checkbox.setVisible(not state)
self.frame.setVisible(not state) self.frame.setVisible(not state)
def add_links(self, parameter: dict = None):
if parameter is None:
parameter = {}
self.menu.clear()
ac = QtWidgets.QAction('Link to...', self)
ac.triggered.connect(self.link_parameter)
self.menu.addAction(ac)
for model_key, model_funcs in parameter.items():
m = QtWidgets.QMenu('Model ' + model_key, self)
for func_name, func_params in model_funcs.items():
m2 = QtWidgets.QMenu(func_name, m)
for p_name, idx in func_params:
ac = QtWidgets.QAction(p_name, m2)
ac.setData((model_key, *idx))
ac.triggered.connect(self.link_parameter)
m2.addAction(ac)
m.addMenu(m2)
self.menu.addMenu(m)
self.toolButton.setMenu(self.menu)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def link_parameter(self, linkto=None): def update_parameter(self):
if linkto is None: new_value = self.parameter_line.text()
action = self.sender() if not new_value:
else: self.parameter_line.setText('1')
action = False
for m in self.menu.actions():
if m.menu():
for a in m.menu().actions():
if a.data() == linkto:
action = a
break
if action:
break
if (self.func_idx, self.parameter_pos) == action.data():
return
try: try:
new_text = f'Linked to {action.parentWidget().title()}.{action.text()}' float(new_value)
self._linetext = self.parameter_line.text() is_text = False
self.parameter_line.setText(new_text) except ValueError:
self.parameter_line.setEnabled(False) is_text = True
self.global_checkbox.hide() self.global_checkbox.setCheckState(False)
self.global_checkbox.blockSignals(True)
self.global_checkbox.setCheckState(QtCore.Qt.Checked)
self.global_checkbox.blockSignals(False)
self.frame.hide()
self.is_linked = action.data()
except AttributeError: self.set_fixed(is_text)
self.parameter_line.setText(self._linetext)
self.parameter_line.setEnabled(True)
if self.fixed_check.isEnabled():
self.global_checkbox.show()
self.frame.show()
self.is_linked = None
self.state_changed.emit()
class QSaveModelDialog(QtWidgets.QDialog, Ui_SaveDialog): class QSaveModelDialog(QtWidgets.QDialog, Ui_SaveDialog):
@ -280,8 +221,17 @@ class FitModelTree(QtWidgets.QTreeWidget):
idx = item.data(0, self.counterRole) idx = item.data(0, self.counterRole)
self.itemRemoved.emit(idx) self.itemRemoved.emit(idx)
def add_function(self, idx: int, cnt: int, op: int, name: str, color: QtGui.QColor | str | tuple, def add_function(self,
parent: QtWidgets.QTreeWidgetItem = None, children: list = None, active: bool = True, **kwargs): idx: int,
cnt: int,
op: int,
name: str,
color: QtGui.QColor | str | tuple,
parent: QtWidgets.QTreeWidgetItem = None,
children: list = None,
active: bool = True,
param_names: list[str] = None,
**kwargs):
""" """
Add function to tree and dictionary of functions. Add function to tree and dictionary of functions.
""" """
@ -296,6 +246,10 @@ class FitModelTree(QtWidgets.QTreeWidget):
it.setData(0, self.counterRole, cnt) it.setData(0, self.counterRole, cnt)
it.setData(0, self.operatorRole, op) it.setData(0, self.operatorRole, op)
it.setText(0, name) it.setText(0, name)
if param_names is not None:
it.setToolTip(0,
'Parameter names:\n' +
'\n'.join(f'{pn}({cnt})' for pn in param_names))
it.setForeground(0, QtGui.QBrush(color)) it.setForeground(0, QtGui.QBrush(color))
it.setIcon(0, get_icon(self.icons[op])) it.setIcon(0, get_icon(self.icons[op]))
@ -365,7 +319,7 @@ class FitModelTree(QtWidgets.QTreeWidget):
return funcs return funcs
class FitTableWidget(QtWidgets.QTableWidget): class FitTableWidget(TableWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)

View File

@ -1,5 +1,8 @@
from __future__ import annotations from __future__ import annotations
from typing import Optional
from nmreval.fit.parameter import Parameter
from nmreval.utils.text import convert from nmreval.utils.text import convert
from ..Qt import QtWidgets, QtCore, QtGui from ..Qt import QtWidgets, QtCore, QtGui
@ -62,8 +65,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
self.glob_values = [1] * len(func.params) self.glob_values = [1] * len(func.params)
for k, v in enumerate(func.params): for k, v in enumerate(func.params):
name = convert(v) widgt = FitModelWidget(label=v, parent=self.scrollwidget)
widgt = FitModelWidget(label=name, parent=self.scrollwidget)
widgt.parameter_pos = k widgt.parameter_pos = k
widgt.func_idx = idx widgt.func_idx = idx
try: try:
@ -78,11 +80,12 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
widgt.state_changed.connect(self.make_global) widgt.state_changed.connect(self.make_global)
widgt.value_requested.connect(self.look_for_value) widgt.value_requested.connect(self.look_for_value)
widgt.value_changed.connect(self.change_global_parameter) widgt.value_changed.connect(self.change_global_parameter)
widgt.replace_single_value.connect(self.delete_single_parameter)
self.global_parameter.append(widgt) self.global_parameter.append(widgt)
self.scrollwidget.layout().addWidget(widgt) self.scrollwidget.layout().addWidget(widgt)
widgt2 = ParameterSingleWidget(name=name, parent=self.scrollwidget2) widgt2 = ParameterSingleWidget(name=v, parent=self.scrollwidget2)
widgt2.valueChanged.connect(self.change_single_parameter) widgt2.valueChanged.connect(self.change_single_parameter)
widgt2.removeSingleValue.connect(self.change_single_parameter) widgt2.removeSingleValue.connect(self.change_single_parameter)
widgt2.installEventFilter(self) widgt2.installEventFilter(self)
@ -114,20 +117,22 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
self.scrollwidget.layout().addStretch(1) self.scrollwidget.layout().addStretch(1)
self.scrollwidget2.layout().addStretch(1) self.scrollwidget2.layout().addStretch(1)
def set_links(self, parameter): # def set_links(self, parameter):
for w in self.global_parameter: # for w in self.global_parameter:
if isinstance(w, FitModelWidget): # if isinstance(w, FitModelWidget):
w.add_links(parameter) # w.add_links(parameter)
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def change_global_parameter(self, value: str, idx: int = None): def change_global_parameter(self, value: str, idx: int = None):
if idx is None: if idx is None:
idx = self.global_parameter.index(self.sender()) idx = self.global_parameter.index(self.sender())
self.glob_values[idx] = float(value) # self.glob_values[idx] = float(value)
self.glob_values[idx] = value
if self.data_values[self.comboBox.currentData()][idx] is None: if self.data_values[self.comboBox.currentData()][idx] is None:
self.data_parameter[idx].blockSignals(True) self.data_parameter[idx].blockSignals(True)
self.data_parameter[idx].value = float(value) # self.data_parameter[idx].value = float(value)
self.data_parameter[idx].value = value
self.data_parameter[idx].blockSignals(False) self.data_parameter[idx].blockSignals(False)
@QtCore.pyqtSlot(str, object) @QtCore.pyqtSlot(str, object)
@ -148,6 +153,13 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
if value is None: if value is None:
self.change_data(self.comboBox.currentIndex()) self.change_data(self.comboBox.currentIndex())
def delete_single_parameter(self):
idx = self.global_parameter.index(self.sender())
for i in range(self.comboBox.count()):
set_id = self.comboBox.itemData(i)
self.data_values[set_id][idx] = None
self.change_data(self.comboBox.currentIndex())
def change_single_choice(self, _, value, sender=None): def change_single_choice(self, _, value, sender=None):
if sender is None: if sender is None:
sender = self.sender() sender = self.sender()
@ -163,7 +175,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
# disable single parameter if it is set global, enable if global is unset # disable single parameter if it is set global, enable if global is unset
widget = self.sender() widget = self.sender()
idx = self.global_parameter.index(widget) idx = self.global_parameter.index(widget)
enable = (widget.global_checkbox.checkState() == QtCore.Qt.Unchecked) and (widget.is_linked is None) enable = (widget.global_checkbox.checkState() == QtCore.Qt.Unchecked)
self.data_parameter[idx].setEnabled(enable) self.data_parameter[idx].setEnabled(enable)
def select_next_preview(self, direction): def select_next_preview(self, direction):
@ -181,6 +193,11 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
for i, value in enumerate(self.data_values[sid]): for i, value in enumerate(self.data_values[sid]):
w = self.data_parameter[i] w = self.data_parameter[i]
w.blockSignals(True) w.blockSignals(True)
try:
w.show_as_local_parameter(value is not None)
except AttributeError:
pass
if value is None: if value is None:
w.value = self.glob_values[i] w.value = self.glob_values[i]
else: else:
@ -191,64 +208,50 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
if sid not in self.data_values: if sid not in self.data_values:
self.data_values[sid] = [None] * len(self.data_parameter) self.data_values[sid] = [None] * len(self.data_parameter)
def get_parameter(self, use_func=None): def get_parameter(self, use_func=None) -> tuple[dict, list]:
bds = [] bds = []
is_global = [] is_global = []
is_fixed = [] is_fixed = []
globs = [] param_general = []
is_linked = []
for g in self.global_parameter: for g in self.global_parameter:
if isinstance(g, FitModelWidget): if isinstance(g, FitModelWidget):
p_i, bds_i, fixed_i, global_i, link_i = g.get_parameter() p_i, bds_i, fixed_i, global_i = g.get_parameter()
parameter_i = Parameter(name=g.name, value=p_i, lb=bds_i[0], ub=bds_i[1], var=fixed_i)
param_general.append(parameter_i)
globs.append(p_i)
bds.append(bds_i) bds.append(bds_i)
is_fixed.append(fixed_i) is_fixed.append(fixed_i)
is_global.append(global_i) is_global.append(global_i)
is_linked.append(link_i)
lb, ub = list(zip(*bds))
data_parameter = {} data_parameter = {}
if use_func is None: if use_func is None:
use_func = list(self.data_values.keys()) use_func = list(self.data_values.keys())
global_p = None
for sid, parameter in self.data_values.items(): for sid, parameter in self.data_values.items():
if sid not in use_func: if sid not in use_func:
continue continue
kw_p = {} kw_p = {}
p = [] p = []
if global_p is None:
global_p = {'p': [], 'idx': [], 'var': [], 'ub': [], 'lb': []}
for i, (p_i, g) in enumerate(zip(parameter, self.global_parameter)): for i, (p_i, g) in enumerate(zip(parameter, self.global_parameter)):
if isinstance(g, FitModelWidget): if isinstance(g, FitModelWidget):
if (p_i is None) or is_global[i]: if (p_i is None) or is_global[i]:
p.append(globs[i]) # set has no oen value
if is_global[i]: p.append(param_general[i].copy())
if i not in global_p['idx']:
global_p['p'].append(globs[i])
global_p['idx'].append(i)
global_p['var'].append(is_fixed[i])
global_p['ub'].append(ub[i])
global_p['lb'].append(lb[i])
else: else:
p.append(p_i) lb, ub = bds[i]
try:
if not ((lb < p_i < ub) or (not is_fixed[i])):
raise ValueError(f'Parameter {g.name} is outside bounds ({lb}, {ub})')
except TypeError:
pass
try: # create Parameter
if p[i] > ub[i]: p.append(
raise ValueError(f'Parameter {g.name} is outside bounds ({lb[i]}, {ub[i]})') Parameter(name=g.name, value=p_i, lb=lb, ub=ub, var=is_fixed[i])
except TypeError: )
pass
try:
if p[i] < lb[i]:
raise ValueError(f'Parameter {g.name} is outside bounds ({lb[i]}, {ub[i]})')
except TypeError:
pass
else: else:
if p_i is None: if p_i is None:
@ -260,20 +263,28 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
data_parameter[sid] = (p, kw_p) data_parameter[sid] = (p, kw_p)
return data_parameter, lb, ub, is_fixed, global_p, is_linked global_parameter = []
for param, global_flag in zip(param_general, is_global):
if global_flag:
param.is_global = True
global_parameter.append(param)
else:
global_parameter.append(None)
return data_parameter, global_parameter
def set_parameter(self, set_id: str | None, parameter: list[float]) -> int: def set_parameter(self, set_id: str | None, parameter: list[float]) -> int:
param_len = len(list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter))) num_parameter = list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter))
param_len = len(num_parameter)
if set_id is None: if set_id is None:
for val, g in zip(parameter, self.global_parameter): for i, g in enumerate(num_parameter):
if isinstance(g, SelectionWidget): val = parameter[i]
continue
g.set_parameter(val) g.set_parameter(val)
self.glob_values[i] = val
else: else:
new_param = self.data_values[set_id] new_param = self.data_values[set_id]
min_len = min(param_len, len(new_param), len(new_param)) min_len = min(param_len, len(new_param))
for i in range(min_len): for i in range(min_len):
new_param[i] = parameter[i] new_param[i] = parameter[i]
@ -291,10 +302,12 @@ class ParameterSingleWidget(QtWidgets.QWidget):
self._init_ui() self._init_ui()
self._name = name self.name = name
self.label.setText(convert(name)) self.label.setText(convert(name))
self.label.setToolTip('If this is bold then this parameter is only for this data. '
'Otherwise, the general parameter is used and displayed')
self.value_line.setValidator(QtGui.QDoubleValidator()) # self.value_line.setValidator(QtGui.QDoubleValidator())
self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0) self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0)
self.reset_button.clicked.connect(lambda x: self.removeSingleValue.emit()) self.reset_button.clicked.connect(lambda x: self.removeSingleValue.emit())
@ -309,10 +322,12 @@ class ParameterSingleWidget(QtWidgets.QWidget):
layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))
self.value_line = QtWidgets.QLineEdit(self) self.value_line = QtWidgets.QLineEdit(self)
self.value_line.textEdited.connect(lambda x: self.show_as_local_parameter(True))
layout.addWidget(self.value_line) layout.addWidget(self.value_line)
self.reset_button = QtWidgets.QToolButton(self) self.reset_button = QtWidgets.QToolButton(self)
self.reset_button.setText('Use global') self.reset_button.setText('Use global')
self.reset_button.clicked.connect(lambda: self.show_as_local_parameter(False))
layout.addWidget(self.reset_button) layout.addWidget(self.reset_button)
self.setLayout(layout) self.setLayout(layout)
@ -326,4 +341,11 @@ class ParameterSingleWidget(QtWidgets.QWidget):
@value.setter @value.setter
def value(self, val): def value(self, val):
self.value_line.setText(f'{float(val):.5g}') # self.value_line.setText(f'{float(val):.5g}')
self.value_line.setText(f'{val}')
def show_as_local_parameter(self, is_local: bool):
if is_local:
self.label.setStyleSheet('font-weight: bold;')
else:
self.label.setStyleSheet('')

View File

@ -8,10 +8,11 @@ from nmreval.lib.importer import find_models
from nmreval.lib.colors import BaseColor, Tab10 from nmreval.lib.colors import BaseColor, Tab10
from nmreval.utils.text import convert from nmreval.utils.text import convert
from ..lib import get_icon from ..lib.iconloading import get_icon
from .._py.fitfunctionwidget import Ui_Form from .._py.fitfunctionwidget import Ui_Form
from ..Qt import QtWidgets, QtCore, QtGui from ..Qt import QtWidgets, QtCore, QtGui
class QFunctionWidget(QtWidgets.QWidget, Ui_Form): class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
func_cnt = count() func_cnt = count()
func_colors = cycle(Tab10) func_colors = cycle(Tab10)
@ -127,7 +128,7 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
self.newFunction.emit(idx, cnt) self.newFunction.emit(idx, cnt)
self.add_function(idx, cnt, op, name, col) self.add_function(idx, cnt, op, name, col, param_names=self.functions[idx].params)
def add_function(self, idx: int, cnt: int, op: int, def add_function(self, idx: int, cnt: int, op: int,
name: str, color: str | tuple[float, float, float] | BaseColor, **kwargs): name: str, color: str | tuple[float, float, float] | BaseColor, **kwargs):
@ -140,6 +141,7 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
qcolor = QtGui.QColor.fromRgbF(*color) qcolor = QtGui.QColor.fromRgbF(*color)
else: else:
qcolor = QtGui.QColor(color) qcolor = QtGui.QColor(color)
self.functree.add_function(idx, cnt, op, name, qcolor, **kwargs) self.functree.add_function(idx, cnt, op, name, qcolor, **kwargs)
f = self.functions[idx] f = self.functions[idx]

View File

@ -4,16 +4,19 @@ from functools import reduce
from itertools import count, cycle from itertools import count, cycle
from operator import add from operator import add
from string import ascii_letters from string import ascii_letters
from typing import Dict, List, Tuple
import numpy as np import numpy as np
from pyqtgraph import mkPen from pyqtgraph import mkPen
from nmreval.fit._meta import MultiModel, ModelFactory from nmreval.fit._meta import MultiModel, ModelFactory
from nmreval.fit.data import Data
from nmreval.fit.model import Model
from nmreval.fit.parameter import Parameters
from nmreval.fit.result import FitResult from nmreval.fit.result import FitResult
from .fit_forms import FitTableWidget from .fit_forms import FitTableWidget
from .fit_parameter import QFitParameterWidget from .fit_parameter import QFitParameterWidget
from ..lib import Relations
from ..lib.pg_objects import PlotItem from ..lib.pg_objects import PlotItem
from ..Qt import QtGui, QtCore, QtWidgets from ..Qt import QtGui, QtCore, QtWidgets
from .._py.fitdialog import Ui_FitDialog from .._py.fitdialog import Ui_FitDialog
@ -50,7 +53,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
self._func_list = {} self._func_list = {}
self._complex = {} self._complex = {}
self.connected_figure = '' self.connected_figure = None
self.model_frame.hide() self.model_frame.hide()
self.preview_button.hide() self.preview_button.hide()
@ -78,16 +81,11 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
w.deleteLater() w.deleteLater()
del self.param_widgets[idx] del self.param_widgets[idx]
if len(self.functionwidget) == 0: self._current_function = None
if len(self.param_widgets) == 0:
# empty model # empty model
self.newmodel_button.setEnabled(False) self.newmodel_button.setEnabled(False)
self.deletemodel_button.setEnabled(False) self.deletemodel_button.setEnabled(False)
self._current_function = None
else:
f_tree = self.functionwidget.functree
func_idx = f_tree.currentItem().data(0, f_tree.counterRole)
self._current_function = self.functionwidget.functions[func_idx]
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot(int)
def show_function_parameter(self, function_id: int, function_idx: int = None): def show_function_parameter(self, function_id: int, function_idx: int = None):
@ -121,7 +119,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
# collect parameter names etc. to allow linkage # collect parameter names etc. to allow linkage
self._func_list[self._current_model] = self.functionwidget.get_parameter_list() self._func_list[self._current_model] = self.functionwidget.get_parameter_list()
dialog.set_links(self._func_list) # dialog.set_links(self._func_list)
# show same tab (general parameter/Data parameter) # show same tab (general parameter/Data parameter)
tab_idx = 0 tab_idx = 0
@ -144,11 +142,18 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
self._complex[self._current_model] = self.functionwidget.get_complex_state() self._complex[self._current_model] = self.functionwidget.get_complex_state()
self._func_list[self._current_model] = self.functionwidget.get_parameter_list() self._func_list[self._current_model] = self.functionwidget.get_parameter_list()
def load(self, ids: List[str]): def load(self, ids: list[str]):
""" """
Add name and id of dataset to list. Add name and id of dataset to list.
""" """
self.data_table.load(ids) self.data_table.load(ids)
# deselect all fit sets
for i in range(self.data_table.rowCount()):
data_id = self.data_table.item(i, 0).data(QtCore.Qt.UserRole+1)
if self._management[data_id].mode == 'fit' or self._management[data_id].has_relation(Relations.isFitPartOf):
self.data_table.item(i, 0).setCheckState(QtCore.Qt.Unchecked)
if self.models: if self.models:
for m in self.models.keys(): for m in self.models.keys():
self.data_table.add_model(m) self.data_table.add_model(m)
@ -216,56 +221,50 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
self.model_frame.hide() self.model_frame.hide()
def _prepare(self, model: list, function_use: list = None, def _prepare(self, model: list, function_use: list = None,
parameter: dict = None, add_idx: bool = False, cnt: int = 0) -> Tuple[dict, int]: parameter: dict = None, add_idx: bool = False, cnt: int = 0) -> tuple[dict, int]:
if parameter is None: if parameter is None:
parameter = {'parameter': {}, 'lb': (), 'ub': (), 'var': [], parameter = {
'glob': {'idx': [], 'p': [], 'var': [], 'lb': [], 'ub': []}, 'data_parameter': {},
'links': [], 'color': []} 'global_parameter': [],
'links': [],
'color': [],
}
for i, f in enumerate(model): for i, f in enumerate(model):
if not f['active']: if not f['active']:
continue continue
try: try:
p, lb, ub, var, glob, links = self.param_widgets[f['cnt']].get_parameter(function_use) p, glob = self.param_widgets[f['cnt']].get_parameter(function_use)
except ValueError as e: except ValueError as e:
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value', str(e), _ = QtWidgets.QMessageBox().warning(self, 'Invalid value', str(e),
QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.Ok)
return {}, -1 return {}, -1
p_len = len(parameter['lb']) parameter['color'].append(f['color'])
parameter['global_parameter'].extend(glob)
parameter['lb'] += lb
parameter['ub'] += ub
parameter['var'] += var
parameter['links'] += links
parameter['color'] += [f['color']]
cnt = f['cnt']
for p_k, v_k in p.items(): for p_k, v_k in p.items():
if add_idx: if add_idx:
kw_k = {f'{k}_{cnt}': v for k, v in v_k[1].items()} kw_k = {f'{k}_{cnt}': v for k, v in v_k[1].items()}
else: else:
kw_k = v_k[1] kw_k = v_k[1]
if p_k in parameter['parameter']: if p_k in parameter['data_parameter']:
params, kw = parameter['parameter'][p_k] params, kw = parameter['data_parameter'][p_k]
params += v_k[0] params += v_k[0]
kw.update(kw_k) kw.update(kw_k)
else: else:
parameter['parameter'][p_k] = (v_k[0], kw_k) parameter['data_parameter'][p_k] = (v_k[0], kw_k)
for g_k, g_v in glob.items():
if g_k != 'idx':
parameter['glob'][g_k] += g_v
else:
parameter['glob']['idx'] += [idx_i + p_len for idx_i in g_v]
if add_idx: if add_idx:
cnt += 1 cnt += 1
if f['children']: if f['children']:
# recurse for children # recurse for children
child_parameter, cnt = self._prepare(f['children'], parameter=parameter, add_idx=add_idx, cnt=cnt) _, cnt = self._prepare(f['children'], parameter=parameter, add_idx=add_idx, cnt=cnt)
return parameter, cnt return parameter, cnt
@ -276,30 +275,43 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
data = self.data_table.collect_data(default=self.default_combobox.currentData()) data = self.data_table.collect_data(default=self.default_combobox.currentData())
func_dict = {} func_dict = {}
for k, mod in self.models.items(): for model_name, model_parameter in self.models.items():
func, order, param_len = ModelFactory.create_from_list(mod) func, order, param_len = ModelFactory.create_from_list(model_parameter)
if func is None: if func is None:
continue continue
if k in data: func = Model(func)
parameter, _ = self._prepare(mod, function_use=data[k], add_idx=isinstance(func, MultiModel))
if model_name in data:
parameter, _ = self._prepare(model_parameter, function_use=data[model_name], add_idx=isinstance(func, MultiModel))
if parameter is None: if parameter is None:
return return
for (data_parameter, _) in parameter['data_parameter'].values():
for pname, param in zip(func.params, data_parameter):
param.name = pname
if self._complex[model_name] is not None:
for p_k, p_v in parameter['data_parameter'].items():
p_v[1].update({'complex_mode': self._complex[model_name]})
parameter['data_parameter'][p_k] = p_v[0], p_v[1]
for pname, param_value in zip(func.params, parameter['global_parameter']):
if param_value is not None:
param_value.name = pname
func.set_global_parameter(param_value)
parameter['func'] = func parameter['func'] = func
parameter['order'] = order parameter['order'] = order
parameter['len'] = param_len parameter['len'] = param_len
parameter['complex'] = self._complex[k] parameter['complex'] = self._complex[model_name]
if self._complex[k] is not None:
for p_k, p_v in parameter['parameter'].items():
p_v[1].update({'complex_mode': self._complex[k]})
parameter['parameter'][p_k] = p_v[0], p_v[1]
func_dict[k] = parameter func_dict[model_name] = parameter
replaceable = [] replaceable = []
for k, v in func_dict.items(): for model_name, v in func_dict.items():
for i, link_i in enumerate(v['links']): for i, link_i in enumerate(v['links']):
if link_i is None: if link_i is None:
continue continue
@ -330,7 +342,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.Ok)
return return
replaceable.append((k, i, rep_model, repl_idx)) replaceable.append((model_name, i, rep_model, repl_idx))
replace_value = None replace_value = None
for p_k in f['parameter'].values(): for p_k in f['parameter'].values():
@ -408,31 +420,37 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
def make_previews(self, x, models_parameters: dict): def make_previews(self, x, models_parameters: dict):
self.preview_lines = [] self.preview_lines = []
# needed to create namespace
param_dict = Parameters()
cnt = 0
for model in models_parameters.values():
f = model['func']
for parameter_list in model['data_parameter'].values():
for i, p_value in enumerate(parameter_list[0]):
p_value.name = f.params[i]
param_dict.add_parameter(f'a{cnt}', p_value)
cnt += 1
for k, model in models_parameters.items(): for k, model in models_parameters.items():
f = model['func'] f = model['func']
is_complex = self._complex[k] is_complex = self._complex[k]
parameters = model['parameter'] parameters = model['data_parameter']
color = model['color'] color = model['color']
seen_parameter = []
for p, kwargs in parameters.values(): for p, kwargs in parameters.values():
if (p, kwargs) in seen_parameter: p_value = [pp.value for pp in p]
# plot only previews with different parameter
continue
seen_parameter.append((p, kwargs))
if is_complex is not None: if is_complex is not None:
y = f.func(x, *p, complex_mode=is_complex, **kwargs) y = f.func(x, *p_value, complex_mode=is_complex, **kwargs)
if np.iscomplexobj(y): if np.iscomplexobj(y):
self.preview_lines.append(PlotItem(x=x, y=y.real, pen=mkPen(width=3))) self.preview_lines.append(PlotItem(x=x, y=y.real, pen=mkPen(width=3)))
self.preview_lines.append(PlotItem(x=x, y=y.imag, pen=mkPen(width=3))) self.preview_lines.append(PlotItem(x=x, y=y.imag, pen=mkPen(width=3)))
else: else:
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3))) self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
else: else:
y = f.func(x, *p, **kwargs) y = f.func(x, *p_value, **kwargs)
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3))) self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
if isinstance(f, MultiModel): if isinstance(f, MultiModel):
@ -440,7 +458,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
if is_complex is not None: if is_complex is not None:
sub_kwargs.update({'complex_mode': is_complex}) sub_kwargs.update({'complex_mode': is_complex})
for i, s in enumerate(f.subs(x, *p, **sub_kwargs)): for i, s in enumerate(f.subs(x, *p_value, **sub_kwargs)):
pen_i = mkPen(QtGui.QColor.fromRgbF(*color[i])) pen_i = mkPen(QtGui.QColor.fromRgbF(*color[i]))
if np.iscomplexobj(s): if np.iscomplexobj(s):
self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i)) self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i))
@ -448,15 +466,17 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
else: else:
self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i)) self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i))
param_dict.clear()
return self.preview_lines return self.preview_lines
def set_parameter(self, parameter: Dict[str, FitResult]): def set_parameter(self, parameter: dict[str, FitResult]):
# which data uses which model # which data uses which model
data = self.data_table.collect_data(default=self.default_combobox.currentData()) data = self.data_table.collect_data(default=self.default_combobox.currentData())
glob_fit_parameter = []
for fitted_model, fitted_data in data.items(): for fitted_model, fitted_data in data.items():
glob_fit_parameter = []
for fit_id, fit_curve in parameter.items(): for fit_id, fit_curve in parameter.items():
if fit_id in fitted_data: if fit_id in fitted_data:
fit_parameter = list(fit_curve.parameter.values()) fit_parameter = list(fit_curve.parameter.values())
@ -468,7 +488,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
self.set_parameter_iter(None, mean_parameter, self.models[fitted_model]) self.set_parameter_iter(None, mean_parameter, self.models[fitted_model])
def set_parameter_iter(self, fit_id: str | None, param: List[float], functions: List, cnt: int = 0): def set_parameter_iter(self, fit_id: str | None, param: list[float], functions: list, cnt: int = 0):
for model_p in functions: for model_p in functions:
if model_p['active']: if model_p['active']:
cnt += self.param_widgets[model_p['cnt']].set_parameter(fit_id, param[cnt:]) cnt += self.param_widgets[model_p['cnt']].set_parameter(fit_id, param[cnt:])

View File

@ -14,7 +14,7 @@ from gui_qt.lib.namespace import QNamespaceWidget
__all__ = ['QUserFitCreator'] __all__ = ['QUserFitCreator']
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[A-Za-z]\S*')) validator = QtGui.QRegExpValidator(QtCore.QRegExp('[_A-Za-z][_A-Za-z0-9]*'))
pattern = re.compile(r'def func\(.*\):', flags=re.MULTILINE) pattern = re.compile(r'def func\(.*\):', flags=re.MULTILINE)
@ -145,6 +145,7 @@ class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog):
self.classCreated.emit() self.classCreated.emit()
super().accept() super().accept()
class KwargsWidget(QtWidgets.QWidget): class KwargsWidget(QtWidgets.QWidget):
Changed = QtCore.pyqtSignal() Changed = QtCore.pyqtSignal()
@ -209,7 +210,7 @@ class KwargsWidget(QtWidgets.QWidget):
def get_strings(self) -> str: def get_strings(self) -> str:
kwargs = [] kwargs = []
if self.use_nuclei.isChecked(): if self.use_nuclei.isChecked():
kwargs.append("(r'\gamma', 'nucleus', gamma)") kwargs.append(r"(r'\gamma', 'nucleus', gamma)")
for i in range(self.choices.count()): for i in range(self.choices.count()):
kwargs.append(self.choices.widget(i).get_strings()) kwargs.append(self.choices.widget(i).get_strings())
@ -300,7 +301,7 @@ class ChoiceWidget(QtWidgets.QWidget):
def get_strings(self) -> str: def get_strings(self) -> str:
opts = [] opts = []
for i in range(self.table.rowCount()): for i in range(self.table.rowCount()):
name = self.table.item(i, 0).text() name = self.table.cellWidget(i, 0).text()
val = self._make_value(i) val = self._make_value(i)
opts.append(f'{name!r}: {val!r}') opts.append(f'{name!r}: {val!r}')
@ -471,7 +472,7 @@ class DescWidget(QtWidgets.QWidget):
stringi = f'class {self.klass_lineedit.text()}:\n' \ stringi = f'class {self.klass_lineedit.text()}:\n' \
f' name = {self.name_lineedit.text()!r}\n' \ f' name = {self.name_lineedit.text()!r}\n' \
f' type = {self.group_lineedit.text()!r}\n' \ f' type = {self.group_lineedit.text()!r}\n' \
f' equation = {self.eq_lineedit.text()!r}\n' f" equation = r'{self.eq_lineedit.text()}'\n"
return stringi return stringi

View File

@ -1,8 +1,9 @@
from math import isnan from math import isnan
from pyqtgraph import mkBrush from pyqtgraph import mkBrush, mkPen
from nmreval.utils.text import convert from nmreval.utils.text import convert
from ..lib.graph_items import logTickValues
from ..lib.utils import RdBuCMap from ..lib.utils import RdBuCMap
from ..Qt import QtWidgets, QtGui, QtCore from ..Qt import QtWidgets, QtGui, QtCore
@ -11,7 +12,7 @@ from ..lib.pg_objects import PlotItem
class QFitResult(QtWidgets.QDialog, Ui_Dialog): class QFitResult(QtWidgets.QDialog, Ui_Dialog):
closed = QtCore.pyqtSignal(dict, list, str, bool, dict) closed = QtCore.pyqtSignal(dict, list, str, bool, bool, list)
redoFit = QtCore.pyqtSignal(dict) redoFit = QtCore.pyqtSignal(dict)
def __init__(self, results: list, management, parent=None): def __init__(self, results: list, management, parent=None):
@ -20,71 +21,89 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self._management = management self._management = management
self._prevs = {} self.maxx_line.setValidator(QtGui.QDoubleValidator())
self._models = {} self.minx_line.setValidator(QtGui.QDoubleValidator())
self.numx_line.setValidator(QtGui.QIntValidator())
self.extrapolate_box.stateChanged.connect(lambda x: self.maxx_line.setEnabled(x))
self.extrapolate_box.stateChanged.connect(lambda x: self.minx_line.setEnabled(x))
self.extrapolate_box.stateChanged.connect(lambda x: self.numx_line.setEnabled(x))
for (res, parts) in results: self._previous_fits = {}
idx = res.idx self._opts = []
data_k = management.data[idx] self._results = {}
self.graph_opts = {}
self.last_idx = None
if res.name not in self._models: self.fit_plot = self.graphicsView.addPlot(row=1, col=0, title='Fit')
self._models[res.name] = [] self.resid_plot = self.graphicsView.addPlot(row=0, col=0, title='Residual')
self._models[res.name].append(idx) for orient in ['top', 'bottom', 'left', 'right']:
self.fit_plot.getAxis(orient).logTickValues = logTickValues
self.resid_plot.getAxis(orient).logTickValues = logTickValues
self._prevs[idx] = [] self.graphicsView.ci.layout.setRowStretchFactor(0, 1)
for fit in data_k.get_fits(): self.graphicsView.ci.layout.setRowStretchFactor(1, 2)
self._prevs[idx].append((fit.name, fit.statistics, fit.nobs-fit.nvar))
self._results = {res.idx: res for (res, _) in results}
self._parts = {res.idx: parts for (res, parts) in results}
self._opts = [(False, False) for _ in range(len(self._results))]
self.residplot = self.graphicsView.addPlot(row=0, col=0)
self.resid_graph = PlotItem(x=[], y=[], self.resid_graph = PlotItem(x=[], y=[],
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)), symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
pen=None) pen=None)
self.resid_graph_imag = PlotItem(x=[], y=[], self.resid_graph_imag = PlotItem(x=[], y=[],
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)), symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
pen=None) pen=None)
self.residplot.addItem(self.resid_graph) self.resid_plot.addItem(self.resid_graph)
self.residplot.addItem(self.resid_graph_imag) self.resid_plot.addItem(self.resid_graph_imag)
self.residplot.setLabel('left', 'Residual')
self.fitplot = self.graphicsView.addPlot(row=1, col=0)
self.data_graph = PlotItem(x=[], y=[], self.data_graph = PlotItem(x=[], y=[],
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)), symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
pen=None) pen=None)
self.data_graph_imag = PlotItem(x=[], y=[], self.data_graph_imag = PlotItem(x=[], y=[],
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)), symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
pen=None) pen=None)
self.fitplot.addItem(self.data_graph) self.fit_plot.addItem(self.data_graph)
self.fitplot.addItem(self.data_graph_imag) self.fit_plot.addItem(self.data_graph_imag)
self.fitplot.setLabel('left', 'Function')
self.fit_graph = PlotItem(x=[], y=[]) self.fit_graph = PlotItem(x=[], y=[])
self.fit_graph_imag = PlotItem(x=[], y=[]) self.fit_graph_imag = PlotItem(x=[], y=[])
self.fitplot.addItem(self.fit_graph) self.fit_plot.addItem(self.fit_graph)
self.fitplot.addItem(self.fit_graph_imag) self.fit_plot.addItem(self.fit_graph_imag)
self.cmap = RdBuCMap(vmin=-1, vmax=1) self.cmap = RdBuCMap(vmin=-1, vmax=1)
self.sets_comboBox.blockSignals(True)
for n in self._models.keys():
self.sets_comboBox.addItem(n)
self.sets_comboBox.blockSignals(False)
self.set_parameter(0)
self.buttonBox.accepted.connect(self.accept)
self.param_tableWidget.itemClicked.connect(self.show_results)
self.param_tableWidget.horizontalHeader().sectionClicked.connect(lambda i: self.show_results(None, idx=i))
self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked)) self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked))
self.logy_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(y=bool(x))) self.logy_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(y=bool(x)))
self.logx_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(x=bool(x))) self.logx_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(x=bool(x)))
self.residplot.setXLink(self.fitplot) self.resid_plot.setXLink(self.fit_plot)
self.set_results(results)
def __call__(self, results: list):
self._previous_fits = {}
self.sets_comboBox.blockSignals(True)
self.sets_comboBox.clear()
self.sets_comboBox.blockSignals(False)
self._results = {}
self._opts = {}
self.set_results(results)
def set_results(self, results: list):
self.sets_comboBox.blockSignals(True)
for res in results:
idx = res.idx
data_k = self._management.data[idx]
self._previous_fits[idx] = []
for fit in data_k.get_fits():
self._previous_fits[idx].append((fit.name, fit.statistics, fit.nobs - fit.nvar))
self.sets_comboBox.addItem(data_k.name, userData=idx)
self.sets_comboBox.blockSignals(False)
self._results = {res.idx: res for res in results}
self._opts = [(False, False) for _ in range(len(self._results))]
self.set_parameter(0)
def add_graphs(self, graphs: list): def add_graphs(self, graphs: list):
self.graph_comboBox.clear() self.graph_comboBox.clear()
@ -93,56 +112,40 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
@QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged') @QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged')
def set_parameter(self, idx: int): def set_parameter(self, idx: int):
model_name = self.sets_comboBox.itemText(idx) set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole)
sets = self._models[model_name]
self.param_tableWidget.setColumnCount(len(sets))
r = self._results[sets[0]] res = self._results[set_id]
self.param_tableWidget.setRowCount(len(r.parameter)) self.param_tableWidget.setRowCount(len(res.parameter))
for j, (pkey, pvalue) in enumerate(res.parameter.items()):
name = pkey
p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'str', brackets=True))
self.param_tableWidget.setVerticalHeaderItem(j, p_header)
for i, pval in enumerate(r.parameter.values()): item_text = f'{pvalue.value:.4g}'
name = pval.full_name if pvalue.error is not None:
p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'html', brackets=False)) item_text += f' \u00b1 {pvalue.error:.4g}'
self.param_tableWidget.setVerticalHeaderItem(i, p_header) self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem('-'))
else:
self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem())
item = QtWidgets.QTableWidgetItem(item_text)
self.param_tableWidget.setItem(j, 0, item)
for i, set_id in enumerate(sets): self.param_tableWidget.resizeColumnToContents(0)
data_i = self._management[set_id] self.show_results(idx)
header_item = QtWidgets.QTableWidgetItem(data_i.name)
header_item.setData(QtCore.Qt.UserRole, set_id)
self.param_tableWidget.setHorizontalHeaderItem(i, header_item)
res = self._results[set_id]
for j, pvalue in enumerate(res.parameter.values()):
item_text = f'{pvalue.value:.4g}'
if pvalue.error is not None:
item_text += f' \u00b1 {pvalue.error:.4g}'
self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem('-'))
else:
self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem())
item = QtWidgets.QTableWidgetItem(item_text)
self.param_tableWidget.setItem(j, i, item)
self.param_tableWidget.resizeColumnsToContents()
self.param_tableWidget.selectColumn(0)
self.show_results(None, idx=0)
@QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged') @QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged')
@QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged') @QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged')
def change_opts(self, _): def change_opts(self, _):
idx = self.param_tableWidget.currentIndex().column() idx = self.sets_comboBox.currentIndex()
self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked, self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked,
self.del_prev_checkBox.checkState() == QtCore.Qt.Checked) self.del_prev_checkBox.checkState() == QtCore.Qt.Checked)
def show_results(self, item, idx=None): def show_results(self, idx):
if item is not None: set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole)
idx = self.param_tableWidget.indexFromItem(item).column()
set_id = self.param_tableWidget.horizontalHeaderItem(idx).data(QtCore.Qt.UserRole)
self.set_plot(set_id) self.set_plot(set_id)
self.set_correlation(set_id) self.set_correlation(set_id)
self.set_statistics(set_id) self.set_statistics(set_id)
self.reject_fit_checkBox.blockSignals(True) self.reject_fit_checkBox.blockSignals(True)
self.reject_fit_checkBox.setChecked(self._opts[idx][0]) self.reject_fit_checkBox.setChecked(self._opts[idx][0])
self.reject_fit_checkBox.blockSignals(False) self.reject_fit_checkBox.blockSignals(False)
@ -151,9 +154,22 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.del_prev_checkBox.blockSignals(False) self.del_prev_checkBox.blockSignals(False)
def set_plot(self, idx: str): def set_plot(self, idx: str):
if self.last_idx is not None:
self.graph_opts[self.last_idx] = (
self.fit_plot.viewRange(),
self.logx_box.isChecked(),
self.logy_box.isChecked(),
)
self.last_idx = idx
res = self._results[idx] res = self._results[idx]
iscomplex = res.iscomplex iscomplex = res.iscomplex
sub_funcs = res.sub(res.x)
for item in self.fit_plot.items[::-1]:
if item not in [self.data_graph, self.data_graph_imag, self.fit_graph, self.fit_graph_imag]:
self.fit_plot.removeItem(item)
if iscomplex: if iscomplex:
self.data_graph.setData(x=res.x_data, y=res.y_data.real) self.data_graph.setData(x=res.x_data, y=res.y_data.real)
self.data_graph_imag.setData(x=res.x_data, y=res.y_data.imag) self.data_graph_imag.setData(x=res.x_data, y=res.y_data.imag)
@ -161,6 +177,13 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.fit_graph_imag.setData(x=res.x, y=res.y.imag) self.fit_graph_imag.setData(x=res.x, y=res.y.imag)
self.resid_graph.setData(x=res.x_data, y=res.residual.real) self.resid_graph.setData(x=res.x_data, y=res.residual.real)
self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag) self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag)
for i, f in enumerate(sub_funcs):
item = PlotItem(x=f.x, y=f.y.real, pen=mkPen({'color': i, 'style': 2}))
self.fit_plot.addItem(item)
item = PlotItem(x=f.x, y=f.y.imag, pen=mkPen({'color': i, 'style': 2}))
self.fit_plot.addItem(item)
else: else:
self.resid_graph.setData(x=res.x_data, y=res.residual) self.resid_graph.setData(x=res.x_data, y=res.residual)
self.resid_graph_imag.setData(x=[], y=[]) self.resid_graph_imag.setData(x=[], y=[])
@ -169,8 +192,27 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.fit_graph.setData(x=res.x, y=res.y) self.fit_graph.setData(x=res.x, y=res.y)
self.fit_graph_imag.setData(x=[], y=[]) self.fit_graph_imag.setData(x=[], y=[])
self.fitplot.setLogMode(x=res.islog) for i, f in enumerate(sub_funcs):
self.residplot.setLogMode(x=res.islog) item = PlotItem(x=f.x, y=f.y, pen=mkPen({'color': i, 'style': 2}))
self.fit_plot.addItem(item)
self.logx_box.blockSignals(True)
self.logx_box.setChecked(res.islog)
self.logx_box.blockSignals(False)
self.fit_plot.setLogMode(x=res.islog)
self.resid_plot.setLogMode(x=res.islog)
if idx in self.graph_opts:
view_range, logx, logy = self.graph_opts[idx]
self.fit_plot.setRange(xRange=view_range[0], yRange=view_range[1], padding=0)
self.fit_plot.setLogMode(x=logx, y=logy)
self.logx_box.blockSignals(True)
self.logx_box.setChecked(logx)
self.logx_box.blockSignals(False)
self.logy_box.blockSignals(True)
self.logy_box.setChecked(logy)
self.logy_box.blockSignals(False)
def set_correlation(self, idx: str): def set_correlation(self, idx: str):
while self.corr_tableWidget.rowCount(): while self.corr_tableWidget.rowCount():
@ -201,7 +243,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
res = self._results[idx] res = self._results[idx]
self.stats_tableWidget.setColumnCount(1 + len(self._prevs[idx])) self.stats_tableWidget.setColumnCount(1 + len(self._previous_fits[idx]))
self.stats_tableWidget.setRowCount(len(res.statistics)+3) self.stats_tableWidget.setRowCount(len(res.statistics)+3)
it = QtWidgets.QTableWidgetItem(f'{res.dof}') it = QtWidgets.QTableWidgetItem(f'{res.dof}')
@ -209,7 +251,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF')) self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF'))
self.stats_tableWidget.setItem(0, 0, it) self.stats_tableWidget.setItem(0, 0, it)
for col, (name, _, dof) in enumerate(self._prevs[idx], start=1): for col, (name, _, dof) in enumerate(self._previous_fits[idx], start=1):
self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name)) self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name))
it = QtWidgets.QTableWidgetItem(f'{dof}') it = QtWidgets.QTableWidgetItem(f'{dof}')
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
@ -223,7 +265,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
best_idx = -1 best_idx = -1
best_val = v best_val = v
for col, (_, stats, _) in enumerate(self._prevs[idx], start=1): for col, (_, stats, _) in enumerate(self._previous_fits[idx], start=1):
if k in ['adj. R^2', 'R^2']: if k in ['adj. R^2', 'R^2']:
best_idx = col if best_val < stats[k] else max(0, best_idx) best_idx = col if best_val < stats[k] else max(0, best_idx)
else: else:
@ -243,7 +285,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
self.stats_tableWidget.setVerticalHeaderItem(row+1, QtWidgets.QTableWidgetItem('Pr(>F)')) self.stats_tableWidget.setVerticalHeaderItem(row+1, QtWidgets.QTableWidgetItem('Pr(>F)'))
self.stats_tableWidget.setItem(row+1, 0, QtWidgets.QTableWidgetItem('-')) self.stats_tableWidget.setItem(row+1, 0, QtWidgets.QTableWidgetItem('-'))
for col, (_, stats, dof) in enumerate(self._prevs[idx], start=1): for col, (_, stats, dof) in enumerate(self._previous_fits[idx], start=1):
f_value, prob_f = res.f_test(stats['chi^2'], dof) f_value, prob_f = res.f_test(stats['chi^2'], dof)
it = QtWidgets.QTableWidgetItem(f'{f_value:.4g}') it = QtWidgets.QTableWidgetItem(f'{f_value:.4g}')
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
@ -273,12 +315,78 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
plot_fits = self.curve_checkbox.isChecked() plot_fits = self.curve_checkbox.isChecked()
if self.partial_checkBox.checkState() == QtCore.Qt.Checked: parts = self.partial_checkBox.checkState() == QtCore.Qt.Checked
self.closed.emit(self._results, self._opts, graph, plot_fits, self._parts)
else:
self.closed.emit(self._results, self._opts, graph, plot_fits, {})
self.accept() extrapolate = [None, None, None]
error = []
if self.extrapolate_box.isChecked():
try:
extrapolate[0] = float(self.minx_line.text())
except (TypeError, ValueError):
error.append('Start value is missing')
try:
extrapolate[1] = float(self.maxx_line.text())
except (TypeError, ValueError):
error.append('End value is missing')
try:
extrapolate[2] = int(self.numx_line.text())
except (TypeError, ValueError):
error.append('Number of points is missing')
if error:
msg = QtWidgets.QMessageBox.warning(self, 'Error', 'Extrapolation failed because:\n' + '\n'.join(error))
return
else:
self.closed.emit(self._results, self._opts, graph, plot_fits, parts, extrapolate)
self.accept()
else: else:
self.reject() self.reject()
class FitExtension(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent=parent)
gridLayout = QtWidgets.QGridLayout(self)
self.label = QtWidgets.QLabel('Minimum value')
gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.min_line = QtWidgets.QLineEdit()
self.min_line.setValidator(QtGui.QDoubleValidator())
gridLayout.addWidget(self.min_line, 0, 1, 1, 1)
self.label_2 = QtWidgets.QLabel('Maximum value')
gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.max_line = QtWidgets.QLineEdit()
self.max_line.setValidator(QtGui.QDoubleValidator())
gridLayout.addWidget(self.max_line, 1, 1, 1, 1)
self.label_3 = QtWidgets.QLabel('Number of pts.')
gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
self.num_pts = QtWidgets.QLineEdit()
self.num_pts.setValidator(QtGui.QIntValidator())
gridLayout.addWidget(self.num_pts, 2, 1, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox()
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
self.setLayout(gridLayout)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
@property
def values(self):
try:
xmin = float(self.min_line.text())
xmax = float(self.max_line.text())
nums = int(self.num_pts.text())
except TypeError:
return None
return xmin, xmax, nums

View File

@ -138,9 +138,7 @@ class DrawingsWidget(QtWidgets.QWidget, Ui_Form):
graph_id = self.graph_comboBox.currentData() graph_id = self.graph_comboBox.currentData()
current_lines = self.lines[graph_id] current_lines = self.lines[graph_id]
print(remove_rows)
for i in reversed(remove_rows): for i in reversed(remove_rows):
print(i)
self.tableWidget.removeRow(i) self.tableWidget.removeRow(i)
self.line_deleted.emit(current_lines[i], graph_id) self.line_deleted.emit(current_lines[i], graph_id)

View File

@ -4,19 +4,21 @@ import itertools
import os import os
import uuid import uuid
from math import isnan from math import isfinite
from pathlib import Path from pathlib import Path
import numpy as np
from numpy import errstate, floor, log10 from numpy import errstate, floor, log10
from pyqtgraph import GraphicsObject, getConfigOption, mkColor from pyqtgraph import GraphicsObject, getConfigOption, mkColor
from nmreval.lib.logger import logger
from nmreval.utils.text import convert from nmreval.utils.text import convert
from ..io.filedialog import FileDialog from ..io.filedialog import FileDialog
from ..lib.pg_objects import LegendItemBlock, RegionItem from ..lib.pg_objects import LegendItemBlock, RegionItem
from ..Qt import QtCore, QtWidgets, QtGui from ..Qt import QtCore, QtWidgets, QtGui
from .._py.graph import Ui_GraphWindow from .._py.graph import Ui_GraphWindow
from ..lib import make_action_icons from ..lib.iconloading import make_action_icons
from ..lib.configurations import GraceMsgBox from ..lib.configurations import GraceMsgBox
@ -24,7 +26,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
mousePositionChanged = QtCore.pyqtSignal(float, float) mousePositionChanged = QtCore.pyqtSignal(float, float)
mouseDoubleClicked = QtCore.pyqtSignal() mouseDoubleClicked = QtCore.pyqtSignal()
positionClicked = QtCore.pyqtSignal(tuple, bool) positionClicked = QtCore.pyqtSignal(tuple, bool)
aboutToClose = QtCore.pyqtSignal(str) aboutToClose = QtCore.pyqtSignal(list)
counter = itertools.count() counter = itertools.count()
@ -43,7 +45,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.id = str(uuid.uuid4()) self.id = str(uuid.uuid4())
self.sets = [] self.sets = []
self.active = [] self._active = []
self.real_plots = {} self.real_plots = {}
self.imag_plots = {} self.imag_plots = {}
@ -53,6 +55,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self._external_items = [] self._external_items = []
self.closable = True self.closable = True
self._block = False
self.log = [False, False] self.log = [False, False]
self.scene = self.plotItem.scene() self.scene = self.plotItem.scene()
@ -71,6 +75,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.scene.contextMenu[0].disconnect() self.scene.contextMenu[0].disconnect()
self.scene.contextMenu[0].triggered.connect(self.export_dialog) self.scene.contextMenu[0].triggered.connect(self.export_dialog)
self.bwbutton.toggled.connect(self.change_background)
def _init_gui(self): def _init_gui(self):
self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter))) self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter)))
@ -110,11 +116,11 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return iter(self.active) return iter(self.active)
def __len__(self): def __len__(self):
return len(self.active) return len(self._active)
def curves(self) -> tuple: def curves(self) -> tuple:
for set_id in self.sets: for set_id in self.sets:
if set_id in self.active: if set_id in self._active:
if self.real_button.isChecked(): if self.real_button.isChecked():
if self.error_plots[set_id] is not None: if self.error_plots[set_id] is not None:
yield self.real_plots[set_id], self.error_plots[set_id] yield self.real_plots[set_id], self.error_plots[set_id]
@ -137,19 +143,43 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
r = self.plotItem.getViewBox().viewRange() r = self.plotItem.getViewBox().viewRange()
for i in [0, 1]: for i in [0, 1]:
if self.log[i]: if self.log[i]:
r[i] = tuple([10**x for x in r[i]]) tmp = [np.nan, np.nan]
for j, x in enumerate(r[i]):
try:
tmp[j] = 10**min(x, 199)
except OverflowError:
pass
r[i] = tuple(tmp)
else: else:
r[i] = tuple(r[i]) r[i] = tuple(r[i])
return tuple(r) return tuple(r)
@property
def active(self) -> list:
return [set_id for set_id in self.sets if set_id in self._active]
@active.setter
def active(self, value: list):
self._active = value
def block(self, state: bool):
self._block = state
if not self._block:
self.graphic.enableAutoRange()
self._update_zorder()
self.show_legend()
else:
self.graphic.disableAutoRange()
def add(self, name: str | list, plots: list): def add(self, name: str | list, plots: list):
if isinstance(name, str): if isinstance(name, str):
name = [name] name = [name]
plots = [plots] plots = [plots]
toplevel = len(self.sets)
self.listWidget.blockSignals(True)
for (real_plot, imag_plot, err_plot), n in zip(plots, name): for (real_plot, imag_plot, err_plot), n in zip(plots, name):
toplevel = len(self.sets)
self.sets.append(n) self.sets.append(n)
if real_plot: if real_plot:
@ -169,7 +199,16 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
list_item.setCheckState(QtCore.Qt.Checked) list_item.setCheckState(QtCore.Qt.Checked)
self.listWidget.addItem(list_item) self.listWidget.addItem(list_item)
self.show_item(name) toplevel += 1
self.listWidget.blockSignals(False)
if len(name) < 200:
self.show_item(name)
else:
QtWidgets.QMessageBox.warning(self, 'Display disabled',
'If more than 200 sets are added at once, they are not displayed to avoid major performance issues.\n'
'The checkmark in the data tree is invalid.\n'
'Please display them manually in smaller batches, thank you!')
def remove(self, name: str | list): def remove(self, name: str | list):
if isinstance(name, str): if isinstance(name, str):
@ -181,8 +220,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
for plot in [self.real_plots, self.imag_plots, self.error_plots]: for plot in [self.real_plots, self.imag_plots, self.error_plots]:
self.graphic.removeItem(plot[n]) self.graphic.removeItem(plot[n])
if n in self.active: if n in self._active:
self.active.remove(n) self._active.remove(n)
# remove from label list # remove from label list
self.listWidget.blockSignals(True) self.listWidget.blockSignals(True)
@ -194,8 +233,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.listWidget.blockSignals(False) self.listWidget.blockSignals(False)
self._update_zorder() if not self._block:
self.show_legend() self._update_zorder()
self.show_legend()
def move_sets(self, sets: list, position: int): def move_sets(self, sets: list, position: int):
move_plots = [] move_plots = []
@ -225,8 +265,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return return
for a in idlist: for a in idlist:
if a not in self.active: if a not in self._active:
self.active.append(a) self._active.append(a)
for (bttn, plot_dic) in [ for (bttn, plot_dic) in [
(self.real_button, self.real_plots), (self.real_button, self.real_plots),
@ -245,8 +285,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return return
for r in idlist: for r in idlist:
if r in self.active: if r in self._active:
self.active.remove(r) self._active.remove(r)
for plt in [self.real_plots, self.imag_plots, self.error_plots]: for plt in [self.real_plots, self.imag_plots, self.error_plots]:
item = plt[r] item = plt[r]
@ -268,7 +308,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
else: else:
func = self.graphic.removeItem func = self.graphic.removeItem
for a in self.active: for a in self._active:
item = plots[a] item = plots[a]
if item is not None: if item is not None:
func(item) func(item)
@ -285,12 +325,12 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
return return
if visible: if visible:
for a in self.active: for a in self._active:
item = self.error_plots[a] item = self.error_plots[a]
if (item is not None) and (item not in self.graphic.items()): if (item is not None) and (item not in self.graphic.items()):
self.graphic.addItem(item) self.graphic.addItem(item)
else: else:
for a in self.active: for a in self._active:
item = self.error_plots[a] item = self.error_plots[a]
if (item is not None) and (item in self.graphic.items()): if (item is not None) and (item in self.graphic.items()):
self.graphic.removeItem(item) self.graphic.removeItem(item)
@ -354,7 +394,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if res == QtWidgets.QMessageBox.Yes: if res == QtWidgets.QMessageBox.Yes:
self.aboutToClose.emit(self.id) self.aboutToClose.emit([self.id])
evt.accept() evt.accept()
else: else:
evt.ignore() evt.ignore()
@ -402,6 +442,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
def set_logmode(self, xmode: bool = None, ymode: bool = None): def set_logmode(self, xmode: bool = None, ymode: bool = None):
r = self.ranges r = self.ranges
self.plotItem.setXRange(*r[0])
self.plotItem.setYRange(*r[1])
if xmode is None: if xmode is None:
xmode = self.plotItem.ctrl.logXCheck.isChecked() xmode = self.plotItem.ctrl.logXCheck.isChecked()
else: else:
@ -418,6 +461,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
item.logmode[0] = self.log[:] item.logmode[0] = self.log[:]
self.plotItem.updateLogMode() self.plotItem.updateLogMode()
self.set_range(x=r[0], y=r[1])
self.plotItem.enableAutoRange() self.plotItem.enableAutoRange()
@ -460,9 +504,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
with errstate(all='ignore'): with errstate(all='ignore'):
xy = [log10(val) for val in xy] xy = [log10(val) for val in xy]
if isnan(xy[1]): if not isfinite(xy[1]):
xy = [-1, 1] xy = [-1, 1]
elif isnan(xy[0]): elif not isfinite(xy[0]):
xy[0] = xy[1]-4 xy[0] = xy[1]-4
func(xy[0], xy[1], padding=0) func(xy[0], xy[1], padding=0)
@ -494,6 +538,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.checkBox.setVisible(visible) self.checkBox.setVisible(visible)
def update_legend(self, sid, name): def update_legend(self, sid, name):
if self._block:
return
self.listWidget.blockSignals(True) self.listWidget.blockSignals(True)
for i in range(self.listWidget.count()): for i in range(self.listWidget.count()):
@ -520,16 +567,14 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
elif other_item in self.graphic.items(): elif other_item in self.graphic.items():
self.legend.addItem(other_item, convert(other_item.opts.get('name', ''), old='tex', new='html')) self.legend.addItem(other_item, convert(other_item.opts.get('name', ''), old='tex', new='html'))
def export_dialog(self, path=None): def export_dialog(self, _=None):
filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)' filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)'
for imgformat in QtGui.QImageWriter.supportedImageFormats(): for imgformat in QtGui.QImageWriter.supportedImageFormats():
str_format = imgformat.data().decode('utf-8') str_format = imgformat.data().decode('utf-8')
filters += ';;' + str_format.upper() + ' (*.' + str_format + ')' filters += ';;' + str_format.upper() + ' (*.' + str_format + ')'
if path is None:
path = ''
outfile = None outfile = None
f = FileDialog(caption='Export graphic', directory=str(path), filter=filters, mode='save') f = FileDialog(caption='Export graphic', filter=filters, mode='save')
f.setOption(FileDialog.DontConfirmOverwrite) f.setOption(FileDialog.DontConfirmOverwrite)
mode = f.exec() mode = f.exec()
if mode == QtWidgets.QDialog.Accepted: if mode == QtWidgets.QDialog.Accepted:
@ -608,7 +653,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
try: try:
item_dic = plot_item.get_data_opts() item_dic = plot_item.get_data_opts()
except Exception as e: except Exception as e:
print(f'{item} could not exported because {e.args}') logger.exception(f'{item} could not exported because {e.args}')
continue continue
if len(item) == 2: if len(item) == 2:
@ -622,7 +667,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
try: try:
dic['items'].append(item.get_data_opts()) dic['items'].append(item.get_data_opts())
except Exception as e: except Exception as e:
print(f'{item} could not be exported because {e.args}') logger.exception(f'{item} could not be exported because {e.args}')
continue continue
in_legend.append(False) in_legend.append(False)
@ -645,7 +690,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
'legend': self.legend.isVisible(), 'legend': self.legend.isVisible(),
'plots': (self.real_button.isChecked(), self.imag_button.isChecked(), self.error_button.isChecked()), 'plots': (self.real_button.isChecked(), self.imag_button.isChecked(), self.error_button.isChecked()),
'children': self.sets, 'children': self.sets,
'active': self.active, 'active': self._active,
} }
in_legend = [] in_legend = []
@ -708,7 +753,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
if background is not None: if background is not None:
self._bgcolor = mkColor(background) self._bgcolor = mkColor(background)
self.graphic.setBackground(self._bgcolor) self.graphic.setBackground(self._bgcolor)
self.legend.setBrush(self._bgcolor) # self.legend.setBrush(self._bgcolor)
if foreground is not None: if foreground is not None:
self._fgcolor = mkColor(foreground) self._fgcolor = mkColor(foreground)
@ -725,7 +770,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
self.show_legend() self.show_legend()
title = self.plotItem.titleLabel.text title = self.plotItem.titleLabel.text
if title is not None: if title is not None and title != '':
self.plotItem.setTitle(title, **{'size': '10pt', 'color': self._fgcolor}) self.plotItem.setTitle(title, **{'size': '10pt', 'color': self._fgcolor})
x = self.plotItem.getAxis('bottom').labelText x = self.plotItem.getAxis('bottom').labelText
@ -736,8 +781,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
if y is not None: if y is not None:
self.plotItem.setLabel('left', y, **{'font-size': '10pt', 'color': self._fgcolor.name()}) self.plotItem.setLabel('left', y, **{'font-size': '10pt', 'color': self._fgcolor.name()})
@QtCore.pyqtSlot(bool, name='on_bwbutton_toggled')
def change_background(self, _): def change_background(self, _):
temp = self._fgcolor, self._bgcolor temp = self._fgcolor, self._bgcolor
self.set_color(foreground=self._prev_colors[0], background=self._prev_colors[1]) self.set_color(foreground=self._prev_colors[0], background=self._prev_colors[1])
self._prev_colors = temp self._prev_colors = temp

View File

@ -1,4 +1,9 @@
from __future__ import annotations
import re
from nmreval.io.asciireader import AsciiReader from nmreval.io.asciireader import AsciiReader
from nmreval.utils import NUMBER_RE
from ..Qt import QtGui, QtCore, QtWidgets from ..Qt import QtGui, QtCore, QtWidgets
from .._py.asciidialog import Ui_ascii_reader from .._py.asciidialog import Ui_ascii_reader
@ -13,6 +18,9 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
self.setupUi(self) self.setupUi(self)
self.reader = None self.reader = None
self._matches = []
self.regex_input.setText(NUMBER_RE.pattern)
self.custom_input.setValidator(QtGui.QDoubleValidator())
self.comment_textfield = QtWidgets.QPlainTextEdit(self.header_widget) self.comment_textfield = QtWidgets.QPlainTextEdit(self.header_widget)
self.comment_textfield.setReadOnly(True) self.comment_textfield.setReadOnly(True)
@ -41,6 +49,8 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
def __call__(self, fname, *args, **kwargs): def __call__(self, fname, *args, **kwargs):
self.reader = AsciiReader(fname) self.reader = AsciiReader(fname)
self.check_filename(self.regex_input.text())
if self.skip: if self.skip:
self.accept() self.accept()
return return
@ -52,6 +62,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
self.log_checkBox.setChecked(False) self.log_checkBox.setChecked(False)
self.set_gui() self.set_gui()
self.set_column_names(1)
self.skippy_checkbox.blockSignals(True) self.skippy_checkbox.blockSignals(True)
self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked) self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked)
@ -63,12 +74,14 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
for text in self.reader.header: for text in self.reader.header:
self.comment_textfield.appendPlainText(text) self.comment_textfield.appendPlainText(text)
tmp = self.line_spinBox.value()
if self.reader.header: if self.reader.header:
self.line_spinBox.setMaximum(len(self.reader.header)) self.line_spinBox.setMaximum(len(self.reader.header))
else: else:
self.line_spinBox.setValue(0) self.line_spinBox.setValue(0)
self.line_spinBox.setEnabled(False) self.line_spinBox.setEnabled(False)
self.show_preview(10) self.show_preview(10)
self.line_spinBox.setValue(tmp)
if self.reader.delays is not None: if self.reader.delays is not None:
set_string = ''.join(str(d) + '\n' for d in self.reader.delays) set_string = ''.join(str(d) + '\n' for d in self.reader.delays)
@ -98,22 +111,26 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
@QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged') @QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged')
def show_preview(self, line_no: int): def show_preview(self, line_no: int):
preview, width = self.reader.make_preview(line_no) preview, width, comments = self.reader.make_preview(line_no)
self.ascii_table.setRowCount(min(line_no, len(preview))) self.ascii_table.setRowCount(min(line_no, len(preview)))
self.ascii_table.setColumnCount(width) self.ascii_table.setColumnCount(width + 1)
for i, line in enumerate(preview): for i, line in enumerate(preview):
comment_line = comments[i]
for j, field in enumerate(line): for j, field in enumerate(line):
it = QtWidgets.QTableWidgetItem(field) it = QtWidgets.QTableWidgetItem(field)
self.ascii_table.setItem(i, j, it) self.ascii_table.setItem(i, j, it)
it = QtWidgets.QTableWidgetItem(comment_line)
self.ascii_table.setItem(i, len(line), it)
self.ascii_table.resizeColumnsToContents() self.ascii_table.resizeColumnsToContents()
@QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged') @QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged')
@QtCore.pyqtSlot(int, name='on_line_spinBox_valueChanged') @QtCore.pyqtSlot(int, name='on_line_spinBox_valueChanged')
def set_column_names(self, _): def set_column_names(self, _):
self.ascii_table.setHorizontalHeaderLabels(map(str, range(1, self.ascii_table.columnCount() + 1))) self.ascii_table.setHorizontalHeaderLabels(map(str, range(1, self.ascii_table.columnCount() + 1)))
if self.column_checkBox.isChecked(): if self.column_checkBox.isChecked() and self.line_spinBox.isEnabled():
header_line = self.reader.header[self.line_spinBox.value()-1] header_line = self.reader.header[self.line_spinBox.value()-1]
self.ascii_table.setHorizontalHeaderLabels(header_line.split()) self.ascii_table.setHorizontalHeaderLabels(header_line.split())
@ -134,7 +151,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
staggered_range = 0 staggered_range = 0
except ValueError: except ValueError:
_ = QtWidgets.QMessageBox.information(self, 'No delays', _ = QtWidgets.QMessageBox.information(self, 'No delays',
'Delays cannot be calculated: Not enough or wrong arguments.') 'Delays cannot be calculated: Not enough or wrong arguments.')
return return
self.reader.calc_delays(start, stop, num_delays, log=self.log_checkBox.isChecked(), self.reader.calc_delays(start, stop, num_delays, log=self.log_checkBox.isChecked(),
@ -165,10 +182,13 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
def apply(self): def apply(self):
# default row for x is the first row, it will be superseded if an integer number is given. # default row for x is the first row, it will be superseded if an integer number is given.
try: x = self.x_lineedit.text()
x = int(self.x_lineedit.text())-1 if x:
print(x) try:
except ValueError: x = int(x)-1
except ValueError:
pass
else:
x = None x = None
try: try:
@ -194,12 +214,17 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
col_header = [col_header[i] for i in range(len(col_header)) if i in y] col_header = [col_header[i] for i in range(len(col_header)) if i in y]
try: try:
ret_dic = self.reader.export(x=x, y=y, yerr=y_err, ret_dic = self.reader.export(
mode=self.buttonGroup.checkedButton().text(), x=x,
col_names=col_header) y=y,
yerr=y_err,
mode=self.buttonGroup.checkedButton().text(),
col_names=col_header,
num_value=self.get_numerical_value(),
)
self.data_read.emit(ret_dic) self.data_read.emit(ret_dic)
except Exception as e: except ImportError as e:
_ = QtWidgets.QMessageBox.information(self, 'Reading failed', _ = QtWidgets.QMessageBox.information(self, 'Reading failed',
f'Import data failed with {e.args}') f'Import data failed with {e.args}')
@ -211,5 +236,45 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
@QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged') @QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged')
def skip_next_dial(self, _: int): def skip_next_dial(self, _: int):
print('skippy das buschkänguru', _)
self.skip = self.skippy_checkbox.isChecked() self.skip = self.skippy_checkbox.isChecked()
@QtCore.pyqtSlot(str, name='on_regex_input_textChanged')
def check_filename(self, pattern: str = NUMBER_RE.pattern):
if self.reader is None:
return
try:
pattern = re.compile(pattern)
self.regex_input.setStyleSheet('color: rgb(0, 0, 0)')
self._matches = [m for m in pattern.finditer(str(self.reader.fname.stem))]
except re.error:
self._matches = []
if self._matches:
self.re_match_index.blockSignals(True)
self.re_match_index.setMaximum(len(self._matches))
self.re_match_index.blockSignals(False)
else:
self.regex_input.setStyleSheet('color: rgb(255, 0, 0)')
self.show_match(self.re_match_index.value())
@QtCore.pyqtSlot(int, name='on_re_match_index_valueChanged')
def show_match(self, idx: int = 0):
fname = str(self.reader.fname.stem)
if self._matches:
m = self._matches[idx-1]
self.label_8.setText(f'{fname[:m.start()]}<b>{fname[m.start():m.end()]}</b>{fname[m.end():]}')
else:
self.label_8.setText(fname)
def get_numerical_value(self):
val = 0
if self.re_button.isChecked() and self._matches:
m = self._matches[self.re_match_index.value()-1]
val = float(NUMBER_RE.search(m.group()).group().replace('p', '.'))
elif self.custom_button.isChecked():
val = float(self.custom_input.text())
return val

View File

@ -5,7 +5,7 @@ from pathlib import Path
import numpy as np import numpy as np
from pyqtgraph import PlotDataItem from pyqtgraph import PlotDataItem
from nmreval.data.points import Points from nmreval.data import DSC
from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample
from ..Qt import QtWidgets, QtCore from ..Qt import QtWidgets, QtCore
@ -83,7 +83,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
if opts[0] == 'i': if opts[0] == 'i':
item.setFlags(QtCore.Qt.NoItemFlags) item.setFlags(QtCore.Qt.NoItemFlags)
item.setText(f'{opts[1]:.2f} K for {opts[1] / 60:.0f} min') item.setText(f'{opts[1]:.2f} K for {opts[2] / 60:.0f} min')
else: else:
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable) item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable)
item.setText(f'{opts[2]:.2f} K to {opts[3]:.2f} K with {opts[1]} K/min') item.setText(f'{opts[2]:.2f} K to {opts[3]:.2f} K with {opts[1]} K/min')
@ -97,7 +97,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
if empty: if empty:
self.empty = self.calibrator.set_measurement(empty, mode='empty') self.empty = self.calibrator.set_measurement(empty, mode='empty')
self.empty_label.setText(str(self.empty.fname.name)) self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home())))
self.update_plots() self.update_plots()
@ -158,20 +158,28 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
self.sample_idx = None self.sample_idx = None
self.clear_plots() self.clear_plots()
def get_data(self, ): def get_data(self):
if self.sample_idx is None: if self.sample_idx is None:
return return
rate = self.current_run[0] rate = self.current_run[0]
slope_type = {self.none_radioButton: None, slope_type = {
self.isotherm_radioButton: 'iso', self.none_radioButton: None,
self.slope_radioButton: 'curve'}[self.buttonGroup.checkedButton()] self.isotherm_radioButton: 'iso',
self.slope_radioButton: 'curve',
}[self.buttonGroup.checkedButton()]
limit = None
if slope_type == 'curve':
try:
limit = float(self.limit1_lineedit.text())*60, float(self.limit2_lineedit.text())*60
except ValueError:
limit = None
try: try:
raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx, raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx, slope=slope_type, limits=limit)
slope=slope_type)
except ValueError as e: except ValueError as e:
_msg = QtWidgets.QMessageBox.warning(self, 'No rate found', e.args[0]) _msg = QtWidgets.QMessageBox.warning(self, f'Data collection with error', e.args[0])
return return
self.calibrator.ref_list = [] self.calibrator.ref_list = []
@ -194,8 +202,14 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
@QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked') @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked')
@QtCore.pyqtSlot(int, name='on_cp_checkBox_stateChanged') @QtCore.pyqtSlot(int, name='on_cp_checkBox_stateChanged')
@QtCore.pyqtSlot(str, name='on_limit1_lineedit_textChanged')
@QtCore.pyqtSlot(str, name='on_limit2_lineedit_textChanged')
def update_plots(self, _=None): def update_plots(self, _=None):
sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions = self.get_data() res = self.get_data()
if res is None:
return
sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions = res
self.raw_sample.setData(x=raw_sample[0], y=raw_sample[1]) self.raw_sample.setData(x=raw_sample[0], y=raw_sample[1])
self.drift_sample.setData(x=drift_value[0], y=drift_value[1]) self.drift_sample.setData(x=drift_value[0], y=drift_value[1])
@ -236,10 +250,11 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
return return
rate, mode = self.current_run rate, mode = self.current_run
new_val = Points(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})') new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})')
if filesave: if filesave:
new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_'))) new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_')))
close_after = False
else: else:
self.data_read.emit([new_val]) self.data_read.emit([new_val])
@ -250,7 +265,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
def button_clicked(self, bttn: QtWidgets.QAbstractButton): def button_clicked(self, bttn: QtWidgets.QAbstractButton):
bttn_value = self.buttonBox.standardButton(bttn) bttn_value = self.buttonBox.standardButton(bttn)
if bttn_value in (self.buttonBox.Ok, self.buttonBox.Apply, self.buttonBox.Save): if bttn_value in (self.buttonBox.Ok, self.buttonBox.Apply, self.buttonBox.Save):
self.export_data(filesave=bttn_value==self.buttonBox.Save, close_after=bttn_value==self.buttonBox.Ok) self.export_data(filesave=bttn_value == self.buttonBox.Save, close_after=bttn_value == self.buttonBox.Ok)
else: else:
super().close() super().close()

View File

@ -55,9 +55,9 @@ class GraceExporter:
break break
if c_num == -1: if c_num == -1:
c_num = max(colors.keys()) c_num = max(colors.keys())+1
colors[c_num + 1] = (f'color{c_num + 1}', sc) colors[c_num] = (f'color{c_num}', sc)
new_colors.append((c_num + 1, f'color{c_num + 1}', sc)) new_colors.append((c_num, f'color{c_num}', sc))
new_s.set_symbol(**{'symbol': item['symbol'].value, 'size': item['symbolsize'] / 10., 'color': c_num, new_s.set_symbol(**{'symbol': item['symbol'].value, 'size': item['symbolsize'] / 10., 'color': c_num,
'fill color': c_num, 'fill pattern': 1}) 'fill color': c_num, 'fill pattern': 1})

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import pathlib import pathlib
from nmreval.io.fcbatchreader import FCReader from nmreval.io.fcbatchreader import FCReader
@ -31,6 +33,10 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog):
return super().eventFilter(src, evt) return super().eventFilter(src, evt)
def add_graphs(self, graphs: list[tuple[str, str]]):
for gid, graph_name in graphs:
self.graph_comboBox.addItem(graph_name, gid)
@QtCore.pyqtSlot(int, name='on_region_checkBox_stateChanged') @QtCore.pyqtSlot(int, name='on_region_checkBox_stateChanged')
def use_region(self, state: int): def use_region(self, state: int):
self.start_lineedit.setEnabled(state == QtCore.Qt.Checked) self.start_lineedit.setEnabled(state == QtCore.Qt.Checked)
@ -43,17 +49,17 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog):
infiles, _ = QtWidgets.QFileDialog.getOpenFileNames(caption='Select HDF files', infiles, _ = QtWidgets.QFileDialog.getOpenFileNames(caption='Select HDF files',
directory=str(self.path), directory=str(self.path),
filter='HDF files (*.h5)') filter='HDF files (*.h5)')
if infiles:
self.listWidget.addItems(infiles)
self.label.setText(str(pathlib.Path(infiles[-1]).parent))
else: else:
infiles = QtWidgets.QFileDialog.getExistingDirectory(caption='Select input directory', infiles = QtWidgets.QFileDialog.getExistingDirectory(caption='Select input directory',
directory=str(self.path), directory=str(self.path),
options=QtWidgets.QFileDialog.ShowDirsOnly) options=QtWidgets.QFileDialog.ShowDirsOnly)
infiles = [infiles] if infiles else infiles
if infiles: if infiles:
self.listWidget.addItem(infiles) self.listWidget.addItems(infiles)
self.label.setText(str(pathlib.Path(infiles).parent)) self.path = pathlib.Path(infiles[-1]).parent
self.label.setText(str(self.path))
@QtCore.pyqtSlot(name='on_savebutton_clicked') @QtCore.pyqtSlot(name='on_savebutton_clicked')
def save_path(self): def save_path(self):
@ -72,7 +78,7 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog):
self.close() self.close()
def read(self, items): def read(self, items):
region = (None, None) region = None
if self.region_box.isChecked(): if self.region_box.isChecked():
start = None start = None
if self.start_lineedit.text(): if self.start_lineedit.text():

View File

@ -1,7 +1,10 @@
import numpy as np
from nmreval.lib.lines import LineStyle from nmreval.lib.lines import LineStyle
from nmreval.lib.symbols import SymbolStyle from nmreval.lib.symbols import SymbolStyle
from nmreval.data.points import Points from nmreval.data.points import Points
from nmreval.io.graceeditor import GraceEditor from nmreval.io.graceeditor import GraceEditor
from nmreval.utils.text import convert
from ..Qt import QtCore, QtWidgets, QtGui from ..Qt import QtCore, QtWidgets, QtGui
from .._py.gracereader import Ui_Dialog from .._py.gracereader import Ui_Dialog
@ -53,8 +56,12 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
if ds is None: if ds is None:
continue continue
item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {gset.get_property("legend")}, ' legend = gset.get_property('legend')
legend_str = convert(legend, old='agr', new='str') if legend is not None else ''
item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {legend_str}, '
f'shape: {ds.shape})']) f'shape: {ds.shape})'])
item_2.setCheckState(0, QtCore.Qt.Checked) item_2.setCheckState(0, QtCore.Qt.Checked)
item_2.setData(0, QtCore.Qt.UserRole, (graphs.idx, gset.idx)) item_2.setData(0, QtCore.Qt.UserRole, (graphs.idx, gset.idx))
item.addChild(item_2) item.addChild(item_2)
@ -87,9 +94,14 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
item = iterator.value() item = iterator.value()
key = (item.data(0, QtCore.Qt.UserRole)) key = (item.data(0, QtCore.Qt.UserRole))
s = self._reader.dataset(*key) s = self._reader.dataset(*key)
label = self._reader.get_property(*key, 'legend').replace('"', '') label = self._reader.get_property(*key, 'legend')
# label = self._reader.graphs[key[0]].sets[key[1]]['legend'].replace('"', '') if label is None:
label = ''
else:
label = label.replace('"', '')
label = convert(label, old='agr', new='str')
sd = s.data sd = s.data
sd = np.atleast_2d(sd)
if s.type == 'xydy': if s.type == 'xydy':
data.append(Points(x=sd[:, 0], y=sd[:, 1], y_err=sd[:, 2], name=label)) data.append(Points(x=sd[:, 0], y=sd[:, 1], y_err=sd[:, 2], name=label))
else: else:

View File

@ -1,80 +1 @@
import sys from .enums import Relations
if sys.version_info < (3, 7):
HAS_IMPORTLIB_RESOURCE = False
from pkg_resources import resource_filename
else:
HAS_IMPORTLIB_RESOURCE = True
from importlib.resources import path
from ..Qt import QtGui, QtWidgets
# def get_path_importlib(package, resource):
# return path(package, resource)
#
#
# def _get_path_pkg(package, resource):
# return resource_filename(package, resource)
#
#
# if HAS_IMPORTLIB_RESOURCE:
# get_path = get_path_importlib
# else:
# get_path = _get_path_pkg
def make_action_icons(widget):
global HAS_IMPORTLIB_RESOURCE
icon_type = QtWidgets.QApplication.instance().theme
from json import loads
if HAS_IMPORTLIB_RESOURCE:
with path('resources.icons', 'icons.json') as fp:
with fp.open('r') as f:
icon_list = loads(f.read())
for ac, img in icon_list[widget.objectName()].items():
dirname = 'resources.icons.%s_light' % icon_type
with path(dirname, img+'.png') as imgpath:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
getattr(widget, ac).setIcon(icon)
else:
with open(resource_filename('resources.icons', 'icons.json'), 'r') as f:
icon_list = loads(f.read())
for ac, img in icon_list[widget.objectName()].items():
dirname = 'resources.icons.%s_light' % icon_type
imgpath = resource_filename(dirname, img+'.png')
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
getattr(widget, ac).setIcon(icon)
def get_icon(icon_name):
try:
icon_type = QtWidgets.QApplication.instance().theme
except AttributeError:
icon_type = 'normal'
global HAS_IMPORTLIB_RESOURCE
if icon_name != 'logo':
dirname = f'resources.icons.{icon_type}_light'
else:
dirname = 'resources.icons'
if HAS_IMPORTLIB_RESOURCE:
with path(dirname, icon_name+'.png') as imgpath:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
return icon
else:
imgpath = resource_filename(dirname, icon_name+'.png')
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(imgpath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
return icon

View File

@ -237,7 +237,7 @@ class CodeEditor(QtWidgets.QPlainTextEdit):
if block.isVisible() and (bottom >= evt.rect().top()): if block.isVisible() and (bottom >= evt.rect().top()):
number = str(block_number + 1) number = str(block_number + 1)
painter.setPen(QtCore.Qt.black) painter.setPen(QtCore.Qt.black)
painter.drawText(0, top, self.current_linenumber.width() - 3, height, painter.drawText(0, int(top), self.current_linenumber.width() - 3, height,
QtCore.Qt.AlignRight, number) QtCore.Qt.AlignRight, number)
block = block.next() block = block.next()

View File

@ -30,7 +30,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate):
rect = options.rect rect = options.rect
rect.adjust(5, 0, -5, 0) rect.adjust(5, 0, -5, 0)
mid = (rect.bottom()+rect.top()) / 2 mid = int((rect.bottom()+rect.top()) / 2)
painter.drawLine(rect.left(), mid, rect.right(), mid) painter.drawLine(rect.left(), mid, rect.right(), mid)
painter.restore() painter.restore()
@ -42,7 +42,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate):
painter.setPen(pen) painter.setPen(pen)
pm = make_symbol_pixmap(r) pm = make_symbol_pixmap(r)
painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, (options.rect.height()-pm.height())/2), pm) painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, int((options.rect.height()-pm.height())/2)), pm)
style = QtWidgets.QApplication.style() style = QtWidgets.QApplication.style()
text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None) text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None)
@ -171,7 +171,7 @@ class LineStyleEditor(QtWidgets.QComboBox):
rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox,
opt, QtWidgets.QStyle.SC_ComboBoxEditField, None) opt, QtWidgets.QStyle.SC_ComboBoxEditField, None)
rect.adjust(+10, 0, -10, 0) rect.adjust(+10, 0, -10, 0)
mid = (rect.bottom() + rect.top()) / 2 mid = int((rect.bottom() + rect.top()) / 2)
painter.drawLine(rect.left(), mid, rect.right(), mid) painter.drawLine(rect.left(), mid, rect.right(), mid)
painter.end() painter.end()
else: else:
@ -193,7 +193,7 @@ class LineStyleDelegate(QtWidgets.QStyledItemDelegate):
rect = option.rect rect = option.rect
rect.adjust(+10, 0, -10, 0) rect.adjust(+10, 0, -10, 0)
mid = (rect.bottom()+rect.top()) / 2 mid = int((rect.bottom()+rect.top()) / 2)
painter.drawLine(rect.left(), mid, rect.right(), mid) painter.drawLine(rect.left(), mid, rect.right(), mid)
else: else:
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index) QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)

8
src/gui_qt/lib/enums.py Normal file
View File

@ -0,0 +1,8 @@
from enum import IntEnum, auto
class Relations(IntEnum):
isFitOf = auto()
hasFit = auto()
isFitPartOf = auto()
hasFitPart = auto()

View File

@ -1,3 +1,5 @@
from typing import Any
from numpy import inf from numpy import inf
from nmreval.utils.text import convert from nmreval.utils.text import convert
@ -50,19 +52,24 @@ class QDelayWidget(QtWidgets.QWidget):
class LineEdit(QtWidgets.QLineEdit): class LineEdit(QtWidgets.QLineEdit):
values_requested = QtCore.pyqtSignal() values_requested = QtCore.pyqtSignal()
replace_single_values = QtCore.pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
def contextMenuEvent(self, evt): def contextMenuEvent(self, evt):
menu = self.createStandardContextMenu() menu = self.createStandardContextMenu()
request_action = menu.addAction('Use value of sets') request_action = menu.addAction('Use numeric value of sets')
set_value_action = menu.addAction('Replace single set values')
action = menu.exec(evt.globalPos()) action = menu.exec(evt.globalPos())
if action == request_action: if action == request_action:
self.values_requested.emit() self.values_requested.emit()
elif action == set_value_action:
self.replace_single_values.emit()
class LineEditPost(QtWidgets.QLineEdit): class LineEditPost(QtWidgets.QLineEdit):
values_requested = QtCore.pyqtSignal() values_requested = QtCore.pyqtSignal()
@ -404,3 +411,21 @@ class ElideComboBox(QtWidgets.QComboBox):
opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width()) opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width())
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt) painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
class CheckCombobox(QtWidgets.QComboBox):
def addItem(self, text: str, userData: Any=None) -> None:
super().addItem(text, userData=userData)
item = self.model().item(self.count()-1)
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
item.setCheckState(QtCore.Qt.Checked)
def addItems(self, text):
for text_i in text:
self.addItem(text_i)
def isChecked(self, idx: int) -> bool:
return bool(self.model().item(idx).checkState())

View File

@ -0,0 +1,53 @@
from numpy import log10, arange, floor, ceil
from pyqtgraph import PlotWidget, PlotItem
__all__ = ['NMRPlotWidget', 'logTickValues']
class NMRPlotWidget(PlotWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for orient in ['top', 'bottom', 'left', 'right']:
# BAD HACK!!! but seems to work, see function for explanation
self.plotItem.getAxis(orient).logTickValues = logTickValues
class NMRPlotItem(PlotItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for orient in ['top', 'bottom', 'left', 'right']:
self.plotItem.getAxis(orient).logTickValues = logTickValues
def logTickValues(minVal, maxVal, size, stdTicks):
# TODO FIND A BETTER SOLUTION!!!
# Sometimes minVal and maxVal are not log-scaled values and the loop from v1 to v2 is humongous,
# The minor list then fills the RAM completely and freezes everything
# Until there is a better solution, we overwrite this function for every AxesItem
# and do not draw minor ticks at all if there are too many
# start with the tick spacing given by tickValues().
# Any level whose spacing is < 1 needs to be converted to log scale
ticks = []
for (spacing, t) in stdTicks:
if spacing >= 1.0:
ticks.append((spacing, t))
if len(ticks) < 3:
v1 = int(floor(minVal))
v2 = int(ceil(maxVal))
# major = list(range(v1+1, v2))
minor = []
if v2 - v1 < 400:
for v in range(v1, v2):
minor.extend(v + log10(arange(1, 10)))
minor = [x for x in minor if minVal < x < maxVal]
ticks.append((None, minor))
return ticks

View File

@ -0,0 +1,66 @@
import sys
if sys.version_info < (3, 7):
HAS_IMPORTLIB_RESOURCE = False
from pkg_resources import resource_filename
else:
HAS_IMPORTLIB_RESOURCE = True
from importlib.resources import path
from ..Qt import QtGui, QtWidgets
def make_action_icons(widget):
global HAS_IMPORTLIB_RESOURCE
icon_type = QtWidgets.QApplication.instance().theme
from json import loads
if HAS_IMPORTLIB_RESOURCE:
with path('resources.icons', 'icons.json') as fp:
with fp.open('r') as f:
icon_list = loads(f.read())
for ac, img in icon_list[widget.objectName()].items():
dirname = 'resources.icons.%s_light' % icon_type
with path(dirname, img+'.png') as imgpath:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
getattr(widget, ac).setIcon(icon)
else:
with open(resource_filename('resources.icons', 'icons.json'), 'r') as f:
icon_list = loads(f.read())
for ac, img in icon_list[widget.objectName()].items():
dirname = 'resources.icons.%s_light' % icon_type
imgpath = resource_filename(dirname, img+'.png')
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
getattr(widget, ac).setIcon(icon)
def get_icon(icon_name):
try:
icon_type = QtWidgets.QApplication.instance().theme
except AttributeError:
icon_type = 'normal'
global HAS_IMPORTLIB_RESOURCE
if icon_name != 'logo':
dirname = f'resources.icons.{icon_type}_light'
else:
dirname = 'resources.icons'
if HAS_IMPORTLIB_RESOURCE:
with path(dirname, icon_name+'.png') as imgpath:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
return icon
else:
imgpath = resource_filename(dirname, icon_name+'.png')
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(imgpath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
return icon

View File

@ -0,0 +1,20 @@
from ..Qt import QtWidgets, QtGui, QtCore
class QListWidgetSelect(QtWidgets.QListWidget):
"""
Extension of QListWidget to change the check state of all selected QListWidgetItems with space key
"""
def __init__(self, parent=None):
super().__init__(parent=parent)
def keyPressEvent(self, evt: QtGui.QKeyEvent):
if evt.key() == QtCore.Qt.Key.Key_Space:
for idx in self.selectedIndexes():
item = self.itemFromIndex(idx)
cs = item.checkState()
item.setCheckState(QtCore.Qt.CheckState.Unchecked if cs == QtCore.Qt.CheckState.Checked
else QtCore.Qt.CheckState.Checked)
else:
super().keyPressEvent(evt)

49
src/gui_qt/lib/mdiarea.py Normal file
View File

@ -0,0 +1,49 @@
from __future__ import annotations
from ..Qt import QtWidgets, QtCore
from nmreval.lib.logger import logger
from ..graphs.graphwindow import QGraphWindow
class MdiAreaTile(QtWidgets.QMdiArea):
def __init__(self, parent=None):
super().__init__(parent=parent)
def tileSubWindowsVertically(self):
window_list = self.subWindowList()
rect = QtCore.QRect(0, 0, self.width(), int(self.height() / len(window_list)))
pos = QtCore.QPoint(0, 0)
for win in window_list:
win.setGeometry(rect)
win.move(pos)
pos.setY(pos.y() + win.height())
def tileSubWindowsHorizontally(self):
window_list = self.subWindowList()
rect = QtCore.QRect(0, 0, int(self.width() / len(window_list)), self.height())
pos = QtCore.QPoint(0, 0)
for win in window_list:
win.setGeometry(rect)
win.move(pos)
pos.setX(pos.x() + win.width())
def addSubWindow(self, widget: QtWidgets.QWidget, flags: QtCore.Qt.WindowFlags = QtCore.Qt.WindowFlags()) -> QtWidgets.QMdiSubWindow | None:
subwindow = super().addSubWindow(widget)
subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandMove, True)
subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandResize, True)
subwindow.setMinimumHeight(240)
subwindow.setMinimumWidth(360)
return subwindow
def setActiveSubWidget(self, key: str):
for win in self.subWindowList():
wdgt = win.widget()
if isinstance(wdgt, QGraphWindow) and wdgt.id == key:
self.setActiveSubWindow(win)
break

View File

@ -21,36 +21,53 @@ class Namespace:
self.top_levels = {} self.top_levels = {}
if basic: if basic:
self.add_namespace({'x': (None, 'x values'), 'y': (None, 'x values'), 'y_err': (None, 'y error values'), self.add_namespace(
'fit': (None, 'dictionary of fit parameter', 'fit["PIKA"]'), 'np': (np, 'numpy module')}, {'x': (None, 'x values'),
parents=('Basic', 'General')) 'y': (None, 'x values'),
'y_err': (None, 'y error values'),
'fit': (None, 'dictionary of fit parameter', 'fit["PIKA"]'),
'np': (np, 'numpy module'),
},
parents=('Basic', 'General'),
)
self.add_namespace({'sin': (np.sin, 'Sine', 'sin(PIKA)'), 'cos': (np.cos, 'Cosine', 'cos(PIKA)'), self.add_namespace(
'tan': (np.tan, 'Tangens', 'tan(PIKA)'), 'ln': (np.log, 'Natural Logarithm', 'ln(PIKA)'), {'sin': (np.sin, 'Sine', 'sin(PIKA)'),
'log': (np.log10, 'Logarithm (base 10)', 'log(PIKA)'), 'cos': (np.cos, 'Cosine', 'cos(PIKA)'),
'exp': (np.exp, 'Exponential', 'exp(PIKA)'), 'sqrt': (np.sqrt, 'Root', 'sqrt(PIKA)'), 'tan': (np.tan, 'Tangens', 'tan(PIKA)'),
'lin_range': (np.linspace, 'N evenly spaced over interval [start, stop]', 'ln': (np.log, 'Natural Logarithm', 'ln(PIKA)'),
'lin_range(start, stop, N)'), 'log': (np.log10, 'Logarithm (base 10)', 'log(PIKA)'),
'log_range': (np.geomspace, 'N evenly spaced (log-scale) over interval [start, stop]', 'exp': (np.exp, 'Exponential', 'exp(PIKA)'),
'lin_range(start, stop, N)')}, 'sqrt': (np.sqrt, 'Root', 'sqrt(PIKA)'),
parents=('Basic', 'Functions')) 'lin_range': (np.linspace, 'N evenly spaced over interval [start, stop]', 'lin_range(start, stop, N)'),
'log_range': (np.geomspace, 'N evenly spaced (log-scale) over interval [start, stop]', 'lin_range(start, stop, N)'),
self.add_namespace({'max': (np.max, 'Maximum value', 'max(PIKA)'), },
'min': (np.min, 'Minimum value', 'min(PIKA)'), parents=('Basic', 'Functions'))
'argmax': (np.argmax, 'Index of maximum value', 'argmax(PIKA)'),
'argmin': (np.argmax, 'Index of minimum value', 'argmin(PIKA)')}, self.add_namespace(
parents=('Basic', 'Values')) {'max': (np.max, 'Maximum value', 'max(PIKA)'),
'min': (np.min, 'Minimum value', 'min(PIKA)'),
'argmax': (np.argmax, 'Index of maximum value', 'argmax(PIKA)'),
'argmin': (np.argmax, 'Index of minimum value', 'argmin(PIKA)'),
},
parents=('Basic', 'Values')),
if const: if const:
self.add_namespace({'e': (constants.e, 'e / As'), 'eps0': (constants.epsilon0, 'epsilon0 / As/Vm'), self.add_namespace(
'Eu': (constants.Eu,), 'h': (constants.h, 'h / eVs'), {'e': (constants.e, 'e / As'),
'hbar': (constants.hbar, 'hbar / eVs'), 'kB': (constants.kB, 'kB / eV/K'), 'eps0': (constants.epsilon0, 'epsilon0 / As/Vm'),
'mu0': (constants.mu0, 'mu0 / Vs/Am'), 'NA': (constants.NA, 'NA / 1/mol'), 'Eu': (constants.Eu,), 'h': (constants.h, 'h / eVs'),
'pi': (constants.pi,), 'R': (constants.R, 'R / eV')}, 'hbar': (constants.hbar, 'hbar / eVs'), 'kB': (constants.kB, 'kB / eV/K'),
parents=('Constants', 'Maybe useful')) 'mu0': (constants.mu0, 'mu0 / Vs/Am'), 'NA': (constants.NA, 'NA / 1/mol'),
'pi': (constants.pi,), 'R': (constants.R, 'R / eV'),
},
parents=('Constants', 'Maybe useful'),
)
self.add_namespace({f'gamma["{k}"]': (v, k, f'gamma["{k}"]') for k, v in constants.gamma.items()}, self.add_namespace(
parents=('Constants', 'Magnetogyric ratios (in 1/(sT))')) {f'gamma["{k}"]': (v, k, f'gamma["{k}"]') for k, v in constants.gamma.items()},
parents=('Constants', 'Magnetogyric ratios (in 1/(sT))')
)
if fitfuncs: if fitfuncs:
self.make_dict_from_fitmodule(models) self.make_dict_from_fitmodule(models)
@ -104,9 +121,18 @@ class Namespace:
graph = namedtuple('graphs', ['s']) graph = namedtuple('graphs', ['s'])
sets = namedtuple('sets', ['x', 'y', 'y_err', 'value', 'fit'], defaults=(None,)) sets = namedtuple('sets', ['x', 'y', 'y_err', 'value', 'fit'], defaults=(None,))
gamma = {}
gs = re.compile(r'g\[(\d+)].s\[(\d+)].(x|y(?:_err)*|value|fit)') gs = re.compile(r'g\[(\d+)].s\[(\d+)].(x|y(?:_err)*|value|fit)')
gamma_re = re.compile(r'gamma\["(\w+)"\]')
for k, v in self.namespace.items(): for k, v in self.namespace.items():
m = gamma_re.match(k)
if m:
gamma[m.group(1)] = v[0]
continue
m = gs.match(k) m = gs.match(k)
if m: if m:
if 'g' not in ret_dic: if 'g' not in ret_dic:
@ -118,7 +144,7 @@ class Namespace:
gg = ret_dic['g'][int(g_num)] gg = ret_dic['g'][int(g_num)]
if int(s_num) not in gg.s: if int(s_num) not in gg.s:
gg.s[int(s_num)] = sets('', '', '', '') gg.s[int(s_num)] = sets('', '', '', '', {})
ss = gg.s[int(s_num)] ss = gg.s[int(s_num)]
if pos == 'fit': if pos == 'fit':
@ -130,8 +156,11 @@ class Namespace:
else: else:
ret_dic['g'][int(g_num)].s[int(s_num)] = ss._replace(**{pos: v[0]}) ret_dic['g'][int(g_num)].s[int(s_num)] = ss._replace(**{pos: v[0]})
else: continue
ret_dic[k] = v[0]
ret_dic[k] = v[0]
ret_dic['gamma'] = gamma
return ret_dic return ret_dic

View File

@ -174,6 +174,7 @@ class PlotItem(PlotDataItem):
pen = self.opts['pen'] pen = self.opts['pen']
if isinstance(pen, tuple): if isinstance(pen, tuple):
self.opts['linecolor'] = pen self.opts['linecolor'] = pen
self.opts['pen'] = mkPen(color=pen)
else: else:
c = pen.color() c = pen.color()
self.opts['linecolor'] = c.red(), c.green(), c.blue() self.opts['linecolor'] = c.red(), c.green(), c.blue()
@ -279,7 +280,7 @@ class PlotItem(PlotDataItem):
else: else:
self.scatter.hide() self.scatter.hide()
def set_symbol(self, symbol=None, size=None, color=None): def set_symbol(self, *, symbol=None, size=None, color=None):
if symbol is not None: if symbol is not None:
if isinstance(symbol, int): if isinstance(symbol, int):
self.setSymbol(SymbolStyle(symbol).to_str()) self.setSymbol(SymbolStyle(symbol).to_str())
@ -313,14 +314,13 @@ class PlotItem(PlotDataItem):
self.opts['pen'] = pen self.opts['pen'] = pen
self.updateItems() self.updateItems()
def set_line(self, style=None, width=None, color=None): def set_line(self, *, style=None, width=None, color=None):
pen = self.opts['pen'] pen = self.opts['pen']
if pen is None: if pen is None:
pen = mkPen(style=QtCore.Qt.NoPen) pen = mkPen(style=QtCore.Qt.NoPen)
if width is not None: if width is not None:
pen.setWidthF(width) pen.setWidthF(width)
if style is not None: if style is not None:
if isinstance(style, LineStyle): if isinstance(style, LineStyle):
style = style.value style = style.value
@ -375,7 +375,6 @@ class PlotItem(PlotDataItem):
class RegionItem(LinearRegionItem): class RegionItem(LinearRegionItem):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.mode = kwargs.pop('mode', 'half') self.mode = kwargs.pop('mode', 'half')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.logmode = False self.logmode = False
@ -383,6 +382,10 @@ class RegionItem(LinearRegionItem):
if not hasattr(self, '_bounds') and hasattr(self, '_boundingRectCache'): if not hasattr(self, '_bounds') and hasattr(self, '_boundingRectCache'):
self._bounds = self._boundingRectCache self._bounds = self._boundingRectCache
for l in self.lines:
# higher z for borderlines improves chances that you can move it when multiple regions overlap
l.setZValue(self.zValue() + 1)
def setLogMode(self, xmode, _): def setLogMode(self, xmode, _):
if self.logmode == xmode: if self.logmode == xmode:
return return
@ -418,6 +421,15 @@ class RegionItem(LinearRegionItem):
else: else:
return region return region
def setRegion(self, region, use_log=False):
if self.logmode and use_log:
region = np.log10(region[0]), np.log10(region[1])
if not np.all(np.isfinite(region)):
raise ValueError(f'Invalid region limits ({region[0]}, {region[1]})')
else:
super().setRegion(region)
def boundingRect(self): def boundingRect(self):
# overwrite to draw correct rect in logmode # overwrite to draw correct rect in logmode
@ -460,11 +472,18 @@ class LegendItemBlock(LegendItem):
def mouseDragEvent(self, ev): def mouseDragEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton: if ev.button() == QtCore.Qt.LeftButton:
ev.accept() ev.accept()
dpos = ev.pos() - ev.lastPos() dpos = ev.pos() - ev.lastPos()
upper_left = self.pos()
lower_right = self.pos()
lower_right.setX(lower_right.x() + self.width())
lower_right.setY(lower_right.y() + self.height())
vb_rect = self.parentItem().rect() vb_rect = self.parentItem().rect()
pos = self.pos()
# upper left corner and a point a little more to the bottom right must be inside # upper left and lower right corner must be inside viewbox
if vb_rect.contains(pos+dpos) and vb_rect.contains(pos+dpos+QtCore.QPointF(20., 20.)): if vb_rect.contains(upper_left + dpos) and vb_rect.contains(lower_right + dpos):
self.autoAnchor(pos + dpos) self.autoAnchor(upper_left + dpos)
else: else:
self.autoAnchor(pos) self.autoAnchor(upper_left)

View File

@ -1,110 +0,0 @@
import os.path
import json
import urllib.request
import webbrowser
import random
from ..Qt import QtGui, QtCore, QtWidgets
from .._py.pokemon import Ui_Dialog
random.seed()
class QPokemon(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, number=None, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self._js = json.load(open(os.path.join(path_to_module, 'utils', 'pokemon.json'), 'r'), encoding='UTF-8')
self._id = 0
if number is not None and number in range(1, len(self._js)+1):
poke_nr = f'{number:03d}'
self._id = number
else:
poke_nr = f'{random.randint(1, len(self._js)):03d}'
self._id = int(poke_nr)
self._pokemon = None
self.show_pokemon(poke_nr)
self.label_15.linkActivated.connect(lambda x: webbrowser.open(x))
self.buttonBox.clicked.connect(self.randomize)
self.next_button.clicked.connect(self.show_next)
self.prev_button.clicked.connect(self.show_prev)
def show_pokemon(self, nr):
self._pokemon = self._js[nr]
self.setWindowTitle('Pokémon: ' + self._pokemon['Deutsch'])
for i in range(self.tabWidget.count(), -1, -1):
print('i', self.tabWidget.count(), i)
try:
self.tabWidget.widget(i).deleteLater()
except AttributeError:
pass
for n, img in self._pokemon['Bilder']:
w = QtWidgets.QWidget()
vl = QtWidgets.QVBoxLayout()
l = QtWidgets.QLabel(self)
l.setAlignment(QtCore.Qt.AlignHCenter)
pixmap = QtGui.QPixmap()
try:
pixmap.loadFromData(urllib.request.urlopen(img, timeout=0.5).read())
except IOError:
l.setText(n)
else:
sc_pixmap = pixmap.scaled(256, 256, QtCore.Qt.KeepAspectRatio)
l.setPixmap(sc_pixmap)
vl.addWidget(l)
w.setLayout(vl)
self.tabWidget.addTab(w, n)
if len(self._pokemon['Bilder']) <= 1:
self.tabWidget.tabBar().setVisible(False)
else:
self.tabWidget.tabBar().setVisible(True)
self.tabWidget.adjustSize()
self.name.clear()
keys = ['National-Dex', 'Kategorie', 'Typ', 'Größe', 'Gewicht', 'Farbe', 'Link']
label_list = [self.pokedex_nr, self.category, self.poketype, self.weight, self.height, self.color, self.info]
for (k, label) in zip(keys, label_list):
v = self._pokemon[k]
if isinstance(v, list):
v = os.path.join('', *v)
if k == 'Link':
v = '<a href={}>{}</a>'.format(v, v)
label.setText(v)
for k in ['Deutsch', 'Japanisch', 'Englisch', 'Französisch']:
v = self._pokemon[k]
self.name.addItem(k + ': ' + v)
self.adjustSize()
def randomize(self, idd):
if idd.text() == 'Retry':
new_number = f'{random.randint(1, len(self._js)):03d}'
self._id = int(new_number)
self.show_pokemon(new_number)
else:
self.close()
def show_next(self):
new_number = self._id + 1
if new_number > len(self._js):
new_number = 1
self._id = new_number
self.show_pokemon(f'{new_number:03d}')
def show_prev(self):
new_number = self._id - 1
if new_number == 0:
new_number = len(self._js)
self._id = new_number
self.show_pokemon(f'{new_number:03d}')

100
src/gui_qt/lib/starter.py Normal file
View File

@ -0,0 +1,100 @@
from __future__ import annotations
import re
from shutil import copyfile
from pathlib import Path
import os
from configparser import ConfigParser
from importlib.resources import path as resource_path
from nmreval.configs import config_paths
from nmreval.lib.logger import logger
def make_starter(app_file: str | None):
if app_file is not None:
make_starter_appimage(Path(app_file))
else:
make_starter_src()
def make_starter_appimage(app_file: Path):
new_path = Path.home() / '.local' / 'bin'
if not new_path.exists():
new_path.mkdir(parents=True)
new_path /= app_file.name
if app_file != new_path:
app_file.rename(new_path)
create_desktop_file(new_path)
def make_starter_src():
home = Path.home()
p = Path.home()
for p in Path(__file__).parents:
if p.stem == 'src':
break
elif p == home:
break
success = p != Path.home()
if success:
bin_path = p.with_name('bin') / 'evaluate.py'
success = bin_path.exists()
if not success:
logger.warning('Location of evaluate.py could not be determined')
return False
create_desktop_file(bin_path)
def create_desktop_file(new_path: Path):
logo_path = config_paths() / 'logo.png'
if not logo_path.exists():
with resource_path('resources', 'logo.png') as fp:
copyfile(fp, logo_path)
desktop_entry = f"""\
[Desktop Entry]
Name=NMReval
Comment=Best program ever (maybe)
Exec={new_path}
Icon={logo_path}
Type=Application
Terminal=false
Categories=Science;NumericalAnalysis;Physics;DataVisualization;Other;
NoDisplay=false
"""
file_name = 'pkm.vogel.nmreval.desktop'
with Path('~/.local/share/applications/', file_name).expanduser().open('w') as f:
f.write(desktop_entry)
desktop_dir = get_xkg_user_dirs('desktop')
if desktop_dir is not None:
desk_file = Path(desktop_dir, file_name)
with desk_file.open('w') as f:
f.write(desktop_entry)
desk_file.chmod(0o755)
def get_xkg_user_dirs(dir_type: str) -> str | None:
xdg_conf_home = os.getenv('XDG_CONFIG_HOME') or str(Path.home() / '.config')
with Path(xdg_conf_home, 'user-dirs.dirs').open('r') as f:
conf_string = '[XDG_USER_DIRS]\n' + f.read()
conf_string = re.sub(r'\$HOME', str(Path.home()), conf_string)
conf_string = re.sub('"', '', conf_string)
config = ConfigParser()
config.read_string(conf_string)
return config['XDG_USER_DIRS'].get(f'xdg_{dir_type}_dir')
if __name__ == '__main__':
make_starter()

View File

@ -6,7 +6,7 @@ import numpy as np
from ..Qt import QtWidgets, QtCore, QtGui from ..Qt import QtWidgets, QtCore, QtGui
__all__ = ['Game'] __all__ = ['Game', 'QMines']
class Game(QtWidgets.QDialog): class Game(QtWidgets.QDialog):

View File

@ -1,4 +1,4 @@
from . import HAS_IMPORTLIB_RESOURCE from .iconloading import HAS_IMPORTLIB_RESOURCE
from ..Qt import QtGui, QtWidgets, QtCore from ..Qt import QtGui, QtWidgets, QtCore

View File

@ -28,4 +28,17 @@ class TreeWidget(QtWidgets.QTreeWidget):
continue continue
it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked) it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
else: else:
super().keyPressEvent(evt) super().keyPressEvent(evt)
class TableWidget(QtWidgets.QTableWidget):
def keyPressEvent(self, evt: QtGui.QKeyEvent):
if evt.key() == QtCore.Qt.Key.Key_Space:
for idx in self.selectedIndexes():
item = self.itemFromIndex(idx)
cs = item.checkState()
item.setCheckState(QtCore.Qt.CheckState.Unchecked if cs == QtCore.Qt.CheckState.Checked
else QtCore.Qt.CheckState.Checked)
else:
super().keyPressEvent(evt)

View File

@ -1,7 +1,10 @@
from __future__ import annotations
import copy import copy
from numpy import argsort from numpy import argsort
from . import Relations
from ..Qt import QtWidgets, QtCore from ..Qt import QtWidgets, QtCore
from ..data.container import FitContainer from ..data.container import FitContainer
from ..graphs.graphwindow import QGraphWindow from ..graphs.graphwindow import QGraphWindow
@ -84,6 +87,21 @@ class ShiftCommand(QtWidgets.QUndoCommand):
self.__data.apply('ls', self.__args) self.__data.apply('ls', self.__args)
class EditCommand(QtWidgets.QUndoCommand):
def __init__(self, data, *args):
super().__init__('Edit signal')
self.__data = data
self.__arguments = args
self.__original = copy.deepcopy(self.__data.data)
def undo(self):
self.__data.data = copy.deepcopy(self.__original)
def redo(self):
self.__data.edit_signal(*self.__arguments)
class NormCommand(QtWidgets.QUndoCommand): class NormCommand(QtWidgets.QUndoCommand):
def __init__(self, data, mode): def __init__(self, data, mode):
super().__init__('Normalize') super().__init__('Normalize')
@ -216,33 +234,69 @@ class DeleteGraphCommand(QtWidgets.QUndoCommand):
class DeleteCommand(QtWidgets.QUndoCommand): class DeleteCommand(QtWidgets.QUndoCommand):
def __init__(self, container, key, signal1, signal2): def __init__(self, container: dict, keys: list[str], graphs: dict, graphid: str,
signal1: QtCore.pyqtSignal, signal2: QtCore.pyqtSignal):
super().__init__('Delete data') super().__init__('Delete data')
self.__container = container self.__container = container
self.__value = self.__container[key] self.__graph_container = graphs
self.__key = key self.__graph_key = graphid
self.__value = {}
for k in keys:
self.__value[k] = self.__container[k]
self.__keys = tuple(keys)
self.__signal_add = signal1 self.__signal_add = signal1
self.__signal_remove = signal2 self.__signal_remove = signal2
def redo(self): def redo(self):
self.__signal_remove.emit(self.__key) # stop graph from rescaling and updating legend
if isinstance(self.__value, FitContainer): self.__graph_container[self.__graph_key].block(True)
try:
self.__container[self.__value.fitted_key]._fits.remove(self.__key) self.__signal_remove.emit(list(self.__keys[::-1]))
except KeyError: for sid in self.__keys[::-1]:
pass val = self.__value[sid]
del self.__container[self.__key]
if isinstance(val, FitContainer):
try:
self.__container[val.fitted_key]._fits.remove(sid)
except KeyError:
pass
for (flag1, flag2) in ((Relations.isFitPartOf, Relations.hasFitPart), (Relations.hasFitPart, Relations.isFitPartOf)):
for related_item in val.get_relation(flag1):
try:
self.__container[related_item].remove_relation(flag2, sid)
except KeyError:
pass
del self.__container[sid]
self.__graph_container[self.__graph_key].block(False)
def undo(self): def undo(self):
self.__container[self.__key] = self.__value # stop graph from rescaling and updating legend
if isinstance(self.__value, FitContainer): self.__graph_container[self.__graph_key].block(True)
try:
self.__container[self.__value.fitted_key]._fits.append(self.__key)
except KeyError:
pass
self.__signal_add.emit([self.__key], self.__value.graph) for sid in self.__keys:
val = self.__value[sid]
self.__container[sid] = val
if isinstance(val, FitContainer):
try:
self.__container[val.fitted_key]._fits.append(sid)
except KeyError:
pass
for (flag1, flag2) in (('isFitPartOf', 'hasFitPartOf'), ('hasFitPartOf', 'isFitPartOf')):
for related_item in val.get_relation(flag1):
try:
self.__container[related_item].add_relation(flag2, sid)
except KeyError:
pass
self.__signal_add.emit(list(self.__keys), self.__graph_key)
self.__graph_container[self.__graph_key].block(False)
class EvalCommand(QtWidgets.QUndoCommand): class EvalCommand(QtWidgets.QUndoCommand):

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import sys import os
from functools import lru_cache import urllib.request
from os import getenv, stat from os import getenv, stat
from os.path import exists from os.path import exists
import hashlib import hashlib
@ -9,8 +9,7 @@ import subprocess
from datetime import datetime from datetime import datetime
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from urllib.error import HTTPError
import requests
from numpy import linspace from numpy import linspace
from scipy.interpolate import interp1d from scipy.interpolate import interp1d
@ -59,13 +58,13 @@ class RdBuCMap:
elif val < self.min: elif val < self.min:
col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1]) col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1])
else: else:
col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3))) col = QtGui.QColor.fromRgb(*(int(self.spline[i](val)) for i in range(3)))
return col return col
class UpdateDialog(QtWidgets.QDialog): class UpdateDialog(QtWidgets.QDialog):
startDownload = QtCore.pyqtSignal(list) startDownload = QtCore.pyqtSignal(tuple)
def __init__(self, filename: str = None, parent=None): def __init__(self, filename: str = None, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
@ -75,7 +74,6 @@ class UpdateDialog(QtWidgets.QDialog):
filename = getenv('APPIMAGE') filename = getenv('APPIMAGE')
self._appfile = filename self._appfile = filename
self.success = False
self.updater = Updater() self.updater = Updater()
self.thread = QtCore.QThread(self) self.thread = QtCore.QThread(self)
@ -116,28 +114,28 @@ class UpdateDialog(QtWidgets.QDialog):
# Download zsync file of latest Appimage, look for SHA-1 hash and compare with hash of AppImage # Download zsync file of latest Appimage, look for SHA-1 hash and compare with hash of AppImage
is_updateble, m_time_file, m_time_zsync = self.updater.get_update_information(filename) is_updateble, m_time_file, m_time_zsync = self.updater.get_update_information(filename)
if m_time_zsync is None: label_text = ''
label_text = '<p>Retrieval of version information failed.</p>' \
'<p>Please try later (or complain to people that it does not work).</p>' if is_updateble is None:
label_text += '<p>Could not determine if this version is newer, please update manually (if necessary).</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Close dialog_bttns = QtWidgets.QDialogButtonBox.Close
elif is_updateble:
label_text += '<p>Newer version available. Press Ok to download new version, Cancel to ignore.</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
else: else:
label_text = f'<p>Date of most recent AppImage: {m_time_zsync.strftime("%d %B %Y %H:%M")}</p>' label_text += '<p>Version may be already up-to-date.</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Close
if m_time_file is None: if m_time_zsync is None:
label_text += 'No AppImage file found, press Ok to download latest version.' label_text += '<p>Creation date of remote version is unknown.</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Close else:
else: label_text += f'<p>Date of most recent AppImage: {m_time_zsync.strftime("%d %B %Y %H:%M")}</p>'
label_text += f'<p>Date of used AppImage: {m_time_file.strftime("%d %B %Y %H:%M")}</p>'
if is_updateble is None: if m_time_file is None:
self.status.setText('Could not determine if this version is newer, please update manually (if necessary).') label_text += 'No AppImage file found, press Ok to download latest version.'
dialog_bttns = QtWidgets.QDialogButtonBox.Close dialog_bttns = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Close
elif is_updateble: else:
self.status.setText(f'<p>Newer version available. Press Ok to download new version, Cancel to ignore.') label_text += f'<p>Date of used AppImage: {m_time_file.strftime("%d %B %Y %H:%M")}</p>'
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel
else:
self.status.setText(f'Version may be already up-to-date.')
dialog_bttns = QtWidgets.QDialogButtonBox.Close
self.label.setText(label_text) self.label.setText(label_text)
self.dialog_button.setStandardButtons(dialog_bttns) self.dialog_button.setStandardButtons(dialog_bttns)
@ -145,24 +143,20 @@ class UpdateDialog(QtWidgets.QDialog):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def update_appimage(self): def update_appimage(self):
if self._appfile is None: if self._appfile is None:
args = [self.updater.zsync_url] args = (self.updater.zsync_url,)
else: else:
# this breaks the download for some reason # this breaks the download for some reason
args = ['-i', self._appfile, self.updater.zsync_url] args = (self.updater.zsync_url, self._appfile)
args = [self.updater.zsync_url]
self.dialog_button.setEnabled(False) self.dialog_button.setEnabled(False)
self.startDownload.emit(args) self.startDownload.emit(args)
self.status.show() self.status.show()
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot(int, str)
def finish_update(self, retcode: int): def finish_update(self, retcode: int, file_loc: str):
# print('finished with', retcode)
self.success = retcode == 0
if retcode == 0: if retcode == 0:
self.status.setText('Download complete.') self.status.setText(f'Download complete.New AppImage lies in <p><em>{file_loc}</em>.</p>')
else: else:
self.status.setText(f'Download failed :( with return code {retcode}.') self.status.setText(f'Download failed :( with return code {retcode}.')
self.dialog_button.setStandardButtons(QtWidgets.QDialogButtonBox.Close) self.dialog_button.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
@ -172,76 +166,85 @@ class UpdateDialog(QtWidgets.QDialog):
self.thread.quit() self.thread.quit()
self.thread.wait() self.thread.wait()
if self.success:
appname = self.updater.get_zsync()[2]
if self._appfile is not None:
appimage_path = appname
old_version = Path(self._appfile).rename(self._appfile+'.old')
appimage_path = Path(appimage_path).replace(self._appfile)
else:
appimage_path = Path().cwd() / appname
# rename to version-agnostic name
appimage_path = appimage_path.rename('NMReval.AppImage')
appimage_path.chmod(appimage_path.stat().st_mode | 73) # 73 = 0o111 = a+x
_ = QtWidgets.QMessageBox.information(self, 'Complete',
f'New AppImage available at<br>{appimage_path}')
super().closeEvent(evt) super().closeEvent(evt)
class Downloader(QtCore.QObject): class Downloader(QtCore.QObject):
started = QtCore.pyqtSignal() started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal(int) finished = QtCore.pyqtSignal(int, str)
progressChanged = QtCore.pyqtSignal(str) progressChanged = QtCore.pyqtSignal(str)
@QtCore.pyqtSlot(list) @QtCore.pyqtSlot(tuple)
def run_download(self, args: list[str]): def run_download(self, args: tuple[str]):
logger.info(f'Download with args {args}') status = 0
process = subprocess.Popen(['zsync'] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True) appimage_location = args[0][:-6]
while True: logger.info(f'Download {appimage_location}')
nextline = process.stdout.readline().strip() if len(args) == 2:
if nextline: new_file = Path(args[1])
self.progressChanged.emit(nextline) else:
new_file = Path.home() / 'Downloads' / 'NMReval-latest-x86_64.AppImage'
# line = process.stderr.readline().strip() if new_file.exists():
os.rename(new_file, new_file.with_suffix('.AppImage.old'))
if process.poll() is not None: try:
break with urllib.request.urlopen(appimage_location) as response:
with new_file.open('wb') as f:
f.write(response.read())
self.finished.emit(process.returncode) new_file.chmod(0o755)
except HTTPError as e:
logger.exception(f'Download failed with {e}')
status = 3
except Exception as e:
logger.exception(f'Download failed with {e.args}')
status = 1
if status != 0:
logger.warning('Download failed, restore previous AppImage')
try:
os.rename(new_file.with_suffix('.AppImage.old'), new_file)
except FileNotFoundError:
pass
# zsync does not support https
self.finished.emit(status, str(new_file))
class Updater: class Updater:
host = 'mirror.infra.pkm' host = 'gitea.pkm.physik.tu-darmstadt.de/api/packages/IPKM/generic/NMReval/latest/'
bucket = 'nmreval'
version = 'NMReval-latest-x86_64' version = 'NMReval-latest-x86_64'
@property @property
def zsync_url(self): def zsync_url(self):
return f'http://{Updater.host}/{Updater.bucket}/{Updater.version}.AppImage.zsync' return f'https://{Updater.host}/{Updater.version}.AppImage.zsync'
@staticmethod @staticmethod
@lru_cache(3)
def get_zsync(): def get_zsync():
url_zsync = f'http://{Updater.host}/{Updater.bucket}/{Updater.version}.AppImage.zsync' url_zsync = f'https://{Updater.host}/{Updater.version}.AppImage.zsync'
m_time_zsync = None m_time_zsync = None
checksum_zsync = None checksum_zsync = None
zsync_file = None zsync_file = None
filename = None filename = None
try: try:
response = requests.get(url_zsync) with urllib.request.urlopen(url_zsync) as response:
if response.status_code == requests.codes['\o/']: zsync_file = response.read()
zsync_file = response.content except HTTPError as e:
logger.error(f'Request for zsync returned code {e}')
except Exception as e: except Exception as e:
pass logger.exception(f'Download of zsync failed with exception {e.args}')
if zsync_file is not None: if zsync_file is not None:
for line in zsync_file.split(b'\n'): for line in zsync_file.split(b'\n'):
try: try:
kw, val = line.split(b': ') kw, val = line.split(b': ')
time_string = str(val, encoding='utf-8')
time_format = '%a, %d %b %Y %H:%M:%S %z'
if kw == b'MTime': if kw == b'MTime':
m_time_zsync = datetime.strptime(str(val, encoding='utf-8'), '%a, %d %b %Y %H:%M:%S %z').astimezone(None) try:
m_time_zsync = datetime.strptime(time_string, time_format).astimezone(None)
except ValueError:
logger.warning(f'zsync time "{time_string}" does not match "{time_format}"')
elif kw == b'SHA-1': elif kw == b'SHA-1':
checksum_zsync = str(val, encoding='utf-8') checksum_zsync = str(val, encoding='utf-8')
elif kw == b'Filename': elif kw == b'Filename':
@ -265,6 +268,8 @@ class Updater:
m_time_file = datetime.fromtimestamp(stat_mtime).replace(microsecond=0) m_time_file = datetime.fromtimestamp(stat_mtime).replace(microsecond=0)
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
checksum_file = hashlib.sha1(f.read()).hexdigest() checksum_file = hashlib.sha1(f.read()).hexdigest()
if checksum_file is None:
logger.warning('No checksum for AppImage calculated')
return m_time_file, checksum_file return m_time_file, checksum_file
@ -273,30 +278,10 @@ class Updater:
m_time_zsync, checksum_zsync, appname = Updater.get_zsync() m_time_zsync, checksum_zsync, appname = Updater.get_zsync()
m_time_file, checksum_file = Updater.get_appimage_info(filename) m_time_file, checksum_file = Updater.get_appimage_info(filename)
logger.info(f'zsync information {m_time_zsync}, {checksum_zsync}, {appname}') logger.debug(f'zsync information {m_time_zsync}, {checksum_zsync}, {appname}')
logger.info(f'file information {m_time_file}, {checksum_zsync}') logger.debug(f'file information {m_time_file}, {checksum_file}')
if not ((checksum_file is not None) and (checksum_zsync is not None)): if not ((checksum_file is not None) and (checksum_zsync is not None)):
return None, m_time_file, m_time_zsync return None, m_time_file, m_time_zsync
else: else:
return checksum_file != checksum_zsync, m_time_file, m_time_zsync return checksum_file != checksum_zsync, m_time_file, m_time_zsync
def open_bug_report():
form_entries = {
'description': 'Please state the nature of the medical emergency.',
'title': 'Everything is awesome?',
'assign[0]': 'dominik',
'subscribers[0]': 'dominik',
'tag': 'nmreval',
'priority': 'normal',
'status': 'open',
}
full_url = 'https://chaos3.fkp.physik.tu-darmstadt.de/maniphest/task/edit/?'
for k, v in form_entries.items():
full_url += f'{k}={v}&'
full_url.replace(' ', '+')
import webbrowser
webbrowser.open(full_url)

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import datetime
import os import os
import pathlib
import re import re
from pathlib import Path from pathlib import Path
@ -9,18 +9,23 @@ from numpy import geomspace, linspace
from pyqtgraph import ViewBox from pyqtgraph import ViewBox
from nmreval.configs import * from nmreval.configs import *
from nmreval.lib.logger import logger
from nmreval.io.sessionwriter import NMRWriter
from .management import UpperManagement from .management import UpperManagement
from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets from ..Qt import QtGui, QtPrintSupport
from ..data.shift_graphs import QShift from ..data.shift_graphs import QShift
from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog from ..data.signaledit import QPreviewDialog, QBaselineDialog
from ..fit.result import QFitResult from ..dsc.glass_dialog import TgCalculator
from ..fit.result import FitExtension, QFitResult
from ..graphs.graphwindow import QGraphWindow from ..graphs.graphwindow import QGraphWindow
from ..graphs.movedialog import QMover from ..graphs.movedialog import QMover
from ..io.fcbatchreader import QFCReader from ..io.fcbatchreader import QFCReader
from ..io.filedialog import * from ..io.filedialog import *
from ..lib import get_icon, make_action_icons from ..lib.iconloading import make_action_icons, get_icon
from ..lib.pg_objects import RegionItem from ..lib.pg_objects import RegionItem
from ..lib.starter import make_starter
from ..math.binning import BinningWindow
from ..math.evaluation import QEvalDialog from ..math.evaluation import QEvalDialog
from ..math.interpol import InterpolDialog from ..math.interpol import InterpolDialog
from ..math.mean_dialog import QMeanTimes from ..math.mean_dialog import QMeanTimes
@ -28,7 +33,7 @@ from ..math.smooth import QSmooth
from ..nmr.coupling_calc import QCoupCalcDialog from ..nmr.coupling_calc import QCoupCalcDialog
from ..nmr.t1_from_tau import QRelaxCalc from ..nmr.t1_from_tau import QRelaxCalc
from .._py.basewindow import Ui_BaseWindow from .._py.basewindow import Ui_BaseWindow
from ..lib.utils import UpdateDialog, open_bug_report, Updater from ..lib.utils import UpdateDialog, Updater
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@ -53,6 +58,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.fitpreview = [] self.fitpreview = []
self._fit_plot_id = None self._fit_plot_id = None
self.savefitdialog = None self.savefitdialog = None
self._tg_dialog = None
self.fitresult_dialog = None
self.eval = None self.eval = None
self.editor = None self.editor = None
@ -63,7 +70,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self._block_window_change = False self._block_window_change = False
self.fname = None self.fname = None
self.tim = QtCore.QTimer()
self.settings = QtCore.QSettings('NMREVal', 'settings') self.settings = QtCore.QSettings('NMREVal', 'settings')
self._init_gui() self._init_gui()
@ -73,6 +79,18 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if Updater.get_update_information(os.getenv('APPIMAGE'))[0]: if Updater.get_update_information(os.getenv('APPIMAGE'))[0]:
self.look_for_update() self.look_for_update()
self.check_for_backup()
self.__timer = QtCore.QTimer()
self.__backup_path = config_paths() / f'{datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr'
self.__timer.start(3*60*1000) # every three minutes
self.__timer.timeout.connect(self._autosave)
self.fit_timer = QtCore.QTimer()
self.fit_timer.setInterval(500)
self.fit_timer.timeout.connect(
lambda: self.status.setText(f'Fit running... ({self.management.fitter.step} evaluations)'))
def _init_gui(self): def _init_gui(self):
self.setupUi(self) self.setupUi(self)
make_action_icons(self) make_action_icons(self)
@ -103,7 +121,10 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.mousepos = QtWidgets.QLabel('') self.mousepos = QtWidgets.QLabel('')
self.status = QtWidgets.QLabel('') self.status = QtWidgets.QLabel('')
# noinspection PyUnresolvedReferences
self.statusBar.addWidget(self.status) self.statusBar.addWidget(self.status)
# noinspection PyUnresolvedReferences
self.statusBar.addWidget(self.mousepos) self.statusBar.addWidget(self.mousepos)
self.fitregion = RegionItem() self.fitregion = RegionItem()
@ -114,7 +135,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.datawidget.management = self.management self.datawidget.management = self.management
self.integralwidget.management = self.management self.integralwidget.management = self.management
self.drawingswidget.graphs = self.management.graphs # self.drawingswidget.graphs = self.management.graphs
self.ac_group = QtWidgets.QActionGroup(self) self.ac_group = QtWidgets.QActionGroup(self)
self.ac_group.addAction(self.action_lm_fit) self.ac_group.addAction(self.action_lm_fit)
@ -139,20 +160,23 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.actionUndo.setIcon(icon) self.actionUndo.setIcon(icon)
self.menuData.insertAction(self.actionRedo, self.actionUndo) self.menuData.insertAction(self.actionRedo, self.actionUndo)
# # self.actionSave.triggered.connect(lambda: self.management.save('/autohome/dominik/nmreval/testdata/test.nmr', ''))
# self.actionSave.triggered.connect(self.save)
self.action_save_fit_parameter.triggered.connect(self.save_fit_parameter) self.action_save_fit_parameter.triggered.connect(self.save_fit_parameter)
self.ac_group2.triggered.connect(self.change_fit_limits) self.ac_group2.triggered.connect(self.change_fit_limits)
self.t1action.triggered.connect(lambda: self._show_tab('t1_temp')) self.t1action.triggered.connect(lambda: self._show_tab('t1_temp'))
self.action_edit.triggered.connect(lambda: self._show_tab('signal')) self.action_edit.triggered.connect(self.do_preview)
self.actionPick_position.triggered.connect(lambda: self._show_tab('pick')) self.actionPick_position.triggered.connect(lambda: self._show_tab('pick'))
self.actionIntegration.triggered.connect(lambda: self._show_tab('integrate')) self.actionIntegration.triggered.connect(lambda: self._show_tab('integrate'))
self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit')) self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit'))
self.action_draw_object.triggered.connect(lambda: self._show_tab('drawing'))
self.action_new_set.triggered.connect(self.management.create_empty) self.action_new_set.triggered.connect(self.management.create_empty)
self.actionDelete_window.triggered.connect(self.management.delete_sets)
self.actionCascade_windows.triggered.connect(self.area.cascadeSubWindows)
self.actionTile.triggered.connect(self.area.tileSubWindows)
self.actionTileHorizontal.triggered.connect(self.area.tileSubWindowsHorizontally)
self.actionTileVertical.triggered.connect(self.area.tileSubWindowsVertically)
self.datawidget.keyChanged.connect(self.management.change_keys) self.datawidget.keyChanged.connect(self.management.change_keys)
self.datawidget.tree.deleteItem.connect(self.management.delete_sets) self.datawidget.tree.deleteItem.connect(self.management.delete_sets)
self.datawidget.tree.moveItem.connect(self.management.move_sets) self.datawidget.tree.moveItem.connect(self.management.move_sets)
@ -164,8 +188,10 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.datawidget.startShowProperty.connect(self.management.get_properties) self.datawidget.startShowProperty.connect(self.management.get_properties)
self.datawidget.propertyChanged.connect(self.management.update_property) self.datawidget.propertyChanged.connect(self.management.update_property)
self.datawidget.tree.saveFits.connect(self.save_fit_parameter) self.datawidget.tree.saveFits.connect(self.save_fit_parameter)
self.datawidget.tree.extendFits.connect(self.extend_fit)
self.management.newData.connect(self.show_new_data) self.management.newData[list, str].connect(self.show_new_data)
self.management.newData[list, str, bool].connect(self.show_new_data)
self.management.newGraph.connect(self.new_graph) self.management.newGraph.connect(self.new_graph)
self.management.dataChanged.connect(self.update_data) self.management.dataChanged.connect(self.update_data)
self.management.deleteData.connect(self.delete_data) self.management.deleteData.connect(self.delete_data)
@ -178,7 +204,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.fit_dialog._management = self.management self.fit_dialog._management = self.management
self.fit_dialog.preview_emit.connect(self.show_fit_preview) self.fit_dialog.preview_emit.connect(self.show_fit_preview)
self.fit_dialog.fitStartSig.connect(self.start_fit) self.fit_dialog.fitStartSig.connect(self.start_fit)
self.fit_dialog.abortFit.connect(lambda : self.management.stopFit.emit()) self.fit_dialog.abortFit.connect(lambda: self.management.stopFit.emit())
self.movedialog.moveData.connect(self.move_sets) self.movedialog.moveData.connect(self.move_sets)
self.movedialog.copyData.connect(self.management.copy_sets) self.movedialog.copyData.connect(self.management.copy_sets)
@ -188,7 +214,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.t1tauwidget.newData.connect(self.management.add_new_data) self.t1tauwidget.newData.connect(self.management.add_new_data)
self.editsignalwidget.do_something.connect(self.management.apply) self.editsignalwidget.do_something.connect(self.management.apply)
self.editsignalwidget.preview_triggered.connect(self.do_preview) # self.editsignalwidget.preview_triggered.connect(self.do_preview)
self.action_sort_pts.triggered.connect(lambda: self.management.apply('sort', ())) self.action_sort_pts.triggered.connect(lambda: self.management.apply('sort', ()))
self.action_calc_eps_derivative.triggered.connect(self.management.bds_deriv) self.action_calc_eps_derivative.triggered.connect(self.management.bds_deriv)
@ -216,7 +242,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',))) self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',)))
self.action_cut.triggered.connect(lambda: self.management.cut()) self.action_cut.triggered.connect(lambda: self.management.cut())
self.actionConcatenate_sets.triggered.connect(lambda : self.management.cat()) self.actionConcatenate_sets.triggered.connect(lambda: self.management.cat())
self.management.graphs.valueChanged.connect(self.update_graph_list)
@QtCore.pyqtSlot(name='on_action_open_triggered') @QtCore.pyqtSlot(name='on_action_open_triggered')
def open(self): def open(self):
@ -241,6 +269,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(name='on_actionOpen_FC_triggered') @QtCore.pyqtSlot(name='on_actionOpen_FC_triggered')
def read_fc(self): def read_fc(self):
reader = QFCReader(path=self.path, parent=self) reader = QFCReader(path=self.path, parent=self)
reader.add_graphs(self.management.graphs.list())
reader.data_read.connect(self.management.add_new_data) reader.data_read.connect(self.management.add_new_data)
reader.exec() reader.exec()
@ -261,10 +290,11 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
selected_filter = save_dialog.selectedNameFilter() selected_filter = save_dialog.selectedNameFilter()
if savefile is not None: if savefile is not None:
self.path = savefile.parent
use_underscore = save_dialog.checkBox.isChecked() use_underscore = save_dialog.checkBox.isChecked()
self.management.save(savefile, selected_filter, strip_spaces=use_underscore) self.management.save(savefile, selected_filter, strip_spaces=use_underscore)
param_outfile = re.sub('[_\s-]?<label>[_\s-]?', '', savefile.stem) param_outfile = re.sub(r'[_\s-]?<label>[_\s-]?', '', savefile.stem)
bad_character = r'/*<>\|:"' bad_character = r'/*<>\|:"'
for c in bad_character: for c in bad_character:
@ -306,10 +336,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
return w.id return w.id
@QtCore.pyqtSlot(list, str) @QtCore.pyqtSlot(list, str)
def show_new_data(self, sets: list, graph: str): @QtCore.pyqtSlot(list, str, bool)
def show_new_data(self, sets: list, graph: str, skip_change: bool = False):
if len(sets) == 0: if len(sets) == 0:
return return
prev_graph = ''
if skip_change:
prev_graph = self.management.current_graph
if graph == '': if graph == '':
graph = self.new_graph() graph = self.new_graph()
@ -318,22 +353,21 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
for idd in sets: for idd in sets:
new_item = self.management[idd] new_item = self.management[idd]
self.datawidget.blockSignals(True) self.datawidget.blockSignals(True)
self.datawidget.add_item(new_item.id, new_item.name, graph) self.datawidget.add_item(new_item.id, new_item.name, new_item.value, graph, update=False)
self.datawidget.blockSignals(False) self.datawidget.blockSignals(False)
self.datawidget.tree.update_indexes()
if graph == self.fit_dialog.connected_figure: # if graph == self.fit_dialog.connected_figure:
self.fit_dialog.load(self.management.graphs.active(graph)) # self.fit_dialog.load(self.management.graphs.active(graph))
if skip_change:
self.area.setActiveSubWidget(prev_graph)
if self.valuewidget.isVisible(): if self.valuewidget.isVisible():
self.valuewidget(self.management.graphs.tree()) self.valuewidget(self.management.graphs.tree())
@QtCore.pyqtSlot(name='on_actionDelete_window_triggered')
def delete_windows(self):
self.management.delete_sets()
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def remove_graph(self, gid: str): def remove_graph(self, gid: str):
self.datawidget.remove_item(gid) self.datawidget.remove_item([gid])
val_figure = self.valuewidget.connected_figure val_figure = self.valuewidget.connected_figure
self.valuewidget.remove_graph() self.valuewidget.remove_graph()
@ -346,10 +380,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if wdgt == self.current_graph_widget: if wdgt == self.current_graph_widget:
if self.ptsselectwidget.connected_figure == gid: if self.ptsselectwidget.connected_figure == gid:
self.ptsselectwidget.connected_figure = None self.ptsselectwidget.connected_figure = None
for line in self.ptsselectwidget.pts_lines:
self.current_graph_widget.remove_external(line)
self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget)) self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget))
if self.t1tauwidget.connected_figure == gid: if self.t1tauwidget.connected_figure == gid:
self.t1tauwidget.connected_figure = None self.t1tauwidget.connected_figure = None
self.current_graph_widget.add_external(self.t1tauwidget.min_pos)
self.current_graph_widget.add_external(self.t1tauwidget.parabola)
self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget)) self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget))
if self.fit_dialog.connected_figure == gid: if self.fit_dialog.connected_figure == gid:
@ -358,12 +397,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.current_graph_widget.remove_external(item) self.current_graph_widget.remove_external(item)
if val_figure == gid: if val_figure == gid:
self.current_graph_widget.remove_external(self.valuewidget.selection_real)
self.current_graph_widget.remove_external(self.valuewidget.selection_imag)
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
self.current_graph_widget.enable_picking(False) self.current_graph_widget.enable_picking(False)
self.current_graph_widget = None self.current_graph_widget = None
self.management.current_graph = '' self.management.current_graph = None
self.current_plotitem = None self.current_plotitem = None
wdgt.setParent(None) wdgt.setParent(None)
@ -374,7 +415,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
break break
if w is not None: if w is not None:
self.area.removeSubWindow(w) self.area.removeSubWindow(w)
w.close() w.close()
@ -387,6 +427,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
if self.area.subWindowList(): if self.area.subWindowList():
self.area.activateNextSubWindow() self.area.activateNextSubWindow()
@QtCore.pyqtSlot()
def update_graph_list(self):
graph_list = self.management.graphs.list()
self.t1tauwidget.set_graphs(graph_list)
self.ptsselectwidget.set_graphs(graph_list)
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def set_graph(self, key: str): def set_graph(self, key: str):
w = self.management.graphs[key] w = self.management.graphs[key]
@ -404,7 +450,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.datawidget.blockSignals(False) self.datawidget.blockSignals(False)
w.mousePositionChanged.connect(self.mousemoved) w.mousePositionChanged.connect(self.mousemoved)
w.aboutToClose.connect(self.delete_windows) w.aboutToClose.connect(self.management.delete_sets)
w.positionClicked.connect(self.point_selected) w.positionClicked.connect(self.point_selected)
w.show() w.show()
@ -415,42 +461,36 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(QtWidgets.QMdiSubWindow, name='on_area_subWindowActivated') @QtCore.pyqtSlot(QtWidgets.QMdiSubWindow, name='on_area_subWindowActivated')
def change_window(self, wd): def change_window(self, wd):
""" Called every time focus moves from or to a subwindow. Returns None if current focus is not on a subwindow""" """ Called every time focus moves from or to a subwindow. Returns None if current focus is not on a subwindow"""
if wd is not None: if wd is None:
if self.current_graph_widget is not None: return
self.current_graph_widget.closable = True
if self.ptsselectwidget.isVisible(): if self.current_graph_widget is not None:
self._select_ptswidget(False, False, False) self.current_graph_widget.closable = True
if self.fit_dialog.isVisible():
self._select_fitwidget(False, False)
self.current_graph_widget = wd.widget()
self.management.current_graph = wd.widget().id
self.current_plotitem = self.current_graph_widget.graphic
self.change_mouse_mode(self.actionMouse_behaviour.isChecked())
pick = False
block = False
if self.ptsselectwidget.isVisible(): if self.ptsselectwidget.isVisible():
pick, block = self._select_ptswidget(True, pick, block) self._select_ptswidget(False, False, False)
if self.fit_dialog.isVisible(): if self.fit_dialog.isVisible():
block = self._select_fitwidget(True, block) self._select_fitwidget(False, False)
self._set_pick_block(pick, block) self.current_graph_widget = wd.widget()
self.management.current_graph = wd.widget().id
self.current_plotitem = self.current_graph_widget.graphic
self.datawidget.tree.blockSignals(True) self.change_mouse_mode(self.actionMouse_behaviour.isChecked())
self.datawidget.tree.highlight(self.management.current_graph)
self.datawidget.tree.blockSignals(False)
@QtCore.pyqtSlot(name='on_actionCascade_windows_triggered') pick = False
@QtCore.pyqtSlot(name='on_actionTile_triggered') block = False
def change_window_size(self): if self.ptsselectwidget.isVisible():
if self.sender() == self.actionCascade_windows: pick, block = self._select_ptswidget(True, pick, block)
self.area.cascadeSubWindows() if self.fit_dialog.isVisible():
elif self.sender() == self.actionTile: block = self._select_fitwidget(True, block)
self.area.tileSubWindows()
self._set_pick_block(pick, block)
self.datawidget.tree.blockSignals(True)
self.datawidget.tree.highlight(self.management.current_graph)
self.datawidget.tree.blockSignals(False)
@QtCore.pyqtSlot(name='on_actionChange_datatypes_triggered') @QtCore.pyqtSlot(name='on_actionChange_datatypes_triggered')
def type_change_dialog(self): def type_change_dialog(self):
@ -474,7 +514,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
'signal': (self.editsignalwidget, 'Signals'), 'signal': (self.editsignalwidget, 'Signals'),
'pick': (self.ptsselectwidget, 'Pick points'), 'pick': (self.ptsselectwidget, 'Pick points'),
'fit': (self.fit_dialog, 'Fit'), 'fit': (self.fit_dialog, 'Fit'),
'drawing': (self.drawingswidget, 'Draw'),
'integrate': (self.integralwidget, 'Integrate'), 'integrate': (self.integralwidget, 'Integrate'),
}[mode] }[mode]
@ -510,7 +549,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self._select_valuewidget(widget == self.valuewidget) self._select_valuewidget(widget == self.valuewidget)
pick_required, block_window = self._select_t1tauwidget(widget == self.t1tauwidget, pick_required, block_window) pick_required, block_window = self._select_t1tauwidget(widget == self.t1tauwidget, pick_required, block_window)
block_window = self._select_fitwidget(widget == self.fit_dialog, block_window) block_window = self._select_fitwidget(widget == self.fit_dialog, block_window)
self._select_drawingswidget(widget == self.drawingswidget)
pick_required = self._select_integralwidget(widget == self.integralwidget, pick_required, block_window) pick_required = self._select_integralwidget(widget == self.integralwidget, pick_required, block_window)
self._set_pick_block(pick_required, block_window) self._set_pick_block(pick_required, block_window)
@ -543,8 +581,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.management.graphs[current_graph].add_external(self.valuewidget.selection_imag) self.management.graphs[current_graph].add_external(self.valuewidget.selection_imag)
else: else:
if self.valuewidget.connected_figure is not None: if self.valuewidget.connected_figure is not None:
self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_real) conn_fig = self.valuewidget.connected_figure
self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_imag) self.management.graphs[conn_fig].remove_external(self.valuewidget.selection_real)
self.management.graphs[conn_fig].remove_external(self.valuewidget.selection_imag)
def _select_integralwidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]: def _select_integralwidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
if self.current_graph_widget is None: if self.current_graph_widget is None:
@ -598,17 +637,10 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
return pick_required, block_window return pick_required, block_window
def _select_drawingswidget(self, onoff):
if onoff:
if self.drawingswidget.graphs is None:
self.drawingswidget.graphs = self.management.graphs
self.drawingswidget.update_tree()
else:
self.drawingswidget.clear()
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def get_data(self, key: str): def get_data(self, key: str):
self.sender().set_data(self.management[key]) if hasattr(self.sender(), 'set_data'):
self.sender().set_data(self.management[key])
@QtCore.pyqtSlot(name='on_actionCalculateT1_triggered') @QtCore.pyqtSlot(name='on_actionCalculateT1_triggered')
def show_t1calc_dialog(self): def show_t1calc_dialog(self):
@ -682,6 +714,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.eval.exec() self.eval.exec()
@QtCore.pyqtSlot(name='on_actionBinning_triggered')
def open_binning(self):
dialog = BinningWindow(self)
res = dialog.exec()
if res:
digits = float(dialog.spinbox.text())
self.management.binning(digits)
@QtCore.pyqtSlot(name='on_actionDerivation_triggered') @QtCore.pyqtSlot(name='on_actionDerivation_triggered')
# @QtCore.pyqtSlot(name='on_actionIntegration_triggered') # @QtCore.pyqtSlot(name='on_actionIntegration_triggered')
@QtCore.pyqtSlot(name='on_actionFilon_triggered') @QtCore.pyqtSlot(name='on_actionFilon_triggered')
@ -718,10 +758,11 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.datawidget.set_name(sid, self.management[sid].name) self.datawidget.set_name(sid, self.management[sid].name)
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(list)
def delete_data(self, sid): def delete_data(self, sid: list[str]):
if self.valuewidget.shown_set == sid: for key in sid:
self.tabWidget.setCurrentIndex(0) if self.valuewidget.shown_set == key:
self.tabWidget.setCurrentIndex(0)
self.datawidget.remove_item(sid) self.datawidget.remove_item(sid)
@ -743,29 +784,22 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
editor.finished.connect(self.management.apply) editor.finished.connect(self.management.apply)
editor.exec() editor.exec()
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot()
def do_preview(self, mode): def do_preview(self):
dialog = QPreviewDialog(self)
if mode == 'ap': success = True
dialog = QApodDialog(parent=self)
elif mode == 'ph':
dialog = QPhasedialog(parent=self)
else:
raise ValueError('Unknown preview mode %s' % str(mode))
dialog.setRange(*self.current_graph_widget.ranges, self.current_graph_widget.log)
for sid in self.current_graph_widget.active: for sid in self.current_graph_widget.active:
data_mode = self.management[sid].mode data_mode = self.management[sid].mode
tobeadded = False if data_mode in ('fid', 'spectrum'):
if (data_mode == 'fid') or (data_mode == 'spectrum' and mode == 'ph'): success = dialog.add_data(self.management[sid].data)
tobeadded = True
if tobeadded: if not success:
dialog.add_data(*self.management.get_data(sid, xy_only=True)) break
if dialog.exec() == QtWidgets.QDialog.Accepted: if success and dialog.exec() == QtWidgets.QDialog.Accepted:
self.management.apply(mode, dialog.get_value()) self.management.edit_signals(dialog.get_value())
@QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered') @QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered')
def move_sets_dialog(self): def move_sets_dialog(self):
@ -816,6 +850,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
except KeyError: except KeyError:
ret_val = None ret_val = None
# noinspection PyUnresolvedReferences
self.sender().receive_data(ret_val) self.sender().receive_data(ret_val)
return ret_val return ret_val
@ -831,7 +866,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.t1tauwidget.t1min_picked(pos) self.t1tauwidget.t1min_picked(pos)
elif w == self.integralwidget: elif w == self.integralwidget:
region, integral_plot = self.integralwidget.add(pos) region, integral_plot = self.integralwidget.add(pos)
self.current_graph_widget.add_external(region) self.current_graph_widget.add_external(region)
self.current_graph_widget.add_external(integral_plot) self.current_graph_widget.add_external(integral_plot)
@ -875,12 +910,18 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.action_odr_fit: 'odr' self.action_odr_fit: 'odr'
}[self.ac_group.checkedAction()] }[self.ac_group.checkedAction()]
self.fit_dialog.fit_button.setEnabled(False) fit_is_ready = self.management.prepare_fit(parameter, links, fit_options)
self.management.start_fit(parameter, links, fit_options) if fit_is_ready:
self.management.start_fit()
self.fit_dialog.fit_button.setEnabled(False)
self.status.setText('Fit running...'.format(self.management.fitter.step))
self.fit_timer.start(500)
@QtCore.pyqtSlot(dict, int, bool) @QtCore.pyqtSlot(dict, int, bool)
def show_fit_preview(self, funcs: dict, num: int, show: bool): def show_fit_preview(self, funcs: dict, num: int, show: bool):
if self.fit_dialog.connected_figure is None: if not self.fit_dialog.connected_figure:
logger.warning(f'Fit dialog is not connected graph: Fit {self.fit_dialog.connected_figure}, '
f'current graph: {self.management.current_graph} ({self.management.current_graph in self.management.graphs})')
return return
g = self.management.graphs[self.fit_dialog.connected_figure] g = self.management.graphs[self.fit_dialog.connected_figure]
@ -900,17 +941,24 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(list) @QtCore.pyqtSlot(list)
def show_fit_results(self, results: list): def show_fit_results(self, results: list):
self.fit_dialog.fit_button.setEnabled(True) self.fit_dialog.fit_button.setEnabled(True)
self.fit_timer.stop()
self.status.setText('')
if results: if results:
res_dialog = QFitResult(results, self.management, parent=self) if self.fitresult_dialog is None:
res_dialog.add_graphs(self.management.graphs.list()) self.fitresult_dialog = QFitResult(results, self.management, parent=self)
res_dialog.closed.connect(self.accepts_fit) self.fitresult_dialog.add_graphs(self.management.graphs.list())
res_dialog.redoFit.connect(self.management.redo_fits) self.fitresult_dialog.closed.connect(self.accepts_fit)
res_dialog.show() self.fitresult_dialog.redoFit.connect(self.management.redo_fits)
else:
self.fitresult_dialog(results)
self.fitresult_dialog.add_graphs(self.management.graphs.list())
self.fitresult_dialog.show()
@QtCore.pyqtSlot(dict, list, str, bool, dict) @QtCore.pyqtSlot(dict, list, str, bool, bool, list)
def accepts_fit(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: dict) -> None: def accepts_fit(self, res: dict, opts: list, param_graph: str,
show_fit: bool, parts: bool, extrapolate: list) -> None:
self.fit_dialog.set_parameter(res) self.fit_dialog.set_parameter(res)
self.management.make_fits(res, opts, param_graph, show_fit, parts) self.management.make_fits(res, opts, param_graph, show_fit, parts, extrapolate)
@QtCore.pyqtSlot(name='on_actionFunction_editor_triggered') @QtCore.pyqtSlot(name='on_actionFunction_editor_triggered')
def edit_models(self): def edit_models(self):
@ -922,6 +970,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
self.editor.setWindowModality(QtCore.Qt.ApplicationModal) self.editor.setWindowModality(QtCore.Qt.ApplicationModal)
self.editor.show() self.editor.show()
@QtCore.pyqtSlot(list)
def extend_fit(self, sets: list):
w = FitExtension(self)
res = w.exec()
if res:
p = w.values
x = linspace(p[0], p[1], num=p[2])
self.management.extend_fits(sets, x)
@QtCore.pyqtSlot(name='on_action_create_fit_function_triggered') @QtCore.pyqtSlot(name='on_action_create_fit_function_triggered')
def open_fitmodel_wizard(self): def open_fitmodel_wizard(self):
from ..fit.function_creation_dialog import QUserFitCreator from ..fit.function_creation_dialog import QUserFitCreator
@ -931,7 +988,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
helper.show() helper.show()
@QtCore.pyqtSlot(name='on_actionShift_triggered') @QtCore.pyqtSlot(name='on_actionShift_triggered')
def shift_dialog(self): def shift_dialog(self):
s = QShift(self) s = QShift(self)
@ -1010,6 +1066,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
def close(self): def close(self):
write_state({'History': {'recent path': str(self.path)}}) write_state({'History': {'recent path': str(self.path)}})
# remove backup file when closing
self.__backup_path.unlink(missing_ok=True)
super().close() super().close()
def read_state(self): def read_state(self):
@ -1033,3 +1092,56 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
QLog(parent=self).show() QLog(parent=self).show()
def _autosave(self):
# TODO better separate thread may it takes some time to save
self.status.setText('Autosave...')
success = NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path.with_suffix('.nmr.0'))
if success:
self.__backup_path.with_suffix('.nmr.0').rename(self.__backup_path)
self.status.setText('')
def check_for_backup(self):
backups = []
for filename in config_paths().glob('*.nmr'):
try:
backups.append((filename, datetime.datetime.strptime(filename.stem, "%Y-%m-%d_%H%M%S")))
except ValueError:
continue
if backups:
backup_by_date = sorted(backups, key=lambda x: x[1])
msg = QtWidgets.QMessageBox.information(
self, 'Backup found',
f'{len(backups)} backup files in directory {backup_by_date[-1][0].parent} found.\n\n'
f'Latest backup date: {backup_by_date[-1][1]}\n\n'
f'Press Ok to load, Cancel to delete backup, Close to do nothing.',
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Close
)
if msg == QtWidgets.QMessageBox.Ok:
self.management.load_files([str(backup_by_date[-1][0])])
backup_by_date[-1][0].unlink()
elif msg == QtWidgets.QMessageBox.Cancel:
backup_by_date[-1][0].unlink()
else:
pass
@QtCore.pyqtSlot(name='on_actionCreate_starter_triggered')
def create_starter(self):
make_starter(os.getenv('APPIMAGE'))
@QtCore.pyqtSlot(name='on_actionAbout_triggered')
def show_version(self):
from nmreval.version import __version__
QtWidgets.QMessageBox.about(self, 'Version', f'Build date of AppImage: {__version__}')
@QtCore.pyqtSlot(name='on_actionTNMH_model_triggered')
@QtCore.pyqtSlot(name='on_actionTNMH_triggered')
def show_tg_dialog(self):
if self._tg_dialog is None:
self._tg_dialog = TgCalculator(self.management, parent=self)
self._tg_dialog.newData.connect(self.management.addTg)
else:
self._tg_dialog()
self._tg_dialog.show()

View File

@ -4,6 +4,8 @@ import pathlib
import re import re
import uuid import uuid
import numpy as np
from nmreval.fit import data as fit_d from nmreval.fit import data as fit_d
from nmreval.fit.model import Model from nmreval.fit.model import Model
from nmreval.fit.result import FitResult from nmreval.fit.result import FitResult
@ -16,6 +18,7 @@ from nmreval.math.smooth import smooth
from nmreval.nmr.relaxation import Relaxation from nmreval.nmr.relaxation import Relaxation
from ..Qt import QtCore, QtWidgets from ..Qt import QtCore, QtWidgets
from ..lib import Relations
from ..lib.undos import * from ..lib.undos import *
from ..data.container import * from ..data.container import *
from ..io.filereaders import QFileReader from ..io.filereaders import QFileReader
@ -55,11 +58,18 @@ class GraphDict(OrderedDict):
def list(self): def list(self):
return [(k, v.title) for k, v in self.items()] return [(k, v.title) for k, v in self.items()]
def active(self, key: str): def active(self, key: str, return_val: str = 'both'):
if key: if not key:
return [(self._data[i].id, self._data[i].name) for i in self[key]]
else:
return [] return []
else:
if return_val == 'both':
return [(self._data[i].id, self._data[i].name) for i in self[key]]
elif return_val == 'id':
return [self._data[i].id for i in self[key]]
elif return_val == 'name':
return [self._data[i].name for i in self[key]]
else:
raise ValueError(f'return_val got wrong value {return_val!r}')
def current_sets(self, key: str): def current_sets(self, key: str):
if key: if key:
@ -72,8 +82,8 @@ class UpperManagement(QtCore.QObject):
newGraph = QtCore.pyqtSignal() newGraph = QtCore.pyqtSignal()
restoreGraph = QtCore.pyqtSignal(str) restoreGraph = QtCore.pyqtSignal(str)
deleteGraph = QtCore.pyqtSignal(str) deleteGraph = QtCore.pyqtSignal(str)
newData = QtCore.pyqtSignal(list, str) newData = QtCore.pyqtSignal([list, str], [list, str, bool])
deleteData = QtCore.pyqtSignal(str) deleteData = QtCore.pyqtSignal(list)
dataChanged = QtCore.pyqtSignal(str) dataChanged = QtCore.pyqtSignal(str)
fitFinished = QtCore.pyqtSignal(list) fitFinished = QtCore.pyqtSignal(list)
stopFit = QtCore.pyqtSignal() stopFit = QtCore.pyqtSignal()
@ -108,7 +118,7 @@ class UpperManagement(QtCore.QObject):
self.counter = 0 self.counter = 0
self.data = OrderedDict() self.data = OrderedDict()
self.window = window self.window = window
self.current_graph = '' self.current_graph = None
self.graphs = GraphDict(self.data) self.graphs = GraphDict(self.data)
self.namespace = None self.namespace = None
self.undostack = QtWidgets.QUndoStack() self.undostack = QtWidgets.QUndoStack()
@ -145,6 +155,10 @@ class UpperManagement(QtCore.QObject):
def active_sets(self): def active_sets(self):
return self.graphs.active(self.current_graph) return self.graphs.active(self.current_graph)
@property
def active_id(self):
return self.graphs.active(self.current_graph, return_val='id')
def get_attributes(self, graph_id: str, attr: str) -> dict[str, Any]: def get_attributes(self, graph_id: str, attr: str) -> dict[str, Any]:
return {self.data[i].id: getattr(self.data[i], attr) for i in self.graphs[graph_id].sets} return {self.data[i].id: getattr(self.data[i], attr) for i in self.graphs[graph_id].sets}
@ -178,7 +192,10 @@ class UpperManagement(QtCore.QObject):
graph.active = active graph.active = active
graph.listWidget.blockSignals(True) graph.listWidget.blockSignals(True)
for i, l in enumerate(g['in_legend']): for i, l in enumerate(g['in_legend']):
graph.listWidget.item(i).setCheckState(l) try:
graph.listWidget.item(i).setCheckState(QtCore.Qt.Checked if l else QtCore.Qt.Unchecked)
except AttributeError:
pass
graph.listWidget.blockSignals(False) graph.listWidget.blockSignals(False)
# set unchecked in tree and hide/show in plot # set unchecked in tree and hide/show in plot
@ -229,9 +246,17 @@ class UpperManagement(QtCore.QObject):
for k in plotkeys: for k in plotkeys:
self.data[k].graph = gid self.data[k].graph = gid
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(list)
def plot_from_graph(self, key: str): def plot_from_graph(self, key: list[str]):
self.graphs[self.data[key].graph].remove(key) sort_graph = {}
for sid in key:
v = self.data[sid].graph
if v not in sort_graph:
sort_graph[v] = []
sort_graph[v].append(sid)
for gid, sets in sort_graph.items():
self.graphs[gid].remove(sets)
@QtCore.pyqtSlot(list, str, str) @QtCore.pyqtSlot(list, str, str)
def move_sets(self, sets: list, dest: str, src: (str|list), pos: int = -1): def move_sets(self, sets: list, dest: str, src: (str|list), pos: int = -1):
@ -274,6 +299,7 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot(list) @QtCore.pyqtSlot(list)
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
@QtCore.pyqtSlot()
def delete_sets(self, rm_sets: list = None): def delete_sets(self, rm_sets: list = None):
rm_graphs = [] rm_graphs = []
@ -282,13 +308,26 @@ class UpperManagement(QtCore.QObject):
self.undostack.beginMacro('Delete') self.undostack.beginMacro('Delete')
rm_set_by_graph = {}
for k in rm_sets[::-1]: for k in rm_sets[::-1]:
if k in self.data: if k in self.data:
cmd = DeleteCommand(self.data, k, self.newData, self.deleteData) parent_graph = self.data[k].graph
self.undostack.push(cmd) if parent_graph not in rm_set_by_graph:
else: rm_set_by_graph[parent_graph] = []
rm_set_by_graph[parent_graph].append(k)
elif k in self.graphs:
rm_graphs.append(k) rm_graphs.append(k)
else:
logger.warning(f'delete_sets: {k} is not in data or graph found')
for gid, sid_list in rm_set_by_graph.items():
cmd = DeleteCommand(self.data, sid_list, self.graphs, gid, self.newData, self.deleteData)
self.undostack.push(cmd)
for k in rm_graphs: for k in rm_graphs:
cmd = DeleteGraphCommand(self.graphs, k, self.restoreGraph, self.deleteGraph) cmd = DeleteGraphCommand(self.graphs, k, self.restoreGraph, self.deleteGraph)
self.undostack.push(cmd) self.undostack.push(cmd)
@ -316,7 +355,7 @@ class UpperManagement(QtCore.QObject):
if joined is None: if joined is None:
joined = data_i.copy() joined = data_i.copy()
else: else:
joined.append(data_i.x, data_i.y, data_i.y_err) joined.append(data_i.data.x, data_i.data.y, y_err=data_i.data.y_err, mask=data_i.data.mask)
name_set.add(data_i.name) name_set.add(data_i.name)
group_set.add(data_i.group) group_set.add(data_i.group)
@ -346,12 +385,16 @@ class UpperManagement(QtCore.QObject):
def change_visibility(self, selected: list, deselected: list): def change_visibility(self, selected: list, deselected: list):
"""Change status of list of ids after status change in datawidget""" """Change status of list of ids after status change in datawidget"""
for item_list, func in [(selected, 'show_item'), (deselected, 'hide_item')]:
grouping = {}
for s in item_list:
g = self.data[s].graph
if g not in grouping:
grouping[g] = []
grouping[g].append(s)
for s in selected: for k, v in grouping.items():
self.graphs[self.data[s].graph].show_item([s]) getattr(self.graphs[k], func)(v)
for d in deselected:
self.graphs[self.data[d].graph].hide_item([d])
@QtCore.pyqtSlot(str, str) @QtCore.pyqtSlot(str, str)
def change_keys(self, identifier: str, name: str): def change_keys(self, identifier: str, name: str):
@ -375,6 +418,13 @@ class UpperManagement(QtCore.QObject):
self.undostack.push(single_undo) self.undostack.push(single_undo)
self.undostack.endMacro() self.undostack.endMacro()
def edit_signals(self: UpperManagement, args: list[tuple]) -> None:
self.undostack.beginMacro('Edit signals')
for sid in self.graphs[self.current_graph]:
single_undo = EditCommand(self.data[sid], *args)
self.undostack.push(single_undo)
self.undostack.endMacro()
def cut(self): def cut(self):
if self.current_graph: if self.current_graph:
xlim, _ = self.graphs[self.current_graph].ranges xlim, _ = self.graphs[self.current_graph].ranges
@ -385,68 +435,105 @@ class UpperManagement(QtCore.QObject):
for d in self.data.values(): for d in self.data.values():
d.mask = np.ones_like(d.mask, dtype=bool) d.mask = np.ones_like(d.mask, dtype=bool)
def start_fit(self, parameter: dict, links: list, fit_options: dict): def prepare_fit(self, parameter: dict, links: list, fit_options: dict) -> bool:
if self._fit_active: if self._fit_active:
return return False
self.__fit_options = (parameter, links, fit_options) self.__fit_options = (parameter, links, fit_options)
fitter = FitRoutine() self.fitter = FitRoutine()
models = {} models = {}
fit_limits = fit_options['limits'] fit_limits = fit_options['limits']
fit_mode = fit_options['fit_mode'] fit_mode = fit_options['fit_mode']
we = fit_options['we'] we_option = fit_options['we']
for model_id, model_p in parameter.items(): self.fitter.fitmethod = fit_mode
m = Model(model_p['func'])
models[model_id] = m
m_complex = model_p['complex'] # all-encompassing error catch
try:
for model_id, model_p in parameter.items():
m = model_p['func']
models[model_id] = m
for set_id, set_params in model_p['parameter'].items(): m_complex = model_p['complex']
data_i = self.data[set_id]
if we.lower() == 'deltay':
we = data_i.y_err**2
if m_complex is None or m_complex == 1: # sets are not in active order but in order they first appeared in fit dialog
_y = data_i.y.real # iterate over order of set id in active order and access parameter inside loop
elif m_complex == 2 and np.iscomplexobj(data_i.y): # instead of directly looping
_y = data_i.y.imag try:
else: list_ids = list(self.active_id)
_y = data_i.y set_order = [self.active_id.index(i) for i in model_p['data_parameter'].keys()]
except ValueError as e:
raise Exception('Getting order failed') from e
_x = data_i.x for pos in set_order:
set_id = list_ids[pos]
if fit_limits == 'none': try:
inside = slice(None) data_i = self.data[set_id]
elif fit_limits == 'x': except KeyError as e:
x_lim, _ = self.graphs[self.current_graph].ranges raise KeyError(f'{set_id} not found') from e
inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1]))
else:
inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1]))
if isinstance(we, str): try:
d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id) set_params = model_p['data_parameter'][set_id]
else: except KeyError as e:
d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id) raise KeyError(f'No parameter found for {set_id}') from e
d.set_model(m) if we_option.lower() == 'deltay':
d.set_parameter(set_params[0], var=model_p['var'], we = data_i.y_err**2
lb=model_p['lb'], ub=model_p['ub'], else:
fun_kwargs=set_params[1]) we = we_option
fitter.add_data(d) if m_complex is None or m_complex == 1:
_y = data_i.y.real
elif m_complex == 2 and np.iscomplexobj(data_i.y):
_y = data_i.y.imag
else:
_y = data_i.y
model_globs = model_p['glob'] _x = data_i.x
if model_globs:
m.set_global_parameter(**model_p['glob'])
for links_i in links: if fit_limits == 'none':
fitter.set_link_parameter((models[links_i[0]], links_i[1]), inside = slice(None)
(models[links_i[2]], links_i[3])) elif fit_limits == 'x':
x_lim, _ = self.graphs[self.current_graph].ranges
inside = np.where((_x >= x_lim[0]) & (_x <= x_lim[1]))
else:
inside = np.where((_x >= fit_limits[0]) & (_x <= fit_limits[1]))
try:
if isinstance(we, str):
d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id)
else:
d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id)
except Exception as e:
raise Exception(f'Setting data failed for {set_id}')
d.set_model(m)
try:
d.set_parameter(set_params[0], fun_kwargs=set_params[1])
except Exception as e:
raise Exception('Setting parameter failed') from e
self.fitter.add_data(d)
for links_i in links:
self.fitter.set_link_parameter((models[links_i[0]], links_i[1]),
(models[links_i[2]], links_i[3]))
return True
except Exception as e:
logger.error('Fit preparation failed', *e.args)
QtWidgets.QMessageBox.warning(QtWidgets.QWidget(),
'Fit prep failed',
f'Fit preparation failed:\n'
'Message:\n'
f'{e.args}:\n')
return False
def start_fit(self):
with busy_cursor(): with busy_cursor():
self.fit_worker = FitWorker(fitter, fit_mode) self.fit_worker = FitWorker(self.fitter)
self.fit_thread = QtCore.QThread() self.fit_thread = QtCore.QThread()
self.fit_worker.moveToThread(self.fit_thread) self.fit_worker.moveToThread(self.fit_thread)
@ -462,12 +549,14 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot(list, bool) @QtCore.pyqtSlot(list, bool)
def end_fit(self, result: list, success: bool): def end_fit(self, result: list, success: bool):
print('FIT FINISHED')
if success: if success:
logger.info('Successful fit')
self.fitFinished.emit(result) self.fitFinished.emit(result)
else: else:
e = result[0]
logger.exception(e, exc_info=True)
QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), 'Fit failed', QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), 'Fit failed',
'Fit kaput with exception: \n' + "\n".join(result[0])) f'Fit kaput with exception: \n\n{e!r}')
self.fitFinished.emit([]) self.fitFinished.emit([])
self._fit_active = False self._fit_active = False
@ -480,9 +569,10 @@ class UpperManagement(QtCore.QObject):
for set_id, set_parameter in parameter.items(): for set_id, set_parameter in parameter.items():
new_values = [v.value for v in res[set_id].parameter.values()] new_values = [v.value for v in res[set_id].parameter.values()]
parameter[set_id] = (new_values, set_parameter[1]) parameter[set_id] = (new_values, set_parameter[1])
self.start_fit(*self.__fit_options) if self.prepare_fit(*self.__fit_options):
self.start_fit()
def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: dict) -> None: def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None:
""" """
Args: Args:
@ -491,6 +581,7 @@ class UpperManagement(QtCore.QObject):
param_graph: None if no parameter to plot, '' for new graph, or id of existig graph param_graph: None if no parameter to plot, '' for new graph, or id of existig graph
show_fit: plot fit curve? show_fit: plot fit curve?
parts: key is that of original data, value is list of subplots parts: key is that of original data, value is list of subplots
extrapolate:
""" """
f_id_list = [] f_id_list = []
@ -503,6 +594,26 @@ class UpperManagement(QtCore.QObject):
if reject: if reject:
continue continue
if not all(e is None for e in extrapolate):
spacefunc = np.geomspace if fit.islog else np.linspace
xmin = fit.x.min()
xmax = fit.x.max()
len_data = len(fit.x_data)
num_pts = 20*len_data-9 if len_data < 51 else 3*len_data
if extrapolate[0] is not None:
xmin = extrapolate[0]
if extrapolate[1] is not None:
xmax = extrapolate[1]
if extrapolate[2] is not None:
num_pts = extrapolate[2]
_x = spacefunc(xmin, xmax, num=num_pts)
fit = fit.with_new_x(_x)
data_k = self.data[k] data_k = self.data[k]
if delete_prev: if delete_prev:
tobedeleted.extend([f.id for f in data_k.get_fits()]) tobedeleted.extend([f.id for f in data_k.get_fits()])
@ -527,18 +638,19 @@ class UpperManagement(QtCore.QObject):
f_id_list.append(f_id) f_id_list.append(f_id)
data_k.set_fits(f_id) data_k.set_fits(f_id)
if parts:
color_scheme = available_cycles['colorblind']
for subfunc, col in zip(fit.sub(fit.x), cycle(color_scheme)):
subfunc.value = data_k.value
subfunc.group = data_k.group
subfunc.name += data_name
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
self[sub_f_id].add_relation(Relations.isFitPartOf, f_id)
self[f_id].add_relation(Relations.hasFitPart, sub_f_id)
f_id_list.append(sub_f_id)
gid = data_k.graph gid = data_k.graph
if k in parts and show_fit:
color_scheme = available_cycles['colorblind']
for subfunc, col in zip(parts[k], cycle(color_scheme)):
subfunc.value = data_k.value
subfunc.group = data_k.group
subfunc.name += data_name
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
f_id_list.append(sub_f_id)
self.delete_sets(tobedeleted) self.delete_sets(tobedeleted)
if accepted and (param_graph != '-1'): if accepted and (param_graph != '-1'):
@ -546,6 +658,27 @@ class UpperManagement(QtCore.QObject):
self.newData.emit(f_id_list, gid) self.newData.emit(f_id_list, gid)
def extend_fits(self, set_id: list, x_range: np.ndarray):
graphs = {}
for sid in set_id:
data = self[sid]
fit = data.copy(full=True, keep_color=True)
fit.data = fit.data.with_new_x(x_range)
graph_id = data.graph
if graph_id not in graphs:
graphs[graph_id] = []
graphs[graph_id].append(self.add(fit))
color_scheme = available_cycles['colorblind']
for subfunc, col in zip(fit.data.sub(fit.x), cycle(color_scheme)):
subfunc.value = fit.value
subfunc.group = fit.group
graphs[graph_id].append(self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No))
for k, v in graphs.items():
self.newData.emit(v, k)
def make_fit_parameter(self, fit_sets: list[str | FitResult], graph_id: str = None): def make_fit_parameter(self, fit_sets: list[str | FitResult], graph_id: str = None):
fit_dict = self._collect_fit_parameter(fit_sets) fit_dict = self._collect_fit_parameter(fit_sets)
@ -558,11 +691,11 @@ class UpperManagement(QtCore.QObject):
if not graph_id: if not graph_id:
graph_id = '' graph_id = ''
self.newData.emit(p_id_list, graph_id) self.newData[list, str, bool].emit(p_id_list, graph_id, True)
def save_fit_parameter(self, fname: str | pathlib.Path, fit_sets: list[str] = None): def save_fit_parameter(self, fname: str | pathlib.Path, fit_sets: list[str] = None):
if fit_sets is None: if fit_sets is None:
fit_sets = [s for (s, _) in self.active_sets] fit_sets = [s for s in self.active_id]
for set_id in fit_sets: for set_id in fit_sets:
data = self.data[set_id] data = self.data[set_id]
@ -584,12 +717,10 @@ class UpperManagement(QtCore.QObject):
else: else:
continue continue
for key, pvalue in data.parameter.items(): for fit_key, pvalue in data.parameter.items():
name = pvalue.full_name
fit_key = key + data.model_name
if fit_key not in fit_dict: if fit_key not in fit_dict:
fit_dict[fit_key] = [[], name] fit_dict[fit_key] = [[], fit_key]
err = 0 if pvalue.error is None else pvalue.error err = 0 if pvalue.error is None else pvalue.error
@ -626,7 +757,7 @@ class UpperManagement(QtCore.QObject):
key = self.add(Points(x=new_x_axis, y=_temp[:, i, 1], y_err=_temp[:, i, 2], name=label)) key = self.add(Points(x=new_x_axis, y=_temp[:, i, 1], y_err=_temp[:, i, 2], name=label))
key_list.append(key) key_list.append(key)
self.newData.emit(key_list, gid) self.newData[list, str, bool].emit(key_list, gid, True)
@QtCore.pyqtSlot(list) @QtCore.pyqtSlot(list)
def get_properties(self, sid: list) -> dict: def get_properties(self, sid: list) -> dict:
@ -697,6 +828,47 @@ class UpperManagement(QtCore.QObject):
self.newData.emit(new_key, dest_graph) self.newData.emit(new_key, dest_graph)
def binning(self, digits: float):
_active = self.graphs[self.current_graph].active
new_data = []
for sid in _active:
key = self.add(self.data[sid].binning(digits=digits))
new_data.append(key)
self.newData.emit(new_data, self.current_graph)
@QtCore.pyqtSlot(dict, str)
def addTg(self, dic: dict, dtype: str):
graph_id = self.current_graph if dtype == 'tg' else dic.pop('graph')
set_id_list = []
if dtype == 'hodge':
for v in dic.values():
set_id_list.append(self.add(v))
else:
for k, (data, lines) in dic.items():
p: ExperimentContainer = self[k]
col = p.plot_real.linecolor
if data is not None:
set_id_list.append(self.add(data, color=col))
if dtype == 'tnmh':
if lines is not None:
lines = [lines]
else:
lines = []
for line in lines:
set_id = self.add(line, color=col)
self[set_id].setLine(style=LineStyle.Dashed)
self[set_id].setSymbol(symbol=SymbolStyle.No)
set_id_list.append(set_id)
self.newData.emit(set_id_list, graph_id)
@QtCore.pyqtSlot(int, dict) @QtCore.pyqtSlot(int, dict)
def smooth_data(self, npoints, param_kwargs): def smooth_data(self, npoints, param_kwargs):
_active = self.graphs[self.current_graph].active _active = self.graphs[self.current_graph].active
@ -729,13 +901,10 @@ class UpperManagement(QtCore.QObject):
d_k = self.data[k] d_k = self.data[k]
if copy_data is None: if copy_data is None:
d_k.x = d_k.x*v[1][0] + v[0][0] d_k.shift_scale(v[0], v[1])
d_k.y = d_k.y*v[1][1] + v[0][1]
else: else:
new_data = d_k.copy(full=True) new_data = d_k.copy(full=True)
new_data.update({'shift': v[0], 'scale': v[1]}) new_data.shift_scale(v[0], v[1])
new_data.data.x = new_data.x*v[1][0] + v[0][0]
new_data.y = new_data.y*v[1][1] + v[0][1]
sid = self.add(new_data) sid = self.add(new_data)
sid_list.append(sid) sid_list.append(sid)
@ -831,21 +1000,26 @@ class UpperManagement(QtCore.QObject):
self.undostack.beginMacro('Evaluate expression') self.undostack.beginMacro('Evaluate expression')
failures = [] failures = []
for sid in set_ids: for i, g in enumerate(self.graphs.values()):
data_i = self.data[sid] for j, sid in enumerate(g.sets):
try:
# use a copy of original namespace if sid not in set_ids:
new_data = data_i.eval_expression(cmds, dict(ns)) continue
if overwrite:
cmd = EvalCommand(self.data, sid, new_data, 'Evaluate expression') data_i = self.data[sid]
self.undostack.push(cmd) try:
else: # use a copy of original namespace
new_id = self.copy_sets(sets=[sid]) new_data = data_i.eval_expression(cmds, dict(ns), i=i, j=j)
self.data[new_id[0]].data = new_data if overwrite:
except Exception as e: cmd = EvalCommand(self.data, sid, new_data, 'Evaluate expression')
failures.append((data_i, e)) self.undostack.push(cmd)
print(str(data_i) + ' failed with Exception: ' + ''.join(e.args)) else:
continue new_id = self.copy_sets(sets=[sid])
self.data[new_id[0]].data = new_data
except Exception as e:
failures.append((data_i, e))
logger.warning(str(data_i) + ' failed with Exception: ' + ''.join(e.args))
continue
if overwrite: if overwrite:
self.undostack.endMacro() self.undostack.endMacro()
@ -879,9 +1053,9 @@ class UpperManagement(QtCore.QObject):
self.newData.emit([s_id], graph) self.newData.emit([s_id], graph)
except Exception as err: except Exception as err:
print('Creation failed with error: ' + ', '.join(err.args)) logger.exception('Creation failed with error: ' + ', '.join(err.args))
err_msg = QtWidgets.QMessageBox(parent=self.sender()) err_msg = QtWidgets.QMessageBox(parent=self.sender())
err_msg.setText('One or more errors occured during evaluation.') err_msg.setText('One or more errors occurred during evaluation.')
err_msg.setDetailedText('Creation failed with error: ' + ', '.join(err.args)) err_msg.setDetailedText('Creation failed with error: ' + ', '.join(err.args))
err_msg.exec() err_msg.exec()
@ -890,7 +1064,7 @@ class UpperManagement(QtCore.QObject):
def show_statistics(self, mode): def show_statistics(self, mode):
x, y, = [], [] x, y, = [], []
for i, _ in self.active_sets: for i in self.active_id:
_temp = self.data[i] _temp = self.data[i]
try: try:
x.append(float(_temp.name)) x.append(float(_temp.name))
@ -901,7 +1075,7 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def calc_magn(self): def calc_magn(self):
new_id = [] new_id = []
for k, _ in self.active_sets: for k in self.active_id:
dataset = self.data[k] dataset = self.data[k]
if isinstance(dataset, SignalContainer): if isinstance(dataset, SignalContainer):
new_value = dataset.copy(full=True) new_value = dataset.copy(full=True)
@ -913,7 +1087,7 @@ class UpperManagement(QtCore.QObject):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def center(self): def center(self):
new_id = [] new_id = []
for k, _ in self.active_sets: for k in self.active_id:
new_value = self.data[k].copy(full=True) new_value = self.data[k].copy(full=True)
new_value.x -= new_value.x[np.argmax(new_value.y.real)] new_value.x -= new_value.x[np.argmax(new_value.y.real)]
new_id.append(self.add(new_value)) new_id.append(self.add(new_value))
@ -952,7 +1126,7 @@ class UpperManagement(QtCore.QObject):
def bds_deriv(self): def bds_deriv(self):
new_sets = [] new_sets = []
for (set_id, _) in self.active_sets: for set_id in self.active_id:
data_i = self.data[set_id] data_i = self.data[set_id]
diff = data_i.data.diff(log=True) diff = data_i.data.diff(log=True)
new_data = Points(x=diff.x, y=-np.pi/2*diff.y.real) new_data = Points(x=diff.x, y=-np.pi/2*diff.y.real)
@ -965,13 +1139,14 @@ class UpperManagement(QtCore.QObject):
def logft(self, **kwargs): def logft(self, **kwargs):
new_sets = [] new_sets = []
ft_mode = kwargs['ft_mode'] ft_mode = kwargs['ft_mode']
return_f = kwargs['return_f']
for set_id in kwargs['sets']: for set_id in kwargs['sets']:
data_i = self.data[set_id] data_i = self.data[set_id]
if ft_mode in ['cos', 'sin']: if ft_mode in ['cos', 'sin']:
new_data = Points(*logft(data_i.x, data_i.y, mode=ft_mode)) new_data = Points(*logft(data_i.x, data_i.y, mode=ft_mode, return_f=return_f))
else: else:
new_data = Signal(*logft(data_i.x, data_i.y, mode=ft_mode)) new_data = Signal(*logft(data_i.x, data_i.y, mode=ft_mode, return_f=return_f))
new_sets.append(self.add(new_data, color=data_i['color'], symbol=data_i['symbol'], line=data_i['line'])) new_sets.append(self.add(new_data, color=data_i['color'], symbol=data_i['symbol'], line=data_i['line']))
self.data[new_sets[-1]].update(data_i.data.meta) self.data[new_sets[-1]].update(data_i.data.meta)
@ -979,7 +1154,7 @@ class UpperManagement(QtCore.QObject):
self.newData.emit(new_sets, kwargs['graph']) self.newData.emit(new_sets, kwargs['graph'])
def skip_points(self, offset: int, step: int, invert: bool = False, copy: bool = False): def skip_points(self, offset: int, step: int, invert: bool = False, copy: bool = False):
for k, _ in self.active_sets: for k in self.active_id:
src = self.data[k] src = self.data[k]
if invert: if invert:
mask = np.mod(np.arange(offset, src.x.size+offset), step) != 0 mask = np.mod(np.arange(offset, src.x.size+offset), step) != 0
@ -1005,9 +1180,11 @@ class UpperManagement(QtCore.QObject):
params = opts['pts'] params = opts['pts']
if len(params) == 4: if len(params) == 4:
if params[3]: if params[3]:
_x = x1 = np.geomspace(params[0], params[1], num=params[2]) _x = np.geomspace(params[0], params[1], num=params[2])
x1 = np.geomspace(params[0], params[1], num=params[2])
else: else:
_x = x1 = np.linspace(params[0], params[1], num=params[2]) _x = np.linspace(params[0], params[1], num=params[2])
x1 = np.linspace(params[0], params[1], num=params[2])
if opts['axis1'] in ['t', 'invt1000']: if opts['axis1'] in ['t', 'invt1000']:
t_p = opts['t_param'] t_p = opts['t_param']
@ -1026,21 +1203,23 @@ class UpperManagement(QtCore.QObject):
_x = x1 = self.data[params[0]].x _x = x1 = self.data[params[0]].x
x2 = opts['val2'] x2 = opts['val2']
sd = opts['spec_dens'] sd = opts['spec_dens']
sd_param = [self.data[p].y.real if isinstance(p, str) else p for p in opts['sd_param'][0]] sd_param = list(zip(*[self.data[p].y.real if isinstance(p, str) else [p]*len(_x) for p in opts['sd_param'][0]]))
sd.convert(_x, *sd_param, from_=opts['tau_type'], to_='raw')
relax = Relaxation() relax = Relaxation()
relax.set_distribution(sd, parameter=sd_param, keywords=opts['sd_param'][1]) relax.set_distribution(sd, keywords=opts['sd_param'][1])
cp_param = [self.data[p].y.real if isinstance(p, str) else p for p in opts['cp_param'][0]] cp_param = list(zip(*[self.data[p].y.real if isinstance(p, str) else [p]*len(_x) for p in opts['cp_param'][0]]))
relax.set_coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1]) # relax.set_coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1])
if opts['out'] == 't1': relax_func = relax.t1 if opts['out'] == 't1' else relax.t2
y = relax.t1(x2, _x) y = np.zeros(_x.size)
else:
y = relax.t2(x2, _x) for i in range(_x.size):
_x_i = sd.convert(_x[i], *sd_param[i], from_=opts['tau_type'], to_='raw')
relax.dist_parameter = sd_param[i]
relax.set_coupling(opts['coup'], parameter=cp_param[i], keywords=opts['cp_param'][1])
y[i] = relax_func(x2, _x_i)
pts = Points(x1, y, name=sd.name) pts = Points(x1, y, name=sd.name)
pts.meta.update(opts) pts.meta.update(opts)
@ -1134,18 +1313,17 @@ class UpperManagement(QtCore.QObject):
class FitWorker(QtCore.QObject): class FitWorker(QtCore.QObject):
finished = QtCore.pyqtSignal(list, bool) finished = QtCore.pyqtSignal(list, bool)
def __init__(self, fitter, mode): def __init__(self, fitter):
super().__init__() super().__init__()
self.fitter = fitter self.fitter = fitter
self.mode = mode
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def run(self): def run(self):
try: try:
res = self.fitter.run(mode=self.mode) res = self.fitter.run()
success = True success = True
except Exception as e: except Exception as e:
res = [e.args] res = [e]
success = False success = False
self.finished.emit(res, success) self.finished.emit(res, success)

View File

@ -0,0 +1,21 @@
from ..Qt import QtWidgets, QtGui
class BinningWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent=parent)
layout = QtWidgets.QFormLayout()
self.label = QtWidgets.QLabel('Digits (negative values position left of decimal point)')
self.spinbox = QtWidgets.QLineEdit()
self.spinbox.setValidator(QtGui.QDoubleValidator())
self.spinbox.setText('1')
layout.addRow(self.label, self.spinbox)
self.dialogbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.dialogbox.accepted.connect(self.accept)
self.dialogbox.rejected.connect(self.reject)
layout.addWidget(self.dialogbox)
self.setLayout(layout)

View File

@ -31,13 +31,35 @@ class QEvalDialog(QtWidgets.QDialog, Ui_CalcDialog):
self.namespace_widget.set_namespace(self.namespace) self.namespace_widget.set_namespace(self.namespace)
def add_data(self, data): def add_data(self, data):
self.listWidget.clear() # self.listWidget.clear()
tmp = []
while self.listWidget.count():
tmp.append(self.listWidget.takeItem(0))
for set_id, name in data: for set_id, name in data:
item = QtWidgets.QListWidgetItem(name) # search if set was used before
item.setData(QtCore.Qt.UserRole, set_id) new_one = True
item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable) for i in range(len(tmp)):
item.setCheckState(QtCore.Qt.Checked) w = tmp[i]
self.listWidget.addItem(item) if w.data(QtCore.Qt.UserRole) == set_id:
w.setText(name)
self.listWidget.addItem(w)
tmp.pop(i)
new_one = False
break
# new set, create item
if new_one:
item = QtWidgets.QListWidgetItem(name)
item.setData(QtCore.Qt.UserRole, set_id)
item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable)
item.setCheckState(QtCore.Qt.Checked)
self.listWidget.addItem(item)
while len(tmp):
# delete remaining ListWidgetItems
w = tmp.pop()
del w
def set_graphs(self, graphs: list): def set_graphs(self, graphs: list):
self.graph_comboBox.clear() self.graph_comboBox.clear()

View File

@ -16,16 +16,19 @@ class QDeriveIntegrate(QtWidgets.QDialog, Ui_Dialog):
self.start_lineedit.setValidator(QtGui.QDoubleValidator()) self.start_lineedit.setValidator(QtGui.QDoubleValidator())
self.stop_lineedit.setValidator(QtGui.QDoubleValidator()) self.stop_lineedit.setValidator(QtGui.QDoubleValidator())
self.ft_comboBox.hide() self.ft_comboBox.hide()
self.freq_box.hide()
elif self.mode == 'd': elif self.mode == 'd':
self.setWindowTitle('Differentiation dialog') self.setWindowTitle('Differentiation dialog')
self.widget.hide() self.widget.hide()
self.ft_comboBox.hide() self.ft_comboBox.hide()
self.freq_box.hide()
elif self.mode == 'l': elif self.mode == 'l':
self.setWindowTitle('Logarithmic FT dialog') self.setWindowTitle('Logarithmic FT dialog')
self.log_checkbox.hide() self.log_checkbox.hide()
self.widget.hide() self.widget.hide()
self.freq_box.show()
else: else:
raise ValueError(f'Unknown mode {mode}, use "d", "i", or "l".') raise ValueError(f'Unknown mode {mode}, use "d", "i", or "l".')
@ -54,8 +57,10 @@ class QDeriveIntegrate(QtWidgets.QDialog, Ui_Dialog):
self.stop_lineedit.setEnabled(full_range != QtCore.Qt.Checked) self.stop_lineedit.setEnabled(full_range != QtCore.Qt.Checked)
def get_options(self): def get_options(self):
opts = {'graph': '' if self.newgraph_checkbox.isChecked() else self.graph_combobox.currentData(), opts = {
'mode': self.mode, 'sets': []} 'graph': '' if self.newgraph_checkbox.isChecked() else self.graph_combobox.currentData(),
'mode': self.mode, 'sets': []
}
if self.mode == 'i': if self.mode == 'i':
start = None start = None
@ -75,6 +80,7 @@ class QDeriveIntegrate(QtWidgets.QDialog, Ui_Dialog):
if self.mode == 'l': if self.mode == 'l':
opts['ft_mode'] = ['cos', 'sin', 'complex'][self.ft_comboBox.currentIndex()] opts['ft_mode'] = ['cos', 'sin', 'complex'][self.ft_comboBox.currentIndex()]
opts['return_f'] = self.freq_box.isChecked()
else: else:
opts['log'] = self.log_checkbox.isChecked() opts['log'] = self.log_checkbox.isChecked()

View File

@ -19,7 +19,7 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
self.graphs = {} self.graphs = {}
self.specdens = [ColeCole, ColeDavidson, HavriliakNegami, KWW] self.specdens = [ColeCole, ColeDavidson, HavriliakNegami, KWW, LogGaussian]
self.coupling = [Quadrupolar, HomoDipolar, Czjzek] self.coupling = [Quadrupolar, HomoDipolar, Czjzek]
self.tau_parameter = [] self.tau_parameter = []
@ -131,7 +131,7 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
self.verticalLayout_3.addWidget(_temp) self.verticalLayout_3.addWidget(_temp)
def get_taus(self, dic: dict): def get_taus(self, dic: dict):
dic['tau_type'] = {0: 'raw', 1: 'mean', 2: 'peak', 3: 'logmean'}[self.xtype_combobox.currentIndex()] dic['tau_type'] = {0: 'raw', 1: 'peak', 2: 'mean', 3: 'logmean'}[self.xtype_combobox.currentIndex()]
dic['axis1'] = {self.radioButton: 'tau', self.radioButton_2: 'omega', dic['axis1'] = {self.radioButton: 'tau', self.radioButton_2: 'omega',
self.radioButton_3: 't', self.radioButton_4: 'invt1000'}[self.buttonGroup.checkedButton()] self.radioButton_3: 't', self.radioButton_4: 'invt1000'}[self.buttonGroup.checkedButton()]
@ -199,3 +199,9 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
def accept(self): def accept(self):
self.calc_relaxation() self.calc_relaxation()
super().accept() super().accept()
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def on_buttonBox_clicked(self, button: QtWidgets.QAbstractButton):
role = self.buttonBox.buttonRole(button)
if role == self.buttonBox.ApplyRole:
self.calc_relaxation()

View File

@ -61,6 +61,8 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
self.freq_combox.currentIndexChanged.connect(lambda x: self.update_model()) self.freq_combox.currentIndexChanged.connect(lambda x: self.update_model())
self.freq_spinbox.valueChanged.connect(lambda x: self.update_model()) self.freq_spinbox.valueChanged.connect(lambda x: self.update_model())
self.checkBox_interpol.setVisible(False)
self.update_specdens(0) self.update_specdens(0)
self.update_coupling(0) self.update_coupling(0)

View File

@ -0,0 +1,158 @@
/* integrands used in quadrature integration with scipy's LowLevelCallables */
#include <math.h>
const double KB = 8.617333262145179e-05;
/* FFHS functions */
double ffhsSD(double x, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau = c[1];
double u = x*x;
double res = u*u * tau;
res /= 81. + 9.*u - 2.*u*u + u*u*u;
res /= u*u + omega*omega * tau*tau;
return res;
}
/* log-gaussian functions */
double logNormalDist(double tau, double tau0, double sigma) {
return exp(- pow((log(tau/tau0) / sigma), 2) / 2.) / sqrt(2*M_PI)/sigma;
}
double logGaussian_imag_high(double u, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau = c[1];
double sigma = c[2];
double uu = exp(-u);
double dist = logNormalDist(1./uu, tau, sigma);
return dist * omega * uu / (pow(uu, 2) + pow(omega, 2));
}
double logGaussian_imag_low(double u, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau = c[1];
double sigma = c[2];
double uu = exp(u);
double dist = logNormalDist(uu, tau, sigma);
return dist * omega * uu / (1. + pow(omega*uu, 2));
}
double logGaussian_real_high(double u, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau = c[1];
double sigma = c[2];
double uu = exp(-2.*u);
double dist = logNormalDist(exp(uu), tau, sigma);
return dist * uu / (uu + pow(omega, 2));
}
double logGaussian_real_low(double u, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau = c[1];
double sigma = c[2];
double uu = exp(u);
double dist = logNormalDist(uu, tau, sigma);
return dist / (1. + pow(omega*uu, 2));
}
double logGaussianCorrelation(double x, void *user_data) {
double *c = (double *)user_data;
double t = c[0];
double tau = c[1];
double sigma = c[2];
double uu = exp(x);
double dist = logNormalDist(uu, tau, sigma);
return dist * exp(-t/uu);
}
// functions for distribution of energy
double normalDist(double x, double x0, double sigma) {
return exp(- pow((x-x0) / sigma, 2) / 2.) / sqrt(2 * M_PI) / sigma;
}
double rate(double tau0, double ea, double t) {
return exp(-ea / t / KB) / tau0;
}
double energyDist_SD(double x, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau0 = c[1];
double e_m = c[2];
double e_b = c[3];
double temp = c[4];
double r = rate(tau0, x, temp);
return r/(pow(r, 2) + pow(omega, 2)) * normalDist(x, e_m, e_b);
}
double energyDistSuscReal(double x, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau0 = c[1];
double e_m = c[2];
double e_b = c[3];
double temp = c[4];
double r = rate(tau0, x, temp);
return 1 / (pow(r, 2) + pow(omega, 2)) * normalDist(x, e_m, e_b);
}
double energyDistSuscImag(double x, void *user_data) {
double *c = (double *)user_data;
double omega = c[0];
double tau0 = c[1];
double e_m = c[2];
double e_b = c[3];
double temp = c[4];
double r = rate(tau0, x, temp);
return omega * r / (pow(r, 2) + pow(omega, 2)) * normalDist(x, e_m, e_b);
}
double energyDistCorrelation(double x, void *user_data) {
double *c = (double *)user_data;
double t = c[0];
double tau0 = c[1];
double e_m = c[2];
double e_b = c[3];
double temp = c[4];
double r = rate(tau0, x, temp);
return normalDist(x, e_m, e_b) * exp(-t * r);
}

BIN
src/nmreval/clib/integrate.so Executable file

Binary file not shown.

View File

@ -10,19 +10,25 @@ __all__ = ['config_paths', 'check_for_config', 'read_configuration', 'write_conf
def check_for_config(make=True): def check_for_config(make=True):
try: try:
config_paths() conf_path = config_paths()
except FileNotFoundError as e: except FileNotFoundError as e:
if make: if make:
conf_path = pathlib.Path('~/.auswerten').expanduser() conf_path = pathlib.Path('~/.auswerten').expanduser()
conf_path.mkdir(parents=True) conf_path.mkdir(parents=True)
cwd = pathlib.Path(__file__).parent
copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
with resource_path('resources', 'Default.agr') as fp:
copyfile(fp, conf_path / 'Default.agr')
else: else:
raise e raise e
for filename in ('Default.agr', 'logo.png'):
_file = conf_path / filename
if not _file.exists():
with resource_path('resources', filename) as fp:
copyfile(fp, _file)
if not (conf_path / 'usermodels.py').exists():
cwd = pathlib.Path(__file__).parent
copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
def config_paths() -> pathlib.Path: def config_paths() -> pathlib.Path:
searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval'] searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval']

View File

@ -1,6 +1,138 @@
from __future__ import annotations
from typing import Optional
import numpy as np
from scipy.optimize import fsolve
from scipy.signal import savgol_filter
try:
from scipy.integrate import cumulative_trapezoid
except ImportError:
from scipy.integrate import cumtrapz as cumulative_trapezoid
from scipy.stats import linregress
from .points import Points from .points import Points
from ..dsc.tnmh_model import TNMH
class DSC(Points): class DSC(Points):
def __init__(self, x, y, **kwargs): def __init__(self, x, y, **kwargs):
super().__init__(x, y, **kwargs)
y = np.asarray(y).reshape(np.asarray(x).shape)
x, unique = np.unique(x, return_index=True)
if kwargs.get('y_err', None) is not None:
_yerr = np.asarray(kwargs['y_err']).reshape(np.asarray(x).shape)
kwargs['y_err'] = _yerr[unique]
self.tg = {'onset': np.nan, 'mid': np.nan, 'end': np.nan, 'inflection': np.nan, 'fictive': np.nan}
super().__init__(x, y[unique], **kwargs)
def get_fictive_cp(self, glass: tuple[float, float], liquid: tuple[float, float]) -> ('DSC', float):
min_glass, max_glass = min(glass), max(glass)
min_liquid, max_liquid = min(liquid), max(liquid)
region = self.copy()
region.cut(min_glass, max_liquid)
glass_regime = (min_glass < region.x) & (region.x < max_glass)
regress = linregress(region.x[glass_regime], region.y[glass_regime])
glass_extrapolation = regress.slope * region.x + regress.intercept
liquid_regime = (min_liquid < region.x) & (region.x < max_liquid)
regress2 = linregress(region.x[liquid_regime], region.y[liquid_regime])
region.y -= glass_extrapolation
real_area = cumulative_trapezoid(region.y, region.x, initial=0)
real_area -= real_area[-1]
t = regress2.intercept - regress.intercept
m = regress2.slope - regress.slope
c0 = 0.5 * m * region.x.max() ** 2 + t * region.x.max()
def equiv(_x, _i):
return (0.5 * m * _x ** 2 + t * _x - c0) - real_area[_i]
def equiv_prime(_x, _i):
return m * _x + t
fictive_temperature = np.array(
[fsolve(equiv, region.x[i], fprime=equiv_prime, args=(i,))[0] for i in range(len(region.x))])
t_g_fictive = fictive_temperature[:20].mean()
region.y = np.gradient(fictive_temperature, region.x)
return region, t_g_fictive
def calculate_tnmh(self, p0: list, glass: tuple[float, float], liquid: tuple[float, float],
tg: float = None, num_points: int = 200, return_fictive: bool = True) \
-> ('FitResult', Optional[float], Optional[DSC]):
dtf_dt, fictive_tg = self.get_fictive_cp(glass, liquid)
if tg is None:
tg = fictive_tg
temp_equidist = np.linspace(dtf_dt.x[0], dtf_dt.x[-1], num_points)
dtf_dt_equidist = np.interp(temp_equidist, dtf_dt.x, dtf_dt.y)
from ..fit.minimizer import FitRoutine
fitter = FitRoutine()
fitter.set_model(TNMH)
data = fitter.add_data(temp_equidist, dtf_dt_equidist)
data.set_parameter(p0 + [tg, self.value], var=[True] * 4 + [False] * 2, default_bounds=True)
res = fitter.run()[0]
if return_fictive:
return res, tg, dtf_dt
else:
return res
def glass_transition(self, glass, liquid):
low_idx = tuple(np.argmin(np.abs(self.x - g)) for g in glass)
high_idx = tuple(np.argmin(np.abs(self.x - l)) for l in liquid)
x = self.x[low_idx[0]:high_idx[1]]
y = self.y[low_idx[0]:high_idx[1]]
win_len = min(len(x) // 20, 51)
if win_len % 2 == 0:
win_len += 1
yy = savgol_filter(y, window_length=win_len, polyorder=1, deriv=1) / np.mean(np.diff(x))
high_idx = (high_idx[0] - low_idx[0], high_idx[1] - low_idx[0])
low_idx = (0, low_idx[1] - low_idx[0])
inflection = np.argmax(yy)
p1 = linregress(x[low_idx[0]:low_idx[1]], y[low_idx[0]:low_idx[1]])
glass_baseline = p1.slope * x + p1.intercept
p2 = linregress(x[high_idx[0]:high_idx[1]], y[high_idx[0]:high_idx[1]])
liquid_baseline = p2.slope * x + p2.intercept
tangent_line = yy[inflection] * (x - x[inflection]) + y[inflection]
onset = np.argmin(np.abs(tangent_line - glass_baseline))
end = np.argmin(np.abs(tangent_line - liquid_baseline))
midpoint = np.argmin(np.abs(y - 0.5 * (liquid_baseline[end] - glass_baseline[onset])))
cut_tangent = np.where((tangent_line > y.min() - 1) & (tangent_line < y.max() + 1))
glass = Points(x, glass_baseline, name=f'Glass baseline ({self.name})', value=self.value)
tangent = Points(x[cut_tangent], tangent_line[cut_tangent], name=f'Tangent ({self.name})', value=self.value)
liquid = Points(x, liquid_baseline, name=f'Liquid baseline ({self.name})', value=self.value)
ret_dic = {
'onset': (x[onset], glass_baseline[onset]),
'midpoint': (x[midpoint], y[midpoint]),
'end': (x[end], liquid_baseline[end]),
'inflection': (x[inflection], y[inflection]),
}
self.tg.update(ret_dic)
return ret_dic, glass, liquid, tangent

View File

@ -62,6 +62,10 @@ class FID(Signal):
return self return self
def manual_phase(self, ph0: float = 0., ph1: float = 0., pvt: float = 0):
"""FID knows only how to phase correct in zeroth order"""
super().manual_phase(ph0=ph0)
def fourier(self) -> 'Spectrum': def fourier(self) -> 'Spectrum':
ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx
freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx)) freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx))

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import copy import copy
from math import log10
from numbers import Number, Real from numbers import Number, Real
from pathlib import Path from pathlib import Path
from typing import Any, TypeVar from typing import Any, TypeVar
@ -318,20 +319,21 @@ class Points:
if pts is None: if pts is None:
pts = [] pts = []
for x in idx: if idx is not None:
if isinstance(x, tuple): for x in idx:
x_idx = np.argmin(np.abs(self._x[self.mask] - (x[0]+x[1])/2)) if isinstance(x, tuple):
left_b = np.argmin(np.abs(self._x[self.mask] - x[0])) x_idx = np.argmin(np.abs(self._x[self.mask] - (x[0]+x[1])/2))
right_b = np.argmin(np.abs(self._x[self.mask] - x[1])) left_b = np.argmin(np.abs(self._x[self.mask] - x[0]))
else: right_b = np.argmin(np.abs(self._x[self.mask] - x[1]))
x_idx = np.argmin(np.abs(self._x[self.mask]-x)) else:
left_b = int(max(0, x_idx - avg_range[0])) x_idx = np.argmin(np.abs(self._x[self.mask]-x))
right_b = int(min(len(self), x_idx + avg_range[1] + 1)) left_b = int(max(0, x_idx - avg_range[0]))
right_b = int(min(len(self), x_idx + avg_range[1] + 1))
if left_b < right_b: if left_b < right_b:
pts.append([self._x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)]) pts.append([self._x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)])
else: else:
pts.append([self._x[x_idx], self._y[x_idx], self._y_err[x_idx]]) pts.append([self._x[x_idx], self._y[x_idx], self._y_err[x_idx]])
if special is not None: if special is not None:
if special not in ['max', 'min', 'absmax', 'absmin']: if special not in ['max', 'min', 'absmax', 'absmin']:
@ -483,20 +485,25 @@ class Points:
return self return self
def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray = None) -> PointLike: def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray | float = None, replace_mask: bool = True) -> PointLike:
if x is None: if x is None:
x = self._x x = self._x
if y is None: if y is None:
y = self._y y = self._y
if y_err is not None: if y_err is None:
y_err = self._y_err y_err = self._y_err
self._x, self._y, self._y_err, self.mask = self._prepare_xy(x, y, y_err) self._x, self._y, self._y_err, mask = self._prepare_xy(x, y, y_err)
if replace_mask:
self.mask = mask
return self return self
def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None): def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None, mask: ArrayLike = None):
x, y, y_err, mask = self._prepare_xy(x, y, y_err) if mask is None:
x, y, y_err, mask = self._prepare_xy(x, y, y_err)
else:
x, y, y_err, _ = self._prepare_xy(x, y, y_err)
self._x = np.r_[self._x, x] self._x = np.r_[self._x, x]
self._y = np.r_[self._y, y] self._y = np.r_[self._y, y]
@ -541,6 +548,34 @@ class Points:
return self return self
def binning(self, value: float):
if value <= 0:
raise ValueError('value must be a positive number')
copy = self.copy()
upper_lim = (self.x[-1]//value + 1) * value
lower_lim = (self.x[0]//value) * value
offset = value / 2
xbins = np.linspace(lower_lim - offset, upper_lim + offset, num=int((upper_lim-lower_lim)/value + 2))
n, _ = np.histogram(copy.x, bins=xbins)
sum_y, _ = np.histogram(copy.x, bins=xbins, weights=copy.y)
sum_yerr_2, _ = np.histogram(copy.x, bins=xbins, weights=copy.y_err**2)
isnan = n != 0
n = n[isnan]
sum_y = sum_y[isnan]
sum_yerr_2 = sum_yerr_2[isnan]
xaxis = (xbins[:-1] + offset)[isnan]
copy.set_data(xaxis, sum_y/n, y_err=np.sqrt(sum_yerr_2/n))
return copy
def shift(self, points: int) -> PointLike: def shift(self, points: int) -> PointLike:
""" """
Shift indexes of y values. Shift indexes of y values.

View File

@ -29,7 +29,7 @@ class Distribution(abc.ABC):
@staticmethod @staticmethod
@abc.abstractmethod @abc.abstractmethod
def susceptibility(omega, tau, *args): def susceptibility(omega: ArrayLike, tau: ArrayLike, *args: Any):
pass pass
@classmethod @classmethod

View File

@ -54,6 +54,9 @@ class ColeCole(Distribution):
tau (array_like): tau (array_like):
alpha (float): alpha (float):
""" """
if alpha == 1:
return tau / (1 + omega**2 * tau**2)
omtau = (omega*tau)**alpha omtau = (omega*tau)**alpha
return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega

View File

@ -1,13 +1,18 @@
from itertools import product from itertools import product
from ctypes import c_double, cast, pointer, c_void_p
import numpy as np import numpy as np
from scipy import LowLevelCallable
from scipy.integrate import quad, simps as simpson from scipy.integrate import quad, simps as simpson
from .base import Distribution from .base import Distribution
from ..lib.utils import ArrayLike from ..lib.utils import ArrayLike
from ..utils.constants import kB from ..utils.constants import kB
from .helper import HAS_C_FUNCS, lib
# noinspection PyMethodOverriding
class EnergyBarriers(Distribution): class EnergyBarriers(Distribution):
name = 'Energy barriers' name = 'Energy barriers'
parameter = [r'\tau_{0}', r'E_{m}', r'\Delta E'] parameter = [r'\tau_{0}', r'E_{m}', r'\Delta E']
@ -23,24 +28,30 @@ class EnergyBarriers(Distribution):
return np.exp(-e_a / (kB * te)) / t0 return np.exp(-e_a / (kB * te)) / t0
@staticmethod @staticmethod
def energydistribution(e_a, mu, sigma): def energy_distribution(e_a, mu, sigma):
return np.exp(-0.5 * ((mu-e_a) / sigma) ** 2) / (np.sqrt(2 * np.pi) * sigma) return np.exp(-0.5 * ((mu-e_a) / sigma) ** 2) / (np.sqrt(2 * np.pi) * sigma)
@staticmethod @staticmethod
def correlation(t, temperature, *args): def correlation(t: ArrayLike, temperature: ArrayLike, tau0: float, e_m: float, e_b: float) -> ArrayLike:
tau0, e_m, e_b = args
def integrand(e_a, ti, t0, mu, sigma, te):
# correlation time would go to inf for higher energies, so we use rate
return np.exp(-ti*EnergyBarriers.rate(t0, e_a, te)) * EnergyBarriers.energydistribution(e_a, mu, sigma)
t = np.atleast_1d(t) t = np.atleast_1d(t)
temperature = np.atleast_1d(temperature) temperature = np.atleast_1d(temperature)
e_axis = np.linspace(max(0, e_m-50*e_b), e_m+50*e_b, num=5001) if HAS_C_FUNCS:
ret_val = _integrate_c(lib.energyDistCorrelation, t, temperature, tau0, e_m, e_b)
else:
ret_val = _integrate_py(_integrand_time, t, temperature, tau0, e_m, e_b)
ret_val = np.array([simpson(integrand(e_axis, o, tau0, e_m, e_b, tt), e_axis) return ret_val
for o in t for tt in temperature])
@staticmethod
def specdens(omega: ArrayLike, temperature: ArrayLike, tau0: float, e_m: float, e_b: float) -> ArrayLike:
omega = np.atleast_1d(omega)
temperature = np.atleast_1d(temperature)
if HAS_C_FUNCS:
ret_val = _integrate_c(lib.energyDist_SD, omega, temperature, tau0, e_m, e_b)
else:
ret_val = _integrate_py(_integrand_sd, omega, temperature, tau0, e_m, e_b)
return ret_val return ret_val
@ -51,70 +62,73 @@ class EnergyBarriers(Distribution):
omega = np.atleast_1d(omega) omega = np.atleast_1d(omega)
temperature = np.atleast_1d(temperature) temperature = np.atleast_1d(temperature)
e_axis = np.linspace(max(0, e_m-50*e_b), e_m+50*e_b, num=5001) if HAS_C_FUNCS:
ret_val = [] ret_val = _integrate_c(lib.energyDistSuscReal, omega, temperature, tau0, e_m, e_b) + \
for o, tt in product(omega, temperature): 1j * _integrate_c(lib.energyDistSuscImag, omega, temperature, tau0, e_m, e_b)
ret_val.append(simpson(_integrand_freq_real(e_axis, o, tau0, e_m, e_b, tt), e_axis) - else:
1j * simpson(_integrand_freq_imag(e_axis, o, tau0, e_m, e_b, tt), e_axis)) ret_val = _integrate_py(_integrand_susc_real, omega, temperature, tau0, e_m, e_b) + \
1j * _integrate_py(_integrand_susc_imag, omega, temperature, tau0, e_m, e_b)
return np.array(ret_val)
@staticmethod
def specdens(omega, temperature, *args):
# in contrast to other spectral densities, it's omega and temperature
tau0, e_m, e_b = args
def integrand(e_a, w, t0, mu, sigma, t):
r = EnergyBarriers.rate(t0, e_a, t)
return r/(r**2 + w**2) * EnergyBarriers.energydistribution(e_a, mu, sigma)
omega = np.atleast_1d(omega)
temperature = np.atleast_1d(temperature)
e_axis = np.linspace(max(0, e_m-50*e_b), e_m+50*e_b, num=5001)
ret_val = np.array([simpson(integrand(e_axis, o, tau0, e_m, e_b, tt), e_axis)
for o in omega for tt in temperature])
return ret_val return ret_val
@staticmethod @staticmethod
def mean(*args): def mean(temperature, tau0, ea):
return args[1]*np.exp(args[2]/(kB*args[0])) return tau0*np.exp(ea/(kB*temperature))
@staticmethod @staticmethod
def logmean(*args): def logmean(temperature, tau0, ea):
return args[1] + args[2] / (kB * args[0]) return tau0 + ea / (kB * temperature)
@staticmethod @staticmethod
def max(*args): def max(*args):
return args[1] * np.exp(args[2] / (kB * args[0])) return args[1] * np.exp(args[2] / (kB * args[0]))
def _integrate_process_imag(args): # helper functions
pass def _integrate_c(func, omega: np.ndarray, temperature: np.ndarray, tau0: float, e_m: float, e_b: float) -> np.ndarray:
res = []
for o, t in product(omega, temperature):
c = (c_double * 5)(o, tau0, e_m, e_b, t)
user_data = cast(pointer(c), c_void_p)
area = quad(LowLevelCallable(func, user_data), 0, np.infty, epsabs=1e-13)[0]
res.append(area)
ret_val = np.array(res).reshape(omega.shape[0], temperature.shape[0])
return ret_val.squeeze()
def _integrate_process_real(args): def _integrate_py(func, axis, temp, tau0, e_m, e_b):
omega_i, t, tau0, mu, sigma, temp_j = args x = np.atleast_1d(axis)
return quad(_integrand_freq_real(), 0, 10, args=(omega_i, t, tau0, mu, sigma, temp_j))[0] temperature = np.atleast_1d(temp)
e_axis = np.linspace(max(0., e_m - 50*e_b), e_m + 50*e_b, num=5001)
ret_val = []
for o, tt in product(x, temperature):
ret_val.append(simpson(func(e_axis, o, tau0, e_m, e_b, tt), e_axis))
ret_val = np.array(ret_val).reshape(x.shape[0], temperature.shape[0])
return ret_val.squeeze()
def _integrate_process_time(args): # python integrands
omega_i, t, tau0, mu, sigma, temp_j = args def _integrand_sd(u, omega, tau0, mu, sigma, temp):
return quad(_integrand_time, 0, 10, args=(omega_i, t, tau0, mu, sigma, temp_j))[0]
def _integrand_freq_real(u, omega, tau0, mu, sigma, temp):
r = EnergyBarriers.rate(tau0, u, temp) r = EnergyBarriers.rate(tau0, u, temp)
return 1 / (r**2 + omega**2) * EnergyBarriers.energydistribution(u, mu, sigma) return r / (r**2 + omega**2) * EnergyBarriers.energy_distribution(u, mu, sigma)
def _integrand_freq_imag(u, omega, tau0, mu, sigma, temp): def _integrand_susc_real(u, omega, tau0, mu, sigma, temp):
r = EnergyBarriers.rate(tau0, u, temp)
return 1 / (r**2 + omega**2) * EnergyBarriers.energy_distribution(u, mu, sigma)
def _integrand_susc_imag(u, omega, tau0, mu, sigma, temp):
rate = EnergyBarriers.rate(tau0, u, temp) rate = EnergyBarriers.rate(tau0, u, temp)
return omega * rate / (rate**2 + omega**2) * EnergyBarriers.energydistribution(u, mu, sigma) return omega * rate / (rate**2 + omega**2) * EnergyBarriers.energy_distribution(u, mu, sigma)
def _integrand_time(u, t, tau0, mu, sigma, temp): def _integrand_time(u, t, tau0, mu, sigma, temp):
rate = EnergyBarriers.rate(tau0, u, temp) rate = EnergyBarriers.rate(tau0, u, temp)
return EnergyBarriers.energydistribution(u, mu, sigma) * np.exp(-t*rate) return EnergyBarriers.energy_distribution(u, mu, sigma) * np.exp(-t*rate)

View File

@ -0,0 +1,48 @@
from pathlib import Path
from ctypes import CDLL, c_double, c_void_p
from ..lib.logger import logger
lib = None
try:
lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so'))
# FFHS integrand
lib.ffhsSD.restype = c_double
lib.ffhsSD.argtypes = (c_double, c_void_p)
# Log-Gaussian integrands
lib.logGaussian_imag_high.restype = c_double
lib.logGaussian_imag_high.argtypes = (c_double, c_void_p)
lib.logGaussian_imag_low.restype = c_double
lib.logGaussian_imag_low.argtypes = (c_double, c_void_p)
lib.logGaussian_real_high.restype = c_double
lib.logGaussian_real_high.argtypes = (c_double, c_void_p)
lib.logGaussian_real_low.restype = c_double
lib.logGaussian_real_low.argtypes = (c_double, c_void_p)
lib.logGaussianCorrelation.restype = c_double
lib.logGaussianCorrelation.argtypes = (c_double, c_void_p)
# integrands for distribution of energies
lib.energyDist_SD.restype = c_double
lib.energyDist_SD.argtypes = (c_double, c_void_p)
lib.energyDistCorrelation.restype = c_double
lib.energyDistCorrelation.argtypes = (c_double, c_void_p)
lib.energyDistSuscReal.restype = c_double
lib.energyDistSuscReal.argtypes = (c_double, c_void_p)
lib.energyDistSuscImag.restype = c_double
lib.energyDistSuscImag.argtypes = (c_double, c_void_p)
HAS_C_FUNCS = True
logger.info('Use C functions')
except OSError:
HAS_C_FUNCS = False
logger.info('Use python functions')

View File

@ -1,9 +1,16 @@
import ctypes
import numpy as np import numpy as np
from scipy import LowLevelCallable
from scipy.integrate import quad from scipy.integrate import quad
from .helper import HAS_C_FUNCS, lib
from .base import Distribution from .base import Distribution
# Everything except spectral density is implemented in Python only because the only use case of FFHS is NMR
# field cycling measurements with T1 results
class FFHS(Distribution): class FFHS(Distribution):
name = 'Intermolecular (FFHS)' name = 'Intermolecular (FFHS)'
parameter = None parameter = None
@ -19,19 +26,30 @@ class FFHS(Distribution):
def integrand(u, tt, tau0): def integrand(u, tt, tau0):
return FFHS.distribution(u, tau0) * np.exp(-tt/u) / u return FFHS.distribution(u, tau0) * np.exp(-tt/u) / u
ret_val = np.array([quad(integrand, 0, np.infty, args=(tt, tau0), epsabs=1e-12, epsrel=1e-12)[0] for tt in t]) ret_val = np.array([quad(integrand, 0, np.infty, args=(tt, tau0))[0] for tt in t])
return ret_val return ret_val
@staticmethod @staticmethod
def specdens(omega, tau0, *args): def specdens_py(omega, tau0):
def integrand(u, o, tau0): def integrand(u, o, tau0):
return u**4 * tau0 / (81 + 9*u**2 - 2*u**4 + u**6) / (u**4 + (o*tau0)**2) return u**4 * tau0 / (81 + 9*u**2 - 2*u**4 + u**6) / (u**4 + (o*tau0)**2)
# return FFHS.distribution(u, tau0) * u / (1+o**2 * u**2) # return FFHS.distribution(u, tau0) * u / (1+o**2 * u**2)
ret_val = np.array([quad(integrand, 0, np.infty, args=(o, tau0), epsabs=1e-12, epsrel=1e-12)[0] for o in omega]) ret_val = np.array([quad(integrand, 0, np.infty, args=(o, tau0))[0] for o in omega])
return ret_val return ret_val * 54 / np.pi
@staticmethod
def specdens_c(omega, tau0):
res = []
for o in omega:
c = (ctypes.c_double * 2)(o, tau0)
user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p)
func = LowLevelCallable(lib.ffhsSD, user_data)
res.append(quad(func, 0, np.infty)[0])
return np.array(res) * 54 / np.pi
@staticmethod @staticmethod
def susceptibility(omega, tau0, *args): def susceptibility(omega, tau0, *args):
@ -41,15 +59,16 @@ class FFHS(Distribution):
def integrand_imag(u, o, tau0): def integrand_imag(u, o, tau0):
return FFHS.distribution(u, tau0) * o*u / (1+o**2 * u**2) return FFHS.distribution(u, tau0) * o*u / (1+o**2 * u**2)
ret_val = np.array([quad(integrand_real, 0, np.infty, args=(o, tau0), ret_val = np.array([quad(integrand_real, 0, np.infty, args=(o, tau0))[0] for o in omega], dtype=complex)
epsabs=1e-12, epsrel=1e-12)[0] for o in omega], dtype=complex)
ret_val.imag += np.array([quad(integrand_imag, 0, np.infty, args=(o, tau0), ret_val.imag += np.array([quad(integrand_imag, 0, np.infty, args=(o, tau0))[0] for o in omega])
epsabs=1e-12, epsrel=1e-12)[0] for o in omega])
return ret_val return ret_val
FFHS.specdens = FFHS.specdens_c if HAS_C_FUNCS else FFHS.specdens_py
# class Bessel(Distribution): # class Bessel(Distribution):
# name = 'Intermolecular (Bessel)' # name = 'Intermolecular (Bessel)'
# parameter = None # parameter = None

View File

@ -1,7 +1,13 @@
import ctypes
from multiprocessing import Pool, cpu_count from multiprocessing import Pool, cpu_count
from itertools import product from itertools import product
from typing import Callable
import numpy as np import numpy as np
from scipy import LowLevelCallable
from scipy.special import erf
from nmreval.lib.utils import ArrayLike
try: try:
from scipy.integrate import simpson from scipy.integrate import simpson
@ -9,89 +15,146 @@ except ImportError:
from scipy.integrate import simps as simpson from scipy.integrate import simps as simpson
from scipy.integrate import quad from scipy.integrate import quad
from .base import Distribution from nmreval.distributions.helper import HAS_C_FUNCS, lib
from nmreval.distributions.base import Distribution
__all__ = ['LogGaussian'] __all__ = ['LogGaussian']
# noinspection PyMethodOverriding
class LogGaussian(Distribution): class LogGaussian(Distribution):
name = 'Log-Gaussian' name = 'Log-Gaussian'
parameter = [r'\sigma'] parameter = [r'\sigma']
bounds = [(0, 10)] bounds = [(0, 10)]
@staticmethod @staticmethod
def distribution(tau, tau0, sigma: float): def distribution(tau: ArrayLike, tau0: ArrayLike, sigma: float) -> ArrayLike:
return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma
@staticmethod @staticmethod
def correlation(t, tau0, sigma: float): def correlation(t: ArrayLike, tau0: ArrayLike, sigma: float):
_t = np.atleast_1d(t) _t = np.atleast_1d(t)
_tau = np.atleast_1d(tau0) _tau = np.atleast_1d(tau0)
pool = Pool(processes=min(cpu_count(), 4)) if HAS_C_FUNCS:
integration_ranges = [(omega_i, tau_j, sigma) for (omega_i, tau_j) in product(_t, _tau)] res = _integrate_correlation_c(_t, _tau, sigma)
else:
res = _integration_parallel(_t, _tau, sigma, _integrate_process_time)
with np.errstate(divide='ignore'): return res.squeeze()
res = np.array(pool.map(_integrate_process_time, integration_ranges))
ret_val = res.reshape((_t.shape[0], _tau.shape[0]))
return ret_val.squeeze()
@staticmethod @staticmethod
def susceptibility(omega, tau0, sigma: float): def susceptibility(omega: ArrayLike, tau0: ArrayLike, sigma: float):
_omega = np.atleast_1d(omega) _omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0) _tau = np.atleast_1d(tau0)
pool = Pool(processes=min(cpu_count(), 4)) if HAS_C_FUNCS:
integration_ranges = [(omega_i, tau_j, sigma) for (omega_i, tau_j) in product(_omega, _tau)] res_real = _integrate_susc_real_c(_omega, _tau, sigma)
res_imag = _integrate_susc_imag_c(_omega, _tau, sigma)
else:
res_real = _integration_parallel(_omega, _tau, sigma, _integrate_process_imag)
res_imag = _integration_parallel(_omega, _tau, sigma, _integrate_process_real)
with np.errstate(divide='ignore'): return (res_real + 1j * res_imag).squeeze()
res_real = np.array(pool.map(_integrate_process_imag, integration_ranges))
res_imag = np.array(pool.map(_integrate_process_real, integration_ranges))
ret_val = (res_real+1j*res_imag).reshape((_omega.shape[0], _tau.shape[0]))
return ret_val.squeeze()
@staticmethod @staticmethod
def specdens(omega, tau0, sigma): def specdens(omega: ArrayLike, tau: ArrayLike, sigma: float) -> np.ndarray:
_omega = np.atleast_1d(omega) _omega = np.atleast_1d(omega)
_tau = np.atleast_1d(tau0) _tau = np.atleast_1d(tau)
pool = Pool(processes=min(cpu_count(), 4)) if HAS_C_FUNCS:
integration_ranges = [(omega_i, tau_j, sigma) for (omega_i, tau_j) in product(_omega, _tau)] ret_val = _integrate_susc_imag_c(_omega, _tau, sigma)
else:
with np.errstate(divide='ignore'): ret_val = _integration_parallel(_omega, _tau, sigma, _integrate_process_imag)
res = np.array(pool.map(_integrate_process_imag, integration_ranges))
ret_val = res.reshape((_omega.shape[0], _tau.shape[0]))
ret_val /= _omega[:, None] ret_val /= _omega[:, None]
ret_val[_omega == 0, :] = _tau[None, :] * np.exp(sigma**2 / 2)
return ret_val.squeeze() return ret_val.squeeze()
def mean(*args): @staticmethod
return args[0]*np.exp(args[1]**2 / 2) def mean(tau, sigma):
return tau*np.exp(sigma**2 / 2)
def _integrate_process_imag(args): def _integration_parallel(x: np.ndarray, tau: np.ndarray, sigma: float, func: Callable) -> np.ndarray:
omega_i, tau_j, sigma = args pool = Pool(processes=min(cpu_count(), 4))
area = quad(_integrand_freq_imag_high, 0, 50, args=(omega_i, tau_j, sigma))[0] integration_ranges = [(x_i, tau_j, sigma) for (x_i, tau_j) in product(x, tau)]
area += quad(_integrand_freq_imag_low, -50, 0, args=(omega_i, tau_j, sigma))[0]
with np.errstate(divide='ignore'):
res = pool.map(func, integration_ranges)
res = np.array(res).reshape((x.shape[0], tau.shape[0]))
return res
def _integrate_susc_imag_c(omega: np.ndarray, tau: np.ndarray, sigma: float) -> np.ndarray:
return _integrate_susc_c(lib.logGaussian_imag_low, lib.logGaussian_imag_high, omega, tau, sigma)
def _integrate_susc_real_c(omega: np.ndarray, tau: np.ndarray, sigma: float) -> np.ndarray:
return _integrate_susc_c(lib.logGaussian_real_low, lib.logGaussian_real_high, omega, tau, sigma)
def _integrate_susc_c(lowfunc, highfunc, omega, tau, sigma):
res = []
for o, t in product(omega, tau):
c = (ctypes.c_double * 3)(o, t, sigma)
user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p)
area = 0
for (func, limits) in [(highfunc, (0, np.inf)), (lowfunc, (-np.infty, 0))]:
epsabs = 1e-12
while epsabs > 1e-25:
a = quad(LowLevelCallable(func, user_data), *limits, epsabs=epsabs, epsrel=1e-12, full_output=1)
if a[2]['last'] > 2 or a[0] < 1e-48:
break
epsabs /= 10.
area += a[0]
res.append(area)
res = np.asanyarray(res).reshape((omega.shape[0], tau.shape[0]))
return res
def _integrate_process_imag(omega, tau, sigma):
area = quad(_integrand_freq_imag_high, 0, 50, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
area += quad(_integrand_freq_imag_low, -50, 0, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
return area return area
def _integrate_process_real(args): def _integrate_process_real(omega: float, tau: float, sigma: float):
omega_i, tau_j, sigma = args area = quad(_integrand_freq_real_high, 0, 50, args=(omega, tau, sigma))[0]
area = quad(_integrand_freq_real_high, 0, 50, args=(omega_i, tau_j, sigma))[0] area += quad(_integrand_freq_real_low, -50, 0, args=(omega, tau, sigma))[0]
area += quad(_integrand_freq_real_low, -50, 0, args=(omega_i, tau_j, sigma))[0]
return area return area
def _integrate_process_time(args): def _integrate_correlation_c(t, tau, sigma):
omega_i, tau_j, sigma = args res = []
return quad(_integrand_time, -50, 50, args=(omega_i, tau_j, sigma))[0]
for t_i, tau_i in product(t, tau):
c = (ctypes.c_double * 3)(t_i, tau_i, sigma)
user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p)
area = quad(LowLevelCallable(lib.logGaussianCorrelation, user_data), -np.infty, np.infty)[0]
res.append(area)
res = np.asanyarray(res).reshape((t.shape[0], tau.shape[0]))
return res
def _integrate_process_time(omega, tau, sigma):
return quad(_integrand_time, -50, 50, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
def _integrand_time(u, t, tau, sigma): def _integrand_time(u, t, tau, sigma):

View File

@ -1,289 +0,0 @@
import os
import argparse
import sys
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate
from scipy.integrate import simps
from ..io.dsc import DSCSample, Cyclohexane
parser = argparse.ArgumentParser(description='Calibrate DSC data')
parser.add_argument('sample', type=str, help='filename of DSC sample')
parser.add_argument('empty', type=str, help='filename of empty pan')
parser.add_argument('reference', help='filename of reference', type=str)
parser.add_argument('--cooling', help='Show figure of found cooling rates', action='store_true')
def evaluate(sample, empty, reference, ref_points=Cyclohexane, show_cooling=False):
sample = DSCSample(sample)
empty = DSCSample(empty)
reference = DSCSample(reference)
if show_cooling:
fig, ax = plt.subplots()
print('\n')
for k, v in sample.cooling.items():
print('Plot run {} with cooling rate {} K/min'.format(k, v))
c = sample.flow_data(v, mode='c')
ax.plot(c[0], c[1], label=str(v)+' K/min')
ax.set_xlabel('T / K')
plt.legend()
plt.show()
return
run_list = []
if len(sample.heating) > 1:
run = None
print('\nMultiple heat rates found:')
for k, v in sample.heating.items():
print(' run {}: {} K/min'.format(k, v))
while run not in sample.heating:
# choose your own adventure!!!
value = input('\nPlease select a run (press Enter for all heat rates): ')
if value == '':
run_list = list(sample.heating.keys())
break
else:
run = int(value)
run_list = [run]
else:
run_list = list(sample.heating.keys())
for run in run_list:
rate = sample.heating[run]
print('\nProcessing heat rate {} K/min'.format(rate))
print('Load data of heating data')
len_sample = sample.length(run)
# sanity checks
try:
reference_data = reference.flow_data(rate)
except IndexError:
print('ERROR: Reference measurement has no heat rate {} K/min'.format(rate))
print('Stop evaluation')
sys.exit()
try:
run_baseline = empty.get_run(rate)
except ValueError:
print('ERROR: Empty measurement has no heat rate {} K/min'.format(rate))
print('Stop evaluation')
sys.exit()
len_baseline = empty.length(run_baseline)
if len_baseline != len_sample:
print('WARNING: measurements differ by {} points'.format(abs(len_baseline - len_sample)))
# max_length = min(len_baseline, len_sample)
sample_data = sample.flow_data(rate, length=None)
empty_data = empty.flow_data(rate, length=None)
# plot input data
fig1, ax1 = plt.subplots(2, 3, **{'figsize': (10, 6)})
ax1[0, 0].set_title('raw data')
ax1[0, 0].set_xlabel('T / K')
ax1[0, 0].plot(sample_data[0], sample_data[1], 'k-', label='Sample')
ax1[0, 0].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
ax1[0, 0].plot(reference_data[0], reference_data[1], 'r-', label='Reference')
ax1[0, 0].legend()
print('Substract empty data\n')
sample_baseline = sample_data.copy()
empty_y = empty_data[1]
if len_sample != len_baseline:
with np.errstate(all='ignore'):
empty_y = scipy.interpolate.interp1d(empty_data[0], empty_data[1],
fill_value='extrapolate')(sample_data[0])
sample_baseline[1] = sample_data[1] - empty_y
# plot baseline correction
ax1[0, 1].set_title('baseline correction')
ax1[0, 1].set_xlabel('T / K')
ax1[0, 1].plot(sample_data[0], sample_data[1], 'k--', label='Raw')
ax1[0, 1].plot(sample_baseline[0], sample_baseline[1], 'k-', label='Baseline corrected')
ax1[0, 1].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
ax1[0, 1].legend()
print('Load isothermal data around heat rate')
mean_isotherms = []
for offset, where, ls in [(-1, 'low', '-'), (1, 'high', '--')]:
# read isotherms and baseline correct
len_baseline = empty.length(run_baseline+offset)
len_sample = sample.length(run+offset)
if len_baseline != len_sample:
print('WARNING: {} T isotherms differ by {} points'.format(where, abs(len_baseline-len_sample)))
max_length = min(len_baseline, len_sample)
isotherm_sample = sample.isotherm_data(run_baseline+offset, length=max_length)
isotherm_empty = empty.isotherm_data(run+offset, length=max_length)
isotherm_sample[1] -= isotherm_empty[1]
# get mean isotherm value
m = np.polyfit(isotherm_sample[0, 200:-200], isotherm_sample[1, 200:-200], 0)[0]
mean_isotherms.append(m)
print('Calculated {} heat flow: {:.4} mW'.format(where, m))
ax1[0, 2].plot(isotherm_sample[0], isotherm_sample[1], 'k--')
# calculate slope from difference between isotherms
slope = (mean_isotherms[1]-mean_isotherms[0]) / (sample_data[2, -1] - empty_data[2, 0])
print('Heat flow slope from isotherms: {:.4} per minute'.format(slope*60))
# calculate mean slope of heat flow at points in the beginning
slope_baseline = np.gradient(sample_baseline[1, int(4000/rate):int(9000/rate)],
sample_baseline[2, 300]-sample_baseline[2, 299]).mean()
print('Heat flow slope from initial heating: {:.4f} per minute\n'.format(slope_baseline*60))
drift_corrected = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope
drift_from_slope = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope_baseline
# plot
ax1[0, 2].axhline(mean_isotherms[0], linestyle=':')
ax1[0, 2].axhline(mean_isotherms[1], linestyle=':')
ax1[0, 2].plot(sample_baseline[2], sample_baseline[1], 'k-', label='Baseline corrected')
ax1[0, 2].plot(sample_baseline[2], drift_corrected, 'g-', label='Corrected (isotherm)')
ax1[0, 2].plot(sample_baseline[2], drift_from_slope, 'b-', label='Corrected (heating)')
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + (sample_baseline[2]-empty_data[2, 0])*slope, 'g--')
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + slope_baseline*(sample_baseline[2]-empty_data[2, 0]),
'b--')
ax1[0, 2].plot(sample_baseline[2, int(4000/rate):int(9000/rate)],
sample_baseline[1, int(4000/rate):int(9000/rate)], 'r--')
ax1[0, 2].set_title('time dependence')
ax1[0, 2].set_xlabel('t / s')
ax1[0, 2].legend()
melts = []
for i, (ref_temp, enthalpy) in enumerate(ref_points.transitions):
# region around reference peaks
t_low_lim = ref_temp - 15
t_high_lim = ref_temp + 15
low_border = np.argmin(np.abs(reference_data[0]-t_low_lim))
high_border = np.argmin(np.abs(reference_data[0]-t_high_lim))
ref_zoom = reference_data[:, low_border:high_border]
x_val = np.array([[ref_zoom[0, 0], 1],
[ref_zoom[0, -1], 1]])
y_val = np.array([ref_zoom[1, 0],
ref_zoom[1, -1]])
print('Baseline correct reference of %.2f transition' % ref_temp)
sol = np.linalg.solve(x_val, y_val)
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
peak_max = ref_zoom[:, np.argmax(ref_zoom[1])]
integration_limit = np.argmin(abs(ref_zoom[0]-peak_max[0]+3)), np.argmin(abs(ref_zoom[0]-peak_max[0]-3))
# substract baseline around reference peaks
x_val = np.array([[ref_zoom[0, integration_limit[0]], 1],
[ref_zoom[0, integration_limit[1]], 1]])
y_val = np.array([ref_zoom[1, integration_limit[0]],
ref_zoom[1, integration_limit[1]]])
print('Baseline correct reference of %.2f transition' % ref_temp)
sol = np.linalg.solve(x_val, y_val)
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
# calculate onset slope (use points at position of maximum gradient +/- 100/rate)
ref_grad = np.gradient(ref_zoom[1])
max_grad = np.argmax(ref_grad)
x_val = np.array([[ref_zoom[0, max_grad-int(100/rate)], 1],
[ref_zoom[0, max_grad+int(100/rate)+1], 1]])
y_val = np.array([ref_zoom[1, max_grad-int(100/rate)],
ref_zoom[1, max_grad+int(100/rate)+1]])
sol = np.linalg.solve(x_val, y_val)
onset = sol[0]*ref_zoom[0] + sol[1]
melts.append(-sol[1]/sol[0])
# plot
ax1[1, i].set_title(f'reference: {ref_temp:.2f}')
ax1[1, i].set_xlabel('T / K')
ax1[1, i].plot(reference_data[0], reference_data[1], 'r-')
ax1[1, i].plot(ref_zoom[0, max_grad], ref_zoom[0, max_grad], 'kx')
ax1[1, i].plot(ref_zoom[0], onset, 'k--')
ax1[1, i].axhline(0, color='k', linestyle='--')
ax1[1, i].set_xlim(ref_zoom[0, integration_limit[0]], ref_zoom[0, integration_limit[1]])
ax1[1, i].set_ylim(-max(ref_zoom[1])/10, max(ref_zoom[1])*1.1)
print('Onset of transition: %.2f K found at %.2f' % (ref_temp, melts[-1]))
if enthalpy is not None:
# integrate over low temperature peak to calibrate y axis
# delta H in J/g: Integrate Peak over time and divide by weight
area = 1e-3 * simps(ref_zoom[1, integration_limit[0]:integration_limit[1]],
ref_zoom[2, integration_limit[0]:integration_limit[1]],
even='avg')
calib_y_axis = enthalpy / (area / reference.weight)
print("Calibration factor of peak: %f\n" % calib_y_axis)
sample_baseline[1] *= calib_y_axis
fig1.delaxes(ax1[1, 2])
fig1.tight_layout()
plt.show()
# give a choice how to compensate for long-time drift
mode = None
while mode not in ['i', 'h']:
mode = input('\nUse [i]sotherms or initial [h]eating for long-time correction? (Default: i) ')
if mode == '':
mode = 'i'
if mode == 'h':
print('\nCorrect slope from initial heating')
sample_baseline[1] = drift_from_slope
else:
print('\nCorrect slope from isotherm')
sample_baseline[1] = drift_corrected
# calibrate T axis
print('\nCalibrate temperature')
real_trans = np.array([temp for (temp, _) in ref_points.transitions])
t_vals = np.array([[melts[0], 1],
[melts[1], 1]])
calibration_temp = np.linalg.solve(t_vals, real_trans)
print('T_real = {:.4f} * T_meas {:+.4f}'.format(*calibration_temp))
sample_baseline[0] = calibration_temp[0] * sample_baseline[0] + calibration_temp[1]
print('Convert to capacity')
cp = sample_baseline[1] * 60. / rate / sample.weight / 1000.
if sample.weight is None:
raise ValueError('No sample weight given')
# plot final results in separate figure
fig2, ax2 = plt.subplots()
ax2.set_title('{} K/min: Heat flow vs. heat capacity (close to cont.)'.format(rate))
ax2.set_xlabel('Temperature / K')
ax2.plot(sample_baseline[0], sample_baseline[1], label='heat flow')
ax2.plot(sample_baseline[0], cp, label='heat capacity')
plt.legend()
plt.show()
outname = os.path.splitext(sample.name)[0] + '_' + str(rate) + 'K-min.dat'
header = 'Made with version: {}\n'.format(__version__)
header += 'T/K\tCp/J/(gK)\theat flow/mW'
print()
print('Save to', outname)
np.savetxt(outname, np.c_[sample_baseline[0], cp, sample_baseline[1]], header=header)
if __name__ == '__main__':
args = parser.parse_args()
evaluate(args.sample, args.empty, args.reference, show_cooling=args.cooling)

View File

@ -1,292 +0,0 @@
from __future__ import annotations
__version__ = '0.1.2'
import os
from argparse import ArgumentParser
from pathlib import Path
import sys
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import simps
from nmreval.io.dsc import DSCReader, Cyclohexane, ReferenceValue
parser = ArgumentParser(description='Calibrate DSC data')
parser.add_argument('sample', type=str, help='filename of DSC sample')
parser.add_argument('empty', type=str, help='filename of empty pan')
parser.add_argument('reference', help='filename of reference', type=str)
parser.add_argument('--cooling', help='Plot found cooling rates', action='store_true')
def evaluate(sample: str|Path, empty: str|Path, reference: str|Path,
ref_points: ReferenceValue = Cyclohexane, show_cooling: bool = False):
sample = DSCReader(sample)
empty = DSCReader(empty)
reference = DSCReader(reference)
print(sample)
if show_cooling:
fig, ax = plt.subplots()
print('\n')
for k, v in sample.cooling.items():
print('Plot run {} with cooling rate {} K/min'.format(k, v))
c = sample.flow_data(v, mode='c')
ax.plot(c[0], c[1], label=str(v)+' K/min')
ax.set_xlabel('T / K')
plt.legend()
plt.show()
return
run_list = []
if len(sample.heating) > 1:
run = None
print('\nMultiple heat rates found:')
for k, v in sample.heating.items():
print(' run {}: {} K/min'.format(k, v))
while run not in sample.heating:
# choose your own adventure!!!
value = input('\nPlease select a run (press Enter for all heat rates): ')
if value == '':
run_list = list(sample.heating.keys())
break
else:
run = int(value)
run_list = [run]
else:
run_list = list(sample.heating.keys())
for run in run_list:
rate = sample.heating[run]
print('\nProcessing heat rate {} K/min'.format(rate))
print('Load data of heating data')
len_sample = sample.length(run)
# sanity checks
try:
reference_data = reference.flow_data(rate)
except IndexError:
print('ERROR: Reference measurement has no heat rate {} K/min'.format(rate))
print('Stop evaluation')
sys.exit()
try:
run_baseline = empty.get_run(rate)
except ValueError:
print('ERROR: Empty measurement has no heat rate {} K/min'.format(rate))
print('Stop evaluation')
sys.exit()
len_baseline = empty.length(run_baseline)
max_length = None
if len_baseline != len_sample:
print('WARNING: measurements differ by {} points'.format(abs(len_baseline - len_sample)))
max_length = min(len_baseline, len_sample)
sample_data = sample.flow_data(rate, length=max_length)
empty_data = empty.flow_data(rate, length=max_length)
# plot input data
fig1, ax1 = plt.subplots(2, 3, **{'figsize': (10, 6)})
ax1[0, 0].set_title('raw data')
ax1[0, 0].set_xlabel('T / K')
ax1[0, 0].plot(sample_data[0], sample_data[1], 'k-', label='Sample')
ax1[0, 0].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
ax1[0, 0].plot(reference_data[0], reference_data[1], 'r-', label='Reference')
ax1[0, 0].legend()
print('Substract empty data')
sample_baseline = sample_data.copy()
sample_baseline[1] = sample_data[1] - empty_data[1]
# plot baseline correction
ax1[0, 1].set_title('baseline correction')
ax1[0, 1].set_xlabel('T / K')
ax1[0, 1].plot(sample_data[0], sample_data[1], 'k--', label='Raw')
ax1[0, 1].plot(sample_baseline[0], sample_baseline[1], 'k-', label='Baseline corrected')
ax1[0, 1].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
ax1[0, 1].legend()
print('Load isothermal data around heat rate')
mean_isotherms = []
for offset, where, ls in [(-1, 'low', '-'), (1, 'high', '--')]:
# read isotherms and baseline correct
len_baseline = empty.length(run_baseline+offset)
len_sample = sample.length(run+offset)
if len_baseline != len_sample:
print('WARNING: {} T isotherms differ by {} points'.format(where, abs(len_baseline-len_sample)))
max_length = min(len_baseline, len_sample)
isotherm_sample = sample.isotherm_data(run_baseline+offset, length=max_length)
isotherm_empty = empty.isotherm_data(run+offset, length=max_length)
isotherm_sample[1] -= isotherm_empty[1]
# get mean isotherm value
m = np.polyfit(isotherm_sample[0, 200:-200], isotherm_sample[1, 200:-200], 0)[0]
mean_isotherms.append(m)
print('Calculated {} heat flow: {} mW'.format(where, m))
ax1[0, 2].plot(isotherm_sample[0], isotherm_sample[1], 'k--')
# calculate slope from difference between isotherms
slope = (mean_isotherms[1]-mean_isotherms[0]) / (sample_data[2, -1] - empty_data[2, 0])
print('Heat flow slope from isotherms: {} per minute'.format(slope*60))
# calculate mean slope of heat flow at points in the beginning
slope_baseline = np.gradient(sample_baseline[1, int(4000/rate):int(9000/rate)],
sample_baseline[2, 300]-sample_baseline[2, 299]).mean()
print('Heat flow slope from initial heating: {} per minute'.format(slope_baseline*60))
drift_corrected = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope
drift_from_slope = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope_baseline
# plot
ax1[0, 2].axhline(mean_isotherms[0], linestyle=':')
ax1[0, 2].axhline(mean_isotherms[1], linestyle=':')
ax1[0, 2].plot(sample_baseline[2], sample_baseline[1], 'k-', label='Baseline corrected')
ax1[0, 2].plot(sample_baseline[2], drift_corrected, 'g-', label='Corrected (isotherm)')
ax1[0, 2].plot(sample_baseline[2], drift_from_slope, 'b-', label='Corrected (heating)')
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + (sample_baseline[2]-empty_data[2, 0])*slope, 'g--')
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + slope_baseline*(sample_baseline[2]-empty_data[2, 0]),
'b--')
ax1[0, 2].plot(sample_baseline[2, int(4000/rate):int(9000/rate)],
sample_baseline[1, int(4000/rate):int(9000/rate)], 'r--')
ax1[0, 2].set_title('time dependence')
ax1[0, 2].set_xlabel('t / s')
ax1[0, 2].legend()
melts = []
for i, (trans_temp, enthalpy) in enumerate(ref_points.transitions):
print(trans_temp, enthalpy)
# region around reference peaks
# NOTE: limits are hard coded for cyclohexane, other references need other limits
low_border = np.argmin(abs(reference_data[0]-(trans_temp-15)))
high_border = np.argmin(abs(reference_data[0]-(trans_temp+15)))
ref_zoom = reference_data[:, low_border:high_border]
x_val = np.array([[ref_zoom[0, 0], 1],
[ref_zoom[0, -1], 1]])
y_val = np.array([ref_zoom[1, 0],
ref_zoom[1, -1]])
print('Baseline correct reference of {} transition'.format(trans_temp))
sol = np.linalg.solve(x_val, y_val)
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
peak_max = ref_zoom[:, np.argmax(ref_zoom[1])]
integration_limit = np.argmin(abs(ref_zoom[0]-peak_max[0]+3)), np.argmin(abs(ref_zoom[0]-peak_max[0]-3))
# substract baseline around reference peaks
x_val = np.array([[ref_zoom[0, integration_limit[0]], 1],
[ref_zoom[0, integration_limit[1]], 1]])
y_val = np.array([ref_zoom[1, integration_limit[0]],
ref_zoom[1, integration_limit[1]]])
print('Baseline correct reference of {} transition'.format(trans_temp))
sol = np.linalg.solve(x_val, y_val)
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
# calculate onset slope (use points at position of maximum gradient +/- 100/rate)
ref_grad = np.gradient(ref_zoom[1])
max_grad = np.argmax(ref_grad)
x_val = np.array([[ref_zoom[0, max_grad-int(100/rate)], 1],
[ref_zoom[0, max_grad+int(100/rate)+1], 1]])
y_val = np.array([ref_zoom[1, max_grad-int(100/rate)],
ref_zoom[1, max_grad+int(100/rate)+1]])
sol = np.linalg.solve(x_val, y_val)
onset = sol[0]*ref_zoom[0] + sol[1]
melts.append(-sol[1]/sol[0])
# plot
ax1[1, i].set_title('reference: {:.2f} K'.format(trans_temp))
ax1[1, i].set_xlabel('T / K')
ax1[1, i].plot(reference_data[0], reference_data[1], 'r-')
ax1[1, i].plot(ref_zoom[0, max_grad], ref_zoom[0, max_grad], 'kx')
ax1[1, i].plot(ref_zoom[0], onset, 'k--')
ax1[1, i].axhline(0, color='k', linestyle='--')
ax1[1, i].set_xlim(ref_zoom[0, integration_limit[0]], ref_zoom[0, integration_limit[1]])
ax1[1, i].set_ylim(-max(ref_zoom[1])/10, max(ref_zoom[1])*1.1)
print('Onset of transition: {:.2f} K, should be at {:.2f}'.format(melts[-1], trans_temp))
if enthalpy is not None:
# integrate over low temperature peak to calibrate y axis
# NOTE: again, this is only valid for cyclohexane
# delta H in J/g: Integrate Peak over time and divide by weight
area = 1e-3 * simps(ref_zoom[1, integration_limit[0]:integration_limit[1]],
ref_zoom[2, integration_limit[0]:integration_limit[1]],
even='avg')
calib_y_axis = enthalpy / (area / reference.weight)
print("Calibration factor of peak: {}".format(calib_y_axis))
sample_baseline[1] *= calib_y_axis
fig1.delaxes(ax1[1, 2])
fig1.tight_layout()
plt.show()
# give a choice how to compensate for long-time drift
mode = None
while mode not in ['i', 'h']:
mode = input('\nUse [i]sotherms or initial [h]eating for long-time correction? (Default: i) ')
if mode == '':
mode = 'i'
if mode == 'h':
print('\nCorrect slope from initial heating')
sample_baseline[1] = drift_from_slope
else:
print('\nCorrect slope from isotherm')
sample_baseline[1] = drift_corrected
# calibrate T axis
print('\nCalibrate temperature')
real_trans = np.array([ref_points.transition1, ref_points.transition2])
t_vals = np.array([[melts[0], 1],
[melts[1], 1]])
calibration_temp = np.linalg.solve(t_vals, real_trans)
print('T_real = {:.4f} * T_meas {:+.4f}'.format(*calibration_temp))
sample_baseline[0] = calibration_temp[0] * sample_baseline[0] + calibration_temp[1]
print('Convert to capacity')
cp = sample_baseline[1] * 60. / rate / sample.weight / 1000.
if sample.weight is None:
raise ValueError('No sample weight given')
# plot final results in separate figure
fig2, ax2 = plt.subplots()
ax2.set_title('{} K/min: Heat flow vs. heat capacity (close to cont.)'.format(rate))
ax2.set_xlabel('Temperature / K')
ax2.plot(sample_baseline[0], sample_baseline[1], label='heat flow')
ax2.plot(sample_baseline[0], cp, label='heat capacity')
plt.legend()
plt.show()
outname = os.path.splitext(sample.name)[0] + '_' + str(rate) + 'K-min.dat'
header = 'Made with version: {}\n'.format(__version__)
header += 'T/K\tCp/J/(gK)\theat flow/mW'
print()
print('Save to', outname)
np.savetxt(outname, np.c_[sample_baseline[0], cp, sample_baseline[1]], header=header)
if __name__ == '__main__':
args = parser.parse_args()
evaluate(args.sample, args.empty, args.reference, show_cooling=args.cooling)

27
src/nmreval/dsc/hodge.py Normal file
View File

@ -0,0 +1,27 @@
import numpy as np
from scipy.stats import linregress
from nmreval.data import Points
from nmreval.fit.minimizer import FitRoutine
from nmreval.fit.result import FitResult
from nmreval.lib.utils import ArrayLike
from nmreval.models import Arrhenius
from nmreval.utils import kB
def tau_hodge(tg: ArrayLike, rate: ArrayLike) -> (Points, FitResult):
rate = np.asanyarray(rate)
tg = np.asanyarray(tg)
fitter = FitRoutine()
fitter.set_model(Arrhenius)
d = fitter.add_data(1000/tg, rate, we='y')
init = linregress(1000/tg, np.log(rate))
d.set_parameter([np.exp(init.intercept), 1000*init.slope*kB], fun_kwargs={'invt': 'invt1000'})
res = fitter.run()[0]
de = res.parameter['E_{A}']
tau = kB*tg**2/np.abs(de.value)/rate*60
return Points(x=1000/tg, y=tau, y_err=tau*de.error/np.abs(de.value), name='Hodge'), res

View File

@ -0,0 +1,46 @@
import numpy as np
from nmreval.utils.constants import R_joule as R
class TNMH:
type = 'DSC'
name = 'TNMH model'
equation = r''
params = [r'\tau_{g}', 'x', r'\beta', r'\Delta H', 'T_{g}', 'rate']
bounds = [(0, None), (0, 1), (0, 1), (0, None), (0, None), (0, None)]
@staticmethod
def func(x: np.ndarray, tau_g: float, xx: float, beta: float, energy: float, tg: float, rate: float) -> np.ndarray:
model_temp = np.r_[x[::-1], x[1:]]
curve = TNMH.tnm_function(model_temp, tau_g, xx, beta, energy, tg, rate)[x.size - 1:]
res = np.gradient(curve, x)
return res
@staticmethod
def tnm_function(x: np.ndarray, tau_g: float, xx: float, beta: float, energy: float, tg: float, rate: float) -> np.ndarray:
step = x.size
Tf = np.empty(step)
Tf[0] = x[0]
delta_temp = np.diff(x)
delta_time = np.abs(delta_temp) * 60 / rate
tau = np.empty(step)
dt_by_tau = np.zeros(step)
temp_0 = x[0]
for i in range(0, step - 1):
tau[i] = TNMH.relax(x[i+1], Tf[i], tau_g, tg, energy, xx)
dt_by_tau[:i] += delta_time[i] / tau[i]
Tf[i + 1] = np.sum(delta_temp[:i] * (1 - np.exp(-dt_by_tau[:i] ** beta))) + temp_0
return Tf
@staticmethod
def relax(t, tf, tau_g, t_glass, ea, x):
h = ea/R
return tau_g * np.exp((x*h / t) + ((1 - x) * h / tf) - h / t_glass)

View File

@ -24,12 +24,13 @@ class ModelFactory:
param_len.append(len(func['func'].params)) param_len.append(len(func['func'].params))
if func['children']: if func['children']:
right, _, _ = ModelFactory.create_from_list(func['children'], left=func['func'], left_cnt=func['pos'], right, _, _ = ModelFactory.create_from_list(func['children'], left_cnt=func['pos'],
func_order=func_order, param_len=param_len) func_order=func_order, param_len=param_len)
right_cnt = None right_cnt = None
right = MultiModel(func['func'], right, func['children'][0]['op'], left_idx=func['cnt'], right_idx=None)
else: else:
right = func['func'] right = func['func']
right_cnt = func['pos'] right_cnt = func['cnt']
if left is None: if left is None:
left = right left = right
@ -46,7 +47,13 @@ class MultiModel:
str_op = {'+': operator.add, '*': operator.mul, '-': operator.sub, '/': operator.truediv} str_op = {'+': operator.add, '*': operator.mul, '-': operator.sub, '/': operator.truediv}
int_op = {0: operator.add, 1: operator.mul, 2: operator.sub, 3: operator.truediv} int_op = {0: operator.add, 1: operator.mul, 2: operator.sub, 3: operator.truediv}
def __init__(self, left: Any, right: Any, op: str | Callable | int = '+', left_idx=0, right_idx=1): def __init__(self,
left: Any,
right: Any,
op: str | Callable | int = '+',
left_idx: int | None = 0,
right_idx: int | None = 1,
):
self._left = left self._left = left
self._right = right self._right = right
@ -68,6 +75,7 @@ class MultiModel:
self._kwargs_right = {} self._kwargs_right = {}
self._kwargs_left = {} self._kwargs_left = {}
self.fun_kwargs = {} self.fun_kwargs = {}
self.idx = (left_idx, right_idx)
# mapping kwargs to kwargs of underlying functions # mapping kwargs to kwargs of underlying functions
self._ext_int_kw = {} self._ext_int_kw = {}
@ -178,13 +186,13 @@ class MultiModel:
if isinstance(self._left, MultiModel): if isinstance(self._left, MultiModel):
yield from self._left.sub_name() yield from self._left.sub_name()
elif hasattr(self._left, 'name'): elif hasattr(self._left, 'name'):
yield self._left.name yield f'{self._left.name}({self.idx[0]})'
else: else:
yield self.name + '(lhs)' yield self.name + '(lhs)'
if isinstance(self._right, MultiModel): if isinstance(self._right, MultiModel):
yield from self._right.sub_name() yield from self._right.sub_name()
elif hasattr(self._right, 'name'): elif hasattr(self._right, 'name'):
yield self._right.name yield f'{self._right.name}({self.idx[1]})'
else: else:
yield self.name + '(rhs)' yield self.name + '(rhs)'

View File

@ -1,7 +1,9 @@
from __future__ import annotations
import numpy as np import numpy as np
from .model import Model from .model import Model
from .parameter import Parameters from .parameter import Parameters, Parameter
class Data(object): class Data(object):
@ -11,27 +13,27 @@ class Data(object):
if self.y.shape[0] != self.x.shape[0]: if self.y.shape[0] != self.x.shape[0]:
raise ValueError(f'x and y have different lengths {self.x.shape[0]} and {self.y.shape[0]}') raise ValueError(f'x and y have different lengths {self.x.shape[0]} and {self.y.shape[0]}')
self.we = self._calc_weights(we) self.we, self.we_string = self._calc_weights(we)
self.idx = idx self.idx = idx
self.model = None self.model = None
self.minimizer = None self.minimizer = None
self.parameter = Parameters() self.parameter = Parameters()
self.para_keys = None self.para_keys: list = []
self.fun_kwargs = {} self.fun_kwargs = {}
def __len__(self): def __len__(self):
return self.y.shape[0] return self.y.shape[0]
def _calc_weights(self, we): def _calc_weights(self, we: str | np.ndarray | None) -> tuple[np.ndarray, str]:
if we is None: if isinstance(we, str) or we is None:
return 1. if we is None or we.lower() == 'none':
we_string = 'None'
if isinstance(we, str):
if we == 'y2':
we_func = lambda yy: 1. / yy**2
elif we.lower() == 'none':
we_func = lambda yy: np.ones_like(len(yy)) we_func = lambda yy: np.ones_like(len(yy))
elif we == 'y2':
we_string = we
we_func = lambda yy: 1. / yy**2
else: else:
we_string = we
we_func = lambda yy: 1. / np.abs(yy) we_func = lambda yy: 1. / np.abs(yy)
if np.iscomplexobj(self.y): if np.iscomplexobj(self.y):
@ -40,6 +42,7 @@ class Data(object):
weights = we_func(self.y) weights = we_func(self.y)
else: else:
we_string = 'yerr' # This is pure speculation that array equals error
we = 1. / np.asarray(we) we = 1. / np.asarray(we)
if np.iscomplexobj(self.y): if np.iscomplexobj(self.y):
if np.iscomplexobj(we): if np.iscomplexobj(we):
@ -51,7 +54,7 @@ class Data(object):
weights[weights == np.inf] = np.finfo(float).max weights[weights == np.inf] = np.finfo(float).max
return weights return weights, we_string
def set_model(self, func, *args, **kwargs): def set_model(self, func, *args, **kwargs):
if isinstance(func, Model): if isinstance(func, Model):
@ -68,12 +71,19 @@ class Data(object):
def get_model(self): def get_model(self):
return self.model return self.model
def set_parameter(self, parameter, var=None, ub=None, lb=None, def set_parameter(self,
default_bounds=False, fun_kwargs=None): values: list[float | Parameter],
*,
var: list[bool] = None,
ub: list[float] = None,
lb: list[float] = None,
default_bounds: bool = False,
fun_kwargs: dict = None
):
""" """
Creates parameter for this data. Creates parameter for this data.
If no Model is available, it falls back to the model If no Model is available, it falls back to the model
:param parameter: list of parameters :param values: list of parameters
:param var: list of boolean or boolean; False fixes parameter at given list index. :param var: list of boolean or boolean; False fixes parameter at given list index.
Single value is broadcast to all parameter Single value is broadcast to all parameter
:param ub: list of upper boundaries or float; Single value is broadcast to all parameter. :param ub: list of upper boundaries or float; Single value is broadcast to all parameter.
@ -87,23 +97,46 @@ class Data(object):
model = self.model model = self.model
if model is None: if model is None:
# Data has no unique # Data has no unique
if self.minimizer is None: if self.minimizer is not None:
model = None
else:
model = self.minimizer.fit_model model = self.minimizer.fit_model
self.fun_kwargs.update(model.fun_kwargs)
if model is None: if model is None:
raise ValueError('No model found, please set model before parameters') raise ValueError('No model found, please set model before parameters')
if default_bounds: if len(values) != len(model.params):
raise ValueError('Number of given parameter does not match number of model parameters')
is_parameter = [isinstance(v, Parameter) for v in values]
if all(is_parameter):
for p_i in values:
key = f"p{next(Parameters.parameter_counter)}"
self.parameter.add_parameter(key, p_i)
elif any(is_parameter):
raise ValueError('list of parameter are not all float of Parameter')
else:
if var is None:
var = [True] * len(values)
if lb is None: if lb is None:
lb = model.lb if default_bounds:
lb = model.lb
else:
lb = [None] * len(values)
if ub is None: if ub is None:
ub = model.ub if default_bounds:
ub = model.ub
else:
ub = [None] * len(values)
self.para_keys = self.parameter.add_parameter(parameter, var=var, lb=lb, ub=ub) arg_names = ['name', 'value', 'var', 'lb', 'ub']
for parameter_arg in zip(model.params, values, var, lb, ub):
self.parameter.add(**{arg_name: arg_value for arg_name, arg_value in zip(arg_names, parameter_arg)})
self.para_keys = list(self.parameter.keys())
self.fun_kwargs.update(model.fun_kwargs)
if fun_kwargs is not None: if fun_kwargs is not None:
self.fun_kwargs.update(fun_kwargs) self.fun_kwargs.update(fun_kwargs)
@ -123,6 +156,18 @@ class Data(object):
else: else:
return [p.value for p in self.minimizer.parameters[self.parameter]] return [p.value for p in self.minimizer.parameters[self.parameter]]
def replace_parameter(self, key: str, parameter: Parameter) -> None:
tobereplaced = None
for k, v in self.parameter.items():
if v.name == parameter.name:
tobereplaced = k
break
if tobereplaced is None:
raise KeyError(f'Global parameter {key} not found in list of parameters')
self.para_keys[self.para_keys.index(tobereplaced)] = key
self.parameter.replace_parameter(tobereplaced, key, parameter)
def cost(self, p): def cost(self, p):
""" """
Cost function :math:`y-f(p, x)` Cost function :math:`y-f(p, x)`

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import warnings import warnings
from itertools import product from itertools import product
@ -14,21 +16,81 @@ from .result import FitResultCreator
__all__ = ['FitRoutine', 'FitAbortException'] __all__ = ['FitRoutine', 'FitAbortException']
from ..lib.logger import logger
class FitAbortException(Exception): class FitAbortException(Exception):
pass pass
# COST FUNCTIONS: f(x) - y (least_square, minimize), and f(x) (ODR)
def _cost_scipy_glob(p: list[float], data: list[Data], varpars: list[str], used_pars: list[list[str]]):
# replace values
for keys, values in zip(varpars, p):
for data_i in data:
if keys in data_i.parameter.keys():
# TODO move this to scaled_value setter
data_i.parameter[keys].scaled_value = values
data_i.parameter[keys].namespace[keys] = data_i.parameter[keys].value
r = []
# unpack parameter and calculate y values and concatenate all
for values, p_idx in zip(data, used_pars):
actual_parameters = [values.parameter[keys].value for keys in p_idx]
r = np.r_[r, values.cost(actual_parameters)]
return r
def _cost_scipy(p, data, varpars, used_pars):
for keys, values in zip(varpars, p):
data.parameter[keys].scaled_value = values
data.parameter[keys].namespace[keys] = data.parameter[keys].value
actual_parameters = [data.parameter[keys].value for keys in used_pars]
return data.cost(actual_parameters)
def _cost_odr(p: list[float], data: Data, varpars: list[str], used_pars: list[str], fitmode: int=0):
for keys, values in zip(varpars, p):
data.parameter[keys].scaled_value = values
data.parameter[keys].namespace[keys] = data.parameter[keys].value
actual_parameters = [data.parameter[keys].value for keys in used_pars]
return data.func(actual_parameters, data.x)
def _cost_odr_glob(p: list[float], data: list[Data], var_pars: list[str], used_pars: list[str]):
# replace values
for data_i in data:
_update_parameter(data_i, var_pars, p)
r = []
# unpack parameter and calculate y values and concatenate all
for values, p_idx in zip(data, used_pars):
actual_parameters = [values.parameter[keys].value for keys in p_idx]
r = np.r_[r, values.func(actual_parameters, values.x)]
return r
def _update_parameter(data: Data, varied_keys: list[str], parameter: list[float]):
for keys, values in zip(varied_keys, parameter):
if keys in data.parameter.keys():
data.parameter[keys].scaled_value = values
data.parameter[keys].namespace[keys] = data.parameter[keys].value
class FitRoutine(object): class FitRoutine(object):
def __init__(self, mode='lsq'): def __init__(self, mode='lsq'):
self._fitmethod = mode self.fitmethod = mode
self.data = [] self.data = []
self.fit_model = None self.fit_model = None
self._no_own_model = [] self._no_own_model = []
self.parameter = Parameters()
self.result = [] self.result = []
self.linked = [] self.linked = []
self._abort = False self._abort = False
self.step = 0
def add_data(self, x, y=None, we=None, idx=None): def add_data(self, x, y=None, we=None, idx=None):
if isinstance(x, Data): if isinstance(x, Data):
@ -78,29 +140,27 @@ class FitRoutine(object):
return self.fit_model return self.fit_model
def set_link_parameter(self, parameter: tuple, replacement: tuple): def set_link_parameter(self, dismissed_param: tuple[Model | Data, str], replacement: tuple[Model, str]):
if isinstance(replacement[0], Model): if isinstance(replacement[0], Model):
if replacement[1] not in replacement[0].global_parameter: if replacement[1] not in replacement[0].parameter:
raise KeyError(f'Parameter at pos {replacement[1]} of ' raise KeyError(f'Parameter {replacement[1]} of '
f'model {str(replacement[0])} is not global') f'model {replacement[0]} is not global')
if isinstance(parameter[0], Model): if isinstance(dismissed_param[0], Model):
warnings.warn(f'Replaced parameter at pos {parameter[1]} in {str(parameter[0])} ' warnings.warn(f'Replaced parameter {dismissed_param[1]} in {dismissed_param[0]} '
f'becomes global with linkage.') f'becomes global with linkage.')
self.linked.append((*parameter, *replacement)) self.linked.append((*dismissed_param, *replacement))
def prepare_links(self): def prepare_links(self):
self._no_own_model = [] self._no_own_model = []
self.parameter = Parameters()
_found_models = {} _found_models = {}
linked_sender = {} linked_sender = {}
for v in self.data: for v in self.data:
linked_sender[v] = set() linked_sender[v] = set()
self.parameter.update(v.parameter.copy())
# set temporaray model # set temporary model
if v.model is None: if v.model is None:
v.model = self.fit_model v.model = self.fit_model
self._no_own_model.append(v) self._no_own_model.append(v)
@ -108,8 +168,6 @@ class FitRoutine(object):
# register model # register model
if v.model not in _found_models: if v.model not in _found_models:
_found_models[v.model] = [] _found_models[v.model] = []
m_param = v.model.parameter.copy()
self.parameter.update(m_param)
_found_models[v.model].append(v) _found_models[v.model].append(v)
@ -117,24 +175,21 @@ class FitRoutine(object):
linked_sender[v.model] = set() linked_sender[v.model] = set()
linked_parameter = {} linked_parameter = {}
for par, par_parm, repl, repl_par in self.linked: for dismiss_model, dismiss_param, replace_model, replace_param in self.linked:
if isinstance(par, Data): linked_sender[replace_model].add(dismiss_model)
if isinstance(repl, Data): linked_sender[replace_model].add(replace_model)
linked_parameter[par.para_keys[par_parm]] = repl.para_keys[repl_par]
else:
linked_parameter[par.para_keys[par_parm]] = repl.global_parameter[repl_par]
replace_key = replace_model.parameter.get_key(replace_param)
dismiss_key = dismiss_model.parameter.get_key(dismiss_param)
if isinstance(replace_model, Data):
linked_parameter[dismiss_key] = replace_key
else: else:
if isinstance(repl, Data): p = dismiss_model.set_global_parameter(dismiss_param, replace_key)
par.global_parameter[par_parm] = repl.para_keys[repl_par] p._expr_disp = replace_param
else:
par.global_parameter[par_parm] = repl.global_parameter[repl_par]
linked_sender[repl].add(par)
linked_sender[par].add(repl)
for mm, m_data in _found_models.items(): for mm, m_data in _found_models.items():
if mm.global_parameter: if mm.parameter:
for dd in m_data: for dd in m_data:
linked_sender[mm].add(dd) linked_sender[mm].add(dd)
linked_sender[dd].add(mm) linked_sender[dd].add(mm)
@ -163,15 +218,16 @@ class FitRoutine(object):
self.find_paths(neighbor, graph, coupled_nodes, visited_nodes) self.find_paths(neighbor, graph, coupled_nodes, visited_nodes)
def abort(self): def abort(self):
print('ABORT ???') logger.info('Fit aborted by user')
self._abort = True self._abort = True
def run(self, mode='lsq'): def run(self, mode: str = None):
self._abort = False self._abort = False
self.parameter = Parameters()
if mode is None:
mode = self.fitmethod
fit_groups, linked_parameter = self.prepare_links() fit_groups, linked_parameter = self.prepare_links()
for data_groups in fit_groups: for data_groups in fit_groups:
if len(data_groups) == 1 and not self.linked: if len(data_groups) == 1 and not self.linked:
data = data_groups[0] data = data_groups[0]
@ -202,17 +258,32 @@ class FitRoutine(object):
self.unprep_run() self.unprep_run()
for r in self.result:
r.pprint()
return self.result return self.result
def make_preview(self, x: np.ndarray) -> list[np.ndarray]:
y_pred = []
fit_groups, linked_parameter = self.prepare_links()
for data_groups in fit_groups:
data = data_groups[0]
actual_parameters = [p.value for p in data.parameter.values()]
y_pred.append(data.func(actual_parameters, x))
return y_pred
def _prep_data(self, data): def _prep_data(self, data):
if data.get_model() is None: if data.get_model() is None:
data._model = self.fit_model data._model = self.fit_model
self._no_own_model.append(data) self._no_own_model.append(data)
# data.parameter.prepare_bounds()
return self._prep_parameter(data.parameter) return self._prep_parameter(data.parameter)
@staticmethod @staticmethod
def _prep_parameter(parameter): def _prep_parameter(parameter: Parameters):
vals = [] vals = []
var_pars = [] var_pars = []
for p_k, v_k in parameter.items(): for p_k, v_k in parameter.items():
@ -224,29 +295,26 @@ class FitRoutine(object):
return pp, lb, ub, var_pars return pp, lb, ub, var_pars
def _prep_global(self, data_group, linked): @staticmethod
def _prep_global(data_group: list[Data], linked):
p0 = [] p0 = []
lb = [] lb = []
ub = [] ub = []
var = [] var = []
data_pars = [] data_pars = []
# loopyloop over data that belong to one fit (linked or global) # loopy-loop over data that belong to one fit (linked or global)
for data in data_group: for data in data_group:
# is parameter replaced by global parameter?
for k, v in data.model.parameter.items():
data.replace_parameter(k, v)
# data.parameter.prepare_bounds()
actual_pars = [] actual_pars = []
for i, (p_k, v_k) in enumerate(data.parameter.items()): for i, p_k in enumerate(data.para_keys):
p_k_used = p_k p_k_used = p_k
v_k_used = v_k v_k_used = data.parameter[p_k]
# is parameter replaced by global parameter?
if i in data.model.global_parameter:
p_k_used = data.model.global_parameter[i]
v_k_used = self.parameter[p_k_used]
# links trump global parameter
if p_k_used in linked:
p_k_used = linked[p_k_used]
v_k_used = self.parameter[p_k_used]
actual_pars.append(p_k_used) actual_pars.append(p_k_used)
# parameter is variable and was not found before as shared parameter # parameter is variable and was not found before as shared parameter
@ -265,58 +333,20 @@ class FitRoutine(object):
d._model = None d._model = None
self._no_own_model = [] self._no_own_model = []
Parameters.reset()
# COST FUNCTIONS: f(x) - y (least_square, minimize), and f(x) (ODR)
def __cost_scipy(self, p, data, varpars, used_pars):
for keys, values in zip(varpars, p):
self.parameter[keys].scaled_value = values
actual_parameters = [self.parameter[keys].value for keys in used_pars]
return data.cost(actual_parameters)
def __cost_odr(self, p, data, varpars, used_pars):
for keys, values in zip(varpars, p):
self.parameter[keys].scaled_value = values
actual_parameters = [self.parameter[keys].value for keys in used_pars]
return data.func(actual_parameters, data.x)
def __cost_scipy_glob(self, p, data, varpars, used_pars):
# replace values
for keys, values in zip(varpars, p):
self.parameter[keys].scaled_value = values
r = []
# unpack parameter and calculate y values and concatenate all
for values, p_idx in zip(data, used_pars):
actual_parameters = [self.parameter[keys].value for keys in p_idx]
r = np.r_[r, values.cost(actual_parameters)]
return r
def __cost_odr_glob(self, p, data, varpars, used_pars):
# replace values
for keys, values in zip(varpars, p):
self.parameter[keys].scaled_value = values
r = []
# unpack parameter and calculate y values and concatenate all
for values, p_idx in zip(data, used_pars):
actual_parameters = [self.parameter[keys].value for keys in p_idx]
r = np.r_[r, values.func(actual_parameters, values.x)]
return r
def _least_squares_single(self, data, p0, lb, ub, var): def _least_squares_single(self, data, p0, lb, ub, var):
self.step = 0
def cost(p): def cost(p):
self.step += 1
if self._abort: if self._abort:
raise FitAbortException(f'Fit aborted by user') raise FitAbortException(f'Fit aborted by user')
return self.__cost_scipy(p, data, var, data.para_keys) return _cost_scipy(p, data, var, data.para_keys)
with np.errstate(all='ignore'): with np.errstate(all='ignore'):
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=1000 * len(p0)) res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=500 * len(p0))
err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape) err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
self.make_results(data, res.x, var, data.para_keys, res.jac.shape, self.make_results(data, res.x, var, data.para_keys, res.jac.shape,
@ -324,12 +354,13 @@ class FitRoutine(object):
def _least_squares_global(self, data, p0, lb, ub, var, data_pars): def _least_squares_global(self, data, p0, lb, ub, var, data_pars):
def cost(p): def cost(p):
self.step += 1
if self._abort: if self._abort:
raise FitAbortException(f'Fit aborted by user') raise FitAbortException(f'Fit aborted by user')
return self.__cost_scipy_glob(p, data, var, data_pars) return _cost_scipy_glob(p, data, var, data_pars)
with np.errstate(all='ignore'): with np.errstate(all='ignore'):
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=1000 * len(p0)) res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=500 * len(p0))
err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape) err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
for v, var_pars_k in zip(data, data_pars): for v, var_pars_k in zip(data, data_pars):
@ -338,25 +369,27 @@ class FitRoutine(object):
def _nm_single(self, data, p0, lb, ub, var): def _nm_single(self, data, p0, lb, ub, var):
def cost(p): def cost(p):
self.step += 1
if self._abort: if self._abort:
raise FitAbortException(f'Fit aborted by user') raise FitAbortException(f'Fit aborted by user')
return (self.__cost_scipy(p, data, var, data.para_keys)**2).sum() return (_cost_scipy(p, data, var, data.para_keys) ** 2).sum()
with np.errstate(all='ignore'): with np.errstate(all='ignore'):
res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)], res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)],
method='Nelder-Mead', options={'maxiter': 1000 * len(p0)}) method='Nelder-Mead', options={'maxiter': 500 * len(p0)})
self.make_results(data, res.x, var, data.para_keys, (len(data), len(p0))) self.make_results(data, res.x, var, data.para_keys, (len(data), len(p0)))
def _nm_global(self, data, p0, lb, ub, var, data_pars): def _nm_global(self, data, p0, lb, ub, var, data_pars):
def cost(p): def cost(p):
self.step += 1
if self._abort: if self._abort:
raise FitAbortException(f'Fit aborted by user') raise FitAbortException(f'Fit aborted by user')
return (self.__cost_scipy_glob(p, data, var, data_pars)**2).sum() return (_cost_scipy_glob(p, data, var, data_pars) ** 2).sum()
with np.errstate(all='ignore'): with np.errstate(all='ignore'):
res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)], res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)],
method='Nelder-Mead', options={'maxiter': 1000 * len(p0)}) method='Nelder-Mead', options={'maxiter': 500 * len(p0)})
for v, var_pars_k in zip(data, data_pars): for v, var_pars_k in zip(data, data_pars):
self.make_results(v, res.x, var, var_pars_k, (sum(len(d) for d in data), len(p0))) self.make_results(v, res.x, var, var_pars_k, (sum(len(d) for d in data), len(p0)))
@ -365,15 +398,21 @@ class FitRoutine(object):
odr_data = odr.Data(data.x, data.y) odr_data = odr.Data(data.x, data.y)
def func(p, _): def func(p, _):
self.step += 1
if self._abort: if self._abort:
raise FitAbortException(f'Fit aborted by user') raise FitAbortException(f'Fit aborted by user')
return self.__cost_odr(p, data, var_pars, data.para_keys) return _cost_odr(p, data, var_pars, data.para_keys)
odr_model = odr.Model(func) odr_model = odr.Model(func)
corr, partial_corr, res = self._odr_fit(odr_data, odr_model, p0)
self.make_results(data, res.beta, var_pars, data.para_keys, (len(data), len(p0)),
err=res.sd_beta, corr=corr, partial_corr=partial_corr)
def _odr_fit(self, odr_data, odr_model, p0):
o = odr.ODR(odr_data, odr_model, beta0=p0) o = odr.ODR(odr_data, odr_model, beta0=p0)
res = o.run() res = o.run()
corr = res.cov_beta / (res.sd_beta[:, None] * res.sd_beta[None, :]) * res.res_var corr = res.cov_beta / (res.sd_beta[:, None] * res.sd_beta[None, :]) * res.res_var
try: try:
corr_inv = np.linalg.inv(corr) corr_inv = np.linalg.inv(corr)
@ -382,15 +421,14 @@ class FitRoutine(object):
partial_corr[np.diag_indices_from(partial_corr)] = 1. partial_corr[np.diag_indices_from(partial_corr)] = 1.
except np.linalg.LinAlgError: except np.linalg.LinAlgError:
partial_corr = corr partial_corr = corr
return corr, partial_corr, res
self.make_results(data, res.beta, var_pars, data.para_keys, (len(data), len(p0)),
err=res.sd_beta, corr=corr, partial_corr=partial_corr)
def _odr_global(self, data, p0, var, data_pars): def _odr_global(self, data, p0, var, data_pars):
def func(p, _): def func(p, _):
self.step += 1
if self._abort: if self._abort:
raise FitAbortException(f'Fit aborted by user') raise FitAbortException(f'Fit aborted by user')
return self.__cost_odr_glob(p, data, var, data_pars) return _cost_odr_glob(p, data, var, data_pars)
x = [] x = []
y = [] y = []
@ -401,17 +439,7 @@ class FitRoutine(object):
odr_data = odr.Data(x, y) odr_data = odr.Data(x, y)
odr_model = odr.Model(func) odr_model = odr.Model(func)
o = odr.ODR(odr_data, odr_model, beta0=p0, ifixb=var) corr, partial_corr, res = self._odr_fit(odr_data, odr_model, p0)
res = o.run()
corr = res.cov_beta / (res.sd_beta[:, None] * res.sd_beta[None, :]) * res.res_var
try:
corr_inv = np.linalg.inv(corr)
corr_inv_diag = np.diag(np.sqrt(1 / np.diag(corr_inv)))
partial_corr = -1. * np.dot(np.dot(corr_inv_diag, corr_inv), corr_inv_diag) # Partial correlation matrix
partial_corr[np.diag_indices_from(partial_corr)] = 1.
except np.linalg.LinAlgError:
partial_corr = corr
for v, var_pars_k in zip(data, data_pars): for v, var_pars_k in zip(data, data_pars):
self.make_results(v, res.beta, var, var_pars_k, (sum(len(d) for d in data), len(p0)), self.make_results(v, res.beta, var, var_pars_k, (sum(len(d) for d in data), len(p0)),
@ -425,15 +453,17 @@ class FitRoutine(object):
# update parameter values # update parameter values
for keys, p_value, err_value in zip(var_pars, p, err): for keys, p_value, err_value in zip(var_pars, p, err):
self.parameter[keys].scaled_value = p_value if keys in data.parameter.keys():
self.parameter[keys].scaled_error = err_value data.parameter[keys].scaled_value = p_value
data.parameter[keys].scaled_error = err_value
data.parameter[keys].namespace[keys] = data.parameter[keys].value
combinations = list(product(var_pars, var_pars)) combinations = list(product(var_pars, var_pars))
actual_parameters = [] actual_parameters = []
corr_idx = [] corr_idx = []
for i, p_i in enumerate(used_pars): for i, p_i in enumerate(used_pars):
actual_parameters.append(self.parameter[p_i]) actual_parameters.append(data.parameter[p_i])
for j, p_j in enumerate(used_pars): for j, p_j in enumerate(used_pars):
try: try:
# find the position of the parameter combinations # find the position of the parameter combinations
@ -454,9 +484,18 @@ class FitRoutine(object):
idx = self.data.index(data) idx = self.data.index(data)
model = data.get_model() model = data.get_model()
self.result[idx] = FitResultCreator.make_with_model(model, data.x, data.y, self.result[idx] = FitResultCreator.make_with_model(
actual_parameters, data.fun_kwargs, data.idx, model,
*shape, corr=actual_corr, pcorr=actual_pcorr) data.x,
data.y,
actual_parameters,
data.fun_kwargs,
data.we_string,
data.idx,
*shape,
corr=actual_corr,
pcorr=actual_pcorr,
)
return self.result return self.result
@ -464,11 +503,18 @@ class FitRoutine(object):
def _calc_error(jac, chi, nobs, nvars): def _calc_error(jac, chi, nobs, nvars):
# copy of scipy.curve_fit to calculate covariance # copy of scipy.curve_fit to calculate covariance
# noinspection PyTupleAssignmentBalance # noinspection PyTupleAssignmentBalance
_, s, vt = la.svd(jac, full_matrices=False) try:
threshold = EPS * max(jac.shape) * s[0] _, s, vt = la.svd(jac, full_matrices=False)
s = s[s > threshold] except ValueError as e:
vt = vt[:s.size] # this may be issue #39: On entry to DGESSD parameter had an illegal value
pcov = np.dot(vt.T / s**2, vt) * chi / (nobs - nvars) # catch this exception and ignore error calculation
logger.error(f'Error calculation failed with {e.args}')
pcov = None
else:
threshold = EPS * max(jac.shape) * s[0]
s = s[s > threshold]
vt = vt[:s.size]
pcov = np.dot(vt.T / s**2, vt) * chi / (nobs - nvars)
if pcov is None: if pcov is None:
_err = np.zeros(nvars) _err = np.zeros(nvars)
@ -487,3 +533,4 @@ class FitRoutine(object):
partial_corr = corr partial_corr = corr
return _err, corr, partial_corr return _err, corr, partial_corr

View File

@ -6,7 +6,7 @@ from typing import Sized
from numpy import inf from numpy import inf
from ._meta import MultiModel from ._meta import MultiModel
from .parameter import Parameters from .parameter import Parameters, Parameter
class Model(object): class Model(object):
@ -25,7 +25,6 @@ class Model(object):
self.ub = [i if i is not None else inf for i in self.ub] self.ub = [i if i is not None else inf for i in self.ub]
self.parameter = Parameters() self.parameter = Parameters()
self.global_parameter = {}
self.is_complex = None self.is_complex = None
self._complex_part = False self._complex_part = False
@ -80,23 +79,33 @@ class Model(object):
self.fun_kwargs = {k: v.default for k, v in inspect.signature(model.func).parameters.items() self.fun_kwargs = {k: v.default for k, v in inspect.signature(model.func).parameters.items()
if v.default is not inspect.Parameter.empty} if v.default is not inspect.Parameter.empty}
def set_global_parameter(self, idx, p, var=None, lb=None, ub=None, default_bounds=False): def set_global_parameter(self,
if idx is None: key: str | Parameter,
self.parameter = Parameters() value: float | str = None,
self.global_parameter = {} *,
return var: bool = None,
lb: float = None,
ub: float = None,
default_bounds: bool = False,
) -> Parameter:
if default_bounds: if isinstance(key, Parameter):
if lb is None: p = key
lb = [self.lb[i] for i in idx] key = f'p{next(Parameters.parameter_counter)}'
if ub is None: self.parameter.add_parameter(key, p)
ub = [self.lb[i] for i in idx]
gp = self.parameter.add_parameter(p, var=var, lb=lb, ub=ub) else:
for k, v in zip(idx, gp): idx = [self.params.index(key)]
self.global_parameter[k] = v if default_bounds:
if lb is None:
lb = [self.lb[i] for i in idx]
if ub is None:
ub = [self.lb[i] for i in idx]
return gp p = self.parameter.add(key, value, var=var, lb=lb, ub=ub)
p.is_global = True
return p
@staticmethod @staticmethod
def _prep(param_len, val): def _prep(param_len, val):
@ -126,12 +135,12 @@ class Model(object):
kwargs = self.fun_kwargs kwargs = self.fun_kwargs
if not self.is_multi: if not self.is_multi:
return [self.func(p, x, **kwargs)] return []
else: else:
return list(self._int_iter(x, *p, *self.fun_args, **kwargs)) return list(self._int_iter(x, *p, *self.fun_args, **kwargs))
def sub_name(self): def sub_name(self):
if not self.is_multi: if not self.is_multi:
return [self.name] return []
else: else:
return list(self._iter_name()) return list(self._iter_name())

View File

@ -1,123 +1,268 @@
from __future__ import annotations from __future__ import annotations
from numbers import Number import ast
import re
from itertools import count from itertools import count
from io import StringIO
import numpy as np import numpy as np
class Parameters(dict): class Parameters(dict):
count = count() parameter_counter = count()
# is one global namespace a good idea?
namespace: dict = {}
def __str__(self): def __init__(self):
return 'Parameters:\n' + '\n'.join([str(k)+': '+str(v) for k, v in self.items()]) super().__init__()
self._mapping: dict = {}
def __getitem__(self, item): def __str__(self) -> str:
if isinstance(item, (list, tuple, np.ndarray)): return 'Parameters:\n' + '\n'.join([f'{k}: {v}' for k, v in self.items()])
values = []
for item_i in item: def __getitem__(self, item) -> Parameter:
values.append(super().__getitem__(item_i)) if item in self._mapping:
return values return super().__getitem__(self._mapping[item])
else: else:
return super().__getitem__(item) return super().__getitem__(item)
def __setitem__(self, key, value):
self.add_parameter(key, value)
def __contains__(self, item):
for v in self.values():
if item == v.name:
return True
return False
def add(self,
name: str,
value: str | float | int = None,
*,
var: bool = True,
lb: str | float = -np.inf,
ub: str | float = np.inf) -> Parameter:
par = Parameter(name=name, value=value, var=var, lb=lb, ub=ub)
key = f'_p{next(Parameters.parameter_counter)}'
self.add_parameter(key, par)
return par
def add_parameter(self, key: str, parameter: Parameter):
self._mapping[parameter.name] = key
super().__setitem__(key, parameter)
parameter.eval_allowed = False
self.namespace[key] = parameter.value
parameter.namespace = self.namespace
parameter.eval_allowed = True
self.update_namespace()
def replace_parameter(self, key_out: str, key_in: str, parameter: Parameter):
self.add_parameter(key_in, parameter)
for k, v in self._mapping.items():
if v == key_out:
self._mapping[k] = key_in
break
if key_out in self.namespace:
del self.namespace[key_out]
def fix(self):
for v in self.keys():
v._value = v.value
v.namespace = {}
@staticmethod @staticmethod
def _prep_bounds(val, p_len: int) -> list: def reset():
# helper function to ensure that bounds and variable are of parameter shape Parameters.namespace = {}
if isinstance(val, (Number, bool)) or val is None:
return [val] * p_len
elif len(val) == p_len: def get_key(self, name: str) -> str | None:
return val
elif len(val) == 1:
return [val[0]] * p_len
else:
raise ValueError('Input {} has wrong dimensions'.format(val))
def add_parameter(self, param, var=None, lb=None, ub=None):
if isinstance(param, Number):
param = [param]
p_len = len(param)
# make list if only single value is given
var = self._prep_bounds(var, p_len)
lb = self._prep_bounds(lb, p_len)
ub = self._prep_bounds(ub, p_len)
new_keys = []
for i in range(p_len):
new_idx = next(self.count)
new_keys.append(new_idx)
self[new_idx] = Parameter(param[i], var=var[i], lb=lb[i], ub=ub[i])
return new_keys
def copy(self):
p = Parameters()
for k, v in self.items(): for k, v in self.items():
p[k] = Parameter(v.value, var=v.var, lb=v.lb, ub=v.ub) if name == v.name:
return k
if len(p) == 0: return
return p
max_k = max(p.keys())
c = next(p.count)
while c < max_k:
c = next(p.count)
return p
def get_state(self): def get_state(self):
return {k: v.get_state() for k, v in self.items()} return {k: v.get_state() for k, v in self.items()}
def update_namespace(self):
for p in self.values():
try:
p.value
except NameError:
expression = p._expr_disp
for n, k in self._mapping.items():
expression, num_replaced = re.subn(re.escape(n), k, expression)
p._expr = expression
def prepare_bounds(self):
print('prepare_bounds')
original_values = list(self.values())
for param in original_values:
already_with_expression = False
for mode, value in (('lower', param.lb), ('upper', param.ub)):
print(mode, value)
if already_with_expression:
raise ValueError('Only one boundary can be an expression')
already_with_expression = self.parse(param, value, bnd=mode)
def parse(self, parameter, expression: str, bnd: str = 'lower') -> bool:
try:
float(expression)
return False
except ValueError:
pp = ast.parse(expression)
expr_syms = pp.body[0].value
if isinstance(expr_syms, ast.BinOp):
left, op, right = expr_syms.left, expr_syms.op, expr_syms.right
# check for sign in numerator
sign = 1
if isinstance(left, ast.UnaryOp):
if isinstance(left.op, (ast.Not, ast.Invert)):
raise ValueError('Only `+` and `-` are supported for signs')
if isinstance(left.op, ast.USub):
sign = -1
left = left.operand
# is expression number / parameter?
if not (isinstance(left, ast.Constant) and isinstance(op, ast.Div) and isinstance(right, ast.Name)):
raise ValueError('Only simple division `const/parameter` are possible')
result = self.make_proxy_div(parameter, left.value*sign, right.id, bnd=bnd)
return result
else:
raise ValueError('I cannot work under these conditions')
def make_proxy_div(self, param, num: str | float, denom: str | float, bnd: str = 'upper') -> bool:
other_param = self[denom]
other_lb, other_ub = other_param.lb, other_param.ub
proxy = {'name': f'{param.name}*{other_param.name}', 'value': other_param.value*param.value, 'lb': None, 'ub': None}
# switching signs is bad for inequalities
if other_lb < 0 < other_ub:
raise ValueError('Parameter in expression changes sign')
if bnd == 'upper':
# this whole schlamassel is only working for some values as lower bound
if param.lb not in [-np.inf, 0]:
raise ValueError('Invalid lower bounds')
if other_ub < 0 or num < 0:
# upper bound is always negative, switch boundaries
proxy['lb'] = num
proxy['ub'] = param.lb if param.lb == 0 else np.inf
else:
# upper bound is always positive
proxy['lb'] = param.lb
proxy['ub'] = num
elif bnd == 'lower':
if param.ub not in [np.inf, 0]:
raise ValueError('Invalid upper bound')
if other_ub <= 0 or num < 0:
proxy['lb'] = param.lb if param.lb == 0 else np.inf
proxy['ub'] = num
else:
proxy['lb'] = num
proxy['ub'] = param.ub
else:
raise ValueError(f'unknown bound {bnd!r}, use `upper`or `lower`')
param.set_expression(f'{num}/{denom}')
self.add(**proxy)
self.update_namespace()
return True
class Parameter: class Parameter:
""" """
Container for one parameter Container for one parameter
""" """
__slots__ = ['name', 'value', 'error', 'init_val', 'var', 'lb', 'ub', 'scale', 'function']
def __init__(self, value: float, var: bool = True, lb: float = -np.inf, ub: float = np.inf): # TODO Parameter should know its own key
self.lb = lb if lb is not None else -np.inf def __init__(self,
self.ub = ub if ub is not None else np.inf name: str,
value: float | str,
var: bool = True,
lb: str | float = -np.inf,
ub: str | float = np.inf,
):
self._value: float | None = None
self.var: bool = bool(var) if var is not None else True
self.error: None | float = None if self.var is False else 0.0
self.name: str = name
self.function: str = ""
if self.lb <= value <= self.ub: self.lb: str | None | float = lb if lb is not None else -np.inf
self.value = value self.ub: str | float | None = ub if ub is not None else np.inf
self.namespace: dict = {}
self.eval_allowed: bool = True
self._expr: None | str = None
self._expr_disp: None | str = None
self.is_global = False
if isinstance(value, str):
self._expr_disp = value
self._expr = value
self.var = False
else: else:
raise ValueError('Value of parameter is outside bounds') if isinstance(self.lb, (int, float)) and isinstance(self.ub, (int, float)):
if (self.lb <= value <= self.ub) or (not self.var):
self._value = value
else:
raise ValueError('Value of parameter is outside bounds')
else:
self._value = value
self.init_val = value self.init_val = value
with np.errstate(divide='ignore'): with np.errstate(divide='ignore'):
# throws RuntimeWarning for zeros # throws RuntimeWarning for zeros
self.scale = 10**(np.floor(np.log10(np.abs(self.value)))) self.scale = 10**(np.floor(np.log10(np.abs(self.value))))
if self.scale == 0: if self.scale == 0:
self.scale = 1. self.scale = 1.
self.var = bool(var) if var is not None else True def __str__(self) -> str:
self.error = None if self.var is False else 0.0 start = StringIO()
self.name = ''
self.function = ''
def __str__(self):
start = ''
if self.name: if self.name:
if self.function: if self.function:
start = f'{self.name} ({self.function}): ' start.write(f"{self.name} ({self.function})")
else: else:
start = self.name + ': ' start.write(self.name)
if self.is_global:
start.write("*")
start.write(": ")
if self.var: if self.var:
return start + f'{self.value:.4g} +/- {self.error:.4g}, init={self.init_val}' start.write(f"{self.value:.4g} +/- {self.error:.4g}, init={self.init_val}")
else: else:
return start + f'{self.value:} (fixed)' start.write(f"{self.value:.4g}")
if self._expr is None:
start.write(" (fixed)")
else:
start.write(f" (calc: {self._expr_disp})")
def __add__(self, other: Parameter | float) -> float: return start.getvalue()
def __add__(self, other: Parameter | float | int) -> float:
if isinstance(other, (float, int)): if isinstance(other, (float, int)):
return self.value + other return self.value + other
elif isinstance(other, Parameter): elif isinstance(other, Parameter):
@ -127,40 +272,88 @@ class Parameter:
return self.__add__(other) return self.__add__(other)
@property @property
def scaled_value(self): def scaled_value(self) -> float:
return self.value / self.scale return self.value / self.scale
@scaled_value.setter @scaled_value.setter
def scaled_value(self, value): def scaled_value(self, value: float) -> None:
self.value = value * self.scale self._value = value * self.scale
@property @property
def scaled_error(self): def value(self) -> float | None:
if self.error is None: if self._value is not None:
return self.error return self._value
else:
if self._expr is not None and self.eval_allowed:
return eval(self._expr, {}, self.namespace)
return
def set_expression(self, expr: str):
self._value = None
self._expr_disp = expr
self._expr = expr
self.var = False
@property
def scaled_error(self) -> None | float:
if self.error is not None:
return self.error / self.scale return self.error / self.scale
return
@scaled_error.setter @scaled_error.setter
def scaled_error(self, value): def scaled_error(self, value) -> None:
self.error = value * self.scale self.error = value * self.scale
def get_state(self): def get_state(self) -> dict:
state_dict = {
return {slot: getattr(self, slot) for slot in self.__slots__} 'name': self.name,
'value': self._value,
'error': self.error,
'init_val': self.init_val,
'var': self.var,
'lb': self.lb,
'ub': self.ub,
'scale': self.scale,
'function': self.function,
'_expr': self._expr,
'_expr_disp': self._expr_disp,
'is_global': self.is_global,
}
return state_dict
@staticmethod @staticmethod
def set_state(state: dict): def set_state(state: dict) -> Parameter:
par = Parameter(state.pop('value')) par = Parameter(state.get('name'), state.pop('value'))
for k, v in state.items(): for k, v in state.items():
setattr(par, k, v) setattr(par, k, v)
return par return par
@property @property
def full_name(self): def full_name(self) -> str:
name = self.name name = self.name
if self.function: if self.function:
name += ' (' + self.function + ')' name += f" ({self.function})"
return name return name
def copy(self) -> Parameter:
if self._expr:
val = self._expr_disp
else:
val = self._value
para_copy = Parameter(name=self.name, value=val, var=self.var, lb=self.lb, ub=self.ub)
para_copy._expr = self._expr
para_copy.namespace = self.namespace
para_copy.is_global = self.is_global
para_copy.error = self.error
para_copy.function = self.function
return para_copy
def fix(self):
self._value = self.value
self.namespace = {}

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import re import re
from collections import OrderedDict from collections import OrderedDict
from io import StringIO
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -9,6 +10,7 @@ import numpy as np
from scipy.stats import f as fdist from scipy.stats import f as fdist
from scipy.interpolate import interp1d from scipy.interpolate import interp1d
from ._meta import MultiModel
from .parameter import Parameter from .parameter import Parameter
from ..data.points import Points from ..data.points import Points
from ..data.signals import Signal from ..data.signals import Signal
@ -17,7 +19,7 @@ from ..utils.text import convert
class FitResultCreator: class FitResultCreator:
@staticmethod @staticmethod
def make_from_session(x_orig: np.ndarray, y_orig: np.ndarray, idx: int, kwargs: dict[Any]) -> (dict, list): def make_from_session(x_orig: np.ndarray, y_orig: np.ndarray, idx: int, kwargs: dict[Any]) -> FitResult:
params = OrderedDict() params = OrderedDict()
for key, pbest, err in zip(kwargs['pnames'], kwargs['parameter'], kwargs['error']): for key, pbest, err in zip(kwargs['pnames'], kwargs['parameter'], kwargs['error']):
@ -37,10 +39,22 @@ class FitResultCreator:
stats = FitResultCreator.calc_statistics(resid, _y) stats = FitResultCreator.calc_statistics(resid, _y)
return FitResult(kwargs['x'], kwargs['y'], x_orig, y_orig, params, dict(kwargs['choice']), resid, 0, 0, return FitResult(kwargs['x'], kwargs['y'], x_orig, y_orig, params, dict(kwargs['choice']), resid, 0, 0,
kwargs['name'], stats, idx), [] kwargs['name'], stats, idx)
@staticmethod @staticmethod
def make_with_model(model, x_orig, y_orig, p, fun_kwargs, idx, nobs, nvar, corr, pcorr) -> (dict, list): def make_with_model(
model: 'Model',
x_orig: np.ndarray,
y_orig: np.ndarray,
p: 'Parameters',
fun_kwargs: dict,
we: str,
idx: str | None,
nobs: int,
nvar: int,
corr: np.ndarray,
pcorr: np.ndarray,
) -> FitResult:
if np.all(x_orig > 0) and (np.max(x_orig) > 100 * np.min(x_orig)): if np.all(x_orig > 0) and (np.max(x_orig) > 100 * np.min(x_orig)):
islog = True islog = True
else: else:
@ -48,7 +62,7 @@ class FitResultCreator:
if len(x_orig) < 51: if len(x_orig) < 51:
if islog: if islog:
_x = np.logspace(np.log10(np.min(x_orig)), np.log10(np.max(x_orig)), num=10*x_orig.size-9) _x = np.geomspace(np.min(x_orig), np.max(x_orig), num=10*x_orig.size-9)
else: else:
_x = np.linspace(np.min(x_orig), np.max(x_orig), num=10*x_orig.size-9) _x = np.linspace(np.min(x_orig), np.max(x_orig), num=10*x_orig.size-9)
else: else:
@ -62,17 +76,22 @@ class FitResultCreator:
parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)]) parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)])
p_final = [p.value for p in parameters.values()] p_final = [p.value for p in parameters.values()]
part_functions = [] resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig
if model.is_multi: actual_mode = -1
for sub_name, sub_y in zip(model.sub_name(), model.sub(p_final, _x, **fun_kwargs)): if 'complex_mode' in fun_kwargs:
if np.iscomplexobj(sub_y): actual_mode = fun_kwargs['complex_mode']
part_functions.append(Signal(_x, sub_y, name=sub_name)) fun_kwargs['complex_mode'] = 0
else:
part_functions.append(Points(_x, sub_y, name=sub_name))
_y = model.func(p_final, _x, **fun_kwargs) _y = model.func(p_final, _x, **fun_kwargs)
resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig
if not actual_mode < 0:
if actual_mode == 1:
_y.imag = 0
elif actual_mode == 2:
_y.real = 0
fun_kwargs['complex_mode'] = actual_mode
stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar) stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar)
varied = [p.var for p in parameters.values()] varied = [p.var for p in parameters.values()]
@ -97,12 +116,24 @@ class FitResultCreator:
correlation = corr correlation = corr
partial_correlation = pcorr partial_correlation = pcorr
return ( return FitResult(
FitResult(_x, _y, x_orig, y_orig, parameters, fun_kwargs, resid, x=_x,
nobs, nvar, model.name, stats, y=_y,
idx=idx, corr=correlation, pcorr=partial_correlation, x_data=x_orig,
islog=islog), y_data=y_orig,
part_functions, params=parameters,
fun_kwargs=fun_kwargs,
resid=resid,
nobs=nobs,
nvar=nvar,
we=we,
name=model.name,
stats=stats,
idx=idx,
corr=correlation,
pcorr=partial_correlation,
islog=islog,
func=model,
) )
@staticmethod @staticmethod
@ -141,19 +172,38 @@ class FitResultCreator:
class FitResult(Points): class FitResult(Points):
def __init__(self, x, y, x_data, y_data, params, fun_kwargs, resid, nobs, nvar, name, stats, def __init__(
idx=None, corr=None, pcorr=None, islog=False, self: FitResult,
**kwargs): x: np.ndarray,
y: np.ndarray,
x_data: np.ndarray,
y_data: np.ndarray,
params: dict,
fun_kwargs: dict,
resid: np.ndarray,
nobs: int,
nvar: int,
we: str,
name: str,
stats: dict,
idx: int = None,
corr: np.ndarray = None,
pcorr: np.ndarray = None,
islog: bool = False,
func=None,
**kwargs
):
self.parameter, name = self._prepare_names(params, name) self.parameter, name = self._prepare_names(params, name)
label = kwargs.get('label', name)
super().__init__(x=x, y=y, name=name, **kwargs) super().__init__(x=x, y=y, name=label, **kwargs)
self.residual = resid self.residual = resid
self.idx = idx self.idx = idx
self.statistics = stats self.statistics = stats
self.nobs = nobs self.nobs = nobs
self.nvar = nvar self.nvar = nvar
self.we = we
self.fun_kwargs = fun_kwargs self.fun_kwargs = fun_kwargs
self.correlation = corr self.correlation = corr
self.partial_correlation = pcorr self.partial_correlation = pcorr
@ -162,6 +212,7 @@ class FitResult(Points):
self.x_data = x_data self.x_data = x_data
self.y_data = y_data self.y_data = y_data
self._model_name = name self._model_name = name
self._func = func
@staticmethod @staticmethod
def _prepare_names(parameter: dict, modelname: str): def _prepare_names(parameter: dict, modelname: str):
@ -179,7 +230,7 @@ class FitResult(Points):
nice_name = m.group(1) nice_name = m.group(1)
if func_number in split_funcs: if func_number in split_funcs:
nice_func = split_funcs[func_number] nice_func = split_funcs[func_number]
pvalue.fix()
pvalue.name = nice_name pvalue.name = nice_name
pvalue.function = nice_func pvalue.function = nice_func
parameter_dic[pname] = pvalue parameter_dic[pname] = pvalue
@ -200,6 +251,13 @@ class FitResult(Points):
except AttributeError: except AttributeError:
return 'FitObject' return 'FitObject'
@property
def func(self):
if isinstance(self._func, MultiModel):
return self._func.func
else:
return self._func
@property @property
def p_final(self): def p_final(self):
return [pp.value for pp in self.parameter.values()] return [pp.value for pp in self.parameter.values()]
@ -209,26 +267,31 @@ class FitResult(Points):
return self.nobs-self.nvar return self.nobs-self.nvar
def pprint(self, statistics=True, correlations=True): def pprint(self, statistics=True, correlations=True):
print('Fit result:') s = StringIO()
print(' model :', self.name) s.write('Fit result:\n')
print(' #data :', self.nobs) s.write(f' model : {self.name}\n')
print(' #var :', self.nvar) s.write(f' #data : {self.nobs}\n')
print('\nParameter') s.write(f' #var : {self.nvar}\n')
print(self._parameter_string()) s.write(f' #weight: {self.we}\n')
s.write('\nParameter\n')
s.write(self.parameter_string())
if statistics: if statistics:
print('Statistics') s.write('\nStatistics\n')
for k, v in self.statistics.items(): for k, v in self.statistics.items():
print(f' {k} : {v:.4f}') s.write(f' {k} : {v:.4f}\n')
if correlations and self.correlation is not None: if correlations and self.correlation is not None:
print('\nCorrelation (partial corr.)') s.write('\nCorrelation (partial corr.)\n')
print(self._correlation_string()) s.write(self._correlation_string())
print() s.write('\n')
def _parameter_string(self): print(s.getvalue())
def parameter_string(self):
ret_val = '' ret_val = ''
for pval in self.parameter.values(): for pkey, pval in self.parameter.items():
ret_val += convert(str(pval), old='tex', new='str') + '\n' ret_val += convert(str(pval), old='tex', new='str') + '\n'
if self.fun_kwargs: if self.fun_kwargs:
@ -240,9 +303,7 @@ class FitResult(Points):
def _correlation_string(self): def _correlation_string(self):
ret_val = '' ret_val = ''
for p_i, p_j, corr_ij, pcorr_ij in self.correlation_list(): for p_i, p_j, corr_ij, pcorr_ij in self.correlation_list():
ret_val += ' {} / {} : {:.4f} ({:.4f})\n'.format(convert(p_i, old='tex', new='str'), ret_val += f" {convert(p_i, old='tex', new='str')} / {convert(p_j, old='tex', new='str')} : {corr_ij:.4f} ({pcorr_ij:.4f})\n"
convert(p_j, old='tex', new='str'),
corr_ij, pcorr_ij)
return ret_val return ret_val
def correlation_list(self, limit=0.1): def correlation_list(self, limit=0.1):
@ -291,7 +352,7 @@ class FitResult(Points):
with path.open(writemode) as f: with path.open(writemode) as f:
if overwrite or not path.exists(): if overwrite or not path.exists():
f.write('# label(1)\t') f.write('# xvalue(1)\t')
for i, pname in enumerate(self.parameter.keys()): for i, pname in enumerate(self.parameter.keys()):
raw_name = convert(pname, old='tex', new='str') raw_name = convert(pname, old='tex', new='str')
f.write(f'{raw_name}({2*i+2})\t{raw_name}_err({2*i+3})\t') f.write(f'{raw_name}({2*i+2})\t{raw_name}_err({2*i+3})\t')
@ -305,10 +366,15 @@ class FitResult(Points):
err = 0. err = 0.
f.write(f'{p.value:.8e}\t{err:.8e}\t') f.write(f'{p.value:.8e}\t{err:.8e}\t')
f.write('# ')
if self.fun_kwargs: if self.fun_kwargs:
f.write('# ')
for k, v in self.fun_kwargs.items(): for k, v in self.fun_kwargs.items():
f.write(f"{convert(k, old='tex', new='str')}: {convert(str(v), old='tex', new='str')}\t") f.write(f"{convert(k, old='tex', new='str')}: {convert(str(v), old='tex', new='str')}\t")
f.write(f'weight: {self.we}\t')
f.write('\n')
f.write(f'# line above from: {self.name} (model: {self.model_name})')
f.write('\n') f.write('\n')
def f_test(self, chi2: float, dof: float): def f_test(self, chi2: float, dof: float):
@ -316,7 +382,13 @@ class FitResult(Points):
f_value = 1e318 f_value = 1e318
else: else:
f_value = (chi2-self.statistics['chi^2']) / (dof-self.dof) / self.statistics['red. chi^2'] f_value = (chi2-self.statistics['chi^2']) / (dof-self.dof) / self.statistics['red. chi^2']
return f_value, 1-fdist.cdf(f_value, dof-self.dof, self.dof)
try:
prob_f = 1-fdist.cdf(f_value, dof-self.dof, self.dof)
except:
prob_f = 0
return f_value, prob_f
def get_state(self): def get_state(self):
state = super().get_state() state = super().get_state()
@ -326,6 +398,7 @@ class FitResult(Points):
state[attr] = getattr(self, attr) state[attr] = getattr(self, attr)
state['name'] = self._model_name state['name'] = self._model_name
state['label'] = self.name
state['corr'] = self.correlation state['corr'] = self.correlation
state['pcorr'] = self.partial_correlation state['pcorr'] = self.partial_correlation
state['stats'] = self.statistics state['stats'] = self.statistics
@ -342,3 +415,50 @@ class FitResult(Points):
data = FitResult(**state) data = FitResult(**state)
return data return data
def with_new_x(self, x_values):
if self.func is None:
raise ValueError('no fit function available to calculate new y values')
actual_mode = -1
if 'complex_mode' in self.fun_kwargs:
actual_mode = self.fun_kwargs['complex_mode']
self.fun_kwargs['complex_mode'] = 0
new_fit = self.copy()
y_values = self.func.func(self.p_final, x_values, **self.fun_kwargs)
if not actual_mode < 0:
if actual_mode == 1:
y_values.imag = 0
elif actual_mode == 2:
y_values.real = 0
self.fun_kwargs['complex_mode'] = actual_mode
new_fit.set_data(x_values, y_values, y_err=0.0)
return new_fit
def sub(self, x_values):
part_functions = []
actual_mode = -1
if 'complex_mode' in self.fun_kwargs:
actual_mode = self.fun_kwargs['complex_mode']
self.fun_kwargs['complex_mode'] = 0
for sub_name, sub_y in zip(self.func.sub_name(), self.func.sub(self.p_final, x_values, **self.fun_kwargs)):
if not actual_mode < 0:
if actual_mode == 1:
sub_y.imag = 0
elif actual_mode == 2:
sub_y.real = 0
part_functions.append(Signal(x_values, sub_y, name=sub_name))
else:
part_functions.append(Points(x_values, sub_y, name=sub_name))
if actual_mode > 0:
self.fun_kwargs['complex_mode'] = actual_mode
return part_functions

View File

@ -1,23 +1,26 @@
from __future__ import annotations
import pathlib import pathlib
import re import re
from io import BytesIO
from itertools import islice from itertools import islice
import numpy as np import numpy as np
from ..data.points import Points from ..data.points import Points
from ..data.nmr import FID, Spectrum from ..data.nmr import FID, Spectrum
from ..data.bds import BDS
from ..data.dsc import DSC
from ..utils.utils import staggered_range from ..utils.utils import staggered_range
NUMBERRE = re.compile(r'[0-9]\.*[0-9]*[Ee]*[+-]*[0-9]*')
class AsciiReader: class AsciiReader:
delimiters = ['\t', ' ', ','] # delimiters = ['\t', ' ', ',']
def __init__(self, fname): def __init__(self, fname):
self.fname = None self.fname = None
self.header = [] self.header = []
self.data = [] self.lines = []
self.delays = None self.delays = None
self.width = [] self.width = []
self.line_comment = [] self.line_comment = []
@ -39,15 +42,16 @@ class AsciiReader:
break break
def make_preview(self, num_lines: int): def make_preview(self, num_lines: int):
if num_lines <= len(self.data): if num_lines <= len(self.lines):
return self.data[:num_lines], max(self.width[:num_lines]) return self.lines[:num_lines], max(self.width[:num_lines]), self.line_comment
num_lines += len(self.header) num_lines += len(self.header)
with self.fname.open('r') as f: with self.fname.open('r') as f:
for i, line in enumerate(islice(f, len(self.header)+len(self.data), num_lines)): for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)):
line = line.rstrip('\n\t\r, ') line = line.strip('\n\t\r, ')
line = re.sub(r'[\t ;,] *', ';', line)
line = line.split(';')
line = re.split('[\s,;]', line)
try: try:
comment_start = line.index('#') comment_start = line.index('#')
self.line_comment.append(' '.join(line[comment_start:])) self.line_comment.append(' '.join(line[comment_start:]))
@ -55,10 +59,15 @@ class AsciiReader:
except ValueError: except ValueError:
self.line_comment.append('') self.line_comment.append('')
self.width.append(len(line)) is_empty = len(line) == 0
self.data.append(line)
return self.data, max(self.width) if not is_empty:
self.lines.append(line)
self.width.append(len(line))
else:
self.lines.append('')
return self.lines, max(self.width), self.line_comment
def look_for_delay(self, fname=None): def look_for_delay(self, fname=None):
if fname is None: if fname is None:
@ -78,31 +87,49 @@ class AsciiReader:
if stagg: if stagg:
self.delays = staggered_range(self.delays, stepsize=stag_size) self.delays = staggered_range(self.delays, stepsize=stag_size)
def export(self, x: int = None, y: list = None, yerr: list = None, def export(
mode: str = 'Points', col_names=None) -> list: self: 'AsciiReader',
x: int | str = None,
y: list = None,
yerr: list = None,
mode: str = 'points',
col_names=None,
num_value: float = None,
) -> list:
mode = mode.lower()
if mode not in ['points', 'fid', 'spectrum', 'dsc', 'bds']:
raise ValueError(f'Unknown mode {mode!r} as selected class')
if yerr is None: if yerr is None:
yerr = [] yerr = []
elif y is None: elif y is None:
raise ValueError('y is None and yerr is not None') raise ValueError('y is None and yerr is not None')
if (x is None) ^ (y is None): # if (x is None) ^ (y is None):
raise ValueError(f'x is {type(x)} and y is {type(y)}, should be both None or both defined') # raise ValueError(f'x is {type(x)} and y is {type(y)}, should be both None or both defined')
if x is None: if x is None:
x = [0] x = [0]
elif isinstance(x, int): elif isinstance(x, int):
x = [x] x = [x]
elif isinstance(x, str) and x == 'index':
x = []
else: else:
raise ValueError(f'x is {type(x)} not int') raise ValueError(f'type of x is {type(x)} not `int` or `str`')
if y is None: if y is None:
y = list(range(1, max(self.width))) y = list(range(int(len(x) != 0), max(self.width)))
cols = x + y + yerr cols = x + y + yerr
raw_data = np.genfromtxt(self.fname, usecols=cols, missing_values='--') with self.fname.open('rb') as fh:
tmp_ = re.sub(b'[;,]', b' ', fh.read())
raw_data = np.genfromtxt(BytesIO(tmp_), usecols=cols, missing_values='--')
del tmp_
if raw_data.ndim == 1: if raw_data.ndim == 1:
# only one row or column # only one row or column
if len(self.data) == 1: if len(self.lines) == 1:
# one row # one row
raw_data = raw_data.reshape(1, -1) raw_data = raw_data.reshape(1, -1)
else: else:
@ -115,33 +142,45 @@ class AsciiReader:
else: else:
raw_data = raw_data.reshape((1, *raw_data.shape)) raw_data = raw_data.reshape((1, *raw_data.shape))
if len(x) == 0 or raw_data.shape[2] == 1:
_temp = np.zeros((raw_data.shape[0], raw_data.shape[1], raw_data.shape[2]+1))
_temp[:, :, 0] = np.arange(raw_data.shape[1])
_temp[:, :, 1:] = raw_data
raw_data = _temp
if y:
y = [i+1 for i in y]
else:
y = [1]
filename = self.fname.stem filename = self.fname.stem
if self.delays: if self.delays:
delay_names = self.delays delay_names = self.delays
else: else:
delay_names = [filename] delay_names = [num_value]
imported_sets = [] imported_sets = []
for i, value in enumerate(delay_names): for i, value in enumerate(delay_names):
kwargs = {'value': value, 'filename': self.fname, 'name': filename, 'group': filename, 'y_err': None} kwargs = {
'value': value,
'filename': self.fname,
'name': filename,
'group': filename,
'y_err': None
}
num_y = len(y) num_y = len(y)
if mode == 'Points': single_len = 1
single_len = 1 stepsize = 1
cls = Points
stepsize = 1 if mode in ['spectrum', 'fid', 'bds']:
elif mode == 'FID': # complex data types
cls = FID
single_len = 2 single_len = 2
stepsize = 2 stepsize = 2
elif mode == 'Spectrum':
cls = Spectrum cls = {'points': Points, 'fid': FID, 'spectrum': Spectrum, 'bds': BDS, 'dsc': DSC}[mode]
single_len = 2
stepsize = 2
else:
raise ValueError(f'Unknown mode {mode}, mot ´Points´, ´FID´ or ´Spectrum´.')
for j in range(1, num_y+1, stepsize): for j in range(1, num_y+1, stepsize):
if col_names is not None: if col_names is not None:
@ -151,8 +190,8 @@ class AsciiReader:
# more than one axis, append column number # more than one axis, append column number
kwargs['name'] = filename + '_' + str(y[j-1]) kwargs['name'] = filename + '_' + str(y[j-1])
if j+num_y+1 < raw_data.shape[2]: if j+num_y < raw_data.shape[2]:
kwargs['y_err'] = raw_data[i, j+num_y+1] kwargs['y_err'] = raw_data[i, :, j+num_y]
imported_sets.append(cls(x=raw_data[i, :, 0], y=raw_data[i, :, j:j+single_len].T, **kwargs)) imported_sets.append(cls(x=raw_data[i, :, 0], y=raw_data[i, :, j:j+single_len].T, **kwargs))

Some files were not shown because too many files have changed in this diff Show More