From a222072b2838a685236c37196cfe3c3d7e4b5ea0 Mon Sep 17 00:00:00 2001 From: dominik Date: Tue, 8 Mar 2022 10:27:40 +0100 Subject: [PATCH] First commit --- Makefile | 42 + __init__.py | 2 + bin/evaluate.py | 18 + docs/Makefile | 20 + docs/make.bat | 35 + docs/source/_static/fit_dialog.png | Bin 0 -> 47372 bytes docs/source/_static/logo.png | Bin 0 -> 614463 bytes docs/source/_templates/autosummary.rst | 25 + docs/source/api/data.rst | 23 + docs/source/api/distributions/index.rst | 29 + .../source/api/generated/nmreval.data.BDS.rst | 23 + .../api/generated/nmreval.data.Points.rst | 23 + .../api/generated/nmreval.data.Signal.rst | 23 + docs/source/api/index.rst | 12 + docs/source/api/models/basic.rst | 6 + docs/source/api/models/index.rst | 31 + .../api/models/nmreval.models.basic.rst | 37 + docs/source/conf.py | 424 + docs/source/index.rst | 26 + docs/source/nmr/depake.png | Bin 0 -> 60395 bytes docs/source/nmr/index.rst | 8 + docs/source/nmr/pake.rst | 43 + docs/source/user_guide/fit.rst | 43 + docs/source/user_guide/index.rst | 11 + docs/source/user_guide/read.rst | 19 + docs/source/user_guide/shift_scale.rst | 16 + nmreval/__init__.py | 1 + nmreval/bds/__init__.py | 1 + nmreval/configs.py | 69 + nmreval/data/__init__.py | 5 + nmreval/data/bds.py | 47 + nmreval/data/dsc.py | 6 + nmreval/data/nmr.py | 179 + nmreval/data/points.py | 665 + nmreval/data/signals.py | 100 + nmreval/distributions/__init__.py | 7 + nmreval/distributions/base.py | 85 + nmreval/distributions/colecole.py | 105 + nmreval/distributions/coledavidson.py | 179 + nmreval/distributions/debye.py | 30 + nmreval/distributions/energy.py | 124 + nmreval/distributions/gengamma.py | 111 + nmreval/distributions/havriliaknegami.py | 106 + nmreval/distributions/intermolecular.py | 99 + nmreval/distributions/kww.py | 84 + nmreval/distributions/loggaussian.py | 138 + nmreval/dsc/calibration.py | 290 + nmreval/dsc/dsc_calibration_fast_neu.py | 292 + nmreval/fit/__init__.py | 7 + nmreval/fit/_meta.py | 161 + nmreval/fit/data.py | 149 + nmreval/fit/minimizer.py | 489 + nmreval/fit/model.py | 153 + nmreval/fit/parameter.py | 155 + nmreval/fit/result.py | 339 + nmreval/gui_qt/Qt.py | 12 + nmreval/gui_qt/__init__.py | 17 + nmreval/gui_qt/_py/__init__.py | 0 nmreval/gui_qt/_py/agroptiondialog.py | 323 + nmreval/gui_qt/_py/apod_dialog.py | 74 + nmreval/gui_qt/_py/asciidialog.py | 203 + nmreval/gui_qt/_py/axisConfigTemplate.py | 89 + nmreval/gui_qt/_py/baseline_dialog.py | 64 + nmreval/gui_qt/_py/basewindow.py | 606 + nmreval/gui_qt/_py/bdsdialog.py | 76 + nmreval/gui_qt/_py/color_palette.py | 84 + nmreval/gui_qt/_py/coupling_calculator.py | 57 + nmreval/gui_qt/_py/coupling_t1_from_tau.py | 46 + nmreval/gui_qt/_py/datawidget.py | 64 + nmreval/gui_qt/_py/dscfile_dialog.py | 213 + nmreval/gui_qt/_py/editsignalwidget.py | 182 + nmreval/gui_qt/_py/eval_expr_dialog.py | 240 + nmreval/gui_qt/_py/evalexpression.py | 217 + nmreval/gui_qt/_py/expandablewidget.py | 52 + nmreval/gui_qt/_py/exportConfigTemplate.py | 65 + nmreval/gui_qt/_py/fcreader.py | 196 + nmreval/gui_qt/_py/filedialog.py | 181 + nmreval/gui_qt/_py/fitcreationdialog.py | 189 + nmreval/gui_qt/_py/fitdialog.py | 159 + nmreval/gui_qt/_py/fitdialog_window.py | 285 + nmreval/gui_qt/_py/fitfunctionwidget.py | 123 + nmreval/gui_qt/_py/fitfuncwidget.py | 70 + nmreval/gui_qt/_py/fitfuncwidget_old.py | 86 + nmreval/gui_qt/_py/fitmodelfixwidget.py | 67 + nmreval/gui_qt/_py/fitmodelwidget.py | 135 + nmreval/gui_qt/_py/fitparametertable.py | 50 + nmreval/gui_qt/_py/fitparameterwidget.py | 70 + nmreval/gui_qt/_py/fitresult.py | 180 + nmreval/gui_qt/_py/ftdialog.py | 58 + nmreval/gui_qt/_py/function_tree_widget.py | 123 + nmreval/gui_qt/_py/gol.py | 276 + nmreval/gui_qt/_py/gracemsgdialog.py | 56 + nmreval/gui_qt/_py/gracereader.py | 92 + nmreval/gui_qt/_py/graph.py | 275 + nmreval/gui_qt/_py/guidelinewidget.py | 121 + nmreval/gui_qt/_py/hdftree.py | 61 + nmreval/gui_qt/_py/integral_widget.py | 53 + nmreval/gui_qt/_py/integratederive_dialog.py | 83 + nmreval/gui_qt/_py/interpol_dialog.py | 166 + nmreval/gui_qt/_py/lineedit_dialog.py | 39 + nmreval/gui_qt/_py/mean_form.py | 62 + nmreval/gui_qt/_py/meandialog.py | 96 + nmreval/gui_qt/_py/modelwidget.py | 32 + nmreval/gui_qt/_py/move_dialog.py | 87 + nmreval/gui_qt/_py/namespace_widget.py | 71 + nmreval/gui_qt/_py/option_selection.py | 71 + nmreval/gui_qt/_py/parameterform.py | 64 + nmreval/gui_qt/_py/phase_corr_dialog.py | 94 + nmreval/gui_qt/_py/plotConfigTemplate.py | 179 + nmreval/gui_qt/_py/pokemon.py | 118 + nmreval/gui_qt/_py/propwidget.py | 57 + nmreval/gui_qt/_py/ptstab.py | 133 + nmreval/gui_qt/_py/qfiledialog.py | 179 + nmreval/gui_qt/_py/save_fit_parameter.py | 131 + nmreval/gui_qt/_py/save_fitmodel_dialog.py | 64 + nmreval/gui_qt/_py/save_options.py | 33 + nmreval/gui_qt/_py/saveoptions.py | 41 + nmreval/gui_qt/_py/sdmodelwidget.py | 70 + nmreval/gui_qt/_py/selection_widget.py | 41 + nmreval/gui_qt/_py/setbyfunction_dialog.py | 225 + nmreval/gui_qt/_py/shift_scale_dialog.py | 314 + nmreval/gui_qt/_py/skipdialog.py | 95 + nmreval/gui_qt/_py/smoothdialog.py | 121 + nmreval/gui_qt/_py/t1_calc_dialog.py | 318 + nmreval/gui_qt/_py/t1_dock.py | 258 + nmreval/gui_qt/_py/t1_tau_calculation.py | 215 + nmreval/gui_qt/_py/t1dialog.py | 228 + nmreval/gui_qt/_py/tntdialog.py | 138 + nmreval/gui_qt/_py/typeconversion.py | 139 + nmreval/gui_qt/_py/untitled.py | 50 + nmreval/gui_qt/_py/userfitassist.py | 98 + nmreval/gui_qt/_py/usermodeleditor.py | 64 + nmreval/gui_qt/_py/valueeditor.py | 85 + nmreval/gui_qt/data/__init__.py | 0 nmreval/gui_qt/data/container.py | 651 + nmreval/gui_qt/data/conversion.py | 171 + nmreval/gui_qt/data/datawidget/__init__.py | 1 + nmreval/gui_qt/data/datawidget/datawidget.py | 483 + nmreval/gui_qt/data/datawidget/properties.py | 78 + nmreval/gui_qt/data/integral_widget.py | 213 + nmreval/gui_qt/data/interpolate_dialog.py | 41 + nmreval/gui_qt/data/plot_dialog.py | 123 + nmreval/gui_qt/data/point_select.py | 196 + nmreval/gui_qt/data/shift_graphs.py | 190 + nmreval/gui_qt/data/signaledit/__init__.py | 2 + .../gui_qt/data/signaledit/baseline_dialog.py | 92 + .../data/signaledit/editsignalwidget.py | 92 + .../gui_qt/data/signaledit/phase_dialog.py | 196 + nmreval/gui_qt/data/valueeditwidget.py | 336 + nmreval/gui_qt/fit/__init__.py | 0 nmreval/gui_qt/fit/fit_forms.py | 453 + nmreval/gui_qt/fit/fit_parameter.py | 250 + nmreval/gui_qt/fit/fitfunction.py | 240 + nmreval/gui_qt/fit/fitwindow.py | 455 + .../gui_qt/fit/function_creation_dialog.py | 247 + nmreval/gui_qt/fit/result.py | 245 + nmreval/gui_qt/graphs/__init__.py | 0 nmreval/gui_qt/graphs/graphwindow.py | 705 + nmreval/gui_qt/graphs/guide_lines.py | 166 + nmreval/gui_qt/graphs/movedialog.py | 89 + nmreval/gui_qt/io/__init__.py | 0 nmreval/gui_qt/io/asciireader.py | 182 + nmreval/gui_qt/io/bdsreader.py | 66 + nmreval/gui_qt/io/dscreader.py | 273 + nmreval/gui_qt/io/exporters.py | 143 + nmreval/gui_qt/io/fcbatchreader.py | 116 + nmreval/gui_qt/io/filedialog.py | 88 + nmreval/gui_qt/io/filereaders.py | 133 + nmreval/gui_qt/io/gracereader.py | 110 + nmreval/gui_qt/io/hdfreader.py | 196 + nmreval/gui_qt/io/nmrreader.py | 34 + nmreval/gui_qt/io/save_fitparameter.py | 78 + nmreval/gui_qt/io/tntreader.py | 109 + nmreval/gui_qt/lib/__init__.py | 76 + nmreval/gui_qt/lib/codeeditor.py | 262 + nmreval/gui_qt/lib/color_dialog.py | 89 + nmreval/gui_qt/lib/configurations.py | 156 + nmreval/gui_qt/lib/decorators.py | 55 + nmreval/gui_qt/lib/delegates.py | 235 + nmreval/gui_qt/lib/expandablewidget.py | 65 + nmreval/gui_qt/lib/forms.py | 402 + nmreval/gui_qt/lib/gol.py | 390 + nmreval/gui_qt/lib/namespace.py | 211 + nmreval/gui_qt/lib/pg_objects.py | 435 + nmreval/gui_qt/lib/randpok.py | 119 + nmreval/gui_qt/lib/stuff.py | 502 + nmreval/gui_qt/lib/styles.py | 102 + nmreval/gui_qt/lib/tables.py | 31 + nmreval/gui_qt/lib/undos.py | 245 + nmreval/gui_qt/lib/usermodeleditor.py | 143 + nmreval/gui_qt/lib/utils.py | 93 + nmreval/gui_qt/main/__init__.py | 0 nmreval/gui_qt/main/mainwindow.py | 922 + nmreval/gui_qt/main/management.py | 1074 + nmreval/gui_qt/math/bootstrap.py | 3 + nmreval/gui_qt/math/evaluation.py | 120 + nmreval/gui_qt/math/integrate_derive.py | 81 + nmreval/gui_qt/math/interpol.py | 109 + nmreval/gui_qt/math/mean_dialog.py | 112 + nmreval/gui_qt/math/skipping.py | 19 + nmreval/gui_qt/math/smooth.py | 57 + nmreval/gui_qt/nmr/coupling_calc.py | 73 + nmreval/gui_qt/nmr/t1_from_tau.py | 211 + nmreval/gui_qt/nmr/t1widget.py | 326 + nmreval/io/__init__.py | 0 nmreval/io/asciireader.py | 175 + nmreval/io/bds_reader.py | 178 + nmreval/io/dsc.py | 323 + nmreval/io/fcbatchreader.py | 399 + nmreval/io/graceeditor.py | 711 + nmreval/io/hdfreader.py | 388 + nmreval/io/merge_agr.py | 49 + nmreval/io/nmrreader.py | 137 + nmreval/io/read_old_nmr.py | 567 + nmreval/io/sessionwriter.py | 27 + nmreval/io/tntreader.py | 379 + nmreval/lib/__init__.py | 0 nmreval/lib/colors.py | 327 + nmreval/lib/importer.py | 70 + nmreval/lib/lines.py | 29 + nmreval/lib/logger.py | 29 + nmreval/lib/symbols.py | 95 + nmreval/lib/utils.py | 19 + nmreval/math/__init__.py | 0 nmreval/math/apodization.py | 123 + nmreval/math/interpol.py | 31 + nmreval/math/kww.py | 281 + nmreval/math/logfourier.py | 121 + nmreval/math/mittagleffler.py | 262 + nmreval/math/orientations.py | 173 + nmreval/math/pca.py | 14 + nmreval/math/smooth.py | 167 + nmreval/math/wideline.py | 383 + nmreval/models/__init__.py | 11 + nmreval/models/basic.py | 250 + nmreval/models/bds.py | 155 + nmreval/models/correlationfuncs.py | 51 + nmreval/models/diffusion.py | 155 + nmreval/models/fieldcycling.py | 124 + nmreval/models/gengamma.py | 298 + nmreval/models/myfitmodels.py | 66 + nmreval/models/relaxation.py | 138 + nmreval/models/stimecho.py | 69 + nmreval/models/temperature.py | 74 + nmreval/models/transitions.py | 28 + nmreval/models/user_models.py | 3 + nmreval/models/wideline.py | 127 + nmreval/nmr/__init__.py | 0 nmreval/nmr/couplings.py | 102 + nmreval/nmr/fc_eval.py | 84 + nmreval/nmr/relaxation.py | 406 + nmreval/utils/__init__.py | 6 + nmreval/utils/constants.py | 196 + nmreval/utils/exception.py | 0 nmreval/utils/pca_demo_pcs.py | 302 + nmreval/utils/pokemon.json | 17740 ++++++++++++++++ nmreval/utils/text.py | 105 + nmreval/utils/utils.py | 37 + nmreval/version.py | 3 + resources/__init__.py | 0 resources/_rc/__init__.py | 0 resources/_rc/axes.png | Bin 0 -> 1750 bytes resources/_rc/blackwhite.png | Bin 0 -> 5619 bytes resources/_rc/blackwhite.svg | 308 + resources/_rc/cat.png | Bin 0 -> 1128 bytes resources/_rc/colors.png | Bin 0 -> 916 bytes resources/_rc/colors.svg | 330 + resources/_rc/cut_region.png | Bin 0 -> 3760 bytes resources/_rc/eval_button.png | Bin 0 -> 2361 bytes resources/_rc/fit_preview.png | Bin 0 -> 8930 bytes resources/_rc/fit_region.png | Bin 0 -> 828 bytes resources/_rc/grid.png | Bin 0 -> 1746 bytes resources/_rc/grid.svg | 101 + resources/_rc/imag.png | Bin 0 -> 2170 bytes resources/_rc/imag.svg | 68 + resources/_rc/images.qrc | 17 + resources/_rc/logo.png | Bin 0 -> 787468 bytes resources/_rc/mean.png | Bin 0 -> 3709 bytes resources/_rc/mouse.png | Bin 0 -> 2112 bytes resources/_rc/open_session.png | Bin 0 -> 6159 bytes resources/_rc/path48-3-5-6-2.png | Bin 0 -> 5619 bytes resources/_rc/properties.png | Bin 0 -> 4644 bytes resources/_rc/punktdings.png | Bin 0 -> 1585 bytes resources/_rc/real.png | Bin 0 -> 1978 bytes resources/_rc/real.svg | 68 + resources/_rc/sort.png | Bin 0 -> 2435 bytes resources/_rc/xlog.png | Bin 0 -> 2187 bytes resources/_rc/xlog.svg | 290 + resources/_rc/ylog.png | Bin 0 -> 2465 bytes resources/_rc/ylog.svg | 290 + resources/_ui/agroptiondialog.ui | 587 + resources/_ui/apod_dialog.ui | 141 + resources/_ui/asciidialog.ui | 377 + resources/_ui/axisConfigTemplate.ui | 161 + resources/_ui/baseline_dialog.ui | 109 + resources/_ui/basewindow.ui | 1051 + resources/_ui/bdsdialog.ui | 141 + resources/_ui/color_palette.ui | 154 + resources/_ui/coupling_calculator.ui | 111 + resources/_ui/coupling_t1_from_tau.ui | 64 + resources/_ui/datawidget.ui | 124 + resources/_ui/dscfile_dialog.ui | 399 + resources/_ui/editsignalwidget.ui | 367 + resources/_ui/eval_expr_dialog.ui | 432 + resources/_ui/evalexpression.ui | 366 + resources/_ui/expandablewidget.ui | 79 + resources/_ui/exportConfigTemplate.ui | 103 + resources/_ui/fcreader.ui | 409 + resources/_ui/filedialog.ui | 382 + resources/_ui/fitcreationdialog.ui | 419 + resources/_ui/fitdialog.ui | 313 + resources/_ui/fitdialog_window.ui | 514 + resources/_ui/fitfunctionwidget.ui | 208 + resources/_ui/fitfuncwidget.ui | 127 + resources/_ui/fitfuncwidget_old.ui | 156 + resources/_ui/fitmodelfixwidget.ui | 114 + resources/_ui/fitmodelwidget.ui | 256 + resources/_ui/fitparametertable.ui | 100 + resources/_ui/fitparameterwidget.ui | 127 + resources/_ui/fitresult.ui | 359 + resources/_ui/ftdialog.ui | 121 + resources/_ui/function_tree_widget.ui | 208 + resources/_ui/gol.ui | 486 + resources/_ui/gracemsgdialog.ui | 79 + resources/_ui/gracereader.ui | 187 + resources/_ui/graph.ui | 577 + resources/_ui/guidelinewidget.ui | 213 + resources/_ui/hdftree.ui | 131 + resources/_ui/integral_widget.ui | 85 + resources/_ui/integratederive_dialog.ui | 162 + resources/_ui/interpol_dialog.ui | 327 + resources/_ui/lineedit_dialog.ui | 74 + resources/_ui/mean_form.ui | 117 + resources/_ui/meandialog.ui | 154 + resources/_ui/modelwidget.ui | 31 + resources/_ui/move_dialog.ui | 130 + resources/_ui/namespace_widget.ui | 109 + resources/_ui/option_selection.ui | 91 + resources/_ui/parameterform.ui | 104 + resources/_ui/phase_corr_dialog.ui | 197 + resources/_ui/plotConfigTemplate.ui | 363 + resources/_ui/pokemon.ui | 194 + resources/_ui/propwidget.ui | 93 + resources/_ui/ptstab.ui | 268 + resources/_ui/qfiledialog.ui | 355 + resources/_ui/save_fit_parameter.ui | 234 + resources/_ui/save_fitmodel_dialog.ui | 125 + resources/_ui/save_options.ui | 35 + resources/_ui/saveoptions.ui | 51 + resources/_ui/sdmodelwidget.ui | 124 + resources/_ui/selection_widget.ui | 59 + resources/_ui/setbyfunction_dialog.ui | 437 + resources/_ui/shift_scale_dialog.ui | 563 + resources/_ui/skipdialog.ui | 185 + resources/_ui/smoothdialog.ui | 269 + resources/_ui/t1_calc_dialog.ui | 687 + resources/_ui/t1_dock.ui | 502 + resources/_ui/t1_tau_calculation.ui | 477 + resources/_ui/t1dialog.ui | 516 + resources/_ui/tntdialog.ui | 259 + resources/_ui/typeconversion.ui | 227 + resources/_ui/untitled.ui | 69 + resources/_ui/userfitassist.ui | 193 + resources/_ui/usermodeleditor.ui | 95 + resources/_ui/valueeditor.ui | 151 + resources/icons/__init__.py | 0 resources/icons/icons.json | 44 + resources/icons/logo.png | Bin 0 -> 614463 bytes resources/icons/normal_light/__init__.py | 1 + resources/icons/normal_light/blackwhite.png | Bin 0 -> 5619 bytes resources/icons/normal_light/colors.png | Bin 0 -> 916 bytes resources/icons/normal_light/concat.png | Bin 0 -> 1144 bytes resources/icons/normal_light/errors.png | Bin 0 -> 1217 bytes .../icons/normal_light/eval_expression.png | Bin 0 -> 2138 bytes resources/icons/normal_light/fit.png | Bin 0 -> 945 bytes resources/icons/normal_light/fit_region.png | Bin 0 -> 828 bytes resources/icons/normal_light/geteilt_icon.png | Bin 0 -> 1680 bytes resources/icons/normal_light/grid.png | Bin 0 -> 1746 bytes resources/icons/normal_light/imag.png | Bin 0 -> 2170 bytes resources/icons/normal_light/legend.png | Bin 0 -> 3645 bytes resources/icons/normal_light/logo.png | 1 + resources/icons/normal_light/mal_icon.png | Bin 0 -> 1950 bytes resources/icons/normal_light/mean.png | Bin 0 -> 3709 bytes resources/icons/normal_light/messdings.png | Bin 0 -> 2861 bytes resources/icons/normal_light/minus_icon.png | Bin 0 -> 1448 bytes resources/icons/normal_light/mouse.png | Bin 0 -> 1839 bytes resources/icons/normal_light/new.png | Bin 0 -> 1002 bytes resources/icons/normal_light/normal.png | Bin 0 -> 1836 bytes resources/icons/normal_light/open.png | Bin 0 -> 1174 bytes resources/icons/normal_light/plus.png | Bin 0 -> 781 bytes resources/icons/normal_light/plus_icon.png | Bin 0 -> 1528 bytes resources/icons/normal_light/points_pick.png | Bin 0 -> 4854 bytes resources/icons/normal_light/real.png | Bin 0 -> 1978 bytes resources/icons/normal_light/save.png | Bin 0 -> 953 bytes resources/icons/normal_light/save_session.png | Bin 0 -> 4224 bytes resources/icons/normal_light/scaling.png | Bin 0 -> 4077 bytes resources/icons/normal_light/sort.png | Bin 0 -> 2146 bytes resources/icons/normal_light/t1_from_tau.png | Bin 0 -> 1346 bytes resources/icons/normal_light/tau_from_t1.png | Bin 0 -> 1327 bytes resources/icons/normal_light/values.png | Bin 0 -> 1635 bytes resources/icons/normal_light/xlog.png | Bin 0 -> 2187 bytes resources/icons/normal_light/ylog.png | Bin 0 -> 2465 bytes resources/icons/pokemon_light/__init__.py | 1 + resources/icons/pokemon_light/blackwhite.png | Bin 0 -> 5619 bytes resources/icons/pokemon_light/colors.png | Bin 0 -> 916 bytes resources/icons/pokemon_light/concat.png | Bin 0 -> 79423 bytes resources/icons/pokemon_light/errors.png | Bin 0 -> 122051 bytes .../icons/pokemon_light/eval_expression.png | Bin 0 -> 387004 bytes resources/icons/pokemon_light/fit.png | Bin 0 -> 2514 bytes resources/icons/pokemon_light/fit_region.png | Bin 0 -> 828 bytes .../icons/pokemon_light/geteilt_icon.png | Bin 0 -> 1680 bytes resources/icons/pokemon_light/grid.png | Bin 0 -> 1746 bytes resources/icons/pokemon_light/imag.png | Bin 0 -> 2170 bytes resources/icons/pokemon_light/legend.png | Bin 0 -> 3645 bytes resources/icons/pokemon_light/logo.png | 1 + resources/icons/pokemon_light/mal_icon.png | Bin 0 -> 1950 bytes resources/icons/pokemon_light/mean.png | Bin 0 -> 3709 bytes resources/icons/pokemon_light/messdings.png | Bin 0 -> 5597 bytes resources/icons/pokemon_light/minus_icon.png | Bin 0 -> 1448 bytes resources/icons/pokemon_light/mouse.png | Bin 0 -> 139128 bytes resources/icons/pokemon_light/new.png | Bin 0 -> 1002 bytes resources/icons/pokemon_light/normal.png | Bin 0 -> 1836 bytes resources/icons/pokemon_light/open.png | Bin 0 -> 200137 bytes resources/icons/pokemon_light/plus.png | Bin 0 -> 781 bytes resources/icons/pokemon_light/plus_icon.png | Bin 0 -> 1528 bytes resources/icons/pokemon_light/points_pick.png | Bin 0 -> 6337 bytes resources/icons/pokemon_light/real.png | Bin 0 -> 1978 bytes resources/icons/pokemon_light/save.png | Bin 0 -> 86801 bytes .../icons/pokemon_light/save_session.png | Bin 0 -> 4224 bytes resources/icons/pokemon_light/scaling.png | Bin 0 -> 34812 bytes resources/icons/pokemon_light/sort.png | Bin 0 -> 89967 bytes resources/icons/pokemon_light/t1_from_tau.png | Bin 0 -> 3100 bytes resources/icons/pokemon_light/tau_from_t1.png | Bin 0 -> 7043 bytes resources/icons/pokemon_light/values.png | Bin 0 -> 6337 bytes resources/icons/pokemon_light/xlog.png | Bin 0 -> 2187 bytes resources/icons/pokemon_light/ylog.png | Bin 0 -> 2465 bytes resources/icons/style.qss | 1 + resources/logo.png | Bin 0 -> 614463 bytes resources/nmr/T1_min.npz | Bin 0 -> 452523 bytes resources/nmr/__init__.py | 0 setup.py | 218 + 441 files changed, 75015 insertions(+) create mode 100755 Makefile create mode 100755 __init__.py create mode 100755 bin/evaluate.py create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/_static/fit_dialog.png create mode 100644 docs/source/_static/logo.png create mode 100644 docs/source/_templates/autosummary.rst create mode 100644 docs/source/api/data.rst create mode 100644 docs/source/api/distributions/index.rst create mode 100644 docs/source/api/generated/nmreval.data.BDS.rst create mode 100644 docs/source/api/generated/nmreval.data.Points.rst create mode 100644 docs/source/api/generated/nmreval.data.Signal.rst create mode 100644 docs/source/api/index.rst create mode 100644 docs/source/api/models/basic.rst create mode 100644 docs/source/api/models/index.rst create mode 100644 docs/source/api/models/nmreval.models.basic.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/nmr/depake.png create mode 100644 docs/source/nmr/index.rst create mode 100644 docs/source/nmr/pake.rst create mode 100644 docs/source/user_guide/fit.rst create mode 100644 docs/source/user_guide/index.rst create mode 100644 docs/source/user_guide/read.rst create mode 100644 docs/source/user_guide/shift_scale.rst create mode 100644 nmreval/__init__.py create mode 100644 nmreval/bds/__init__.py create mode 100644 nmreval/configs.py create mode 100755 nmreval/data/__init__.py create mode 100644 nmreval/data/bds.py create mode 100644 nmreval/data/dsc.py create mode 100644 nmreval/data/nmr.py create mode 100644 nmreval/data/points.py create mode 100644 nmreval/data/signals.py create mode 100644 nmreval/distributions/__init__.py create mode 100644 nmreval/distributions/base.py create mode 100644 nmreval/distributions/colecole.py create mode 100644 nmreval/distributions/coledavidson.py create mode 100644 nmreval/distributions/debye.py create mode 100644 nmreval/distributions/energy.py create mode 100644 nmreval/distributions/gengamma.py create mode 100644 nmreval/distributions/havriliaknegami.py create mode 100644 nmreval/distributions/intermolecular.py create mode 100644 nmreval/distributions/kww.py create mode 100644 nmreval/distributions/loggaussian.py create mode 100644 nmreval/dsc/calibration.py create mode 100644 nmreval/dsc/dsc_calibration_fast_neu.py create mode 100644 nmreval/fit/__init__.py create mode 100644 nmreval/fit/_meta.py create mode 100644 nmreval/fit/data.py create mode 100644 nmreval/fit/minimizer.py create mode 100644 nmreval/fit/model.py create mode 100644 nmreval/fit/parameter.py create mode 100644 nmreval/fit/result.py create mode 100644 nmreval/gui_qt/Qt.py create mode 100644 nmreval/gui_qt/__init__.py create mode 100644 nmreval/gui_qt/_py/__init__.py create mode 100644 nmreval/gui_qt/_py/agroptiondialog.py create mode 100644 nmreval/gui_qt/_py/apod_dialog.py create mode 100644 nmreval/gui_qt/_py/asciidialog.py create mode 100644 nmreval/gui_qt/_py/axisConfigTemplate.py create mode 100644 nmreval/gui_qt/_py/baseline_dialog.py create mode 100644 nmreval/gui_qt/_py/basewindow.py create mode 100644 nmreval/gui_qt/_py/bdsdialog.py create mode 100644 nmreval/gui_qt/_py/color_palette.py create mode 100644 nmreval/gui_qt/_py/coupling_calculator.py create mode 100644 nmreval/gui_qt/_py/coupling_t1_from_tau.py create mode 100644 nmreval/gui_qt/_py/datawidget.py create mode 100644 nmreval/gui_qt/_py/dscfile_dialog.py create mode 100644 nmreval/gui_qt/_py/editsignalwidget.py create mode 100644 nmreval/gui_qt/_py/eval_expr_dialog.py create mode 100644 nmreval/gui_qt/_py/evalexpression.py create mode 100644 nmreval/gui_qt/_py/expandablewidget.py create mode 100644 nmreval/gui_qt/_py/exportConfigTemplate.py create mode 100644 nmreval/gui_qt/_py/fcreader.py create mode 100644 nmreval/gui_qt/_py/filedialog.py create mode 100644 nmreval/gui_qt/_py/fitcreationdialog.py create mode 100644 nmreval/gui_qt/_py/fitdialog.py create mode 100644 nmreval/gui_qt/_py/fitdialog_window.py create mode 100644 nmreval/gui_qt/_py/fitfunctionwidget.py create mode 100644 nmreval/gui_qt/_py/fitfuncwidget.py create mode 100644 nmreval/gui_qt/_py/fitfuncwidget_old.py create mode 100644 nmreval/gui_qt/_py/fitmodelfixwidget.py create mode 100644 nmreval/gui_qt/_py/fitmodelwidget.py create mode 100644 nmreval/gui_qt/_py/fitparametertable.py create mode 100644 nmreval/gui_qt/_py/fitparameterwidget.py create mode 100644 nmreval/gui_qt/_py/fitresult.py create mode 100644 nmreval/gui_qt/_py/ftdialog.py create mode 100644 nmreval/gui_qt/_py/function_tree_widget.py create mode 100644 nmreval/gui_qt/_py/gol.py create mode 100644 nmreval/gui_qt/_py/gracemsgdialog.py create mode 100644 nmreval/gui_qt/_py/gracereader.py create mode 100644 nmreval/gui_qt/_py/graph.py create mode 100644 nmreval/gui_qt/_py/guidelinewidget.py create mode 100644 nmreval/gui_qt/_py/hdftree.py create mode 100644 nmreval/gui_qt/_py/integral_widget.py create mode 100644 nmreval/gui_qt/_py/integratederive_dialog.py create mode 100644 nmreval/gui_qt/_py/interpol_dialog.py create mode 100644 nmreval/gui_qt/_py/lineedit_dialog.py create mode 100644 nmreval/gui_qt/_py/mean_form.py create mode 100644 nmreval/gui_qt/_py/meandialog.py create mode 100644 nmreval/gui_qt/_py/modelwidget.py create mode 100644 nmreval/gui_qt/_py/move_dialog.py create mode 100644 nmreval/gui_qt/_py/namespace_widget.py create mode 100644 nmreval/gui_qt/_py/option_selection.py create mode 100644 nmreval/gui_qt/_py/parameterform.py create mode 100644 nmreval/gui_qt/_py/phase_corr_dialog.py create mode 100644 nmreval/gui_qt/_py/plotConfigTemplate.py create mode 100644 nmreval/gui_qt/_py/pokemon.py create mode 100644 nmreval/gui_qt/_py/propwidget.py create mode 100644 nmreval/gui_qt/_py/ptstab.py create mode 100644 nmreval/gui_qt/_py/qfiledialog.py create mode 100644 nmreval/gui_qt/_py/save_fit_parameter.py create mode 100644 nmreval/gui_qt/_py/save_fitmodel_dialog.py create mode 100644 nmreval/gui_qt/_py/save_options.py create mode 100644 nmreval/gui_qt/_py/saveoptions.py create mode 100644 nmreval/gui_qt/_py/sdmodelwidget.py create mode 100644 nmreval/gui_qt/_py/selection_widget.py create mode 100644 nmreval/gui_qt/_py/setbyfunction_dialog.py create mode 100644 nmreval/gui_qt/_py/shift_scale_dialog.py create mode 100644 nmreval/gui_qt/_py/skipdialog.py create mode 100644 nmreval/gui_qt/_py/smoothdialog.py create mode 100644 nmreval/gui_qt/_py/t1_calc_dialog.py create mode 100644 nmreval/gui_qt/_py/t1_dock.py create mode 100644 nmreval/gui_qt/_py/t1_tau_calculation.py create mode 100644 nmreval/gui_qt/_py/t1dialog.py create mode 100644 nmreval/gui_qt/_py/tntdialog.py create mode 100644 nmreval/gui_qt/_py/typeconversion.py create mode 100644 nmreval/gui_qt/_py/untitled.py create mode 100644 nmreval/gui_qt/_py/userfitassist.py create mode 100644 nmreval/gui_qt/_py/usermodeleditor.py create mode 100644 nmreval/gui_qt/_py/valueeditor.py create mode 100644 nmreval/gui_qt/data/__init__.py create mode 100644 nmreval/gui_qt/data/container.py create mode 100644 nmreval/gui_qt/data/conversion.py create mode 100644 nmreval/gui_qt/data/datawidget/__init__.py create mode 100644 nmreval/gui_qt/data/datawidget/datawidget.py create mode 100644 nmreval/gui_qt/data/datawidget/properties.py create mode 100644 nmreval/gui_qt/data/integral_widget.py create mode 100644 nmreval/gui_qt/data/interpolate_dialog.py create mode 100644 nmreval/gui_qt/data/plot_dialog.py create mode 100644 nmreval/gui_qt/data/point_select.py create mode 100644 nmreval/gui_qt/data/shift_graphs.py create mode 100644 nmreval/gui_qt/data/signaledit/__init__.py create mode 100644 nmreval/gui_qt/data/signaledit/baseline_dialog.py create mode 100644 nmreval/gui_qt/data/signaledit/editsignalwidget.py create mode 100644 nmreval/gui_qt/data/signaledit/phase_dialog.py create mode 100644 nmreval/gui_qt/data/valueeditwidget.py create mode 100644 nmreval/gui_qt/fit/__init__.py create mode 100644 nmreval/gui_qt/fit/fit_forms.py create mode 100644 nmreval/gui_qt/fit/fit_parameter.py create mode 100644 nmreval/gui_qt/fit/fitfunction.py create mode 100644 nmreval/gui_qt/fit/fitwindow.py create mode 100644 nmreval/gui_qt/fit/function_creation_dialog.py create mode 100644 nmreval/gui_qt/fit/result.py create mode 100644 nmreval/gui_qt/graphs/__init__.py create mode 100644 nmreval/gui_qt/graphs/graphwindow.py create mode 100644 nmreval/gui_qt/graphs/guide_lines.py create mode 100644 nmreval/gui_qt/graphs/movedialog.py create mode 100644 nmreval/gui_qt/io/__init__.py create mode 100644 nmreval/gui_qt/io/asciireader.py create mode 100644 nmreval/gui_qt/io/bdsreader.py create mode 100644 nmreval/gui_qt/io/dscreader.py create mode 100644 nmreval/gui_qt/io/exporters.py create mode 100644 nmreval/gui_qt/io/fcbatchreader.py create mode 100644 nmreval/gui_qt/io/filedialog.py create mode 100755 nmreval/gui_qt/io/filereaders.py create mode 100644 nmreval/gui_qt/io/gracereader.py create mode 100644 nmreval/gui_qt/io/hdfreader.py create mode 100644 nmreval/gui_qt/io/nmrreader.py create mode 100644 nmreval/gui_qt/io/save_fitparameter.py create mode 100644 nmreval/gui_qt/io/tntreader.py create mode 100644 nmreval/gui_qt/lib/__init__.py create mode 100644 nmreval/gui_qt/lib/codeeditor.py create mode 100644 nmreval/gui_qt/lib/color_dialog.py create mode 100644 nmreval/gui_qt/lib/configurations.py create mode 100644 nmreval/gui_qt/lib/decorators.py create mode 100644 nmreval/gui_qt/lib/delegates.py create mode 100644 nmreval/gui_qt/lib/expandablewidget.py create mode 100644 nmreval/gui_qt/lib/forms.py create mode 100644 nmreval/gui_qt/lib/gol.py create mode 100644 nmreval/gui_qt/lib/namespace.py create mode 100644 nmreval/gui_qt/lib/pg_objects.py create mode 100644 nmreval/gui_qt/lib/randpok.py create mode 100644 nmreval/gui_qt/lib/stuff.py create mode 100644 nmreval/gui_qt/lib/styles.py create mode 100644 nmreval/gui_qt/lib/tables.py create mode 100644 nmreval/gui_qt/lib/undos.py create mode 100644 nmreval/gui_qt/lib/usermodeleditor.py create mode 100644 nmreval/gui_qt/lib/utils.py create mode 100644 nmreval/gui_qt/main/__init__.py create mode 100644 nmreval/gui_qt/main/mainwindow.py create mode 100644 nmreval/gui_qt/main/management.py create mode 100644 nmreval/gui_qt/math/bootstrap.py create mode 100644 nmreval/gui_qt/math/evaluation.py create mode 100644 nmreval/gui_qt/math/integrate_derive.py create mode 100644 nmreval/gui_qt/math/interpol.py create mode 100644 nmreval/gui_qt/math/mean_dialog.py create mode 100644 nmreval/gui_qt/math/skipping.py create mode 100644 nmreval/gui_qt/math/smooth.py create mode 100644 nmreval/gui_qt/nmr/coupling_calc.py create mode 100644 nmreval/gui_qt/nmr/t1_from_tau.py create mode 100644 nmreval/gui_qt/nmr/t1widget.py create mode 100644 nmreval/io/__init__.py create mode 100644 nmreval/io/asciireader.py create mode 100644 nmreval/io/bds_reader.py create mode 100644 nmreval/io/dsc.py create mode 100644 nmreval/io/fcbatchreader.py create mode 100644 nmreval/io/graceeditor.py create mode 100644 nmreval/io/hdfreader.py create mode 100755 nmreval/io/merge_agr.py create mode 100644 nmreval/io/nmrreader.py create mode 100644 nmreval/io/read_old_nmr.py create mode 100644 nmreval/io/sessionwriter.py create mode 100644 nmreval/io/tntreader.py create mode 100644 nmreval/lib/__init__.py create mode 100644 nmreval/lib/colors.py create mode 100644 nmreval/lib/importer.py create mode 100644 nmreval/lib/lines.py create mode 100644 nmreval/lib/logger.py create mode 100644 nmreval/lib/symbols.py create mode 100644 nmreval/lib/utils.py create mode 100644 nmreval/math/__init__.py create mode 100644 nmreval/math/apodization.py create mode 100644 nmreval/math/interpol.py create mode 100644 nmreval/math/kww.py create mode 100644 nmreval/math/logfourier.py create mode 100644 nmreval/math/mittagleffler.py create mode 100755 nmreval/math/orientations.py create mode 100644 nmreval/math/pca.py create mode 100644 nmreval/math/smooth.py create mode 100644 nmreval/math/wideline.py create mode 100755 nmreval/models/__init__.py create mode 100644 nmreval/models/basic.py create mode 100644 nmreval/models/bds.py create mode 100644 nmreval/models/correlationfuncs.py create mode 100644 nmreval/models/diffusion.py create mode 100644 nmreval/models/fieldcycling.py create mode 100644 nmreval/models/gengamma.py create mode 100644 nmreval/models/myfitmodels.py create mode 100644 nmreval/models/relaxation.py create mode 100644 nmreval/models/stimecho.py create mode 100644 nmreval/models/temperature.py create mode 100644 nmreval/models/transitions.py create mode 100644 nmreval/models/user_models.py create mode 100644 nmreval/models/wideline.py create mode 100644 nmreval/nmr/__init__.py create mode 100644 nmreval/nmr/couplings.py create mode 100644 nmreval/nmr/fc_eval.py create mode 100755 nmreval/nmr/relaxation.py create mode 100755 nmreval/utils/__init__.py create mode 100755 nmreval/utils/constants.py create mode 100644 nmreval/utils/exception.py create mode 100644 nmreval/utils/pca_demo_pcs.py create mode 100644 nmreval/utils/pokemon.json create mode 100644 nmreval/utils/text.py create mode 100755 nmreval/utils/utils.py create mode 100644 nmreval/version.py create mode 100644 resources/__init__.py create mode 100644 resources/_rc/__init__.py create mode 100644 resources/_rc/axes.png create mode 100644 resources/_rc/blackwhite.png create mode 100644 resources/_rc/blackwhite.svg create mode 100644 resources/_rc/cat.png create mode 100644 resources/_rc/colors.png create mode 100644 resources/_rc/colors.svg create mode 100644 resources/_rc/cut_region.png create mode 100644 resources/_rc/eval_button.png create mode 100644 resources/_rc/fit_preview.png create mode 100644 resources/_rc/fit_region.png create mode 100644 resources/_rc/grid.png create mode 100644 resources/_rc/grid.svg create mode 100644 resources/_rc/imag.png create mode 100644 resources/_rc/imag.svg create mode 100644 resources/_rc/images.qrc create mode 100644 resources/_rc/logo.png create mode 100644 resources/_rc/mean.png create mode 100644 resources/_rc/mouse.png create mode 100644 resources/_rc/open_session.png create mode 100644 resources/_rc/path48-3-5-6-2.png create mode 100644 resources/_rc/properties.png create mode 100644 resources/_rc/punktdings.png create mode 100644 resources/_rc/real.png create mode 100644 resources/_rc/real.svg create mode 100644 resources/_rc/sort.png create mode 100644 resources/_rc/xlog.png create mode 100644 resources/_rc/xlog.svg create mode 100644 resources/_rc/ylog.png create mode 100644 resources/_rc/ylog.svg create mode 100644 resources/_ui/agroptiondialog.ui create mode 100644 resources/_ui/apod_dialog.ui create mode 100644 resources/_ui/asciidialog.ui create mode 100644 resources/_ui/axisConfigTemplate.ui create mode 100644 resources/_ui/baseline_dialog.ui create mode 100644 resources/_ui/basewindow.ui create mode 100644 resources/_ui/bdsdialog.ui create mode 100644 resources/_ui/color_palette.ui create mode 100644 resources/_ui/coupling_calculator.ui create mode 100644 resources/_ui/coupling_t1_from_tau.ui create mode 100644 resources/_ui/datawidget.ui create mode 100644 resources/_ui/dscfile_dialog.ui create mode 100644 resources/_ui/editsignalwidget.ui create mode 100644 resources/_ui/eval_expr_dialog.ui create mode 100644 resources/_ui/evalexpression.ui create mode 100644 resources/_ui/expandablewidget.ui create mode 100644 resources/_ui/exportConfigTemplate.ui create mode 100644 resources/_ui/fcreader.ui create mode 100644 resources/_ui/filedialog.ui create mode 100644 resources/_ui/fitcreationdialog.ui create mode 100644 resources/_ui/fitdialog.ui create mode 100644 resources/_ui/fitdialog_window.ui create mode 100644 resources/_ui/fitfunctionwidget.ui create mode 100644 resources/_ui/fitfuncwidget.ui create mode 100644 resources/_ui/fitfuncwidget_old.ui create mode 100644 resources/_ui/fitmodelfixwidget.ui create mode 100755 resources/_ui/fitmodelwidget.ui create mode 100644 resources/_ui/fitparametertable.ui create mode 100644 resources/_ui/fitparameterwidget.ui create mode 100644 resources/_ui/fitresult.ui create mode 100644 resources/_ui/ftdialog.ui create mode 100644 resources/_ui/function_tree_widget.ui create mode 100644 resources/_ui/gol.ui create mode 100644 resources/_ui/gracemsgdialog.ui create mode 100644 resources/_ui/gracereader.ui create mode 100644 resources/_ui/graph.ui create mode 100644 resources/_ui/guidelinewidget.ui create mode 100755 resources/_ui/hdftree.ui create mode 100644 resources/_ui/integral_widget.ui create mode 100644 resources/_ui/integratederive_dialog.ui create mode 100644 resources/_ui/interpol_dialog.ui create mode 100644 resources/_ui/lineedit_dialog.ui create mode 100644 resources/_ui/mean_form.ui create mode 100644 resources/_ui/meandialog.ui create mode 100755 resources/_ui/modelwidget.ui create mode 100644 resources/_ui/move_dialog.ui create mode 100644 resources/_ui/namespace_widget.ui create mode 100644 resources/_ui/option_selection.ui create mode 100644 resources/_ui/parameterform.ui create mode 100644 resources/_ui/phase_corr_dialog.ui create mode 100644 resources/_ui/plotConfigTemplate.ui create mode 100644 resources/_ui/pokemon.ui create mode 100644 resources/_ui/propwidget.ui create mode 100644 resources/_ui/ptstab.ui create mode 100644 resources/_ui/qfiledialog.ui create mode 100644 resources/_ui/save_fit_parameter.ui create mode 100644 resources/_ui/save_fitmodel_dialog.ui create mode 100644 resources/_ui/save_options.ui create mode 100644 resources/_ui/saveoptions.ui create mode 100644 resources/_ui/sdmodelwidget.ui create mode 100644 resources/_ui/selection_widget.ui create mode 100644 resources/_ui/setbyfunction_dialog.ui create mode 100644 resources/_ui/shift_scale_dialog.ui create mode 100644 resources/_ui/skipdialog.ui create mode 100644 resources/_ui/smoothdialog.ui create mode 100644 resources/_ui/t1_calc_dialog.ui create mode 100644 resources/_ui/t1_dock.ui create mode 100644 resources/_ui/t1_tau_calculation.ui create mode 100644 resources/_ui/t1dialog.ui create mode 100644 resources/_ui/tntdialog.ui create mode 100644 resources/_ui/typeconversion.ui create mode 100644 resources/_ui/untitled.ui create mode 100644 resources/_ui/userfitassist.ui create mode 100644 resources/_ui/usermodeleditor.ui create mode 100644 resources/_ui/valueeditor.ui create mode 100644 resources/icons/__init__.py create mode 100644 resources/icons/icons.json create mode 100644 resources/icons/logo.png create mode 100644 resources/icons/normal_light/__init__.py create mode 100644 resources/icons/normal_light/blackwhite.png create mode 100644 resources/icons/normal_light/colors.png create mode 100644 resources/icons/normal_light/concat.png create mode 100644 resources/icons/normal_light/errors.png create mode 100644 resources/icons/normal_light/eval_expression.png create mode 100644 resources/icons/normal_light/fit.png create mode 100644 resources/icons/normal_light/fit_region.png create mode 100644 resources/icons/normal_light/geteilt_icon.png create mode 100644 resources/icons/normal_light/grid.png create mode 100644 resources/icons/normal_light/imag.png create mode 100644 resources/icons/normal_light/legend.png create mode 120000 resources/icons/normal_light/logo.png create mode 100644 resources/icons/normal_light/mal_icon.png create mode 100644 resources/icons/normal_light/mean.png create mode 100644 resources/icons/normal_light/messdings.png create mode 100644 resources/icons/normal_light/minus_icon.png create mode 100644 resources/icons/normal_light/mouse.png create mode 100644 resources/icons/normal_light/new.png create mode 100644 resources/icons/normal_light/normal.png create mode 100644 resources/icons/normal_light/open.png create mode 100644 resources/icons/normal_light/plus.png create mode 100644 resources/icons/normal_light/plus_icon.png create mode 100644 resources/icons/normal_light/points_pick.png create mode 100644 resources/icons/normal_light/real.png create mode 100644 resources/icons/normal_light/save.png create mode 100644 resources/icons/normal_light/save_session.png create mode 100644 resources/icons/normal_light/scaling.png create mode 100644 resources/icons/normal_light/sort.png create mode 100644 resources/icons/normal_light/t1_from_tau.png create mode 100644 resources/icons/normal_light/tau_from_t1.png create mode 100644 resources/icons/normal_light/values.png create mode 100644 resources/icons/normal_light/xlog.png create mode 100644 resources/icons/normal_light/ylog.png create mode 100644 resources/icons/pokemon_light/__init__.py create mode 100644 resources/icons/pokemon_light/blackwhite.png create mode 100644 resources/icons/pokemon_light/colors.png create mode 100644 resources/icons/pokemon_light/concat.png create mode 100644 resources/icons/pokemon_light/errors.png create mode 100644 resources/icons/pokemon_light/eval_expression.png create mode 100644 resources/icons/pokemon_light/fit.png create mode 100644 resources/icons/pokemon_light/fit_region.png create mode 100644 resources/icons/pokemon_light/geteilt_icon.png create mode 100644 resources/icons/pokemon_light/grid.png create mode 100644 resources/icons/pokemon_light/imag.png create mode 100644 resources/icons/pokemon_light/legend.png create mode 120000 resources/icons/pokemon_light/logo.png create mode 100644 resources/icons/pokemon_light/mal_icon.png create mode 100644 resources/icons/pokemon_light/mean.png create mode 100644 resources/icons/pokemon_light/messdings.png create mode 100644 resources/icons/pokemon_light/minus_icon.png create mode 100644 resources/icons/pokemon_light/mouse.png create mode 100644 resources/icons/pokemon_light/new.png create mode 100644 resources/icons/pokemon_light/normal.png create mode 100644 resources/icons/pokemon_light/open.png create mode 100644 resources/icons/pokemon_light/plus.png create mode 100644 resources/icons/pokemon_light/plus_icon.png create mode 100644 resources/icons/pokemon_light/points_pick.png create mode 100644 resources/icons/pokemon_light/real.png create mode 100644 resources/icons/pokemon_light/save.png create mode 100644 resources/icons/pokemon_light/save_session.png create mode 100644 resources/icons/pokemon_light/scaling.png create mode 100644 resources/icons/pokemon_light/sort.png create mode 100644 resources/icons/pokemon_light/t1_from_tau.png create mode 100644 resources/icons/pokemon_light/tau_from_t1.png create mode 100644 resources/icons/pokemon_light/values.png create mode 100644 resources/icons/pokemon_light/xlog.png create mode 100644 resources/icons/pokemon_light/ylog.png create mode 100644 resources/icons/style.qss create mode 100644 resources/logo.png create mode 100644 resources/nmr/T1_min.npz create mode 100644 resources/nmr/__init__.py create mode 100755 setup.py diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..8c9b2d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +.PHONY: clean + +#binaries +PYUIC = pyuic5 +PYRCC = pyrcc5 + +#Directory with ui files +RESOURCE_DIR = resources/_ui + +#Directory for compiled resources +COMPILED_DIR = nmreval/gui_qt/_py + +#UI files to compile, uses every *.ui found in RESOURCE_DIR +UI_FILES = $(foreach dir, $(RESOURCE_DIR), $(notdir $(wildcard $(dir)/*.ui))) +COMPILED_UI = $(UI_FILES:%.ui=$(COMPILED_DIR)/%.py) + +SVG_FILES = $(foreach dir, $(RCC_DIR), $(notdir $(wildcard $(dir)/*.svg))) +PNG_FILES = $(SVG_FILES:%.svg=$(RCC_DIR)/%.png) + +all : ui + +ui : $(COMPILED_UI) + +rcc: $(PNG_FILES) + + +$(COMPILED_DIR)/%.py : $(RESOURCE_DIR)/%.ui + $(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 + convert -background none $< $@ + $(PYRCC) $(RCC_DIR)/images.qrc -o $(COMPILED_DIR)/images_rc.py + +clean: + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000..6860c71 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ + +from .version import __version__, __releasename__ diff --git a/bin/evaluate.py b/bin/evaluate.py new file mode 100755 index 0000000..7fbd533 --- /dev/null +++ b/bin/evaluate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import sys +# 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 nmreval.gui_qt import App +from nmreval.gui_qt.main.mainwindow import NMRMainWindow + +app = App([]) + +mplQt = NMRMainWindow() +mplQt.show() + +sys.exit(app.exec()) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..6cfe3d2 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= /autohome/dominik/miniconda3/bin/sphinx-build +SOURCEDIR = /autohome/dominik/nmreval/docs/source +BUILDDIR = /autohome/dominik/nmreval/docs/build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..9534b01 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/fit_dialog.png b/docs/source/_static/fit_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..559aa1420a4e60d187d74a2d0215363e9e758d98 GIT binary patch literal 47372 zcmb@ubySsK*YCXn2}uP3krI$D0qK5C{ZAN>WrA0(pcCfxsuBz=LlF zBl4@jKacGtH60-kOe!$u9`5Mf`UwQ`5+Wt~R>du4Z_!l)r6dl1`0O%WN>Z>ZIT^;=js2;>si zma`hOZ}fx1Poc2h>zR<4!a@s7OiV$AbVDN>UER!wrGt3Q7j0_6i7AC8FxibY95=q} zp}M?c(5hmfrk2PYVooTx)@Z)kD>ORWooCi-`jk~*`S|bL{!Y%HwL&CA78bz_=u0@Y zKM+T8CnO~$yfEF(!$3#Mwn?56cMd1d&PwH}NhseI$PkP)6Gb^D-reW1|22Q<#;dIz zE{^ta1sTb2l5xKrLy1_t3ieEXb&PyDMHiOT(rOYVCph5NI;YNjt)FSL*yOy0h#CB@ zSZ(mj7ukO;Yf{E+$;U3ks@c6^=0s9thGST_HOKWtWvA@MCYdaDh-jOQhX+#YS7z+> zwY3+7gjR1<9?l3!Rpr0Hotc}NnVma$yu^$h^3(9cX$&=alw{`6>&s5?m`L7O8(S|V zfB%RGra(*h@9wzVU}3GUqMvxlO4rnA#D*b8!DgK^{i(6K_;>i<;|h7E8wM3h{$0|d zpmWoI_LL%f(f(}rIiTSDz_k=ltP57IhlFUMR)j!i8bdFwnT2 zLsj3u&FuaJ55ZtHsFst1nVOn1;1xF+(MGD!g@=Uq4-c0g2tg)#N4{DyKY5*a&rMMt zLaG*b;iOviNgw;CZmA{vYs2CZJV?#Yu#!(Fk6|S62f}U3~pN5 z!pDb8EYEZF_``N0a``GNKd7nU<#~Bce&?j5q-3PxIB&i>TSsF*?ks|)l}W3;%tKLz zzchXJ>=_?soh+{_YAZ|Z-hW6gnxSlmSMp<7{#Il_4;jIxqPkHpHwqB8 z5kzJl7?zNRER(!@ch^5%j*I%ALqd)i-Jg+I7+Dm)+iZWQth`M1iZxMIm}`>w;bAaG zG)BzW6@`)O^4{NWGkkjT*KCadTP1|JrE>KMJS`S;C!L{*QgA|=b0>p{kfIJf^bFNE zH8Mq0jAghGMIIPip?@(tIF^a98l*`cpY4-$Nv*u8BKRgLi(jw=tW}M9@Px-@2Bghp zCJe47G{{9twW-S+5t8w=tE-D7yZad~?o(lbSClZhq#X{o%d_P6KIY;PT*$|ktUrJ5 z4m$hpee6FF5W)QYU`{IYU8<{QUWI(I-xD;*6j-x`7nB!k7AGy*HKq^QQ#s3;iZOLK&#~6mf67Hx>{w zCDvqSuCB)OWR0tfw^oYqne+tQudD-;M8yOXd5qL9mdXTqE=agN_ELQh)YM;@)H2f1 zfhXDfutVg^#9BwE-A`mFrCF%G#q+S^*XU|!Xqe1-cBbk$ZSGwKox$tpyytruk)J0P z(&XQXsAbH#XjaAjP(x80(pGVx?=wO`*kbHw*4RGjZq+BD<3-rTM)(-(N& zlCM5YvddxJWPi-6yOirl?Xlu*XxMq!X2hC!b#(>p?`sb%p@%3w!6PAfs@doMv*PI2 z(O4h7w1FAe#_X_Y8TGQqd)D;J)lIm&8S+2t%d&S3Ux$|W{24m~q@QB`wOUL-mS*}DGw-zVX^ zx>uJ9M~|0$D=I1^$)lVO7ny0f-FwRTqm)Lkv53=Htjca!$#NohzfZGNI-UF~*dilI z3wRQmRd+bYHeodJ@oguSY|@ngi_#P>*(-W_tMwgc5q@!u<0GAFCK^u81Y3chohKK9 z_t3=Gqt3sdv`f9yZ;enIr$aF3rHWC4+LJNxIb5_E;8m}cdj%>ycG{g=2cxl315f)? zQc|w5NL=oew6UE-M8~=d`-8Eh0=_PlA^5hQCHK%AGA&-a-L75VbjbwvG3&MM%=;>8 zxIH3!1@2(yizffo`Ni|~TN;Lxvad%6t;=f`L#vz9@H=%cQaCnwvJ=5Tv)AM@y!_w50nz-|>%Uxx5c(^k+ zhKw#^p-R*H@hO4Z#pW{yg7y8FqXX|=NuTRmg-E5P#p_d7Ql3BMy!Wx=$->$pq7Hvf z2_oN%Y*DF~FKRS8Ro0&QMjQrX5j)*X5EQBu4HUgaq^*@v<6|j^xJH$>O`vpTGpXB9 zt{47Dr{o8oK}l)podv|Og94EDX0=nzPGoL!vP@(!ESEu*9)z#=?D1}o%)Fg z!I<~Qo2j?RBEg;!OI0?(mc^Od6#kKe(ya?*>I<+QdEv~X$=&IRe0s7w9Ixazwr{39 zPlIZU*AfKvDipUuvp|?~bF{MRPv$wCE+Ak76=i$(PWm9LuPV;Ua-!ga`TdfAhi8uR zb`kroiH$-UPqhOq8qG|;#&Xth_dNAQ!*}?M@FZRiU1B?%j#hzH}GvAo+S)e3Rd-y~Sjo-sdW~6D^R?lKS@2n|f z5b7Hm=E(+3gBaRIo#iZuUuI9>iIUk{Kfr#}^x~Ke>wZg36;YZ+BdksZn~X| z%k|m|M_`YZD+iN#ufj=P*MB5(v*qIm<2_C*k${^I1ijQL9S0?G9`#yVX_X zhNjpr)hR)g>MM8t60V2*T%ORaQmcfGp``ap!!5?IT)yf;3_y2GmjuubZ(&Tdn z*4+zuGQ~+Iv)c$yrwf|p8CB;g_xfUgjAu+j3xrkaS3=b>=!%3rueurM^?H8#R90C= zsc_IM&eTaS4#v(V$t%USD<-Nxo_wbfzU=U-b#N52+3$W9~ltMX7i z1;v!gZm;@~p6a$al==$dm!?j*|2)df%cGK|sy1`oY-r%Df=*0K(6X5h-obo<>7oqy z+3JpigR{H8-fPUtTVvP1di>)D#UC7OLqnt6Td&!xE8vKqb_U1Z>ybuBlLkx3ChzHI zBlV1o*l(QD`YyL7#HTS8e@%;ei^9UjUZ~g^U2I@$niQYr0}S_%A6Z~p`hDs9P#(EV zsl^9;#oV9I&CA90ZNkVX!4jq7I0zRq=wVo28#VLr@TlXMs&p9a*J3XyK;4&$Rgp${ z|9WDf^h=T%RjzCGSAO0#S2kX4LJzBh>3W56s_@C{2uPetCdFd?@#7C4?CKts8ohpQ z<6~~4GEGCr)AUY)8rE0G!Eq{_OsZ8rUm?YSx4W%6`s{lZ1%jYHsWhGHVN^$cP$;7b z?Xz1}YU+Amdu>za(j77te4~fO*WwEm6r!@r<=#CrF|uiO(EL5RN(};ZOc&P^A{Lz~ zRcXs2vIK4a&P*B0sgm9m`{;Md;a|R>ka@MS=$7>uhIC=a#gKk1S90vh^AT9(uvrvG zNo28+ez@=kw40exi~hP)X1$hYxX&syO29nbUvJ+TRca79{8p5gFLSJCD%Gk}oaQZ( zbxA2IFK3SbY_d*RZ;Ow5;?75LWg=~vq(Q#Td35r@H%;+de7sV#3*$k4|I0k8y!Cc< z2M1|65s`!Zj^SFX=`tn%n3R+soE&!=%EfqOuh4@}`H>r6jd=G;WtYH1~b>|{VD=aB5Z~OMGU*Y5QJ9>XV zsY#)HcDp6(l@@Mvq)t5pbr(j}wkM}&XP#$UKHEF&N^J$c?QKxsrrUW^m%2KBPcCMS znjA659;gDhx?fcr@*Ss%p--E+p3v_ogbwH3DHiq>!siv`cMfU-zd7wzx9vO;5D~pNFtp`l2BS{6^pyo8U<*wxlq3Hrq!r z$;#e5N&hN@!otbM&RV3+aK}fi$M5LaV=Cu*u#^mv#xPM36WLsgSdm2LFFC_qdT`I6 z^~pU}*_~+Qef>d%iq zT>uFpN8_GjWo2YzW>w9+_EA#hC0RPg~iJ2o;3Xk*7=dM5Fn?wD5GW<7j zJ*x!#p2y0%o&iA-=+dcOaf-&eBY8`=3pZ2>7-(4fE%th@n`Kq>Wa;<<>~Z>OYn%cM z3|`+e-aBp}quV=Y-hWMvsWzUayU3&vk373AYx8%UW`gQ6rU>R&RG8-^TqvwkJqaC1 z;}*f|_BB+#x?_Y8Xa6~{1B%CTM_WwHyTPO&=sNaxPk*KdL+hWhyc+f9#cSavr$do@ zG!z}Z%(@fP`;<#E+BG2>{|=3MOW5_#pCIRHx^P)+^h{SFH(^aIR%h^ru31F3A%Fmu zC&RZrn%~XSy)7cd`C!qmYsWd@>tufd9v?lr%e_`73$AmOmfXj6s`?asUrR7{xcrt0Q*ype?LJsRE zH7=9M^>ztm8mVb%9j4XIF57P$uioH=<&K0NSY8b@rt-Kv-#f_bD}ofP?VRb)*IQ*P za#`$rQ7_lW5k|*o#31zIbUMnID%BeEm6lOhuC!7xp1`SWXlV2A9P5G$3R>VkLGamI zTiY3{FDNiyPdZal8r5N%HoE*e(%B=7wP#d43&Nr{llh2?5K>t=xgZRJXI1wjA=$yN zu%(vo4l=JV5)%{K!WA@XUA!F}9P%UN1@G;O@}{qrZi6x5leH1Mv-i0B>YGk37NgT~ zAzoq!i@xTzrpoO;-p{e!5{B5i;?mDQs3q!SDRaW2;2P#ataM^9vbZ zF>h#*%i$dYvY$;?^(SAaqiXoe_=V>C?+XJe;hJx4ZEZ*LN^5r~HV6p9kD)gX2$V1B z3k(qv5skWGgDC>JgIJ|9cV``)J;`;p%k8ez-pzvT^(d+hx290$yF5%i$doJn-BAVTDoB0|651O2vtWo|4k)Q-36y_oc`B?r` z8UCXkgbIqFo1aYJXK59F`uMk1aO-oSVsrBZl~QdTdPu>~5+Z$;I8wV3e=ui|k9p?9 zSoA!RaqX1_o9}7Vni-$}@j_`eeqekBj%rlKkN;2psbhv391W-G-G#ym*iio?oa! z)BLEBHI+4M$y*~fHb9HA!@*9Jr6R*^-n^XcSFcl3<2loW^O8|kR>oOsHG2K|JSF)N zWZ$m@;*?6GZ#=*`m1g!?1r7)2u6UY7II|*4^Q~Wj(6j*kN4ws9Z_;`sI3e1f9Q7C%n%`}(%;e}V9pwDHUqb_P3yCHlfT3b66Z^r~t#03Xq%*u=vscQ)s2`k4(D$Kp=%kh03*V(WX5B_0 zxs-mYH+}t_t_Bq?(wLQiF^M;x!R$2wqfBh^$Dxd8sQ8FX*VhO9v;7gobIcg%1vj3w zZ7gKZ#J0D$VX&*M-Q9Q3oOe#S{l42?$A@Ew7z3$cwknz+(o5Yk0#7LHy(jVr$J_~< z%4~_j#nM;pdf#TClOPL6I>9Q(QxeBxqGQy{H17|qH@%Ej6WQ`^M~ZS)=oTAHu0axq zhK9z-$e_lk?*R0cK(#RZfn46b`TXgnuLxpYUDn>W(&fY-S*4c=o1OO^PA_|f>?}JxYlCPBP-pmRI3(+><|Zs(|Q3LhtSH_ zcBweHx}-BUb|Pa?TffE2VqjG+ZA%?`q`T1@lm9lkE-ycymuqJDa#;lgKPGDay~cYB z)x*9mkzT{^SE8CP5EY<=9+a%dZAQtQuBU{82YrETj;8YYG?wQr5D?he>9oDJf&QFz z#5XnDT<|^GI%q{UQy^(|-XB}gWMkM#q_2rSd`-@}13d3nY#~)T=a1ouh1(Yxgf|}7 zoL6VHgq*2!^~FlrNN0t2{|8Zn_90dSuB8SaMV^?q#j7_<-|kkNMmT|4uP#e!RoqOT zi1?7r<#=p;c9vVEkm56q!)AIj;Zzx)=Z;29cX&L5Hd&l>90B&kLS}sh6dp?RshW_aZmhT^kb+kVj*gF01T>)A`$E?W6Y7Uc8qrY5fK_qCXK*0hSz&vh zwO1CRjApV#O*Qn5ecI-z;&Dt~o7))+PK@NMMb8TvY5AoG=d5yiClYKnkVM}ui?Y4S zbL1?`pex8q=kYn1`j*UYxz?SqCp?{t(3Q}NZ&kc^y)BcK!0 zHLNHux)S#MpK|$uLD})K@p7G3Jq?YC8mUzWlJ=DNv^Gz>U#EpJ6NI=z+Sm?|Q zP7ac_1Mz8J!t^Kj7u`lPPmugaH-aDPRasft{iwaA?evj7<3Nvo!OkkMvN(VFh;lkt z?8MZkO`!SwAnfxOOsvQSVj|)EM}6gI_;DYCKN%*fNZWw9+Wr$5hBrXZPC-X6cQw&+ zx+Wbj0aG}#8EY8TI2;Ab9IekpfO)&KlHG$vDj@K@>f&a5b(~7!4I6u$La(&k(H1JT zLaJt?S8pI2wN|ZlrSrAC$r#&5y1Y>~crdFx7Y;y9|HEo95_^ApGo9M5dUK$dKGV?P zgAkm;?V9=E8U2;*Y*oG+ymr}}B_w&O8O4mG`bo)A?Q3)rRlMXC2GQ8Ovw_{TU!!}Y zyKttEK|Q7Uero$+6DCm=^O}KX6nHLVQw926Q(XJ;1}DK>!=#rJX^OJnT>}+nlUYw( zRJ>9{c9tLMnOJU4&I^Y|b}phOpTQ({ZX$UHX-f|e@qMjYE+T}c6gz9#X$2V>u-zzH zT7n~|H%(>%i1>xoWkkxOfm$A7PL<`3m-BzQ%ceW1p8w z{>Wx7r*f{1v2Td-oRn`NJCgoZ;rt!HaYb`#stpL^b4^9p^QBBE)#=#S*`ukW|0-HRO$Gw%H@o;S`_i?l=)LPtS{; zN|(+&G<@blGDFos330hpmie#!rQOdVMH(PVyj%5_*27leWnZvi@HNUQe7yYqqxV@t zHEOmeaWea2&3ieWg=7oC;kvrIoyF?rAN#S~+}ttu7bg{D{zwf?Dgv)QhSHo!DC_ID z12KzUy*x}b%ZJH(?cUgW0B2)!^Kl@%;G(mlqN0K24x*!2gbHs} z;-Awj@N4wi;uet6|0X091a+D%gU~D*ztq%Lp$g|7TcDf+ceIs%&tZL}>){ck!GJe2 zJ5_8pw0ayG7KW>}zP>J(bnbZXvVs_)NOCi%lkrJkFS;+@?l0xoeBvP@+9VTzz$u*gM8f zgV)j#=*B+Mw=#pDms-M^HEBB8Sn1dsm4nhqWEk$g*#aT4w-3nXHHO=NfC7jH4-b4X z&;88tJGWeFw{$fOEN8#+An{?8ktV0R4~s!^#0Rb%u8PIB2>1C+dYT@PcVb7p5} zK@RP7xW6OdWDFjX)BVMFnUiP9^=a*HjZEf_2?yR=zC8+QWnKo5J{o7q^pb_XzOjy_ zrQX6;vV`kx1=3xH|1Hb*w|)NFZm4}&AN>Ufbd&q!>U2j+VE-r#Z|M4K>>9V}UdDTh zb5iuU!OX&=DF!^Nn$6Fej(87)x7&x|H-;HCC5Vd-aN9T^d@3rR&kB++1HL6Ez95)n#KZ63>BnyI zilWhf6;0`8spa{RZry;HhLf^@!8ZH_5)u*+0Fyk?AgRik=|<9Yl=s9r+3~g}6ZQgn zFDrOGXf)2peg?F=th2&ULBJ>AeyRv74Z?IO_j(1PqcxeECSA=!P=|7VO*(^msjn%yP_N8ieAHI0u zF#XF3UkRue)16L(`AQ3M^SN6D0X-i6w5z>F6IaHBFiakPV{o>?`5H_c%*!Y;Q{-Xt zWM%{yf3*f$yGJiyGS0i8H#l`oOc>96eM@$YrSvQg2qFUJj^d_N$)(GcBA%ONx@*l^ zT)snf#LW^O?t~Q>z>$#29NjsFvB5O-1BWq$JXIWUhI)Hx85l~m>&udUz(GuWHGfc$ zUpzWYXR!(WJ|r(9DE?)j1XU*~pE!c?$o0*X=o^UY;m%p|{(Noe0Zq|d>ope9qq=(3 zFA}qkv3Aazht2!*7CgFe5R4|*`^?5fsoSfwxROIoiyfi*yHgV&f+RZPM-cJ*Ks5@) zC1B$JB;4|W+iaUIJ2UU?b2rH#1%|G7jng3T@#|hUIpJiNakv$TD_> zA6%rFvH7o3096PP0-8iuT~YCfi%U|6{`S!~Y)2T>%`5wVq)PAV+K z^k(#Dl9ZNWS)+eTLZZ}L-@4;=8 zz(E*s2?@S1BNd z^X?Z95zV@Bd(}iGru0lqx78)PqQL(5$Gf5uk*@ax&miWE?|s_hNiVU5W#wS1_o9ei ztQks{zfehn#opMA6|j~92^ue~)^}JQ=nK7Uo2J894esmkW&Cf?l5S5`g-3}gWTffB5VU+@NrW#ZYseTk>+ zYqvREY87czUHj0 z6|$M0ZOtzm#a?7r*b9FK`D=xRRr=k*qDSKw=(&{Jga;5$NBf}Hw_Q&zX?(PO*0Xf(`BUK;hx}g zo(0F#cpF|7Z49cIO2=UVo>z$g*jT*&q?x-$w|v?4>BYheobJeEP^ZA5R+W0i4ER}cE$d4v8iJv*+B$5~i4vzO7*&ty} z<8|I!HXA-%^0Yr+Q;P8GXdh0~o?ufqr;JHTsxz8yOXE)gnL0he^N&zz;uDcx{vL3E z)G1^epR+6HRq8OvG% z&<_YON?Z4yv5NHup?iCreR%&RVKXNG-owf#pc&0{Z6CVa?=K#c^{>n%oG2mocm z=iqAp>tDtCem)rxaqxMax&bB-s3UEB0NG(Tnp!yDpDWYBD^P4NC@6^B6=F)5FB`;y zL)%b;F49jVvqVau^!DRSm1;{Q4)quMuFsui{mAmO%|$<;%a2>mDTMlu09E+ZC_kbK z_c)zz(D##FaXh1Z3XkJ9bn?ppx~HGP&RPgiqN#UR47Q>!F9XueWO*I-eh%u}fiQ1Y zLmT8|xy;CMII5Z|=Fs0OtF0Z^uGWhM<>l?xT|3B;B%t&$-V=G`Cca*iB;Y7-tn!8| zN3B$AdD8WWcz3blJ;M63O7Vbhg`|Rl9V}W5m?y}`?n3ui`GsUo7M98-y);n07P+qD zBVlIX`>`LDWaY=x8!k9USb28zZ&q9?-(V}I&W_+RVm z=CIfdW4SoTN1&dxPCvaS!=&3tRZ-2D-y7vm7N_u7WlN3$&u+18e3ROa(vzv!$2jND zy&>hY$@#!`y`2vCv#NSfXS3pb0F|(HefECR5)y)Hbba5@kd&lbJpeZJhte$PH-Z7^ zVr)Yj+T?L7E|?%d9`(7)*mj=C3HBoqms!byH4{j5Fo=VQ)tJ@nb;alMz{+G&pHQ*SsK8TrXp7I&vvfSs8nmT?EQeRA?F zX-2HbFC|xQkl*yV8{D6jmB)W#O95KTnDXCQtj#aKk<{jk@aF{Ww!6RF+8rV2b1B4sQj{Kt=%mssf*0!5q9N2i(A&%S5D4YF{4Z7C8;$+(#(Vnd zvgHQ%;syZovD+#iuy+;$Iwg8kYF4A38k7)!oT^8yV9rdr@^GTZNA z(~H*^k!f7Bl_J3wAXB=pEm4DBKG@AR5Ucl;wSvcfY~1LfSSoN~ZLH!7wun=0+F~je zv4ED2PDy#W0!rik@65C`Js=hkpmzh2!zoo>UVh$X4=+TNT0WJQnl91vf|Rb2eBIua zu)<-m34j^#H%m;&F&)O08r^(LFE8Nohtv9ddrRNtyYylRwe{+|-U--m*=u=-muj(7 z!@>_o+H^IOgQ4$L#(?~mk}`)A+T?RG4GuIG_>bNQ<)O+;YjiIsSu~03rz?k-MRM#( zYsKlgt}{P|QDC4&&eU?cRV@b;eF;!gQE`Hv&Vp!#d1~$0tuq*-tYbyxt%~A)jc@13 z9SXY~hNj;QbKw8X^e=RH{`cDVg|e?c7g0p9OuDY8e>%T!LEK9%Pj zzN7gfbOWy48Qn z=JQZDHcXc}tsge;!c{Z2aM|MJWT8m&3wBGdO0vJmw(HJ_&m=S#i!>0xNP9+DyVLcNH)>Jw z@gyzx2K7v-jXJh4r8wz8sy9HH4g7KQZKmN0^PKWNDLtLE#(lnoU>q<=(s%5r3`uXzv%A62bM;6D_lnep_sQ8pEHV}ZZh3ETyvwTGmF(vi#Wt}!-7RopzCVeS0 z#AjlNOuDlRm9t54{Z-YDcuaca#X6Smljfs8KKyahhyDC0|2Xeb!F;O3LRDOt*Zry_ zSFH8!_G+r|B=Fc1i&Rj+=O$;mAj-qz8};pc{w-n9xkQ15<@2{cIXQfwrvze30RJ{H zGh=>Yy;#Ys{|Y;-Yb8nvMy~Xu`Qn4!61MQpB-`e(8vtujOVjvnF92R{SgAoCrPJy| zK~LYT)G0034WEGr0!~_5*v~>XGGvikuO3hc0Oa}n?A#t2_e*SCP(a_JRuIdAhf!(H z&JtnfgDqxQd3oA+Qswa9sD{cl&to_7VZD}|i_d)$2Y)kQMdJSkS(zZcY7LK!4EGdC zcd(t$1HOgAW2-U!u%V`=#vKixGCwaOfslXeFSP&DwA#DJgZfpAClJuohWF>{R;GU6 z-EHaze3gjoL`6mQ+1pyXvuA#8hUS&uHUYskR*?KD;r+3R$JJ^@g+Qyv81wkpnCFEfA;%c~ zac6LPw-=0LD!JYki|7xF{bvauAA#1R9(~tXo$P3^ZLLd6m)aO_Pw;V{iAo64($J{T zEH@dcM+%hr2Zc+@$mrL5aMOAse66glt*ohd8BlY(5`22<^&dy+7s~!GGV-Hq^wd&_fa`GaNckmBHr6q_s+T3)+ zP%OrqoK4kSO!L&RM&O-C?=VEJDJI?kh86??K)p%zywI-nApSq11cO6EX>5WSo_6Yh zW&mVehTUNrYN;ssC@3s6U%2egx_&QY+ng@d(<@JNUq?VtM7nK(M+_j}{lsy0u(`qM zZmHpR`6uwCiPtqZ_z!~?*P)1V^yarpaOX3=uT{=J!Z#7-pYQMRa(y|4lCZh7l!n(T zEjBp?$J}%W2L@t2e`7s#^AWC$Dd1{&vHO`A1}V?0gF(WJOp>?B_3>XM5LE7eg4K-S z`ja1~)xjW50nycaf8^ewSR7&Fi$e#l6s=lII`ln(=N>;B6T(1+?khqtXsQ4z0U)?U zQ5qfg#Shc?`?%d>(xQ}_)fk8J``f5g*-u=7TLJ1W=mPNNG}~>eYLdr z*R#IRnnpDEU-^D6CN?Chzx!%;@9(a^EDh+jxVf+IUzkZ+K)$Qj9`O`4unM}c zu%PLP-_(q;HQ$V{E37mTSIlX8V;3rn1q42jhR^uEJniaNH#Nyy7t#kX)T^tNMyH!tf$Mp_KwX$iFp13tln2hTM!+uf?m@di{Z@J3s#f z@MJGttPw^tOIDh~2@Hyt_S--Li)r24n()#5O%CY%Q*{rigmOJW-<3ZgW8g^MpU7Oi z&y%Gq&TArn70}{A?6@{_T(FfdocXnfZD*uxf1(#i^{Prcx^MYt(IIpLwfgB`S&x5@ zW~be^7DJt7t*i6oKL3Z`Jb zT(;koG3-A2zc)zrUUVD02I_oRS5~AGaO&lH{GT{%Zrk1?SQXz-s2JFo02&q7W9^$L zIaq9N3eLV_2EMe>>P_aqXq!ShKiXVNU?fdi}dRImqTN0Si3NY9(x$x@)5AECjJm(Q0Kx4;KIIY}tj($*;8 zGL>reX}#43PGM)NwBNLP*18U0)7j$7%V0ZM(bH=a?gF?ztm_X5?W;EhBaj%1TXwj) zW_)#N-v$C!u@Mc=`EcrNV^ZJa%svfN@)#;7IG%~!9#5y9y$#*;=u1kHg^?J547^rm zbK5rze~3E3Y5T@VGj2CASuUMt8}QwLLW+!3;L@K%(K|zp{G7sbYv$;9lHpt|pU#Ed zVBcm`ZQ93KzE!O_+5EtFNE5t09V}j>26c@8pmF!^HJ?SVKK!8Mk0H$foXS8%kge@0kHCu znc1`L#qmNB&)NHOQ@P~V1YhWQ^&u~NQ^9R2X127i<4(R$HR&T)^l7jnupOWvPew5tg{6Yv(e^YcPE_Y6= zJc^L0f!jRQ+I?oEqcaGTDnLV1F%LFsn z@$&m-Q~;1?@{eLWp&_D(40!!Omtvl(sjOS7Sy>gmb3GKi+&%Q|E4Q<@6K1XVWyoJrTBDzWj#Fgn|AQcT8JM zGr1=OTSR%%_9CJbm9o{tCBM8Cq}+CxS2r(FJl+^^uV;uKA3v^}QahdcWz4#7Z3DJ2 z=q|}>TD^LZ=q>_@emgX>-Ep2ZUtzAR$IHo43DRl?dO+OQ2eibMRJNjwK;D`!dp>QB z9uh)|z0^whd#?P(QbxU`uC9v5>!k)=ob6JR5r89$<9$;a_J#yq{in*y-Zh_twB1ie z9qDkk1JoMjNApH9o7A36qe6;cHQu!a1Kx=1TcfS>f%;4~3soSzRC96L0Y$hf++zAU zTvhQu%4&8XNVA-&@MDb>^CiLqdr4kN#ng1_7$;x5&Rj3^I}lr34>20uck80-kK)(X zx&I6%Uq*8ptBbE^M?dCw7|2yf2a-2P@O_Bz?gUsyjb;>ue}NMqV;(C?R6ywHXc%IL zJ%JD@mUZK>qh1NJe_7Dux0u?@s9zQAZ# z(wp-vH}~6JHOIJb63wijYZkbt!otV5Rjz`ZJVO^DAyqt}Y6besfUpXvM;#_tARqxM zNp|?sjehYvTv<0CxTB>lrL^P6k56wrbLB^=6m;o7gEs99j(@00{c{>7j$}q&93A)d zi*pWvrM@xYM4z;~Yipovb2l~y>DU50>T`8^$wukCJJ$%}#=e1I0Qx6{LP2;#gVa4b1{ep+e_Pm~LS8&ad!$kq~u0CNXc# z{pv6;NERl`ROdZw%(&GlO#ZnoLskN4&rhgyYV803Chks;i$Vsw=lZ_&G^g5SOZ}S# zaNp&)T@V3X-42(O%QG4@1H=6|W1JhHSsS$6e*5Ot((*DnsJWs7bOR25>!G&_Dk4;G`$>JB%lun&E@HYJOCzpO~e7yK2 z1j(Oe$!rgZYd!KQS4U*Df`aX96kNNLY<34T)}Yl9M7(X1HQv){bv8a<`H@dUOS60Z!c+g!6z;ewhud^Zk4$xO*rN(GvwM)qg z;3VLEur_T`*Fp7Cr`abvGjjz1X$-n8N5NC2baM!?foZkY!wWV6PwpQJ2>8S>>9viu zr^{$*d7Q@Nb2qx94!4ee1+KGE=b$nMESmsboHS^$S?rwVVD9Pn+u+D{5HVZH zL{1JVm(ID9#vS8+-!HX;Ux()hGYo?;90@YEh|O|%&|?oBoDI9n!o)tRdiu7IKe5?>)|Ql#l5@L= z=y-XyL+fnSX6eSal-(2QYrc3D#-7gS-N(sNsM=h&@d<7}^IuZW!=s{!m<4at#*&*H z=?1yHO8j;N8c{RTZ8N!nd??^@vzG4by(0B8;0caEyZ8K5ivmT_%6#7c{5=FBWMgU1 zl`RoI0R9GhqvE}>{dx^1igc5ERxKv&sYZzQmtWTi3d{ZTc8Z-GkWLvIk|$d~{rIoL zD1pn!fPd|U0)hNa4%h^}*wC-o5U>3Td<3CwK>dEKvQLHlg}NbqpPq_B6yX4C3rXr` zUjtfmVv^*WpZ5#rVQKs9fL0`rg2ejU+k44@qKw))QnG_V9+9@iqT zbU^CrzW#&)iIsT&NC`Jv8gn6wCiG75tr7rVK%;9rK&o~UlaU#|Sy?DCQ&Kvw#@m3N zU!eZR+)~$=AJACUJ6WRa z`^T9Qk<`@G+L~HJm&+4}JG@VXqoZT$ZI{p1w%a`z;@i^?n_Um@FHeR5BoRR@xIU42 z`|AF<^}3N*;JLM}X?$E=g*R%ih?z^>>oZou(N&J1Vwl*?;$h~uX6ZUK9=3{ z9>}`H=(GJR&-r|h#})=+vkJbi(*yieyOH5&bhQGAvY3H;#k$RSy33#h2Hra{B;a+c!;$r^Al>?9g#Y+x!dz{0= z#lYgiKhgf@9S$$9-~5*sI0$1z{j)cPioYc#<(L^Sv^W<7tpPN-NTRr(a0oCR48~_T z1A`3uAHsKDh>1zK?rf=yrXe>sCmz89zLx3o=(l-vs^zoBFx|?ti8T1*CS5>s>VlBK9QCg>W%C9;a`BeezDm)vQ12MG#(D&_2l5_ ze@p^T{W+)u1h^hPM^`RS6AOF+a0JMRo-C5c3ReVqK(Fx=VQU-f2UPlWCgc`b7`d@I z{KYoVubHAG#ogTd={6t7@yr8bx~>luBo5!+-3ASm;MFA_9;rOFagBsz?CgUh047xO z_t7j?W5f)OiB1wXL!|UXqLKQ`)6?}$#FnosIMAs26@33@SF$wP_^)?J^m*U+KNy3) zPH#lj!G!^ayG34DBq)rfg02Q#fRkSICouUCrdPatIRY9ZUsiy&8Gw|=wg;76wtu<0 z6jV}(RAs;$?=Q5lfU*;yBegPImjmq&P{(3oV$cvc|BkO`=ar0NpuHmVwIC;Rd0r+N z*(=adnvwB+ZQM?;H*NCPuLFOD4f*4kw@G$6p!ubj1zOAWnvZu&m#Z8ds;a9&U~vbO zSdR;kElK^iUOJ|_+RD1>+F5)Dp;vTI!n#1-7b=Qqa%OE}a(12WE0sGoQB&iH&lFD@ zl$u)Fm^cfZEa-;O)SLwoF$&p(Jb=#(XzKO}#o>j8O3iUl+S>>NZ^jrM;`f}t8%b(= z=qi{)2QSF@cy2jWBlZ1LBPcNt{}1-wIx5QUdmA4MP(V;p5D*X$6cD9TT0lA_C6(@S zfI$ff0g+O=yPF|~?hxrlVCaUShIkJ=&-01z`u_fT*YD4lY48Ls$=(dXo~w8Lv64>wYLinIoaW25Lj^J zT-IR)4-4N}DeCvXE=ceoh&)(nGfG+C10!xl4Q9w@@ zykCznOfp;wWNz{g$_{E6+q=8j+1Ro^*9-RlD#&JjL`XscJzKDUV#Eqefy_4~PzZVV;s>)Px7kV1Uk2vYwe7N;dR_j; zE5*8dmRS85qpZrN2qwaQZp|aJki>n{`sZe=99{)~CTP?Bx%N0Uz1; zQ{ZVEIy~Y&2>H)WeF8yD1z;EDFHRRRFS2r@2~o(vZ5#|kkO>4VUQKp&PBsPR3(3n* z=m{(&d|shlHz3)&@e~kLDFyMnm&m(>9u;VMOG@0j$?|f+E{6-}moOmHO5#vSvkvfy zMD+?qs#e8UjR6k&!-wktTDcyT#|_}P!Xz(Mk2Sz|Icr0M3Wfp^|4($m8-8Chrs_swBK41oLzybd_Zlxahl?+%{D39hTdNK1$fcf#;2#nG zK+#*}EU^=0Hbga9w7Pr>?)0`=pH^HE0QTU_A1;?QKanRCr{Hzl2aNt0tqZ%43cC$P zAdL-*Pb`2!03ee3#vAy8NlJXi850L0jK(G4AV3lW220s*lz)<#5qUD zLo8e#d}&rttRbYZ`D&)md9s=DBBsJd5hQSDlW!I80;B<8>UciGX)Pf5s~&!q8!{DAcuDNkS!Jn z4_u(8y}p1$?Cj(qUpp0|*#@%TefOGHjOoJ|BRuNfpS-W_>qmBNHkMHoWheAp~^2m&kFjXovE^dBR1j5~Jz=nx| zq0@b@H@4$0Hml$2Dc4sXoQn_uBF14dam+RuPQ%1Uv> zjja`g(O7`4KsEa42pp6GvU=|4Qbc#PgI;fM9~A+CgdEM~U-w*N zOasR?GjZ4X4SzlNGs`XV0@w~xT`%@!*O22imhIKGJ>aqMn$8`4{@kcO<6_^NyCUMZ zVAuxIr3cHYw>>;ONL)`>C9h+$0XGh!(iE9D5g#Y`QV5YKd1w{GY2DG)cVUkXHf#$? z=xw)gG_6lN*l=FEOUcV_+Q?1a+`OacFC{hOfsn(p-8IrY%}2Z_=~bzzcCY;`oqkt_ z&DKH%#Lqe0tYd$UTlayt=V_;7>toz>;z(DaJ(aUmi0RAE=dNaDl^+!~Vp%ag$x+

(%#}_CbC%X7YHG-Ft8Se4 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/logo.png b/docs/source/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9cab63dfb90fb2b92d87302ddaa0ff6f9cbfc983 GIT binary patch literal 614463 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�RpHDDf;33lqV(w&k>9IxnKVgL8W0iDK0Fd_Yystqsd&F?a}X10&F6ERTBoyhjLt-F6lz?bh|lko zf&6|n#N&F2vlUKTX10^81nOr2ID!$aul%g=nCdiEifLR_&I(&a8-C_o-iw7?DaVyF zx8CzkH!?K9NQ-8ymgTzKU%!v6g2f~gNl~g?s{#uMOOZQ{Hn^)RqUwB(AG*3f zZiQZ*vmSfRHGiOs32sYq6)I!k%Tpk4I^z+2#J4S{Nay;52D?E#Ez_N9Lc&cU)-=+@ ziA|D%v?I5=Xl$w_P?mX@Z$|#I2|BRCIn26GWrU{V!4{xfP*D?LLuq-(a?@erMslJT z<4}JAwbSFk|DHt47-wdLD2X41RnD?D3rpm31)Q!_{JEkbMr-vb(qAvvPB$+Zo7VI028SUtvvDf9y_8yoojF8;jcwFVZ+QOdlr-h z5W8(G9cUFr9KjIZ*qB%vn}YerstWDFN+i>D^r4=iD|fEwZUkIXBhFXY_VZ0xk5}~U zZCiV9JX1}jir?TuW4l#2w7W)pCYtP?Joto^`&3d57D!7EVPLFb;jN$Zv} zl@sbdWamIBr5R>ZJGCwa&)nhR zy1(JpZM5~d`4A1W`o(*kNE{I5s9(pQlP52}wdGtct`>ru+WO3+{&Tfd0p>tpkx#H& z+r;y8>o?Msm8aWuMg$?^y--R^<%p%EFh>YdOcv}|4Xlvzjop(~+OnleE(G0W3I0#|sF zd|mwgb3}~i$Mkl^n-NS$3(x2dr6}A@t}wi0nu*H7pYo)u9Ay8fi%##Se^qa|TYfia zNgPp;85KVLDV?|CUYDMHLQcEXit{*;qJI*hwl`7S@c)3j?1g|6VHmuRh0Aa&lNC}GPUVUl887e zYxt4<`PDp|0(hRTs#diZ$e(Jqt&v20U0eH(W&4NibYQ*l0zNisLXj3(fy94S#A== zPBy|4JjnSDh*@pbv7)kHZTel`rYbnyJ2kffa_=Bvl-!UVm!p@YVRZ+{MO3xlAoxFq z`VRvuXMl29ZvPU<|F&Q4Mgkx*L|maI875ONWphWzg!d+xkkIE!1bDV1A|kf$5nOHq zHF55O6~{)k)=bTY9ap$&)*-WQNeB-7Gj|N$XV9~bb@M^J{R}&DMW?*tKi?Y{YKsMF zy$;5ua07PCdO7>yxsB0|W|FqR!;OsEbXSp0tG|9@RGueO)(+DXViM7e(sf@*&|m^d zgiU2n5QQiM@TA3ya^u*m0*H9@?k%gYF8v`f%DZqM~~L0J861)cj_TL~sP zf95o0d2FcHuqPjcQ;lC?8gcK%q(36NC6Aggm?k?xq&by4`>q zzh*wNWv`#buItSWBf^Kha3O|ekP0teiTgMX+aAKM;qXn`!K>FWL0P0x_z|gi**Fs& zi^8b%YMt`@i?YhUJrMn*R2>pt9|@CQ^Dy{O&)B<6xR6ke4LhkmHw4;npC%<>|2Hw`}OHsMWq24cF{GR<5vW3X*Iqq6q*^Hkblw_jX6rKNu{9MWy z_S(XYmSFSVZE-#XY9$C)bz4iza6o%GF>z{ zQgcwY2xdICvrL^k8g`?W))=9rq)EF$qQlr!1O6fg zPpFkR6v)XZLr&W8b25S~!uky+u!m7-?|M%rMSiL%e{zg*>dB5I-*E@j5h_r|!o2b$0aI;~7zV_aLWEB-B(}M6_Xcw1&gf z`*-?AR>1sYW)bJv@6bTbM`+RujlI+H&nD>bmvks|Coh5JC%o-+BuwcoZ4PwNa~A`< z3a;DaYxW%Xlg%K6*amc#*ba$4oEFW7wda3o;t2Wur7NPM6y#}e4CsvKQ64}h-{*Xm zcIq&$3<~vS#kvWBG{@7zG*$oc(b(tAH+I?$Ay9$>B9k0gj$VT+fITPrclJw4ir>W$ z2P%)sco~{@5}eF>vzQ7Gjd_ZZ0AB`)Y!9*zz7<@8)j=mG^0`(2mi@I9#+x;4k>j{R^Xb9pV-D&|BqHWU% ztNzNx!^#v=M2Onwji!i1GQBtK9?*i_w;MQ19o^r1gt(?lqMrxW>a}weYnk zFYFU6#SuwKTORJ76ctyP_=Dd4s3%mTw_9g-dkL}RLp)C|v&u1=?`PHkr6jX5* zmzq$|v5QD0(tZ8*x87(E^*lmiT+gORYpEsdb@$#Rzda2DkIkAUA+apvFmOPDT?D>e z{2;2S9A%#uU2lw-CelT;Bmir?;hVSC}8pj|7Q`0%~q^q z*T^HlJtgTkFr@7*UCU{y7S}*ug3OS*Mbu2R7su}I#3EO6{TW0R8%!+Y^n{rAA@rA1c3an@RT zuR^^0!+YBY^y!SPKe{-cpIMvjsr^V%aeYEHw21brVRdQXwc&T7o2MKTe9`cC~rvqEG4E_!-R@^Qbs zdPEc}aOhQg1? zdKGT>6j^ll+tjs-i8ce_qEC}Wmgld+!ond6^pPZ0VN4&cM>5$J?BBnu-RiKJB`j2i zKr{&Jt$^~~S97u2G^1!SYxnUAAY2~U(Kd3+XoPDIMg9r3S*(s1M+gIqM(O#(F~${C z5#)6SNcueCPN!kK)6YWbi*&Mh3*{D;UDWhv0-RAq=z$oX!h>kG6e&UZv{K-PZb8WWM}E z;v4rdKII$v(0u_RZZ_SNS4NmrsVwm;9L6!t$V36t0)lA^N6-{mwCx}bmmfBjT!RpV z=XWPw5IY;Mm1bw}?*vAaA)GmzNU%rwqW-hMA?<3C+I`KJ1dyL@Dsg?ZzphmKLGTjp zM(!UKXAc?pU_FeiK3!g|hFwFNGU&>P&X5Esd;$GUHB*xS_4S7p9gDYDbq<*LD05+P zOlr=28@{#aa2QD!tJ_G--rcL(%?RcN;>TqoAP48iaXjCjz{^KyaXRw6jJ4$X^-JG6 znnuI&;hyA#-|9Oqy1YO2#m5W}REG9i$`{W;*p5W7I$OyH72rqWW0grt%<3}sO37$N zj-$H?6wxD%u{|7IX^+(vuwXeB;U>1C9VBvp`*Ge1=Jm5m*DRiBsjH~Abpi{z1CEaR z?%wM$e`-8yceR4+*V2{CmR~Ez9Wr$x`_G8hD9ve1C709?A75L!=VVq~c3?Ta{rL~* zFlCfbhy-5NoKY@L#tq-uy6rrcKua@e%&Nh6O4g_I zgAD)3&1pvB@Y^t7F@1LVf!W|||3@-S*F6?}W$Fo|a#VA#<$22)*LmkIyuIIxuceL1 z=unA(Q*zm#u=ph#{D?HPv({?RvP)r5n?!HPL7)QFA9<{M-(Wz%yn0!PjN!U&h56Ib z4RNSIP_ZtHK$N3UiAL|RmrmfY*LVyOnC%yik2#y>(|!^D2jdivql{-gjHmCh3r@vM zu;XdIn5jOS2vF`Tp!{UT5p*kjG)QKL$Ka zsjOJp)}Mdg)=9NL54w@hVpC7fIT>O`aSL!QO|#owMI%K|7=KZrnpP3f>Q?)HFKVy9N0G8+o#cgwl zdJ*L~n{2)ceA~Jmt%m|Aeuc~Y!OyjiE3>3je-v+F{gNO4U`g~3wU-HGZAa;dOcb$$ z5USN7wLFd0{V&Bd9H3WKGPZ5CIT(>?PSUa5Ja0Y75G#`bFMZr$zQ)MRJV>6WG)pdz z&ze-X3OpT`>}r4st?=QxB^G;5i?@q8m6o%md_SnewBx`D(4NOO`Xl8kL|xN+c87gQ z=Dbk$l}}b(Jy!T#kLMQl&uQ`(YK?vtI?4YuTV>bBH?ArzWo4UdOG}Sg3sJ%oS7Heb zpBr@B*zis1W%V8RSFpNnY{L=}c2z_wK(KGNV~#SdN$i_PR}PC*#7z^Lm<;8(0;|j&dXj#Dg5^Ra=3D z@$r<{4R%fla}X7sFAd}A2=25`yxhU|&(5*PlA3YxcZ>KY z-~V##%~gr46XE$tDqt7H6Z8qdv8|lBQQ-O99cuv0Q>(74DErIhDW6|9i&dQIN}W1* zrk0JmQShUd9C>p`zz~maJl?(YM1Re7QGdGs(_*W`5y}AzWtbV$W)TfX@q_ErQYS#n%$oa?FxP}#h{F1dvN;RR1Z5IeJ>Dp> zkJmp;@Lf+bhd!i6?fS)Al!B)2an42;1-)N8Cc0))ZjoS`tb~0<1)^V-jd6Z zi_%V|Oa9ek#FzWM?riLC)J*Whn|%o=V>dfDobeB+8vUXpA$J?NorlOUq|FA5cu14t z)#2A1q!|VKXlA1`1&TBK)|*KV{CA~zXB3rJ3H}u0tsGFQAg_=$ps*+hMuO;%YE~jN za!)Ys%HkG&HZ=0OZAt0KVLlocGCB5@OX-^8B26JVDJqY`lye?Sfsj9^occ9s-94cMcnaE$)%7GPyV@mkP3$dmpPn~4` zD@!CvT7m8U^q7d66=O7q;u7)gs3E^pG3T&@KtX1MRr4yK`GP&BsMu5w{T3LJhnv4g z4x|`7QP6}A8x0#U%p^;AoE4nu!Qp$0kq6{u-84AZ5cSO$HOJE?!yDHqI=nr*N_tK@ zQNHHU651PJJe`NIYhYyXshLD-eV5fm7?)#36MSXL!nb6Klxw&|Ypjj#_&N3V-rVAG zrSm>J^+Ea2wM*o}XS+1HY_Pf2b4~tsX4{tO8SaQ6N`?^;2T27jky|@NfOJA?uh)u5 z!Kr9T7^fsu#5yKRmigdC79FSaiykB^3;TwyVjm5#KW_XVTs_XOHM(A3lxo+TT|Voz z`Xn)52#A2++X0OrrcV2yhiyV$yo;A$1w-j|Kt)2HsYEw@Xd$GjV3h?TQdCOAz5S1B z8nOQ-VFPTHxyz?Y75Ciw-Q2o2?LfBddyKVRZZ20WI5Npp-}g+bDs8WZ_Q$^;m}Wqp zaHzzCl4{lzpO#{TCRfi}e>=w2PutDOm<;{Z7*OtZ?m`UZL=o2p6pTVMbYrYZf3@|E zrNs1v`ZQp8q;fg;c^*_Z`{Hq^Omn5@A}9JB%x$Bbt2_JlBgCA@LP5dkzW~kLk9!Nh zWT}lCgPH)<-y5ggf0mET6zC-K$ai?`xDF(-Hzu6&Fzb8oZe6oC#7`vJ6}^n&dw{T$ zEO69M>GjgG1nxqCVM|xnq+&qMbKwK#2N)(df7O((PV1+*Y=RTx0rB*&e&e~$GT%K^ z5+R`6C5txw=jzpWUmslExaT7FIwS-5mx|~-r2Uyc@+)+aqW`9!+TkwUwr*5>t!dBW z&-}YV-`a|Idil9hqM4TIEhqY`2y=15B41BMU=<&pR$n;|j>I-Ert|UHi@IH0_6Q}{wyd@ zUVgVy6RV&m=kD_iv^*{Nc`W~F&a%aW$p^)uJe_i%dsk`$vGH2-VC?qgRO4s5-tMP? z5W97~=BW4pZQk%1Tw53!kmKtJ+i)~d?&8Vgo2)FoDg3drf_uKC7%upY-FE#B{z2)K zPtlKYLF@_gSX_7U&sRE1CbrY`lUnx4IGsLAJvL%oCfkY|;151q%^`*)vmlCu<}K)T zOB*9_4gXI9-{q4^8ztBgrHkjLsW-ABgKpKwj=`?qc%&GCygr7femcwoA1O);VWM zn<&SP&H8g}jkXN>s>i!xT13K6G>D}>*A#?NnewL9prD*%6F^R|XD&jnnixr7KG7Bn zSihAW2M!LX7po2J!xsVapO0Q$eLY6dJ|-Yk*#o)|5O^}v6aYr&4@W!fji9eoqW zTJ@pPoF%7!0Ym*aYYb=RD;A#K^r~t5Wd|$J{PlImlZ$}r$xgSC?B;f^T zGg2h`D}{0~?koE7v<{z#y83wkLXqblisKb4WgYBY%{9a~g{W&sGRdY`72<0W6|6AvKhnhs7;?!Ht4dRY05s-hVb4EAo*Oa*;LG!{4|@3TGD3M zTPtbd@=A;GZRz)YAl&{l3guEy7O0Y~Stl}HrK<61kS=y-kC1Iuijtp)OlIawZ~|zT zBN=ptsY&aANQYtA>KINd8yDBpipwyJTPhtY^C!ipMTA#iSTF5*G9lpO)#Boy%B<(Y zcUOa{i)^WaJWFqN=h>P0#w+8h%6)C}toXO*?))N;2^M+umuF4hLPJmf89F*~X9B;T^E_D{`s3e!*&MAIbWwk1+QHgXIg8MH? z#4>OiJM!MCU)1<&Llz4r_;YPlpYoQT|7Rx?s!;}o?VoNj9V0pfsD~5|H{$}{SD;PL zcSL4ALRN?P+R#|5hAaADor$Vu7e*73WW!?XBrI8d=FX^m_tV{S`Bu&1%i1f1@s#O{2;Syr zpH_si{@@5mnj*HgqrL@!zC2o7Y%D+y-RhRUy~1VqdX4iWZ9V zqjkdQX7W2f|94De=@nDIHPKCk%fK?L43WfcdEyg?A%%sg0UL1Zx1P#QtZAEDM^+0z z7KdFOd*Zte{nV28_djx6;`SnkztC;24w_IyJ~yV{h+0x}25wt4Ozt(wTG~GZ-|A_V zhrX{rCFJ5-ipYf~YB6Nk44SFN(n;yy)9{=R}glZMAqS-a8P4wTM1~% zdZBYN2#_~g6(Bf37!VhubHRJaa5?oW%js64+VHOmd_2QU z>UWHt6iUXb%0?hPLjccKpwAu!iRn`h#6r`;s2ARB+9&ZBrW$8h8ydorwuD0Xp~YFG z>d=u7fht~$ITOnE_zv3e(BiFl6%MSi^Z2^WPCErLOLrhu5=Zp}y85V%&wg{)^rudD zo!6$r_Ogc4>_j=g!H~8Yp~_+3SEW*1y3Vq~3oM=jmlF7(22y0%U^8KD28p;Bh7)l& z@-HT=iV0?PLplJ*$A;h}L)b4XGn1&I(WQNz0My=g!a<0-ytLGl6255YmsxLNXyofl zWb+x|NfV#360_qXLyi>~+1h^o0;U);B;3saZqckfTG5c|g1IA(mon$*ZI8ce&ChKB zd<2ly17O&OS;jB;M1Y`c?V;&~*ZZCk&~9bFA7$qefs}1N%hDYj94s~4lgLH9q@<@y z(a(G#KQXzOoJ94zmGT=Xl>7a2)RkfU`nR7pv~=3t{)_K_&G-lzyl%&jAGS6p&r>+& zFI)ACS$D^>s3sV3uKba(FrAvefxuSV=Jlc3SCEQn~yO{bE~`3!Wo7^!*9dRw<2^Ah<~bsvl4C1j%vg92dOr*=n{LSyMU>OACiy8dQv&kx z8;JZ}?QxNH17^;TB0U-Uj)tU(je6bn;1??;d`XrFgy`YLQXxS9@mf$kbnz0G$6n=$ zmJ!Q^_1WR`WP-6qoV2J6i9_Hlx0_#ErNgyB=}kRwm>)0$6IORU(=&HHk^|3tF{9jM zw(t~p*AT?uo= zN^{nY((z_Rd?$tBRL-5m{Q1W9zy9iH@SX)ha%#bV0S7Z(Kcj;r5 zcX$ZRLc3WP_3D~vK3#uXFB}qIRlrI2!IAnJ!eu~Kg{aSbVvSA^gIn6}I?0=X#Ul^D zBnUUG!>nhLAx1|qX*w%N=ep19X`=cax@Ls~Mw;22IFT)NnUCh-0ef(v^D)}nb**dF zZuNY|C%FWRdwn3QM^_!BrnzC!FlSxc#ZDOwswjml9{U;$54WOj<|uE)5kQ;aMSIR0 z;=<;DqVONwD1z#M0$**SW8J+I34fm0Zp4JP#{28P&2YY~RXD zq`ho4qY1%pBrmjRg12_S$5MD7o@14!8u?3vXPhhhs6DXmC`*yXm>!fBK_fnA4ukLc zftU3aC1h<-OKrO|^9JwKE?UpmFE|C0rKsJ0?mFRy$+e?`Cc8?hL%ELj~~0xxW< z#}zqH0-6XA*Eco}0$@L&K(uMm16=8JC+AIBDyU51tbj$HPTTs7cMk}KHDfBN2U5M- zTyk5#-LWo&2Bu+8=X?_#u2C!iE;Q;0~J25D`i&8W$KQwU(hj@9NjJ6>*)UFU6nAyLZU>5Ze1T3TD%bIyJtwYnlj~O zrV%fVA53j*)iM ziF#q)9u>F86*=bXXO9pCq?hRKbkKZ!2d)*9->%g{)B#|xVV1+Y-HEs3EQ%sK zrmNZeR%g;(;$ie7I{UqcVy)3W+Iq=Cgo>12TqE}L zMSryOEd8GbRk0%tX!E7^f<|e6YSrifM6hhew567c%1<^Pv#mCn=OlAqj&FOIMf2wQ z0aMwX12@(7tQC}sgNYIYas5)BQ!kt2FC<>fXLd1w1|!qFfK60aj(JGEpKgdb}TNp(LkkcF~1~7$X{1x7Q6|=;s0ByAEp^cm_8%axwwNK0qQU zp~3+n0>z7P0_eeA+Sb2`{mZtyk&6&`1!k$h*FV!p`$bnefNMxA+Vh+wCCM*CNF}(` zBeD79$T8PiKKLbX#9J*&l46#$7cBwta-x8yC~)@Tn-6mUQTxZeH%h=qgOpg}FpgqS z=hb89QA-Fn@K1s`uSW*G{=@{#J8wvY?&yBp{r(X)j|4s{3E-Kfv_z%4%xaImI`_WV z`Sb4CW*5n6YIYjx0DmTCPa`Ni7bN{v7wtdnp&S+iee4L7OrCsb$^TZ?ZrAL%NQ%ge znZj#UD0G6bAj*zvhKy^~nOv4K(DZEcUf;oliZB-{Cjy|}r@p{SvWKuPFku`Eq6Bz3|5j=iW@UTG0ExiQoH`ja&BNy;MXwF0Zn&#g@}DZ2;-Ve zkK-31gl3PtUF^q?00oT5`eo{^N8P}#G|mUQVKazO#Kuu-5xEUffr04SR;1r8%6&~? z4pu`fbpj3#ZygtbBs*_0LgA|MXJ@u~9KCeCIfUd;^4MRbm2gr+HKh|Tv$9nfJK0LE zg{qkx{vH=7!lU_R7$6mESlNV{-RP2u{FKcjSa>~pAv!f9z&$(nG>HmOrX-%_lYEARt!Ohgi-T;V_`P&Es?L}Yj)@Mol(Di2oMZd_J859%7=T&Z z^n6YSPQ^b{(*N@w4`4MkYTMez1|x8mmzS-=T@Hgj0Tw97`%|4rw*g1dc&});;<@dSIEsHxO~!E^priz_ zZ<^&AH04uv_V#{YrM>MBHA3N7nN|O%Ck)esk7^Y{iv_;24$|mcT4#pu=teVLWznSA}BfvhaqRL#1N?s`p|6R zA9WZRr$myv042}yWmK)gz|d5^O3ezT_V(SuE)SVQ&|2v4gFt{^+Cc8-!?J6nrY3P# z>7-l-ObW5)q^prYVMvCEM2l=H{HPXPK3FddDx>!;fmH&2pQrNbxMh_4&jOSB&F-lu z4JAXShEHfI)&#uaW&f$=kcTJ-<7mgBcj;C5;~I|{g;=92K5D4*k{5mg`~l)t!?Xf; zoC-uthym=5>84U>*N-$mGK}34X}WGTnDkdD(F5X~1#bS<6KdD{9NuQ03fF~lmEqrk(2dCXc6Rk2emew_Q}-dm`=S+X`&&p z|6}EpqAF>YP`#dYW@cle4{|Z0kC%?6AML;J3}P=Ucn%HYcl!1|rrIbkPCBV|c!$ zxZts(hOhpyP0MfIo7q!uL~uyR zij+vFd>+^OU$z23aR2?UHf(C{jPixMcLw}Sfbo|$JnxFS?4x|M82i6euDU9Ojk&r9 z06IT7N@;HQqpMVV5}sDx9ocPSs`KD9{jKW#!9X9}8RTYnPwX8?eFA+x%uyy?wbQhba73n&~inHr(<71TVWil8SnJ zsPZd>Cg$Tuj~1ZSRVGL?)GU#XUuZ<0(AFlOJ)l=h@UhNmK-oQRoZ*!yW}(=B6GTvS zRgNncRV@nih_mGpiXW=BE*jRVj{2d1rAh_`9)qjvQnR4K-5vC4n8z&+OC%jv1zdk| zmzr$aaP%%~X3%OI>g1Hvyy zrHn30goY!utST}bkcFDLXwW_K)W}&APAhZto2po8vv*kd-Erf-hu>kSuC;M+2hh+u zS5#HcoVnGq^|<%@rv3zVrX(B_7dCnC~f;Kg(DGw`sPMhj*(1iNcCCN-wKwWpzV zd2{H|`1O7}XMm67Vuxvbc3;rleA2+s*tfFug?g$tP*~Nr2a`knmX|$Bpc7Hk%dn&T zdl>B~^j(=ytJ+|H5XVp)&Sm6ZG)~tm2)Ob9#VPQOC$EalZ6C4B935js9^Li)qOAZi zoREGLxwECdF;w>qjx5FwtvP`C0GiO(R{?-gd3WauAgKGUui5%lvVoc~p@`00r>3z{ z0thbIoMi(v`!@GS0JjoYu>fg|7-L;)|NZy#KcKo&-}rX2Cs5m7F~?;(BLOtR6=cI@Aj!l#q6t+oC_@lY7ML1e9TcD$nO-kQKFJvoHnvzrJqw{#YI&WMmra}Y#Nom&grM) zbHl5{`#Yd#iaR_H$YgmQ^Pfg`(1z4vm+qu4$_X5NZTku*x;anlTpg-SPrqGKg9|>~ z39Y2G<_#ib-E?wEARblh9lj74=nrAb>h|!bsp{;VtHui#W$juij<+=-d+OtcPVeYr ze*FXIaoI)DkoVc7Rzy6S-FKxzE!k8)afduOyj^>)uN2exE3gPILse|}L@qi8y^Xnm zP=QWLks-en$*Jg~qVcQjz~P&C!54Am+Q7=KF142<*YjNmi-GQsTbve$L3DQvgF_oh z=1%#xmVvc1IG#i%V|fyjkVr<_;CTbbY0C&U40{2B$i~g@a(?W&Ay!Q3NFU9=V-cVm zMX8l#=%}c#91P5)cPgOHY6=IF0=Rz>=&h`ot@3^+-C}A|Bi7T|ev^u>NtE8XN-;=A z*FlN;=l_V8+WIX&QW{xb#J)|4xg5LnH_k2u%P*F{T4>BtGY)XDz zGPuyPQ_9VB)A)PJ`N#KC@qlp@$A#ToIG}pC@JFAOa*k_ZQSoW{l2z3;*>bHG zABZF#TgVnbBnvG)NRV1%!CjP|i0^%@d|;VE-4K$w{K-hOuOBI& zo446&s?%#y-uvFOUQZ4ytA31k^NJn&Gm#=cJ^JI$Bs! z<_8PDkEU~R9e^I6f~`@x=W3r!B#CRq6)Vz)-}P1E;2EaaOK@sJF!cXiO- zTxPV>ULTxc{Uf9%6h6PTwL_e+kMy>_z}jh0I;yJb5{UP`b~(GYPmOD7wzA;PFdNZQ z7RFU%@JvzwQ|IFIR-7bCaZ$F#BQ|0*R@oW-l>9P!6T0-OtmfEq-tQ`Nf;R{$Arf4f zS)U;1Upc6fm^aQ87mmBD$p*+2BZ>y3k6%8Eg6+J{(&&!QL?`~!CPP>CcUT%zIu8>Z z6Q5|tK1t2On0(q1C4{b0mmZ5`t*Fx91)OFAH*58JonA?mm7jrh-12 zAs`?CCW2_4B*1WS!@5la(8Zz(G{|aYq2cmO-|>Q5wQ4nFXU<#+RALm1`+-vwBlIi+ zbkfowG_l)iln3f?mZ3Qr@+1$9kta`V9GvBib7uhii%PsU1rU4z8iQV&4@4oRXI6b@ z=O94EXBdA0W{+z0?Ewu*fL^y!6R8{}-)HT#kc&gxdOUUh#3qYrMwX0rZXtLM=#|3C zwW?SMQGppd02cmt;dfj@R(D6Gyy8Aj8a zU5MK;JSE)#JT`!3`WBmS&2gBhf|2>A?}piNe@tV=G2*?%sB4`fN;R60{{Y1ZY2D+K zXE(=3O-?Q?(zQVGEKGEG!M<-!2fgCvc&cPFZM?S1gTe9fG{=pV+u!blKDZ%K=^bem z)TU5%QJn)^!pZ?*WR2WTv^53#96yjqG)6DZfNUtCOa>|RKrN6Y%YRINQ9 zl0j-u$*m2+Avkmnan~)Uv?AIND&bD=ZFtj-lGr7f)Ud^j{2V-go|9bWJ3Gjw>o3wUO!B`0e(jW{$+0DF?BEg(wdaC;S)xscj6suYR@ zmzkE&iAlV_0>s0&?br=H@84#1nXQwJ_9_1r#4AXx>#jP09?F^sOs94KdGW5wyPwNn zpQ}Ik7OV7c_71l{*;Ilp63yp`%J%~vE;gsyH}05(ZgbHb_eSDi84SJA25WzdR3+5v z_PVv%z;du8wT)?)i!NNoq>0r4{CV+n)P!k6^lV02ARsaQ2z`EFi9Xo#1zwuEkIBWOfeP?sOue@4i z9|&2pvOsq{YI&(*e?1JmF!B33Ygc$E@X#@()749fFh~&z?rtQu!-;gTz4W-IUc)Eo zLz%KgT(|E?Gtx=6v>^46kWvxP^O@fNyNCA$K2a@261>Q{SS0kpjq#@yOW?ZFZQD+R zv33DeN{dT!QTYWC6bUY40-{WwpOclG3Im(Jc9iWyMtGjc-O;+~1wnX}*ozElNv78Gg-dK|uemZdOBq@WZy9 z-}dfB7mROT|8@O?deTOKo-?>A)-Jwb`6qoDzpBB+UnJdmhZkUj0;Jn{0;z+Ala zqlx&3RyW_>CNp?}(PY8AHCbW7;t8koEbnla?+boaBe-^-7K_t++Qo4WKP|wRuo%Tq zfiC+yo+*#R)_*$V{hx1qOo6FqeKLY$+a-fA;WBC^~Qxdpre=c_|@dT@Gs%$X$Pl!9;|eONq4UAt`y<-nHK}rb@rWv3QR%YDTk)pn?V+x98Qgl6 z@&}a3N**@ zIFRZ{#Z-=o@iB-y@EMg3xQvA%#G&D7g5=2X#<6UDi2f9!P?F-bWXo>+kr~D>^NZ;H z;hJlM>by3HvJBcIzQKP1+`kD;Lg+wkVvNerj+u*?^4&T#nZ%`Ef?NRnCrh+uI!4$q zQ@~!k5=W^K-9#&hYJlcQpZ9yxaT5y7*^KgOz1lZHA&xzoz3T3TQuMOYqRRR9QA2V4 ziF6m5N#8S$BTD8J9xa^{##L=i+cBa81EzsNKL29(6 zBS1{jN@#K+uA!7%L^vP3eymw*2ytX}?4ZL`=I6Fd{#LKERiFJ))<803T`I#aH-slF zk?ZIX-encWKf~g(oBxsr0 zj;^174v+iDgn%7?f?0GT5)A%)m5r!lQ54wj040kf27$EIVsE|lHXSoa(0Fm^blLhO z;$2ES!S50qo*%5(CgU?U1mbZ8vJ@laW(-ArpJJTzL`~%kmzmXrT=8-E>Guy+jDyYP zzUc6k&A=xOEXgJeu`Y5g#OlP*Pqe7YJf8*4ckK_(NBHapBSpgUo)iu?fpPi7{(jx+ zj}abvL+i-wiC?Nld*I%wvaI;MD$ii zX~W`K<(!GCs;lSD?mdoe4EOEdx<42UIv?p1DNuO^^EfKhFAq~!JzlrldF$fIT@`iD zKb?eY7RCj#CK@xr%gx-tFkMJkO7{$}L+U|m%8nHr(D8Oh!$S-aK&MD6%0^E9%8St= zz`V;q&?osX0Y2lZwZS<$H>pKh1$PRK*`^nRun!?Jp0vZMuo2Qifi~^Gg@6 z4nu}ex#1gA0qdAfe39tV_;QrX+iz_X@^;l@%b5N}WD9ZD2RMIJ@rqC;n!WWuC2zD) z!ValKMv*)1=jOebK!{S@5LOt8$gok+n&DVbl>Hbz9z-!nTUC;3SfU}}1WMsI6Bf+J|Wz+%N92k)E&-+s-CFIS0zP8rsxkXlf+W&FH5cy%D-o$`B&&>znooCWPt)BHCE#<663|qA=F{IvsSB z2jv8Ex^y;?(8^^CxbhLj4D8>S5*W7d73VIgXN@b7m~8-@0H_*265}Cj2iW&w|9;h; z1{f=N%!V=kf&%^@6y~NIGwR^cAv1uy(a2h}Fr0nbe}X0^#%@%Ccrksf?f7S#xiRvx zF(A`Jn(oMr?>_^cV9c+c-iW(|aq4EkW8v9oTY=PY2aa@U8RKdq5rsD?q^+>ybIISv z{jZWiR*mbSy}9L!JN=B|QuFMoR;`ndk#~CcC6Zs9lJBwF>$u+&bF0e{NcD;GeyP?J zbN5vZSi^ppp0RIUTtOu?th=t}cGt7xzMa|p-Xsl!S>fGyczGwKLK{-~1y01{=>ZDhEQW*o%M34A! zeF!I>kRs}>RXhtz-wS;TBhqfZ`EqK)Z||>Ff^%_qaimCmo`S78@V#N^7`g7>vWjea z3Qn#&x6aecOuxj)2k;yTYc1%GCM+%94@y85qTYL%*eq2)}9}oxr3ogHo$$Dky;uQyLvJ7q-E^Di*N8q=+ikn5X1P2? zTJ(CNBZnY`dtsPG7@i0UV-rLhOfTh*^stuk@8&oxI73u&LGXc%kAcWKS@ty}xC{Gz z{-s9f_HI;zTo6_#6-4QQLHDS$y5U1Qf({1>wI%hN0f{iWOxoL-!xD`mMv&~G_~&FC z96;q|%5&>O|5=a{i>_u!<&fT?`fF7_jmqar}+hmx^ zv=bK>&+LK0eN42jSEZN!*gTxk%)V&gUa>3LwKE%PSo~HQyL9nzB4JDJ8rM!-apvmkQ6_&VI=*_Uo?dNrA<8DYTr&eb}fvA15$3YjRnksiv)v zLZ0zbH`t3Ma`108pc=W@Ez|LPK@u`hBAi2{CcVJc9T;Z$Yythl?;l6M@f!~T*t3T~ zm&fB)uHVN_1oL%2&kxf0rhGV@_vEIh%?+J)MSMIvVW8VNqw#o@Iu{v9IeW(*OBQFn zeGwz)8&HXWLVD5BS&1p3eA1qaT7k%;?a|(0EObbd)&*E!{L18#JmUcUq5#O5*r64r zg_!&#m17=MGiLuiMIWh2NnE_7L8c?paj6S3ZZ)S;WH^8=K#y0Qa;9fx65A9x^(Nd` z6~7=tKwS8G$=xv_=b|!NQr2&=1~Oamud)ER+!#Kjj|RR^J2_)eze~1>Wq3;X@xNhk z+v_`tZ^&=1W;~=`Qg`I2z26^i2|hQ=&aZnhzA5fP4rn*d?&N=9OAjeBshJs6A*@bg zwHwY8Vz^_E1XSG}_@pF>@&-Vos6y48+PN&^(#s;$s5C1Z{S&t{UPx-+Jv-{?M#)3W z;0jgW2RA>ao~l2(KZrh7?`)qJjOtRRYz+(QUE|?qu~4-S55vunQ%1&zr4T8gC#@16 z&yBuv5?WMy?`MEP^7htIK7JVHVKQ{2p^CC}${PEfbL0}=b6AWT`qa@a&O&kAp4IVH z;cDv%Z7n)%KM+HLatav=t_0}B!&tD3JLe%qy}f)6EBI1>x)5J)XMoG49Vh}hu}vwT zmdNUZqWnefv9(E(zNf^;jcDr=Ts7isZnetUSf;D$NadQ`1C6}W$F0&$g78zq?aSLY z=jz;`$^k8i1X4B-oeUvOLFuM?Map{635t@eOz~VFkDL(hv~{CELER>e>c-yVY@yt1 zdH09#O&=07K(kdsa3{sMe8OalJDWB4%Fy}qtaG}#;| zAtJemAF|87QDplM%FbXqK$}1grXCw{Zf?r1(Y~%pr~UkP50TYF@ZfX4nP)lsz;^uQ z70z=+x7lKG>ZJHi^Q06lsMFq<&@*A{s^ts0v}m7QqM7TOMKtOHZk1tc&*mHR2ep=g zz_=*pi~{3&u6Z}8fq&w>K(_o_K+X>YAxOPZ8@WCar10=Jy1xC|EOR|XGv zzdX&}>LXm$RkoU!_?~)@gj){^{jsSXM2$0q!LyL6Mgfooj@T9fY*`Mt+YE}FS_<4; zoW$|fnGc>WYRm0N-54I}=-C1<=OHT5eL79Urjnk~oY+>Rw*6zXglCL>10cH2>F7aY zjY?^cUCt{_h;0FT)~ScKmv$HrySohO6MP5izk|)A?M0%bdWVF(QjBk12QUWIK8&U) zvv{gxp}}OcRF-mVo8+m61%ln;0>`EiwKxT}MzVqMWrdvJW%xx*^vs`4tON3PO*riL z!Ior3M!t(KG>Rp4K?2d9@7Gve&hMZ__g|JsyR$|?TgOUgelJo=?N9 zm73+~yD42$z!ihiP^*Z|q}S5(4+sFWy=dSa1z1hf0c|~}%b~ioQO@dFy!jdFS3{G` z!nykwRp7VP{R~`^JU)*s!!aaa{R=OgOvMnu4FUWRn0o?R&gpr%l0E~A{wLtkL)=l|TvVJsCbHOJJl)vlv zRx_9?JB1sWbnfZBbv%1Gmz@Z{t%X^o+}uKa)mrm`C{qNTOcplcn+{+S^*JxTZDZ>Bn9+~3=#=(xyy za9p(=E}@rNyI%Psp<)~kC?@hoHf#3x_lF2Xpnc3SB4AM6D7E zdPUkEeOP5{ z`|H60-l9`bL>jvS1kavYV^5{ij^~$uo}pm>=t{K!4fY^_?@*OF_EqFzN$TaZ?^rsT zys2`3gk8DAYmu1!7AeE7KurLp9^Yc7gjPQ^4@h-MZCk(kT7| zG#Ev{Cw|Xu{i>6QNT1B-05_YGtjbd!NE%Wd87&EQLDkSYQ4S<0koX)i_}tfL@88K) zKTV;eyHsMAHUiY?H((U+A@I%IJQ$AH<0l~0ys3@x{Xd!;uwCRQa?5S3t$jD5b{%Y_ z_E4G%USG@M<6S$!`@tYHHv!@rm`J+VU^GsD;&-%x>HO-N_s((MC>&O`IkDlM2X?Nj0SFL#-g86ZE@h%96NtO1Dy5@?Uc>C1Xe24Wr9@a04 z7SC(E-VkT4lMletnE&JGoB|?!`!M{k&9&KXs?D~I$+odso3XXowry^Yddap#%z4xA}4U(kRhXWn+l9wL`4lj_&)XHE~m*;It9P4!AXi)>%P#S2Hs2(Zl z9%2aC;hrM-Nd4!CFvtGq(m!x>yd1v%JJ+brNich60}4WTe$e08i`ZIJPuZBVj?o6R zg_6H|ix;CDdfhZw?1!^57*GlQxYr;QJ!&y4`k!IclBUgW&BqTf^wd)3pJ_)9bT55e z(Sl}+gn2pO=RRFq>@s~ti?@A+$;O}n54ss7xOCtadb}+AbGz zK7on7Uoi}ZzBAum{Q0txwn3Eig+~>QmI7vSTCD$z&D07e)AAZGg*=tCywivq9UG_n zcUr5mTF=kUQAL&sY^F$iS1Up-5ITliiid=6z9iKgK){jzX+P2DfGu~Y((p~}-(S9C zLlUkJ2w@^i)vgU_iGk4P*fVzWnV6WIA1{poW!iALDplJB5RmY^Hk-e)YIg+Bosw~g zcb{wA0dK$$307f@xA#SFWL}|Kskj$VO4f?|{JjANf`?a0jK>+DxFk7Q9A&(RndJfMfVVGVX-7 z;bh|fq!H;Z$q;GbN zFIt73&^ZQ(Dy*aki@8sz+&eIwPiG(wRvE6V$)*At`w@9p%2+sq;dwZGLR}y`Y}Pc% zAgoEcpRl)N5I@7x8jCjQFYNnT`>%kW#bU^6?*yEkW$AhO=_z-!123eAw!b0i%Bp#Wq6~()GD&0y|B5Ap8BplcQKc3 zate1nT%y+^`)pquqejwES#UIY!Gh5(Z30?k7bo8vU6l*=%4(aWF7-Q#-#Iz|$C!5a z($@i13& zzYMMg7dp}4CGbunJ(*}}(_ngn$9_kbgmFCQ-nXTLKk+*miAiH~pA<^qF9aIJ0|< zn4nU*+&rH9v8KAahf>1Bb@iWD^RYEOmJ3OCfK!~5&G^z1GB9~8rIiV~46@GqomET` zmm$EvlfG5OW%5~-r7C=jnm2QYlS+7h=*w| zLILtR%Lz3{5TUcVB1d_JD^<)r@P{Sdj+Y|d0Z@-@nG0n?c|eC;MzsE*bKQ4RRY`Sm zwS9FbD^pr{x;Mn$=c~iLEtmJ2y~3&-v&Tsi&AXW0Y1=blrzzEINL>iqgR z{`Gkoe;R0l>NxU0f3DsC7@&t_Y_$%f5c%8U)bMO(r*@Mx0~-UrexQox4M1YgB;+&i z)m3!=9imE*=bL!;lg(wvJw#oKwA~-8=O9+KipKOMF*qtBG|)EVU-`;|EQ&uzMt=NB zBER36>lkh!yttvkr(EFTN?wAxb!V&Nqo#!Z;wjQ$$LG;8 zv5rvCX9WifndD%*FOZ&*5tyiGYisXJ@m=f#w`_X)iJx_KGmeMWfyY*@MNKvSO3$wP z`TZPMqH6H8*=a_Kx`@&Z^{?+6(<-t!<2w-ZehidbIhZ6f;wkU+nJ6 z;#uZLb-%C6JnoOFnP$zFk)rQK^+8)l!iOl_DXA_%Y<(^TPw83uOu)?t-DEMsJ((g2 z`ay80Rz6B92dHcc8uJrl(0#b)zV1^I{5s^H?kbydeDto(<$E~QT}SpM9e!Z2>Nw27 z0_a)SeCFDWMQHSl6IYMJLPL7Ay$7}q_VXw7%*4pJArW11%u~Z!tMs*1bq?l2;WFi8 zki{lJ^_wkY3Yd;XKP_>hptDnVP(^3YJc#9r8FcV4(Xkv?V7AmVH@^^==huaC=XZX? z&dZ*Du!jHrg&T1wzJGL^=@V__S58cVSG^=x+~bE!0X~a)g3lPplbf|1s>A%Q$ak9M z_*lGY!I+Q8Mo5ftrh8D76oEd{NfezYeWAztJqoUzg`Jl$sV!X8QWbcsqd(oEOoEuvDOTk2XW%Farh=i8 z9LuDQ)Wpp&v!6UUH8jBuZriNIdaZ8h6vvqW~POlaxgefQdz5a(`F-2rdNM(MEj zc|2~4A%~f%C)&zGNZP1|TdD6^m2ID5+7wd%yutG44ssXC)HTDY|K29C{EpRLFEaZX ze&}(!8Jip_nX^^K`>v&5_MP{vR(6MWw{bv2(CoWSZ^Lx=NagY}O*E-8{9E6TvgN(L zP~zPIl2=rjUqFH+JP;Oz6&Fc5&_S+p5j5>QZLTV)Y%|eN+OD~Tmq7gmJwnE2THncc zRzv1r75w@alW+XA?|s8RrzynNIbAfzU=cM!0-yx~owwg80P`Zx$DPnNfQ-xS`po{Y z;l4FdVwgA+Szt-=eln%AQC;bYI4@b;ad9)5JIbXjAdpIJeaQEdUgN)Iks9j5eL&9#1Yqso>HHrtbMtFIDIMf{_yds=c(rn zM*HufIG@I8r0F@YbCUa(CoDj)N2n-*c4jctCy8aJy{V|lkvs9Z-hon(LFsBpbhY48 ztm`J}NJg|_+U}YvKOgf<_TjqXM&i@^L$B~s)A`F&y2$Y}+cJ2#ac|L#QIWgz-S3Xh zeu5XkfN6BD77gLeB%50>i>CH{DD!%4MA@gPR2dAr=wfWT{~$v6qD8Qw!zbCO1oOTxpzSZ+E<>-HY6qd*#Sx{JmQ#Oe=a}rnf zl$g|#7-Yp3*h1K7z}oiKb$ox%M#|3Cv0hLZ=U+yz*N`P9L}8c zvY459II*@lG)(dVwfcP7v1sk|_N<;j4_%o#I8KFMu)_?dR0fGkTqT+8tGU}osI6XZ znFVD9ta-9TSRTAn?`#bh(noD&LJq454Kf&{820}vvncX1gA7CC9h1k|82Atq{xh?YVx34Tj=u|z5Nx(jl zE4zt1#v(}J?_1%y!Q!Dd6^P@*x;>NU4d1Ton+5u>Nsh`xD?I+SlH7Sb#30A2`Ij?$ zORH!l8+zWqgOgt5?)pIXoi=Mg&A>)jTIeuIjTySMDp{tK$C_)6{LPE?j@~Oa;$Xcr z@&5QGM{pWQB>=Jcr`uAY$w;Q|k=IwBy?i{`JFyM(zzfqkjp$uNlDlOE@#fvZc!~4& zGv*f@^pHx;^ewJo4Q>DN@HuQJZ11DgkRa^rB~WQ>mPvV+^xEA_iKzN!oT`mHjm(fb znBKIivQ8XRB3RUO77a1Jky{^CR<^(-E;&}CN;3$PyQ-)h;ZZr!?!-QG*je_l4(e6N z;A%RFQbN7JDNMScnoOgPUnmqKk6zj)3`tyMQFg`4IW2pkgECzIN5#6WY6YL2LTicF zNt)Ve*N-^iv~39NcZ_~C?U@?UX3Y9Vmxzd9xBi4tyr@u183m%hPL2Gd7O-plZOwgO zVmNRb47k_FPhKPaUew=TO)vh`u({V|xuSZly|hwN4kDMr3Z@tLvxsVv%Wz4y^Axno zcj!=*1iLZN#v6<83akeOjG&qG%;AOb;Rx8~(c?Pk2U8zxn+&@7`b&|tXO6#}ye|29 zB-r4{=CESD81#(2|03@^+-Dub4{|h90ijEO3r)`W`z11b$^EooKGCJ*68YedZHAmrm#gxQhfhNbzQb9?`r zwy*6!adF~};SyNvtIX*Cp58!NpDqO419VczKbzXbQkqAHDXr!pEk;$?XTZ<;dT(bMVap=uLQP{UZ zCHPtdI<+8pI%6#FN3BZhsOgNL4f)Y#ay949Z1hsp7K!WiFbi_(j&y)rZ@>Tj4dU4;J`yRSK-naB@xcp(Q zMb8*^daeDy)g3@tc%o~YYw zG#)hFP^cL8tG1Sp6~NA3K-eW1J0Yu+k`eVkLzhu=6p|j(nn0|QB^52FX9kaPWJ1b^ zI`Ud|(5UTcglDo%4hg<_!DTi5Sj*gK=Wo)16G?KRzDBc3*a3|?E>HpDk(P0tVj3xE zDHP)E?vC@Xl@T8HZ@iV6pNfKf{W7U~ygoPAT=w#v?+y#T8cbzrfwoqSl#n~kY_*{U zjmpk>QEFM{$m)_n53kT@uwY|ZwM<8Za1iJxi;ip~W1R+u1VSJvhE>NZlo5}_=TB3)UVbP_aeBhsrPZ>6&!n_s z+eC$@igGQRl-kSFH?!7-fGPG=m>{rFG!^S)?R!m4^)9XK>L|7AF^=LP*C#3l&*Cm# z{q66McN!ou94&6tDZh&>uJ6q{rcDDHdE}w$5&TR(l$%duzgCBMnX-k3+iY|#sjQmX zk)j@wy^gX`uC`%`cc$juO=y9d9dLRBa=Dh(+G!WSL$`a3i{(0MZzb14>$UXz$5bSwd>ltNJ6qqz(H?yz^1y+wdNPQTRHj_X^_$fU15gvFcAQb7%?fc68 zu;uqL@s;oGy=ixWy>COihfI+!QQnB&!JrH0`XGF@+DNwrU(ooQ>ZWXm;0JcDGx>+5 z+o{6Jpv#x$;gi397w4C6;AkPn_3LViSZ`Onvl^77cul5ejl!mW9%)^cN`lPS_f%g1 z3^4rxZ;|VbuZ%@s!cwzvTaX5fGM%T_&dtq>BZ-mmlCiQW?sLp6v(RvNc(h?~X7JUI zEQ3w5&^ivnvey&!sd$~=vNZ#4ne|chtNy^%byJ&FKWHOqYaf+7#YC5Pfs{89gvUZK z&hXk3ZT<)-1?EX_WLGLf8Z%%tX=dG`%H~5-Nyy=C0gdq#Jxwi$^f0A(qnhPD?F$JB zRCU(kU)Edtb-R*brk_GL#Kg@W9_D8%yL^S-y6-NMEMOtoZ65ZYm(}Mk-$8J~9szg} zyyXbQE>8Q1?R{l!i!#TqyhWn3!(P2D+E>#3*Yz(%`T#M`EhkjVNP{v_B z4GK|917XW3$JKDg7pqVHl@}ZKO(RxUWmaLEa@YKK89R<69{fW*!>K*@=1g5e#5WUc z)Bk2D%=po`$e|SBOr4X{)6OQxQ2)LI{~3m~Q=G2%!RM`e$k+!dj#Kdz+T^2#1B9g* zNzXm_oVgV6z((e{U4A-O2d+58>O3cx!=--n)5-U84fUEuw5IYi4tiFX)xY1XbC#R@ z^xyPWF9i$Q4lqP;ghETIFtTv^D}_uS*kITZ(iT|Xb{^d-sV)?jetadNq&WF}Ybb}# z>qo2#bELI^R6EO=)$`m{NW+p0Jd!Wo&o6+>4nE`e(s_|yha3R#LUMmqn$wuA$MfD$ z*f+J+9GE~J>CAuIE3odp3~nEri-)OZXH>01;f&K3t@&(91-ywgq{52Ek5wNW9e1c1 zQppgcq|95{DVq}9IEQ=pPYtbp^5}?#xy4ne5BAFzmC(58=*Lb=u&o~#ii^`l|4bI6 z?FDC{i!}D-`@j+Mg5D4Np*;%kknBP)nH&CVS;a>J7vh@I3D+t9aw(>0MhGWg%3&tV z&%a(&EcY=ds~gGui^+)EDVF@Hg!2zUK~|hN`lHjTCpGd96>370h=&7DSA!a?LuQ47 z0?cS(ZmIei5$(DP6GNDG+!+$vYc_Y1z4L(^pZD@Z&%b}wrwrPstkcPNgM>Vqx7Yuk z4F0x@IwNc$YEMifl6;bAug2bc$JYJ|R`V}I61A?{7(%8{wm~z;Mx~#ZXJ~VjR`nbp zN#E9!5ebRZK=t~OXqwl{6WBy9?wc%D{VA#3INmPkM?aoH9GdJvLZa9}`L84%wgX{0 zk$jr*h=LjEKRAs0W_{@)pHI)d6#Srj9;vMp9*YuST(Mg3;URH?^Q51te}kDKOp8Cd zA}GfY3VI>dPEmf-JI~1ljPhigWvwiKWtl75;QYig)Py-@S)^{np%x%LG9#}7@vfD> zJ2L(ArvzUp4PC`LtAg==RR=s*>j+}ocZ>4fxBWMrKUas%>>{L%n_39pk6r`^3rU_y zLt}|3H6m0rTess=D8LQ9%d`f*2+wU=k%4f-6Ov!ZKVB*|Yp?HWg+^1~wfG6)55Y?~kN4>vm~ercg3UMQk)|MuAcXhq$M1;$XH9g@bRw#eMy zsJN!mt1vz~tk?eWS2Y3+Th@QhH(PC&c=Pbv`)^`(fnCP-@_X+O6>3vH??E%{PhEbq z6Vq-~bZo2>3%kg2skIqfijl@6`r|VX8NFTFDjlWLgQqBU94#)I%iBFdQE5ky8p zO|b^bm@|-?+84jDUtT=lR?Xk{E__1(NX7?Cx2TU}MQAU>XrMGQF2m)Pzu(WfR{G5xS^Dt_Gt{V~8l}%Zp_1S~ zzUhWqsAwQFkZ7-{Y1WBjFAB+B8T}v%G)g|NVn9%>Kkv4`e_=skSxv&OKlZYq^Z5kjC;wj*oit+Vv5VTRScS zqr+cBbA4{P?RISbeNxgfJwKWvRO84xd9%MbQ~L1b#JXm6kXM|Q4pX(KB?&$2a}vfD zp8F8HS{ko$rccgomK)NN7g~afw@Wk&Oy6KT>9q4dRD$!gz%qY}y1Hkgqp4r`2h>om z^_-iB@1HYu!AGuk)_6{y@zPkQ!)*72V)Q>ULwjV}3EPmc3=yCqorZSb>{-ho;5=74 z+20STB9ZqdMrsa)5adiukV_p^0|cL?EqTP2tjYG^OxasEedTp38FA=Gs}ubeK3t+?FXM~T=K79Gvt`@+^U-t9U19^8RuB~rRrNM9Qd9VV^qiGp8=Rm>2s z7V?v_n5Iunx>7(>Sv;B-SW1PdW`Uh*deaC)2iqYVDWZwAYvn)svAiOkUA&g@?D-0$ zzg_K0Y<#{y8+LV&|!Os(%79Y1im z07`C|*I`cF7Ley83OW2kv2QDiml#6t7OiK^Yhf4?DQ@`YRbB(N6&1$^i$tB78{@}Y zXUSRmP%o!ysXX&{@vIp2p3l2PQA3* z^D$pN&zSC4s3{2*_JWAF+R){?Bh)gSM-)lF_V&l*`o>6L-=av(pGylT6&C&@C_*)2 z=fAkm9KujXF4evC!9P$5bzxWW(YcBtahJ6HdK=t6apf8~oVD53yb;?7e~x~57(M%` z;SuqxC{Wu_QOSwj-yH$%aplIPL%&9^J0uJPbMr5hX?T!$>nb&`%Le3W_ohvO^ls5D zbu@Ruglxq~CRU4DRG3M~QeAWVR3uZ^*x$b}Kw3txd-HIlyu;&mfMj<^YE$04!qM;b z3K2rovoDSpIYKQcVY33d*D^W`w3`>Dh20K5=gUTxDTiqTLN`_kGbeXtkX+XMgg;wb zan;!AY>Uew6|Hq@dL->%GyQHHT7BPm|G^-GaSV)1O#|!c2?STP4uSlBiut}A=By0%k%WL5 zO;|RF{L5Uo4!#8OSh6^LKfYN8S&%WH#!RLh(w0$0$H)&j3z_2U%$k@EvKDVk(J|ksu^Lb4V#=Qy%XK?d6nrMCMg~co-spR zDkHvKqSK19BVGXe?ki!Zxp*~8Gu?j}2J&;kpSfwf9wJlFvn5vG!YEa^lbVX`cATc) z(4?T_D66tIr{|{d1m6~^o}S$zCK&d(57KcEecv7odK-~1XZX%Rt_1YcOofr^E=@9g z)`>%uf9ez?@`xVFT*7-vizUMYs7KM1*XUe8q*IP6d3I553{^HedRu#SPJ<=l_nK`E zh7+(CLj947T@xps6YbS25i)`;-69uFvix4tEUTUh$tb8l#eXr#6oM0!7~FDTOU}fZ zT)0kcz0T5I4&=6Vf+RXaZsauI-h{ql7vc{`V<}nFo-xZPlOFN4@~%LTO2*L+mnUN> zRG(5Yz@+u>>Y(vpB_~mQkq*_BQhUm=rR;hxcSF~={AM?^$^to*Y-DSs25xR3o-#`k zy?pk{E!}LLXj6`n!x$v!y4RK zl&5-K;#>;Sm(vbb3W+O=YZBsWa>*DF35^ER*U%j^Tn%CpAkg-PZRPs5^-72!3Y_^A9wwX#?nj5-dm|1+t{^$zcjBfl5^2K#vB2A- z947is@1N*wQyLp5OXK)uCjG*hZVc{I6l#}n`Cd5LB$zB3fI^i+++}!ZfOfrVT+Uzd{6N{_@>H_`` zg8F|f=iE8hrrjH$q3!*vlfFLP3_7j%ehwWVn*z$k+`ZER_sajs%ax0}fO&0q)BD0s zkZIcade$R=7!Dyz3umf@3-eGPfZ&JUA`WfS`YC6DdP) zt$9s9mn$C$?LcnTrHkM;{0enpJeBDNC={^Y!$w9%HtH}84a^F8*=XLu`n{G@+Lg0> z6of6}C2A51dPSjhUKq#Za!dQ|A%K(OWx&OBNpea{D%z1Xl7K1J>2B3LqCmK9!M!5^ z)2=hBS_L6HH4hq$nK+1+%ucP0$S4i;83+h$n*K5mc_6)c@E0+#Wi=;Zrj04JL++M= zV~P+7u?|I3Cj(S{=i?H`EwUtZ#mw1R9}gC;>*qx9;a9aIHAu?w9UUu|ah;|%X$m`xk*+k+l>Vq*O;W^!H z|CfuMUzN)q_ie5Y?AmJd7qXuBr--*)(`;Ms(3`#oxO{6ix2#95eBug5bh6k;sgZL= z#lO_BG%&@Jq;-X>NblpKC{*&$&D6+j11N{cSTvw4)AED}nccTJ&jCM942gF*SB=4t zwPlMbW9K`oA#v5srmCNyG#2a~70yW00ufy(e0HUdz&Dt~u9Ii^ik?IXZ(b*Cx%~x* zs=99SFN2@^elCYNbtgs%A42mH2wElf$2aOuFu0&0$k>F5i&KCa7pdtoy_tZ2`)WsB zeaP6tqI=+?|3u`qfwfhnKZ~BW{+~N8#=7=yFbLDC^cVN~i+j(@MwXyw$M7p1ILDjb zw{p>>uxHXTNpV87vZ6dq#|Au~BxCbjSqVThVL$%*JumxQC+6-0{ev&&+US%{h3iM3 zYou}rxVWZ)v%237(*nEjsVV!!jz%w1WA;f~aESxRtYg{JOFbd)J9e72- zI@OJCnRD&X?ssdbcso6~D{&BfqH@R)-*lXCxW48k?fU)H`=LP8d|`Yl*bUQ;=WFP9 zf#$7z39&FKP)pf0&;IA{9Z>-=3Fz>-k2KuJ`N-@UA7|>Cn42TiCv3)@oBFq}rm&dG zGUh&QFr}MwlkC_KP4$G!2%xq7j;XROw@t~3CZF9Ju9URu+A|21B7~N2y=o7E&9i2O zu>`6jYkcWa$*)Eu$B!K3mLg|T8F(oow+>*9g-DOmnH@CF8t^WVhaCKRJ|Wy!j3d$; z^0fcJLg{l=M$5n#NLJ{($^WUEJ%8iO2u62=yJU`Mkc@{Sjk*Iq!|3`nEDmXY;(}|h zV)9a{H2;zdbME!`^62($2e0x#sQM3APkDvhRvnlB#yL8bY074(VZ*Qc**w+B^alH1 z`LnAedZTw} ztE#I(dg98KyK>jtVklR$!-6s$8(-rNU3Bcs40iC($&6tT4V91&ww;s?vb%k?Q_v*P z)U6z+I5!w=>{J^QMV*dj$WWY;DxNlMYP&*E``ten^$s-s?le7telx&-I#_RW{>=Uj zKg?Ct=j4nRXXf@rds zaHX9R%wzJ2$*DQ4W})WI)T;-@so#Kq?AhTyphfWlacM&CLwPKtlQN_vuOr z#ijFAp@!w`!^GTI3ZP+U;q6UOj0xPkacy$+yS&~~pA^kr%PH(yh`2BPR91QtxMPcZ zE^f)D2)9CM+_^hvimO7*$|qVkM2AHAMOZDI|@Gj#XqqSs4NG z{35PineS@*oQ5pJOK87CyMa-EBc(d!OaVxNLKzwnCgleuxilnoeF6uCIBE0%%sQL( z7`oBWO?iy?U=D$~K$TeqIxZ*J=ML*6Q9n(GI_SZxJaMG#&yxZzffSVn zsRQw!6w%Ocld^qvgd29jCu+8qRwvSOK>N(cA06#xSz z(BI0j<{{WKjuP|t((juYCYKc6*4Rp9RD@9nmBB<}wot2HeBo#jbe?^``jFM(`c<%I zCCQp4&5G|!N@Ws$CCKoj7h$^0y4D5TC0Q)!MC&Wu`K zZh0DVu66Wv&89tOUCOwdgy=d)2gZVj`^T!bw=F+c5n)=TO_GgF>jDAYb+UQw#1WEq z2HJ3izwm*l0`S3MC%i`Nx%At2_j_RjDtXLYTvFj1!^hTHlHjdlpOH$a;h4kifq6_jx&7 z-g)xg!%9Cp7dx$70urdm0{BsJ@I-3&qs040&zm`8*A*@7fl0!*P#z8hdf-E;u5sLwjl2ZPF2T8yi;et@1R-)IB zvMM?%=`_1s2H^P~W4or?4maHm)j4L2ZqL=Um|bNdKsSkD zE9_Q9!~2Cgis1boO8Ac>>8Bf~JK5*XVe#Kjm-Gd#IK(uV$gn!5gtq%U1c~qiV37j! zASZfreO9JVrAkTLfvDn$fg}22qYZH_QaYvto%uRd^Q`<Yl4mmo8X&pigmz{5Y)z$@JF>5t*TL^>M_8x_6sHO}lo#-SlG9S{y;_n_Ege zvGwtqL9{D0KV4$A8n%RsVYHx~@)N+4911@003{#c1d3>--)5}uJvXUk->0*iKf!7c zJ9a<+=Fc^v!m#h!;jQ8GjjFF=E-|7x*_H`p2`KI(byTE{oD)b8uLCp|SLN_@HkIq1 z4}gh=1`}ef0`8NxRrDRU4gb=b^hi~lim6=qLvy^SzJV@s0ohWXzCRrV(uRVNIJJIQ z(IbAQq3N(45ndTxoubW}7Fq;*D&s;MFl|Z(jq`G|Nj}AeTOuLgVU@hvW0=o=o%7X~ z)cR$x^7^G?j@MCIAGXVgNmOGxTxnZ(0k?bgcO)J>o%psGC1~NHf_yj;J=wa^jY4Mb z!VT9x<~Lq!9P6K*&@KOyi^GSH%gCr^&Z93G%J!`;-y(rg%lqfCcj}g{`q&eVhCGIX z{x3rp4%!+csczG;CG*tm|G*$2>kN_V_(7?kr z^ycF!Q?B_7HDczPuFsi`;odn)H!Ry`-y!rEZHVWnk^VG2HJLSIK{hL_!=)gvttK-? zI%D0pukn!=QtD}86(YQmsNf)a|D=rU>T$V{!~?#e4#_uGFuDwqx_N#gsIzam!r0)4 zi+?H{H$64}drT8nGE1In5Hfl-U&@Tu|7vPE`R3WOLRx1pwc`q}f2k;U!wNjJ55 zWR8A9+vdonL+kM9wmu~H-x0#5X7oT5PVoII_D1OA#BUNXT-T{@V}(qx#l7ifkItf!RtQb9nj?yR8ER{s!g` z{#uAFzov^*S0s`)VKNO+hUYrd|wPE5xLKnQB0qhDh0 z7;A0>%@*F+5s%?(myd>cF*Gh*{X@SL%r7AuP1NW|)(>-tkN=|6bov7iG2N@F%2-?> z!^{~*WaE!Ma~ur14__kd%~KcldIYBIV$vJZJp=5mw3#m}FRO0)R2?+PH|fpkdyU4e z9CF|M*~;J?U9v2f+>3hY{F8`jQE=I5%IA!rwCBi_RYgj4+Gcbj(INaX#B0_u-LM}n zy!_m?5HvwuMe}%$#ulLtf_L<_SslUvo~A9orG8?PYimsCx%>m07v2D_ULjPLPnZ6) z7lSwG>q?LmjYvirUXtkNXmia|*DY{tv+H6(vOz|5(Z}9gcAVI_}P=D=f%Fyn|vW0)s$6brOJ+^AnR7 z1JXAZT%>=H+cyOzM?HOgyCCVm|JioEFQ?;IyE@lYRIL2|EmG2CgaF6%bx4X^fxx#T z)0CGaYmH9Wonv$D*4a2;tLQIZFq)(?{#J;=RJ5}tbhxgY1!1oILCup*O}N$q(d*^WV?!5*EkD}# z2K&3GJ@(2n2pi!7QxrJVsu3p`gw}_-e#3TDz;2Sv9*2TFYaaRS#*@sZMcA+7rBXKs zKK8myhQAz%5&r#O;JgZZ(1BY=f`2P@1j+c|IR4 z5;OI%?)p6*$KDhQT~qsAIDAjL+x)DDpKWYtxL0H{9vHn&Cl75ORR>*P5BED-QZhsl z%E0Zi0t3PK8GhB=!<43a;CI%P(GP|%gnIf1u-zX-{2!|@TaSNbk3>RcGsSYg?_pAL zaq?soo$AVrj(M?to8TMY41Kot@N+Hfl3?8o)=n+(e`qAeU2w$gRYx`t9CS@LOQYCX zw;TyU3`xbvQTtMblyZa^JtZZroL@bt!K{}IhdI`E6cp&J1Sb_!h{+tUMB_i30S2*X zhvsYOQ6n1gdZsT=pItsX2Fa-0lkkj{%O1|I-t@dlef-F@?@q7ICUQvu6%DFU7KhsD ziloFnlJeGfc1{5EY3eV=^MtX(S%Tj)6yH1irX4=+9biXfOT}1d{FR!G%*~_!F(ai& zz9+eN-(>6gy|KUq5}Om(TLX142fOX^UveVpsmd$D3CG9^p$4_1V;n&R8qvC#5)OQO z3K`HSuI;IM0*OUmW4w!>`1po)SH7JNy*r;}er#Uof2v}OL-^X=f`j=@P)iN*bmf9| z-mN;TkFKemLS${FhN#WAXN&vBZkS}dAE>v_F}*Uqu2Kp0{l2l#GB}$9Lwz!Is-yFG zEFthXUCL}F!>Y11Flj4HvvrsOtJERqvu2fjB^BL7tU||S$UIS^#l*%I-A;-QvB{$|xkzjsiP@Ny=j_gzQ7;dF^L}T?Zn%SG_epAv( znL{o}1AG~hi+H;~Y|Br2Cu0wLe<2DF(M@(d@jPTl4Zy&Ye>J=TnPCK|;<47$f0N!p z9s4hU7JMZWaw=s$yP4lM=5wMBy{F^}D7ntdVA&4`*48HKxYv zSGN9D;DSYv_B)baUc;8Nu7hi;u7(Qu5YPMgr3euPG&Qm5z{Yi2W>fK+Z;o!5_MEK% zV#*Ls5NQ=8=+cB7A@6By{hf9CMx!a`N7+Rm$5g`uv2MDf3z?D%D{;)@9L`6Y;oCk{ zo5v31kUHQ-B;}qzdz+U%WV%i*|E`$btn0vgYluD6W6{WAW^M;?(zd1nPfY+*=nu9BA`eJ+vjWnramuRCHZFI|zFefII zl@Tmfn8oqVTU)Ry1a7ws?W)_&Y?RK{|{IKU$C}*&{)Lk(d`>;R^1y z&XSsc7&)PnCpXB$`9~AT+a=RtN{3z|H;Q`5n(~Y&N$Yq;Ck0fkiFk#}3uln>v(!Ud zRkZ!N=WCT43;wyca6In;iH^9KvjKs1-f_lCTrE@qi1ibXd~msE77SwA>OX)QW#SqL-@|s$t7Oh2g>4 z=a8?y<8jt+OwAfVf;6qY(P|D)#LprQ49T?AZg^@I?pY;k{~}HD6fch@>n@~jow!jB z+s)2fS!`NcIat_z8c2}2P8Raa%6&@D-I?#XS-b53kcKI^hikX>lwR!v11K8<-sf$J zVqJ{d+D7`LQ;wm&r8tC~3_piB|FCyfo^a4$ijWR)UaH_Bd6MC`emC=*4F9L^t;}$JJ25@|m$R!bDkq09# z)mc8Cj_ih#a3*0r`BAk31*|AbE+T}>~c?gYFG zA8mx*P1hXX z0(n*7n)?L|^q-5V5dc0DP-x~p%%Il=Xg5jZni?9wNzwz)w&%HRS-bZ+9d_ltkyA$r zggq$`?!r00VW7V1l6=g|!={9vpQDYI_{-FK*4so`rh3tOaAo%*0tTM*^S#_txaeR4 zbF>&O_BmJN(sH`sm1@w;#3r?l-!yqIzMrcb^3=oOA|wq2RSK{ zR-1c~d4SS*+8{G6A1)Lg-Vxa$r;K<3O(Z;QidPlGopdMAIZVEyo`oRXb=LM_I)AHm{lds=<%+h$`+cLrlx^d- zF#o6zF*uQ!OkxH${whrdl2TntRlfPR;?Ro*avU*VMB3@)z9VdE-`AY&sGU@yJNCe2 z*eCyo^~seiamAyKhhJCXBs>FxOPD_OiWNPFH z9rANc`%lO;i!8#gnwPi)aS|T}MA4a92JZNgX>`!<`$)XC8R#a0Q9O?lTX?;r|_MhAC zZ5cniOtpDi6}pqzmf#Pk3`=MQSmmYdMl)`!cAdI=@6n4iY*hkgv&B1+<{333tS6T#8Bq{5hx93^k)?!7hopgO zW~HE|cPgps_lUJN7Q`H|g61q3r4?_WHDD-g!0G(ujg1@JWIPcyk4t!fe~$&;gc{Lh z&Z<2x>N~~mSuSx`zl!_9KyUG}dDpf9XPuyYwlQ=mX04;z3BP zX4Pndch5Z&L&si~Yg_+eZ4e+jPs+b&4s%Twpa<8-6x&5`K*UHMD<_v;Tm29lCAa5x z{)KUd^c6Qs^rG(0DH`gpgdLiwaYqzx{Z`iU5wAAd!j%>p9Nq8H-%_r~wA=A2hS_~^ zipK=KFTedG^*AmV6o#Q`_w|0Z;N-p6k%wjXK3V1qHC9n6(B8}g|*O4io-9qotlAE-P<#D-U)n^)xqd$cd`r7clOwJ5muV!qRFA2-?a9UKn z8gD5^d5c!6{8%78*Y@8iM9_=gQ2SU@>t7>qBngN#N>Mw`R{1kaXm_B z1KJ>-x-nlPm!Y<*T_a*n{{{$QLpSd^?Yf@K0kZkOLDF9@=k40Mx}S-O(xC^nQmx}v zX3tzlZnErF2GnD|S-9`YsLQU%vjmf<)2xjC$6`ku9e;>L0DnQxA6bSvHb`t*)}JUVULnlplDiK&6#yd&c&&90+fTfv>}ySVL8Tbh4V z?bI+wES`nayH1eepz-$LlGY0cW*7>t>IjA+f6t-NhsW=_1~zgT^ca1&q)2S0_hRKJ zo0g08x%*+VhmX|0iD;PSsNDrTh0CM%4FYcPK%8&4T)Lw0bKbK#qJ%aliXwb?Qx2dc z-L=Irlr0AQxN*tF>cj~}!8R(Bw8p-tM{#BJ8a$G_EQvE|+1tqBh`5!#YDNjork>fh zaEgulK_*}+J?nsSw`v(<1pG+{}reAc&^6b&hZx3W36WL zQS+`jZTjfx7#!?0DAqZ=m%1TsM26D195m2N{SaWhNCP%xKXE zZosR9lzHSbTv6NpGaz=x;znWRf5?NRVQI<-M+$3Uebp6i5!!m&{4nae9XA6JWDg-k z|Gs`ctzld13O}Fyk23KGRn#b*ps3@gEVZp7M9ISNLnj9ZWy1ZE2=f~sjlQg~rsa;< za=R))H>3Z^vc+VkO!<4$Yej&=!+#LXA>#1RoxarXr-8ku_LF%(X`Rb6B(!xzOIY7; zCRLwmms)?8TaE?D_8zl+x4-|pOv!3hRXj|EFX$WaPtE|uIMQ`1U7UGI60FF$Y~sv@ zzp*i1D`7`R{8PvZJ@;b&FOqaXE;X%xqAR3m-DOdq|F*g1st3h4uBH3!4B$5byAJ*2 zar%4P7dH~Q=~H==u@9ZFv)`K;B7zh?K=>3@Q>!5kAeyoF!>Xp})s{VX zug(n|yt~b+D5Y01iXNi`6;{TG5bWTD+timmvlxev{XbX=xjX|@LQgX9mniR=z=3h` zV!MN?Y-`#-u;W1>*8V|W>e#Z+zEQpeJ&A%_oRJjck|dG>vFVpugUR^q*tw2zfwDQ` zE7OJ53GmEI+-yiZdE57U$Z>Zg!Q;=z(H@orJ401DT{KBO67P3Mo(>T1ws0UHdL*dG zs-Z6sZHrSw#@NO&%3861RqmNwaDAhwtaaukXw#NdPnioiQ|aG@4YJk;#wH*|oqcR) zkT2uMT7I528BCfU=+w01{LQ%bOIv>UVwM3uvXXXe+-ki$6o5sGkQ|zq>T5Yces+!& zEc8bI^&~p2$Va}it0!Y5JNwKnTcDIw-J2o^YhM=hUGRo}s>?TH;>?yen{XkevZnI7 z3&kfa^uiAnFBvENPH;{k#O({#Vf1YRsAlm}sA-|dYP2{SDaY^K1LS#30pwJHUa*?_ zKybrh!0hh+m}w_r2_i%faHs+DIK0)}@1Eyvr;K)8+mZk_RtBBWdkX@1^Oey9IWn zZjrD~6SZcU>;U9;Gn9PCQ^QZymFZeFoJ0qoFfOqB{7_z=aV_*uwB3;VMh_&1#AvX# zS|voZQ@Ys+W4o_X)~9m|?*$xI&bvP#byFEX6jLX2^HXK)cpcKjUQ#bzzoozxl~a!b zedetBy@ef9m}JvE$$B2zrKRQVRiHijk`^uC*iQ1jD`>Fd!Rc@FU@=neJDFD-TS zM;4+hTlSZ4;VI^Bv-trualH>TJMLG`W>*l2oCZtRZ}kvg?D7#P)?`xk05+uqvLwZ; z>c9koBqd^Y&^=s?hC;><4xUk7-ZClX{&u;JQ-3n=kB9Z~@2@8wL6>@tIi-{)pe*hf z#^R#p2we4Lo8O?|pJcMzO%a!?#Eu2{N0zP>${RY;JZBHXFT@e|(73*6F=*;p;&ccn z9i>Td^@TK2B1nH|g9&c~hefP#r3V-S&7da>JH;&hdkVxO!V-2k*n7#SJEW4U(G-Q9tnW)!=9aVCPVjoXL3vTnUO*ktjj`-zF*d#Kk9&` zSVDRF;?6#?o@3C*_Kcmf2-8J8)2g$HF6zwVv*Csj_`v0@J==F`>J71mX;NHXMCUs) z#RD zmr(@9EPXXyh(BC@L$FwWhfqF50M!fv^{H}~X-f$_% zE;+71BBqTOItmLHzI;H9Z0U)0MeBo$69xFmddVZPsfeNVi>Y84 z!mZ3(8BQ>K^_8S5I6jIqy1FgD;9X?=KI^@r6(w;IS?9W1WBGNImCZ{98{{?&8*ZSZ zUDDnpQqFEwS#{VslnPBnK$8H`dq|chN4x|ULNAw@jC;2K{ZTWXT?nM4e6ZRPK;ksK*@B!n$uN%O)wPtXnsqWiuzlSAhzrI&0N zcYo>L1sYFyem~n=m~I*Rc%IihDHLnsM-HD*%-4^jH1lg~^}_W3h$FMRJciEr*1^@# z$+@L4joofdTSce^WfyHg_x2wbSMZ^AiUV~Kuszh%ui_(zeyqDWU-?0-JoH#{J=2)`OGzg(PSHMJ`38l?wi~3`o%Bi`sC2EX`z_GDGI%@cRzR$2wwMu@^qBM zt28twL3{JSAItJXgL~YATPO->;|qSPlgxhoq7q196DE^{TwMtVpKO&XlOxtVDCN6F z_S$pd6F4%x)oI3!?{q${jeKTMP@$%Usqdbm(7ffOu$BFZC>T0Om=+h(AW2FSjZ~HI zZ9|G|oQo8RJpi4g{p*Ta0k$ZCNHn7I32ZuG>m#`=$&T`Qx!*1x!XrY$4ON=)kDBbl z-m>AAgdKt5t`56YrT8SJ=nj|xkpl!s^9;JlAT?L|*6n^c-@i3)|8&NvDEzm?K%>9DewJD= z#f&Gqk#SgUQcC?H!r9;yXCw0a7lU6r4l9|H-fE2@6b5q64%pcA?;x?jmE{2;#k0|< z$O~Hxr&GeelhUN0z6viHM!C!)QWcuEIpL(T9XaE^L;zT)t8m3v+L~O!KKEaNz+^Pr zaD5WYMOP#+i-3J>ln^_;PN+>K5(dk6Yb1}#qO*A^br5l;kbgE!OD-mVj@qJ=E_Ugl<49?Gspq6kqWWc0XaYqs z*Kcl*ex(ntR^2|QD2}=;9%N+fih67xTwEVe6+Ln3b9h_?7ue&7nl}qK`{zb15O#Uw z*|RAODh5wn9d=7vQMDUh2f96ri}VoT0i+8K(yi2eTrX3u*|9jaF{N)j+x2BK50r@J z4ni%;#*BwnC>k&sgHKeGHtW5Pv&>?HrRe#35-_C_(17(l%Qsps6E6y(Z#1vZlz*qc zs^$8ht?>@9-P$6_JnKgkz~Z;D!!;s` zmKKVxism{ix!CNy{X%f#{k|pC@4_1r%#cl9ym%V?5~yCEiEdJ3#gwFE^@ zmdS)8h{QFva!4yTBC^f>R!z{}s-j@uSm{)KD~D-&P_ZwxWNp^1jD)Ndr74zSK&&~q zvinkP)TFbAif~<7x>&|bKP%4twDsaWl-rlybj5UuQdxz4pfos&T&f!dlte)BBo24)Vdd=%%@j2iVh3_^LnT3rr65$dXUs z77uL0X5mc9&elTOgZ{>V9|^a??|fR$_X@U(Wbx%st95*2aacU*H%r!0$HG!_@LW69 zU4~CS7e!3`AE&BQ4LrcNA1y-&F?gyP!aZ|-yo%)?9d#UuaFlL=`hyJi8M9=Ej?WJ& zEoVn2AT^r}I0_iO>_PyUBpa!m<-Pt;Lr9t$X*B6gli}HZ&$QdA&IF9BbeU1S2^KdA zLhfgn6>gio(Vc?6?BX7gLj{e!V|nM6K|OSlj_?!#sx_6wcRAvt%qhmuvv39du8GZX z(2|wwpPLGgzYZuZ|bvVmP8d->x-cj zpYZn|<)-Bi4$^~*8o_CUufX8Mx6zquD4le0rL!19(qu{>q$oWUa^nDEV4I3i#ih8-|jQzLj zl@mi6-JqvKey%E~_0$f*WW9{jk$n8anN$IXG#-0eVW-r^NXq-R41&nYX8RS!kuHu2 zkr6qqFeT(=(EgdO{Q~?hxFHKW<~G;07_-VxulKmFd{eUIU^^{=8SqDj`Y+yM**B^^ z50M2KFhVA%{b(>J;&GnVe^p8K63S&^N#N&Q)jLNn#C!C4rpARMzTC1q33hTGQ-GeP zQ|l_Dr-3fOM#zs{+0wqJpBpw;(pAHazcna&H4w)F6lx+R68U6eC>LpT3t6UeR(GP; zSe%(B3BaK>yFCxyL@zN0QyQQGUVZfRAJF^cvsP_ZgTav&Pk$G4_m)d@w{Mr(^fs2Z z)&>v|L&dK7VJ>9aN&5UBoadr+zqh3qO2u9u6#}pJjfrr)??87QDyfJ%g7Q-Hp*TfE zNjB23Z@zYzlYBNkuJt}L$87y0tOWY3fU{eHTL|mv+HS!CT*K0DLc z+P1I6s=G7DZ#|xSn`&F_6MdDtDIB*zNspJH5)tg^5|k-isuNN+ zCCn_t-%Zm>kyQy1{4^R|@MidLcVf-LoNBbda#A-!yV>7>L_AKfYRZ6*o>h`mok~Kt z$6kd6&E$3z9VRu!epJDcx zMUF{rM-(PpavdgYYg53x3wOx%{S`*MRBaV5y3p1q1<_*#+7J+aib-H zfl%IT1vWvdL^$mP&Rti+uUfJD84YuGUOeJw4<`?YtE396-t68R@p_okzgFJ)Nsj8D`ScaOe=9(!lN_ZWN%!c^4WbM~Y$O9$i?N*cF< zut2uOE4I=9mMHr6I4w+s5pGVD9`(@{VipA%JG<_=eK0sGXTYTDJG==DW3$bDF0l^A zX)D}j0P=$4n=fU9h6S26)QBW;o&$Y=!y-9R1`9UQ=|Om-UuKLV1$$T?2rnmDYJfff zBhC!&5BZRs|5Z-4S|tkfE$LyZ%>-q$iYPxq>bZmz(#*x9R-a;mf|j0<=QHg6F-q7xMzKsi=NKZHO?V4#q0S?7g0x^@clN z+`>wLn0~{PN2|L)&Bf4wK$if*4mIpDD+_}ksSa*E=U&4`-w9-gA$(u%wp#N;)`Cmp zi;41}!$V%S`B`6DXXnAOqs|h|iW)HAIz7)c#ox>7RG1s?k11Un*x>_~V|{_9zt7oup2d z#gS*%xIeO{>o@QUaI!xnaQk=BjH>b$Wo8BoJ0-)l(w6ieotOxIj*NKzUi!(zyp(^I9Y>KcVhVFe*S6A7N#Ryi<)CM^FoZ$7|C4=KJPx5Bl_+1uOik1LTi>!^d-^<%UvPGC1i3~{7qKn&Zqu!!i6Vsw9 zU&pX>9zXfrE>B(kRaIn5{}$kIBoG>df6()Cez<<&gYplS26P~1j-5rVK9z|ERn8S} zo=Lcy|1kQJsKl&GdSsscrNoc7`s7KAPWCLIzYb5OUbEJC<7ov-`N}0G|8A~jGj}1s z+(Xu%2=5QwpFbq@H-22&ysqEv{Bp7zOB6qnCdo{i+qS7E`o>{hkb^b2F+AsW0;M@9 zL}uF;XZRofI=#LaJhkWvIz=dG*V1EZr#pR<@%{VW{`k9&?B`gj)`*iv1v(#%l2rsF zK=i8ss;zer1KV`iQMYL&@#2AFW0tRx!^zJ6r3?0RCD$*L5pkW7lgW{|>}3DpsSyh( zX$O$nc(0?ga+fHZhfCVxH*0ui-K7mKBkWKP!kB4eC*SEIEjB8DH8Wq5CsA)|vnbr5 z5t#im;QE>003lqJHHqMgxTBv}1a7a)+yop}x7`I}HR`_)@fIWcMSaP@^fZf9%}Vvt zG%F>VPmpWqJ38cHUP$xVD6#u;5%fcbZu_rOmA~j^ztB6{-piZ~x5KGnRWA}aAlr}` z-J$XgKmpW0fPjb|wz3%Fnfq%rbwmKWys|8C#P3~7n&8!^OMsrK)EXUx zrq(t4Ms|XpWw1HC9NL8pb%v)nI0=1W&3)}`yR_;LzJO^W_XDek6D7!3tx14ge6lwr zZzc4<4ldq?;83GhXO|;22mBloZX_|L>>$aD6!y3BvW57v+;-?15@p5d=ox)C0fJH{ zHJ%O}F!89#J8=7y-!UurE9|)zJGXuPV|8`)b#3Ym-{5IfbE)M!(e70myF36*>vjgl zCvv^!u%ECiJBt-E+4dD(_&zh(PF@?Pw+P%4!#rGwt&jkoj;I!bzU??0X2a4-C0XVX zryQSmkFKk|L_{trL}yU^AC zwma;tY$7ak%!vE?OL~jI`ZLj*CKv+0b`g@sVLDgJ{K?VuNDFSyJ!ry?3z6pfsPFs_ z=)CIAofZom z7fMI&L)Sy6JPBBYd-3V!P+J#d#EcUsag_0pqfk1BX|67-oU)%|QhoMQzWB#}_0&0* zkIhuLXU^Xduy)+CZc@1Je6PhmR?UD{%P9wLYvPT&pBDba$IbM6Wwp8BXTFad^Z<4l zv_vJsUXx>2FPrv+8Ta9*2Hn@&saHgU#~tirYoN+YKDbQ_#L0yEJ5dCFKh7do@Jhga z=9EHI89$&bPYsZ}|3j!X7~)9;Yt&YjGqProb@eCFq zrsw9yI@yotcsRU!FFKxstu!+=Zx&ap^IO7pH9sm0yiUqWAN#2UXE4=LRT9jWWww~A z9;Mqm%@kH=?h1#Rr7}M(-LFXHzj~X-$dz8T) z?%sg16(zw+=K7Xpl#sf|IXvU`Oq0qb+2MH3PQ{c3W$0Ja2SIVPt@Y*0_a3HGtx;mk z>pMpKFXcG;f5T9lvRSQ4kntcD%{O^*+M3XSVsn{LMhuBaXhIm978@AI*mL`I<= zzlY6N)VDoJPFzx`Thlyt2uL}7u}f`o5(G)-Hj4wTtYw2mno=y)QJsGAQiFRCX-%phP^3akHQ3weC-+bgvTvx*icO)7Ov-@iK7GSb^Mw*j+P(P zNgQL0z~9cxG+*=nYktpEb^VN2`UF7xCxQ8xJLWDuobOzlj%-AazhT_B@?n6;BXXo= zylM)?n_Vouoc&}SkRD$dsa;U{y2!1+d-Y2@4m>n5^wS0-Ifj|3N=MuVHa9=mD;Ig` zx5v_F%8sLlr^SOOwX<9=pVGIvh0EnOyR|izq6m9k9v?flSKL_RXeedS9(I2&fYto-B##DS9)O~33wkzf6w`vwDJ$MbB3Y*Xg`7}4PIJKjN>aj zS`x{e5-lWEZ7+$9ecs8lVNX2#pM(w=j6hcf_bfLY5;4gMZODAB=G6LQKh3@-G_B>Y zd{?DcoRE)aYTJvR{!U+rp4gfv5B~JZ{G?3V(+lb*v&a!rwjYSFS{Rmsk&q3qG)mKv zl=k?s5I&lq$+P|^FiDq8QoA>@COf=WJLL}4JM$TAi577nyYJ8pzeAuxDjsL=Hpt1$ zOZ4Ogf=|N810x*}OHmlZ5&vZS;r~z_{2SqqFj42j5l>8 zb48#|w6Kc*4Ngh39ye7n=DoZgNp*h@wm3EuxUV51mSJVhp7?w1Vw+(VF9ab9H6<2u zq$x}&39IW|{|zfh>Y9~m%1PEyR@WGQYGL8aRQDr3AZ&6c0RS7me&OsgKO+L{O3+A! z^*;%2l66Ei{Z=C*hnTY&zkRUEWWb(ks7x?a%_Ke&|{}*M^cyfY}K6&Us2#@1< zK^$&nd-W+TFO9$Q)Y>XCDEtmv0f<8ubo>DC%1jJ+PGMS%JWs+SZFvsOx7MC4y5+`5#dq;0c z?o;33_DMm5iz}jpRdu}*6ebfd|k;WEcz7oLt7alGjB~3_bu$W{c zj4dQi6Q;vTCMtxuI6W?wOhe|UuvpQtPwf$a-2_+LwOB@zHZ(12Jb*nsS z#VH&Y6V+bri&J2$$TMOtpYxkVzK54T@v@7xEw%0hwqzyMccAJG^N02)79WnZOOV0} zXwklEaieSMxoh=88>~!AobBrBDxCmX#ZlAsObe*&g~t8`HZ9qW0e0cv)fNznAVU6QP^AU-hDJ(N0*|BC$P=xphpqMexT^#cY@x zyUB&@(ChJ*H9Q{g>cHuc3sqF<3ltZAcl;X|=D*=tg`aPvYHHbt52z=l{Xer1)t7@2 zl#)=*V(#p+h`F%L)Ri+L!bfPtgOioC`VK5ZH7BzzRv zItIha`U5}P3XqzQt}x&rSIs-{trJGy-iufMVLB`%^OBzhV<}tuEx!EM4wkY#(b(Hc zE+QS~K)|k#(D_lHN6F_b9&m(>xB!EA!7aO<^ z@p*SqYmPuxbvhXfS!};2W7w)TVBE8)UV0I(-v?4UI$mEklZ@pyQ80=rVQK94I>AlI zo?uGIlk9Mldqe@kfX6BRbaBe*(3Wc}nv6|E@4v&(Qj+0@h_G>tw@np1R z1igRyozb{xter2|jSouSP9TdUg3)rSr<2g_gq;5~L3~lrn|d3Ht&J)z6$r=BLTUT* z(>cGkI<+U5jgYqLXJrqnVC0SeZb2{i2BG2-BP9-zVa*2kSvxlJkm(90_g%k8O+=-o zoZImT6&Rd5coBRhvHP_R9YZJ(gBq~y9`%r8*Mc`+U)jSG=<>~ zE@AG-#Kd#~AImy3G`%Ea^CiOtFAncCvDb3JpV zY2;x_Ov1I#^W7Q0sdM2owg)c98baFf=O=+*9UeHkWj06Xs z9lxii-2FwNr!louMkgf%jWLq4t2LKCU&8hmJHoY16P|8yov33qsNVGdxHQnIusHuD z(ULrr5cWz_iOC=Z@D`%N*Do|gQJ8)TVbRMCM^?qES$zDD!!4*ykFw|z580_x=Fds2 z>JjLN7~8H?@VF4{3@F&~_nz4sa*b$-h#HJt0F=>xQ(?;EDEM!e&b7gWqB*+sxJ_I(h=>T72%<6rbBW?B$|6cWC25@c%|6P5BX-2hyPA z$U5q2#ooSUlOxHKac|7EV$Zj@t>T_>82Mjxf~%RHeRzAtn2o03SHAINBgAaLoBulI z7>_=_Pg}anNMhUlgf=wCA&PKl^rL;DhAzpYHRaA=`2MaXMo54}6Rf$@#LyPPCDv~B z0G=?YQRd{>DG?lU=SP;oC3!41c%~418c~D>Io1rFoY2Z7yw0jB3f!gU3@zAw51&i< z{@urh75_q0rGEajuDN;e+7ONi`>)+X&*~4=3-iz4XaFkV*4`KsltMa6&RsOu+IZ~y zQLNK$G}qiIa#|8r+21W?=h-OV?$~Rz&vg7U1;TWaQ8Z&&MTyFo7&$)MR+D<}fuI)P zvq=C~IOx8nhHYh8(v7v&pN}jE-rXIoS7)NJDxXNck-lRsu1`LWnrl#;|8D+?F4{;X zDwF;4d6ZYK^F7I1k7zR#o(@)tFbNfck<7Bnnqk+85Mf@=8jqT1wR$hgrzK>Q-LaML z^dLIAu0KMjUrsjs3w$xV0$&DEJ@GatWH}za(2urv71xzZdtweH6 z?O>?8=+E~TLci0E{44NTXS8;?>p%W8)BY!R z(e8TPXX28yqdE;px`p}tFXT<=yGPJ*s(-Oll4)n`VvAIa>5;?5yh}gU+s$svI;EY$ zF{#G}El&UGVy&_IuO1P*cLUfalKZiJp%W@Sk9SNDrom0%W-z5ebpmDkAgWZ`?8^1V zSOb-OyfkIbWy0;(3uyJhzL?`$hgDDBun^aQ{?(_Z89O8Y&FTH-JLQqk)CE|~h-3*x zLK(6eT-x_**P`axIg%cbCF0T34XL(SXP3@klv|ZIKCp_P@(hp4U=GKB4HVn-zn7x zrQ5m-_J)^A6&_(gBrXOGIE`EjH;+a{nEjict`uF-Z{Ehv*ht#Kulb+jhilW@TcM1QEA+na z+Xhsk&W%s?QqSlmmD+8F$1m(WPJUYfnhdIEfnp~GV)0(F3hKkIo{CIP(rlt;0x91G zBsbNT7B{!%nGXT)IEMG#h}>)GRA>L@>*_`6l4oF_F{CGceJ=2vvk_*EDEG#Biiyf* z+*4nvB}y87xpT&|1??U9z<~n;G?{4YL9tZV1a^Xc;eS?ZhFV#q zW+C(W2i3TtG^B%S^5EmB%1l2iI@E{X>;(OmI9wZ|PfdcjhGa*R!E@ICidFXwE+}9& z#d4n~Pc>yxgQhre0x2O43HdG)e-=IoLDm+aA;8|nRwk*<8Y!twa#~wL_@d@1bE?^+ z(1HeyqI*idRzL!Ba@~uLK)^c?21Dc>MFHd!}Y`m>9(=+MbH`LVcjvH8qcXVv49df-ZKda*QkO#81fKH zl148Ggm58!vX+R_1SL-y_!*0Ay2lte7W!jI?G*O-&$5z1o)5pZne|c0tj?~0e z-KjEw(y=vz%{TWFRI&>(Nk(_OwsU*P9NN*{mi%7~1mq{Y>JU1F!I4XR`Dpm`Pm}Hrm(%KYU4kp0%5X(uUhoWN9#w($Tq_>mwf>19P=CvJII^Wxt zX;RhLk)6Eq#(N(Q2p~rY(;p9bt&!FK>za%`bJgI61O+BCLb=!cheonCb4A6A1|W?r z*+{Tbib0|ovTEhMo`m}RyZBHMb%C~qMdZOQu%+=jEIBqew``lH-1x@&3N4HI^-zP! zd2ImLfBkhh_Ko{rm;yy)vSj82 znNUu*I#I8k8{M!iDjMJ<51ZZxP6YSrvEkYm2o;jvvT&#qmpvknDPZh$8{SrfE< z==XjIkR5^bMg-xdgf%>{8Ae0Tnns^$wp zr4_$Q?#EPDEug3f6}gg#LhAMY2Xn-__(RL+UQgqp?? zS2ZK$XEqrWw4W9r6$}{3F424Kst*=Sy#SE|4GI~enltf0+YsPFOr&jhGH-*0hJQGR+M<`x z7Nr`%!b<)*T#r12Lb%r-O~rjPaVXu`|8H+AjY&fVg+aD1HYN(SW!rV}G#CFd0C&9`~TK{qBB=iPHxzR$NR)G1h zhsnGz-!6biN%2Sul*c-~bomgH{;ve_+j!Wd&t-jmH4V9g{cQm%r11ztyCChF3bQ{4 zheMgRn!SIgzSG}5t>U~2m=6#Rc{|MvKfP<+0#f4z z7aV8q+zWop{pD!BXrh+;2OT*$pzMA>?Vgj`(BU#zviEYJVL*+riNBOj%ZVRpk_lN@d^!vypNB|>>(biV9^jXW_U^h7N%-(@ z5$Gq2?TCEnI9&fL41Up zI668yKtmGY_g3@4k^6pIn)z2R#SBy8O736m=fY2l>H}e;3tTD>zB4#{3L+oV(mr>1 zVW84FmJ7m797UbUbr<_yD^Amv#ly81tf}DSVItbylAE0D^E%P}-U4t;eAB8&WX6No z_08j4ltGXX5hG!mlArtq%mbm;?ql5v9K9)cdw|rd)aEbhgYuAV+HOBeXnai zfcY1VpR^cQU$8{VIbNNXelAg^Jyw} z%7S>FwNZ>1y3%U(G@L8Yi-wY+M9GaXQdz}`JHyeIYva|!5hLRftpgaD*VA)}ZuYEW zvzOznZG89;H2s>N-rc6ZliC^`g*^3;B!<%77A^#A5?6n+V-g&QnV)d51C~e1R0#zbnqrpvKNpG;g8E@+M^!Si zV&Z);8qMkv^KxnIpvq-=9L*d$>zGs zaqG^6Id9JmTqGzScB+gMSyux9)Too3k**1r_2Tiv#AkX^#%AEYn(zTyNwCD)E!b`p zQGCAp$*^yna{jsH)alH@fbYMXNg%Y;$m*v`uC+HhuN?vX%*`j|#upNKxCzR%U8T`( zg!8MxZvmfs)wMjmYBYQ1=#_O_n;1I%>cbWUHpS1meo<9bNYDiGJF*&(YprXCK0`IT zK3w{WsqmC0yam0hEEQlnOtMI{{Kv`%!QW=lC>)rbV%k#1bO75|zT7^%=qTu$9xF1f zo5BLzW}0$rNVpROyam`)d@nUvU|>5a>~f5nl)U#9X}r}+iqD*YljyMH&XzjOQonD$ z;tK%#ATWl{wyVWM$hivets&_3w8cQZ*EEQX=SC+X5p4?A_|_b<=OLrclYsaPOqskP zW<%Zyl9Z=Aok4F(?R{$4L91>efm^5bnY(v}L!E102-dQ1Y2ubklx(;m$ze?NI|m zii6i}BlR0?N+hcELq3~R-1l|!rU~&;5)&I%!^PA}wNp;&@aeYvKDN#E4GEc9+u9M{ zPbSWY{Pc4Z$ryY>JT_=mq0FbQ&XG0x4;%10dGV^CHOIxJt*>9adX{y&@sS)Tv2fPz zxDrFacOUpCm8F=~J3 z?6cOK*rUn{l=z*uK8Qqph%^MVnZFeyY*ZI77**8M*(_(B?^;)iYdnX3zAq3R`4hbm zN6DhJ4d}>ZSp%MGQ#?@RaWIE~rnpx&%RSBsRnMqJZCcw*cvr=rf-`bV{R2Sdp+Xr3 z*QOH~(d)Prx6`h0aX>1iOa+vQ*sUB639 zC&cY~EU!(EI%s7%UMiPC9gh!_{kv5<7?y<9LUDieeBWespy~WF4t_Sls;;s%EXq2U z>DoW#_s>v)zO3RW3zmtP*zyJu37xYyza;!PS8X|MBU^3=#2yM1nojo;naQt5uub=O zgPyP2`lX+XUNE#lJ72jwM}-^{lVt5p>7mlyp_1EN*vKZhTO5ehV(~C0Tg)UXy2bv= z3~+~9(x`tL54fCex{CEXZulz~bNKw@v3~kxN+Ak~Yf;JjX-mt>nT86xg^Qaia>%-} z*?L~#&xaFFOEzUgbIo5PVxOSNtB~Y~%Z0TgW>WLJy1az?a0MN1Q%Xx6HCg$EyLy$V zv%Fm~2k6#UPC!9-ywCD^uXJxq^lzAUG>dUvB+|~9S)%7OI7nItJC~MbKm9TdriR13r6w#+(_WUJS}o>OJT zIX2VTy`ho`MrY&(+g8Kiky$^O#;bRIy))N8!tQ2C4{cc@nNV$OXIi{|=5aZMZr^dI z0)k$tJ$GD@K9ABQc?F!3hy8%B!OZ3oEE77027_K`AgL3-n)p6me|(*dm>5^Q(x3Vr+t^VSQjEa!hr zQU&`Kk#Cg#PpN?Pj+AsnQIl-v6%z6$snF5;F4SD)>LRV z$v*v{lH)4-<{7k ze}ss*a9Bv}yta!&OPKt3LyZ@5+5Zx$BL2ipXHLZ4_5kPoN9i$}vsaD9=;Y!XdP+N+ z=jL~L@Y7OiYX~SZ+d|LCw=7Uf@qq7JEx0C`0o;y~xg%XuN>?YUOAD2i!^0|>yR@Jr z(!`?to+Lw)s&aMU{^xzfQz1SGpv|e`XUfk_#8J{3?hq0whvk!I)>XDBs3CD?{Hs1S zbbKMd+eNW01=D_7<3;L6yZ?P~@5mh~$kxLV=BgV{3ZoP)^FaOCLF3_5``86Huc%(q@MbxxmU+jl+Y5Ao;Id!TY z+C<1X)O^k>%$}d`Ibzb{gNP~6Ji(Neoru}RNP*`aib~A@?9PVz3&`sO!?k)8K z!79?7>9VS`a+#LSSgtOPza-%Hk1xJbYE5Cz_b1g}$SQP=c}WG~L9=0VU}+~6!M7V) zK(9;^2Yprdq{Bu$zyU}29(kq1Qoun)LI+37Ak9Y*KC7I?_@`rkSq;xFca2;kWq<7s z!;ON|d46s+aGzS}W{+rp0eDgs3CyHdIP*w9H3KV2StUisA$oY{-jjaE$z>PYO`J_B zlTk;@vsldXHKPt>@@*zz`JeO9^r@9A}=K$9TG zHoF^k0Qc?g9ZvpL99-(5YOp;OpjoXD^$*)*_S_K|7`nD4D?!0383+x#y`pF1N_KsI z5X-x~zt_&u$n4N6P>M91iUp|kX8tH5ihwA)DjqO;qqD&6<(wEBPSC(-0;-9Pj;+Ai zqL68_hwiMiOk#~~ZDWATK41=E*o(C=h2lR#_ufgszwa5~n^06MMsd>K&0dJM?Kc;Y z6T&y~a}UUku%}YYDPTV?me6U~T-x*)W7zcDs8uhOkdrb@8yAby>hhUY^kPmQ!PdX^xd-Vc zUt&x}oC*vJYwnE2<^ZTD1ml0od%pPy{37r(mQF3IqOZ^AojdtHjp6rt=Y8IGio69t z)G(Zx8Kfcu9V+RyM#W&te=IA)Bv&ARrxvzlKK>G=2?Efp7_`qCcYk(1fMMD!Lt%MqJ4>*9pOR1}x~EH?_3jgR9rrdu2bK)VRli z+N8ui^<|>b4v}71)c~^C74T4f-vey1&3)|vW7cB67uSEk*I%Ar70SZR8P|Vd4$5N2 zS);I}w~}!S#n&GrEoYng+UAgiFhvr6d)u(#PhA&ub~JL0!9huQg<&mqDaBQ4z|pH% z)|KDQIX8k2X+KHVaKTHF@m3LyMH_p&hb29sYYZxVHz(ZMe*Z%&p~IPQLDyu?!*k>x z>?Z2UFTq4~DQuBUyo^6+NRvRK6Muh&CZ{ce_Cw#UxE|WGM^J*V2R9~8Ac=JQi_b|^ z@<2+@qdqtglj%AWyY=*7VFS;@!r^7_cD%_U*!p_guT|!ao0vVDZ}Bywzx}|+yC7oU z-b76`{FPwInsoyNL!mX1!73u-{Y)g zWhcdO-=|`=b9#23Wdk_YPVuRUMjMC=EKAV45iHL+W~9J&IEwAf-E&)9+%n$nlqA(P zgoR6b1rwdmLJ=|A_B{-s%{7?$-ZHyvSn2j zep?N-LP4q?MT7Kco47d*7URz%Y^Bu<=y(uY9Z}t*27%FT&_eL0YnQ{{m^N(KtAHQLD!0CxUgFSn2a~OQY&mU<{&3z4R`2&jB!Pg9 zyI*iu7~AyQ;!W7{l+{QG`Y1fWs*~PKP9w)jf zDZ36K7T5SMWyH#k4nN}sg4wg}RioZ8=dm%FeR-q6PmANQ-$LabWjuG9ZS6K--|=%Z zsgT_7d8B_|b^Yv7D{N9bkWA%4b)kDOc7gsru}ARr3hJ*toU7Od&D*n`H-fyE^L%3J zPQN#JDPNq=g~a!G{6ok7=dI25Kmzw$Qi2m?4GbIH=mF2V)L5Oq-^;f_831sO)JU9F z-YE*)t`t{Ly7FL+#}D_}Fdv&Grag7+=T`boG03!W+<(@e@`Fdn;Apq2C`Fi^X02#i z^%&dlk_NZBV}I&6zHM$=EqbG#XYGCkPm-h};sl}?ElPs74zq2TrXsGa&GFXM(TB8C z3+o^O$V@`*Q9OM-YNlBiZLCULV*1ogUq~xjj=X5B04T&RpIHfGMrRMw?&+gagHhgpUXmFiAEvheZoQT9Q+zaLkozY=&#s??7F;mItAS{n%1(ri%t=08)A&{E{v3@ zda@7atXPt0$w!PruPK2MNPefE`21dp;+E>>QOQ3XD3c?ZZ$N2)*egCO-6_d6t@dDR z-_|x`!Gv?``iOhm6Vk|?4>jFGWcuL9_U6F*{J`egZMmU|!@N9SgHypv85B-B|K{VX zF0R0%0R7FP9JehpExpkV-!)IS=jKo5@0{bX_G9}oZ@$IXKW*mz)8sO^sd1{b6*;5Ap9nlz7E>A--=PUDhA&UfXl*EI3eYJVV6M1rAI z-pPYo@bkfdX%eh0LE1A=375xK&NP2zj_y4;C zyU%!W{PO(v4Y=Y8$jvbHVsXNB8;ZK9KgqrJ;zJVpU|!jA0;QqQ{SYZcfJ%_oLWQ|ymFOnd{OpbsL7tF?w?6WtuB2$y#d1*8gw-GZw z78UY$lYc+k#N=>aTgLDi(wQ}Fof~c)eY_52S9OLUkPAnLi`(apV25a=d-lC=^|Unp z!Rq_OorO!LKlqE6@e(o@5H$=1A9z2uBf=z8h;O3*XZ@V_WX=Gd0?G-x5?Ph3R8ls| z8IovFR#%!et&nZTi5_T~C|uc@V`$$QKga4Hy|CmQq&4JyWZmG}8Bb1kQxA8=WSS!W z2tt~p<;1#GE}D^u%3PuZ8&wbYsqfP-5z@fJ+2dg%_H2(Qsetgr7u7C-PCnfBiiPX( zLAjW5<1J)Nj)fTcF`5G7tpa``Aq$k`NS2rHTp33hHSyzq@;0K4MA~=aXon zG;5GFU-yV!e?aykZfs;9BT2gNes&H#Y1g6%s zC#-jU4yqxnH($`(2g)gY;#d9kGtLD*=bU*5&$0oLpLwR~6?<23>mIc_2ze%1%C+8KT3%kQDHJdVqQ_N1bDKuI${NyGWUV@+WlXO^k#v&oEV;A%^ ze|vh_{JZxX;*a4JE;rhE%F_PkQh`X!6s=`*AK@DJ^LBTl&ST!zd773c!B_K;K0Q3Y zVQKw&zT>D&UH+UIYvIxv`+H!4969h&wEqR`J9y`iQL*%Q^rN|4W2sL|{nk*HIZBQe$8G-k4ZTbwkQ72bSutxNO*N7}sngPxvys zx=8o?Q-s&M|228-fCf*mdU46%Dr_!&iUq0`CjAuRb8bhN?{~sC)*b7ZSBM&DDQdcw z<~*-4>m*AGXl!|kIs6;&WFO=q0>i3+KB}!w+BixaDg~<%V;IJr;l4V#ODkqxmDp7a zpXRAPs;Wsb3PX!l6s!pyz8s36wrHTUzd1_rpZK6lxAC9rKONqY-W39~e$*P$hS7br zs6j3`5rYgkX!6J76`G!kQO=cyiLM#pQ{C*=`@Tvv^}uhNE8PMlU=aMu=GO3hzox18 z3}yhlsY!j|v~3W?g;LlL(4AbD=_FnDaRo)RKYcLgsynpSC<0Q>1rc~NB17o+4y4zP z9!NKbRfJ<#pLuBB3=#W8XNyV$11yo`H%_U@-45Qj%83@%dS_A;cjl(svG3Bcg3cOCT+J7Dym{FBKDPD*6MTK}{S5f9@btsNJfp2+V_^AA_Jd6x;a>X z&51Z(S83}uMworVg@g&U7MTIl`H8?D$5kgOW`AJm^hVFp*Oqunzuw}D-rBlkVvIE@ zi5c6#MK!Ap?Q89=G}dX4OenI24$=|1Aet8iPTZK?mE9fF_fh|0g3vca)jlg236FDu zR9EjGq`ZzKsf>5G>EB?q@mYJ%HQLpw~C=%I;-=$J{ke&RiW(I+-1$AK9YJ%d04&(@Hj0gnQtn#xgvgO$qkP&Feej5}zN zPWrN+!e^<%-4CYuII1^SHk`FGUPd_W1wEUUTerRRa*|o;w*C3jiJx;9W_`Fy^!JlO zeHT|QA$IcsBs>Zy3ldi+LmqnX(4jvhC0a&>xU{T_K1{2}?H{UAzTj9qN-P|6FJteK z|7O63{l`2~K&WTWr#<#Hkk5zsxRR{s#H>8X*o;|=uB(*hSBltbQ<@rtMdNWTrRCa( zDYE17I2CNP`QcfX3fg|tJ@ZvEtUs z%F+zOaf`VdvcDq|9@6D|=eT0cCc(%lm0a>QBq$~)pO8_ey448FZefFysA(!4RvtwwgT@l_X-NPFGb zHZ&D0hlhvX`vvZ~>B5I)==USSw3N^2Fudnx2qNJ7`TJKIk>Bs^I=#XS_|?t-%A|sm z6BbSpwFM7^2eKuyPNy`n61F|=hs7*9xcE@l>~yxkpXF!yiZ=ouXKLlaAyB`0dV3JM zE(UqxcIg_#Mp$l}s77!V|E&IOd+|&u+NVrz+Q#9pkB5Hsry&5+bynOzN0&g&Hm=$( zR=m3mI5Ctu5l9EzLV@{5uh&C5dZv12+_(;}FVF}?g8kK`i(u7~)Cb;NXjAnP>qDVP z;&ifDG0z8ibgrqU@bcqc$JD7eNkDd}gwy z_F*g({f`^@`$Au{j;^o!I^gqo4>aLq|9ZZ#ul=u!Q&U(j=mestHl!^P`c$D1Y)`IaWsmRcZ7UlX!L@qT-6pVg=B(IZW@}uz zf5=Vwr#>kd4eyDilUY#XgX&!Sf)u3-W*YKML-MS9r4b$B~suc5R*XZ#R=4UB|PcPv%Zx7;J8!eq1=|r;PuvD@tDaOoo zA-UZ6ua#DItX6NU%``x2FJRn<$*O0cIW?HbRZ|xZy^BA4}_d~O<|I4JJrdQe0 zr;IE{0bo0D#f_8Y5xu-3XiqOWT39G?DxHt|HUr! z>?$Osf?iWo!%UdKn133B_Q_J$;iVnqkuxtyj=vwyY0lT-*8R%eUL*F5eye0|!S1d}`RK74?5)oep$ z&6aScTJp==ZUsDlkTd+5u|m!NI=~r|)sG3gTh%7C`3L9mzK;mA?!w?%r{61S{`Uvb z-EH7Eot?eGP-Zk9Z~T!-j9xcEz$pN)Q;w%=JMl=bKrZK*+Bn4BfnYrhKC`-@5^&*H zk$XJM@4;%;#tV$N%xgGl%mw2g|vN$|OD1>y$vRr5Id zKJ5ix*j}fZ>&(VhuKWzl4g5&>W&z4Wph-Zx%2&i>U+wZvzOSAiB)>u1xUO0{``)Ft zS)I~EpvS&GFKTKS$!0h;G!~SSB@yTsaH-G_oMrM(v>hvCPiaI{&miKIMQjf)n{{j*( ziy8SRL&w#>hnl^CflODoqf(<;4PRzfJ_AQ18!Mb>`F>iUt z-OlaxxA&^@FBW?B-+GE$W4%ct%sOWCBs1zy*)f-Vj76G{^qk z{Rt0Hdl|K;V!minD^dz$J4NRP>7NYUx}Yk;QQQ9C9AIeQ90Cf|MuFNv>Euu#&jSDe zX#f0a`Tg}-Q&$JdqKtaIguPTD16+b3W1DIpjxz-B=-Y~SY+*`xo+PX=La%?xyDB&Y zEZzC$4RauR?Yi})*`u_ab?boDbs`C{>)QHaW3%B43HdV^*^@!3%9bbx-5=qP;@#wx zmQnzn)lQWM51DGfX6<)ud0qf&B}7!ZG{dR%AHMBZ4~O1+%{%_F4AWC;p9R=t)%_Zn zNO+V!v5QwuT~P+<+Q%QL_OWruGFTM-<%;QOiLgPpkE?LA8ogyJ=bdqd=K=tT$}e5c zpWH;bqpLzHR~=uLwkO-yX>lDMVzvvq>|}QHv)f}FK9ta`&+ed<=p7>M89t)NOochQfruE%P}XR(Y1a_$z-~_2uYK6j5CQmBA+(03R|f$yu1;W z8AUF`9JUvJFy_5doZxagPvX(Wd1elK5*(TJ9K5{jK3Qa9YC3ec$O{@#i61fuqQD?p zRJ>mwO%25b1lPifw-&};!k`JYAUf3yrN&(@dPE= zu+bBtX^)I4x%MrWkS`I*(77&e9}tq>7&dG=Gb7l*wB|S!*(EWB8UZrKtkO7dauKxh zl)6_c*J_h+ChUbU4Nei7Y=P{C_!SzROwAZ}grX?8hPb6P?9KpO*gQ7YF7v3mdf7-S zhPRILG0+^Q0vjA+DXMOCNh{ATo&#P;uE~4%_x6J;Z}0UaU)(CjBi!_p5oyaSbXmSQ zSL7|HGysYW{xlRd2%!bMO#ZN8wVNHe^=cDx50|R065^zKD}K6uCLME9PcOiy1Z)T6^38r)Pc63iBVbIU=(PN=47CsomhDYBU?64hFp? zPj1jY8BxHmC69<0rhE#nEK<*hE*z!2(4DVoHGCeY-^)jggmcnQ3#tV@5^Jz$w5gcX zOpT(!tu-YiwxQrLWk$g_FskUAE-hog2ewQt#e6e$SVp}mv0$oxO6_~N1}*oF*nj+B}w zlke>ghWrOK#OHO3Y;EOe0UUbIE-s=1kZd?%tct=~J3GE%31lTDZ3 zYXz<-$8mHoi|eo+q0jqjEO%$*WaFb%jwVTzE6HMIU{f880FHmA0nuIhcdTSZ0Fj!e z7uu*~mKo6cG3u1E{RW$|$N4>%{}p)qw|S=^TNc&$Y&@C_G2qhP^R5mpqEp4!jf3){ zTqG7+ih_SgT0Pn1e!$u#O(f+Ps(KXkiWPyTOwO(P;sHH2%0&f@j5Ypj*qrV)o}k(Q zL3<7^%4Dn;zh*SD(T~^|#r&A0_l`cjYh1HnsL)3>#2j@v1%4&IcUO09vv(Z>oB&Lt zzuO96i=o&2VMwG<%u#?7r~!XdsnR8*ukIS+&)-RLls$y#>Lv)Rglwp{eAcZGK_b9*u+^F2k=X zf6n0e^a*j>eyOAbH8jXFL$hYnK&kvXl&rJ{uDF&;TH9WWicZNQd>2iWdI@i;km@ON zhZ?Qu;bTo)qtt+>pf<-&yOv9tXgGt}(nY551=e$+H[sAhc;i^3h zIZohcKTmfhwPvh?6kW1DY#puy=&%>G@TB6 zjKoJreq4=HDNvY9I(KB8K&QaKcPniUNO=YxgE71jsaS z4a!d@mw{)LFJ2hso?JAl1wn=U{1H-0sFVID(h>q7X$*l^oS>n;%`mvuk*IhPSjkIj zWx(8L;C2iRp(BPmGIjx7H1b$5Sq<|o^z_BP+H}D`2cF*wAe?m?o6dHnbP)!X{zX0@ zgFhluQc_UjGM=h_o=7^mrGjE0L|1h6wfB#Rz+6h*H`Tm->MC*4Wg`6$v06QqCWp!>#~{DbCyzz(H+xR)7tf$D}LH^D=YB7 zF4evSYRlYpbBJDeuG$z2;h%8siuas~k@tf&zRRYGKoJV%o?m9zPW%d5hlVm1UzsPq z*`cq)x;K}_-p|0vN}oHPT$48PBGd*o^w)xwIH{UEOq4tgd`lOBVy@5cmACZesf%g% zjCr4wjTL~`are&NZHj^5XOt8P3ptR_DAm`LNynO0T>A))#aeCU8}?kof5iFtCKGlT z_1vHD$p4KyTw8t0FmoiAaVp?~-q{_J0@I0_=S9rRG7Vc`DV&&fKjbmY|0BtccVlMm z_j&@7lT&!!$G2i7{yQr;yNbMf-+T>bqL-x^@ugra{ zM>1=_?b>rZu7Zw@WHCyuCfo@+?nIU%lAif;Ly^XUqnxeuTF+D5jZ}db54Yk;LzqKe#mWP zZ!Ko!9lTGgbqWUg#=2E;tdh%G7EKfUJm(w_oZ`zfk0ps{opCfmgAUL056M3-R$%0t zoj1K4YNge6{v1(h1Xw>jIOHN~VR-ESp*dboyRAKrCym)xh@r-g$- zx;RY;&pXDGqEoOsVbIl)rBi4qy3rtqXH{A5v=dr7Rex`G*cM`BubK(i`OFn!_jw83 z3O{fc)f~E00mm4C@x&J-hn6TqE zHZ&k#jajYK9sTrsJ1g)1#PY8T)EV=KuveWT!b>m4R5Xn1c0W|26qYou)0G^t-x&8N zi`p!4$-TC-B}$=|Fz-)uFeMm#I_!fXrjmd{8Yu~FZAdjA*$2_zvUVMAWvCQwvv8tF zF(|-}D%;7N)7~AP8y`>C&ju-6#p#nvUHt)-{Th+7At+lz4nVWi=$(%|diZp8tx*_) z*y^ciL*3cSJ6Cpx%m(fd$$q#ARblE9PTAQ2f(71w?%R$2yu;v5TsZZVSd%2Ri(J7m~DGme3;Yq;xAN9i*A#J? z8y?+puJNh~jf%}{diC06SprTZol*MPlBKbcY&m@{}^EhV+r?_ zO8(J`c;Jy6|!>1)m=VD=|Fq?L}yD1_+ za0O<&f0IN`V5>)3byU(cwLToZFgZH$$CL#MDRU7ob?dM+5p*hGk3Ln@Safk#JHdpx ztS)lu+z*1%>w$CC_@V}Bv&b6$Lf~m|dHxJUqmIGP$?V**0@Z;}|K3V#=e)wW+6M2# z`E7lj#sr<@(^sL4+C55|A}lP(IP`r= zNu6J=#=V05_R_y?TgqmMb~}BHE>sGdV~x#z*~{>mmV>vm2k)A{X)p{nr#^Dk$<_CB zMccR)#hfg7IwUp#mYW1yM}|m7boTY4-Ro-zW40a%rz5m!>v(HS=_cse|5xb~zkO*U zAjQafZ(LQv1g#`JZ&O?*RGm8MFH)vv)U|kpuq4Ja`8D^Z(xR1Sn(Y&uDf7WeedURU z#zxTDmg9yGrsvl-ZxTU(Xc}@<|26uWl$Wo3&dQ?&zdrC1T>9ce=OhU!ujQqvKjcJ}_z&ZnPd-33(&eE&nCP8vc15~!@mF?4wd3TA*$6m0X~C}KlnSA>Ece96?<7`D z#v4QlK>k8J$!G~D|?9>?jQ`D)=!l?P$q$T5c z>{YNYBfrI16{nsvGxtaCZRi+igi2_9jw3u_d=?>h)5RGsnuBBNnegnpCr~8(rhz;N zc7Uu*);3V(K8UhL#i%kdCd|e9H98@jx69$1Om1$+QBLR+Dm0rn$b(eL2i(>akHs6W z6S|lTy~(KuQ-f^>`t*^elJH)#o@Q~p>6Z~OEI^-^)lVpKuUFQJ;f%< z3=Qp*A_=@8&w6D+#+IfW>-YI(vMcE5=os@8vf(3EgAW1U2Y0at zV!_m_;L~d8r^cIVd;-meQ-M9K$Et!!9^hq=SO#^c8Koc%Sorom_2>q^FG2~uhyX&Zj1!~F(&XW?Z zMFsjpLxB_Z1h5b08e^LXsbh0i_}MI16rLA~~?c zKkDA?3n$+Fvs0tlham^MYrVY19;s@zZS`g*mrP0p_pjxF$RHGzDy3rW5@Pu{j zb@>RvZ3b;S4rsMqVFaFlix(2Ep>e(mUiAlG;@Bi?$nHp(4_Hyg@CaF}Qk59fJf} zs0{Fv6S|ixtF+W~ZZd@&pQQ6~wlRPoWhNz3I=a*nGL@|vKN=tj;GN)|7R8;O9-H;M zoiQ?gEoFH_DB*8UkxjXdix;GkWxyuUx)uE3)Xq_loM)q~Md&WR^G7j`fL+hc`9+VhVlJN{H)vw8$qXQ$T(obcvNmd28f2B!l-hWKz{~^f?-<9(Wg7uB)wr?`#3RC#a|sgoGXN{J2kGH zd}LWTo5Pq(lCR70kaH)i9>6LTozrGV-DBlNPA4Qe*FH&Xj_6C+eJcKbq8+#84x8B? z9`Rxjq3WGYlk*=UA578-Q%(*rW)7p2L$m81E=Y4#d z>;16;aR1A1#wpX`eNPF*13r6wb)Eq0l<>N?$H&LOE%XWSo~a?>z>MuR)6qa8{@Xp> z9GIed3;AIQ>@~Hn7>YqFv3yLk&D~r82?PI?$nrne@{j&Al>Mz#lm3r`WyVja^}`_j zJ+Hlp`qYgVdaux3C!K|BhlFz@TX@{$;qWsl(r@3BZ!ht0+!-Iwsv{QetB26Z6>9qC z{q5T6F9lvJy=;M}e_L|Lu;C$s>)4RRf>%8fl??sQ8~roOF_toDgRJ3V3=;u*H_cLr zcWUn*%~A*pCXp#LLol)O!e!)=yRy{a=%uCThBQmGVQ!!lJs}1*F_?&^5R%~o(=zaV zCYr<85Ue(;ygOhKuBY_&R+fuiFXTl(Oc3X1gHyu_0D(xmqDrxmy@8MJSzbS(t^Q$c z_Fefz{GyDn(sGb&s0Y0g_-hYBnX%edFON&n0W>Y9)65SgmB857^0fFS8RVv~DF4}$ zW(Fv0FCkMsrD>ob5 zCl}!78I5?C#A4+64ed=-=iK0CY04&plylOS(_@^}?;K9YZP>-(ORP(l^C02#&YB_# zN!s)(QPfb4qNZwomyo9&r z*X#Q7)n|m1ZPqz6mq*FBc6M*^X!s-2%EaKn@_z8|4waG_WfHu&Kj-hnfOeKlQUUKc zWNqUc89XIfRf;cYGc}AamOd?r6%ShE+_zW7I2hE5?pU1_BPut|k9I&-6TNl&#^riz z3^3OUE9D;(eIEo)YxTcrAQJGZt9;MR%zU0*ZM1*?yuPXYvgG;4W917Zj4RDD z&=b&2vBd`Y83!F59f8b%+TttX5^zH@G7z| z%oE!-c=}?OJ7_6@As9jd#y;cdP_}*uJyU}j2N#q@!!gP|YCcHK?L$W9R94b}t0awC zkizm_2Pa$Yzf0-I*5%ms#B~HHn0q#SB`kI(q9CZ{y!;2OIj^eR^~5pCg>tm0@9R+I zTGFslrViEC3-a0uprzm800So)&ZBRA?YqCZ&Klc!)Hogfu~g)Qo6-<U#OVki&jcY)q{fI*A*JV}dL+0BhtZ8Zsmu(s?cVMMB z5gVQV!L~z^IXEJAblwtmx?Dkimw#JCk&P)e^(~w76E0m)aI zbxi_yE47-{Rp~mmz%lWl`eTbRCMITjX67}v`!?;bX7!)BkLfuhP%*P~bKuqM+@H&F z`*6)N|LAOUE)em78QoIW#wg1-+@OKDc z%oUnz1dAD;VNlHBpCl_+B%y$ZB-L27b99q=I6kz}Mb9kHYe5nJb1HlL~5&4RXDw2$)nLX(=yZC84Rb%tMFeAzUQ7ks zB@g7(lTbCZw_lP#-)?d$`xGz=90VOXZ>K{L;yOdU{IYwG*l^ih+nKxjxlIJ~>}{l- zC#?y|idAkwm3*K?amXoTMX72&dQlc{<&mbt+`Pm6M6B2k2>V)DoidLFgI84)h0TqG z0r0U&&f*5@RM{_8B=9}5;FXsgZ|W2F-XNVHWYC)uqWXt>sXm z?zec7$tCoD%AjeNEI_O6mXt8~H%Fqu){>9X?95@nmo}hUizdX=(17_l%U?=>>75hL zUxl=ZX2eDhX>H_0NTsQ4Oh9tSqF|HF0}A49Q)k7MLxmLaH@nZt-i0AUYtlm(8)G(q2zu zd;AAx8jBews^1Kdfo)#~;!>m*5xBfs&5}!zv%xZ>fl3Kw_y6PQtfJyrw;+5VxVuAe zcXzkJ-GjTkOYq?C?!kk*yIYU|!3oZw!Qpo98^fBl=3%D0_g}l}D=X)X#^EFqBhGIy zDljJ4b%|?90}j~kslx{m4VycU`LSMk2D@?}$lMrHa8f2jxOD{Yk8_5*6#v?wHKf&% zsBkVO3tv)9%!V#9O2eA(Xjo6ECY zU3BccMNCDRe4np3DiiM*_6_JnYo-LL!CQ?@>QUZ&8>R#$Yn&&$yp70Z_oswYg2VxJ z79eY-Q^_Qd90D^+ss*TT`v~ZH+dKc&q*>wy*s|*D-TrR!>k^kRwh2)&1Wzy>~E?BKUXB3;-A>XjMyPm-`=-eBRrQ{QL)4xur$qszqy6bCLZ= zLY9hdtD8=eo=1HV)f@GkkC2RnTw(n3+tP}k(5dXkqhfd}Z&c}*rdL7%nORz2RpHF! z^ysRZ#J5i^eF}oFTt=sfZTj|gPCJsL54|5kf#8S0jDb@oN92g}EM-}4Uw;7N^xC2v z5aAWadB@|NB~i8cKvhOZQ5(Qzra?N25^{B@9b|X)- zEua(Zrw|Q#Nk~!9(xb-k+L?$C$kP2{rstSCLomKsuod9JJT=fEtXQLs;xjVSSy zHjl!;z+w+Hj**r0J(d$Rfw3KtEUSF+ImOH4N#q&hU55yUKx~OofHg1nmyMy7AvCx~ z1Kbw(71^qR-1)yv6FfUh7`N-~s`$d{J-wPlnV<@4$o2Q9^gLCx;@!wmJH2hFK$6<; zWnQn(NN!XP#=a=u5En=p;lUqMiSvE}lG5Ves)D}A>9zkx+CZGoe zcuvKIhlek`zP_>;_4+@p7~T1Ne74whn=g-h`uQBkRT}=+V0Lmcdd^b5pyN93RhYsd ztRgW@&ZJnPTyb2q-&)YPY1_=r4bz4xukf0(K^9uiHW7oOJF^ng=9B53&0AWlj<)F} zTR0algqgp`46K8cal~D)feqt+V<{&AQvmQmm6^=@YmntIL^#k0Eg*BYoL5&+>fm zX&Y$hE#o@AX-sfbIS7a?X8)2Fo6oS_lcT=68E~GIcD@jAQS@G`#X!i@H8|1Dz425s z(g!G8UFkTyF6X>fcZ3E;`c@_;`QhPIz(S(k!!B*;HU|6B5DtR{OX$MvW(3NGn`yYD)fF6m3vwQlzWcbkz+0gg&ygvdwiG9oY z&21ZqUMzFUL7x>Yt7*1r5+P71&HghL)QxA$?*3i&+cCu)KPRWR^0(epf&a;rfnZQP zr=raqU!y^TU+<9L_C3_pR18)|T?*y=WixFgh5esZRVTAh4L_VoQ{Ed`oMb31-=mp5 zzYLca$!O085jnt+t|0YHSy?2lnnVY~y8ucJlax<1yawNbvc^pXlHlC9?EQVW_U))k zh0o26R(i0qEt3$ZpZ$7pe^u_3UPk5XRyqs!r>e2hk6PP7qGlKy;7re4Tnk0#r24?eNv=2Ai5Urop-O7M4yg&fqTi$TuWqI=>Xe{;Ck!omZW+Gxi}vGj9%-MCa_O_ z@39sMETUMdcGiOVTGL+M_K-5>ahBr4g)4SfzjwB;S0K&yHUjI2dDJwtskD*wINpdv z`eV{B;?MT$%=WG~==g6UV zIc>3Gc_k8&ejxoe@iSABKa5sLP|asj&jvv`Xz*ktC6#GJ^46UGGn~)KcUg|>;?w>bm#yMOM6a=FKV$u zhmX#TtY$K*yqn`{SZ3$n&CR*Y7|m?(5N2fYB2r^7G8f|$5Ahr#k)ZKRCQ3==u`Fz4 z!%MNU-B!1DCaih~FJ{gdtwP=guPFQ;uCHO0$zO!l5iqO-gQLRLsADmucA>acg)wuy z)2G7Y z?E3f{!HmMP5Att=!{^{$6xt(OhhfRgXe788vh%*HAKnj!d@>P4l7l+7{aG1hIkEFY zys&I_pDoHbQ9qhOxvTDiHu6Z`?%+6Z0^oN$tqI(jmFu+nE z76`|G_~9Ama@MR)>t0|0=gJ^N7e7uLXZ#H^DMdDl%8a-5yFCvfg@lz$lJ=~BfgKMF zqpnOjr>zZcBubv>@AD=>7y;(-*xMY_y*cGwDN8zsJ>}IkE`WQ(`km-wKzZaVpU>gsmO+* zw$_xS9W}2rp;8QTT zB{});6Z*g3794eLzcB;LGa#}W!Kqj++_{zjtC8&tDVAOqJ4UjVQ3gNaElDoKNni%j zRMW=GTa6^drDCT{%?{SZMrsstw$mElrW=#u&OQoUT@>JvYPBG@5|E>^OddK@7Yd^v>Sh^!%dgEk>2+c1aHZoNW+@1t8Z)VRAYoUY;o=R}3_hCqK`r8S_2~zr{YO(Gh z8)zS|_#|$7%IHNt+Vz?*`bRGrj;?NkNMpp9p z!BumFUmP~vgZ2|W4AIIXLI&cu(CChHlmu2)^12Oc393RtB+Uq47ms?bEu-Ht_Naz6 zZm}s)DW&F8pgp3KwZd3!3(naT5qMZ}`u<1J-*geP?6U$jRooRU2D>*iT&c(N4DyXN zQ)UUmqX|KKW7ZyBv{^gJP!yC(bh#?LbQ6uap4lm7bT+3ZNpi9*usf+T2DDMJFY$ z!w0I;Urz1edh@Pt=|CWZkS6#+YT2AZgHwDVP@?!#Va~LKRJbzuF$L~{g0!>}(!ppv zCSRKu#p~$6sC`&KID6GMq`TaOIQvEA95w-@V22EpNj^RK{ChtC1A)Ka_r0$gbhwt; zror7{ew^-&#E6`mb7r-ZLPE7>xly_kfqf8 z3VL=mo^Y5%-y0=27BQ1$V_eEH9%Hj+t@~@SbdH+(LCI`u=TE1y>4_E&tr7a&-p|mE zzuTiylarWuo97RKV&Go>rWG2HfR4aTOIaadXd>QGj!L+PtiUhu?!56KRLzV;jKSBz zuvnwv1s;wjWLg=`zXg|M=whskQB|-?(f>6ya7i}46D_m4&&V157d=kR9`y1pE&tTN z{M~WL6Bk-xA7*F!6Wn2VT-=zHU*~_?=*_7F>}9;(SEPA7TcslNNYRiM*lk*?kC(R< zKHw-H{$H6&K%z{(D4;Kkd*uOb^2bRh2(H*IzB{df>$oJ@;_v155MaFVdGE;iuEEwC zG1KA!2sCSFqWYMepMF#R3FwsLORFru&Qm-pgwI&7gnu!lXKE39FdcO{^Was%$dY4c zw}%|mbKk8V6xTudEIf@#Z+3C#Mmd5;v-4zCVBz=?7~@9))`S;F|wq4GM9qt77j& z>%SOw1nv9hcp9(M`3O*mlU27IlxHCjKDgdlN%Z4@!H*D__`bGSs$uYsO&rLub}lYS zoT@+zAX48oVc!6-4^=5>^CaST$&CCQh*ySnp@bMlp-d$K`Kjo-6a%sVIRV?25Ux=2 zN7}3+0*pM*$#+=>dOjRwltXcrR zE71;~9Qk(Lm$n=HzfgL5^vfMEagZ2$WkC5@$<&G z6wnN|BdNY6KUw(0^ReZ>?aq}_sT~cY!P{Hz!?J|bZAG+nx2A;9JVa$_We~>UX=)Ub zXb<6TjFvDrHfgn#FGB}aZtp84%hUAvGOv<#%Z54omT-e842_YIv9QGwAVUE$ZWDzG zZ^1v4X$=0mh(=Xu4Nw8zvj8{2o7T>lO}Rec5@3`#gDZ*6n|(N#c88g2D_o z)rgH0q2)Xjib8si;wJfW>nqaeD!s5NiBQ5~nWfd&y$yuS7-HDao zqvDfBOWOhVt=Xls2h}_rQ2OF3MYL(?Ds%p>UwvNryij_$ab4ckmRY3t)fED zf-zeW7U2>J2+fhdb4yg!q@xvqCqDI8v<+XCK*(pRPU5auMN;EoWDVx4Ke$1BKVIaM z(HlL(;`q8Nv}n{8KYu>!rbH|_-t};K^n|FrFiYMTs~R-ETF^26vK5e$@HP&LIMf_< z#5|#N$O^_UlAJd6sj;+Wf|I~t?<`Rxm5gWov|e{m0`|+T4>;_)bHbNISmlN_*d}sw zZwO~@q2Z^CCUq$q7f)vzEb?T3O=Ab6xc&5Z#G!V0fFapzhUP0@AmMea_Kq*?mWAZO3(Z-m5JZD!XUXxh{^)6eK{ z_$fqq;WGxvNQL)ejlhu%%KxJbe7(t`td8>Bj4@V>MPz&AfBZYa^=`)y@9z(Iyh_^e zBD6&?F1yB1$cNcpH}r-!&41TXT-c=X4Yt;V%aD@JFcwnJ`;c_rX_9CuO8Yg^ZvxQz z%XTB4w7$35+C2pVl+K=w*bjaeE@J=jS>8K*$IuchtJk0{yCO)8fHF$M3O6?cxItkVV=1d^+8_9}nt>lZv5473ZvqbGf{PLG1zKck~sJ z@3&mFY99&1+p33SCBoqKaG;byNz>J&WDc7qr@qfOtP>jNmKtHfE${s-Nw|63PtaBh zVUb*(m#I&@s9uy?Sx=Fx{5qe6U9$=eA*Kz@StN)Lc_W*unw0FIU+po^YF9fFHagsr z6T&un6u`wukUshl_BWiG>%H}C4Q`E^-kKRQg+_!EG7$=jY2*UC{ba8GvI9TzhuaQL z*rY^wO^6UI2#Ib4O2X!DAnYnI#bX_Ieegj_`kU(koAU;aw>3C3d#H{75{f5|n+Ajd z6yaQWcCG7YWMt^->r2pyRWI+Y241W_UTnWR0L$H~d@vBw+T7gqF`J(I((b%_@%cO` zct1a+^?GHc3lX>BE`oDLCo^ZvCRF~*ay3;5+48#g@5TA6PA4!zmnW0U*`Nm{Y24~C z|I50aK>%3u@9YfQ8o`e>hoX!BDm26U{>J49PZ53M!CZ^W#TGWB@^?0i9nm>ryEDOz z!e`v)Tz?`+Xhlv=`cIjhJ)3XLyIqQpB&7w1VYC1-WBY(`+c!Dx-eY}I{TVFao*1bl zBu9kYq6OmzrF+l_k6i4jY-&pFOkXf94EU#{ZYh9xaoJE?*HQujy zG2-wlYx5ssynnvlB2ELB+n0DD?=bDU-=b+K6jb!`9an#(06Lk@w?nB%l8yYv@2B!sKZ1E;|SVfEP5Zumb{^gQFt%r1yElX?nR$ zfYAk7e^1`Rv=yS|3&gd6rcQ_2G+KC@2ed;cfevw#wN~fbcB-79fkwvNi@yKu#`fOm zh5mO+F*xNrx7SA(65lJR5o^)vpRxCuD+V#w&apG>$~b#OqaY-H(Kbk;C3TpWm+|PT z$(^c6)t`)&bG>z$BLU=hzPj45MeI<%$h2olx^E|vQwL|4=ROziabBlf*(mRq{E()W zou>3dTs6q3`57^6 z2;J1OM)=|Js>OQ6HE2+*G`B9#y0<4x6qg-${N{v^aL?Xwz)kH{*;K%^g~=Hr);G5D z$G6>z^9A+%$V}w9WSWJUG_@+-B5e(Rg0cc*k&-G2K1wc;UhEqvv!bBr&-?CMgd9h6 zaNu>vml!m9*ZpY&ea%BIe`N#2nW_y*sdXuG@&#U)D&vE?w8cuc23VEtifbL3Q%7;* znpze42Y0&g`bq8itIH3U%TyT0M<=6gfl>LSW_L8Po2)!4ACT2}-%Yzqb`}Y})`w}HWqP|Y?Dh*C-w($_I$Oc3m>3! z`Z{kC%C%J_ANuKx#~@r0TCFtJPoI(H!gMM2*D2}(UAN~G^6VEui8?4B(_b3^GGpK^ zcdbXX!6%0i-TjZ6oVuxf2+aoWcjI=9SH;{N!mpUSLt(|IP?)KM5*(U8-&KT{_RJ9F z$tBUY97V~^YKk+r|R5BDRBQrZsgX1?Pi3$lhL3)`Nz zJB;b{&)@1M)4*q)-qMm9@nP=v)QiJ7mCycZtzF^SS z+VO4phj+NVw&7t}b-l~={SsyQ7>)3)uHHp%?K>XFO-*{%Z6c}-vABNe_1^(dT*DxZ zd5y$K2wOfhmzvNN{!#Oi0q3d>(t4_zrUjy{*3=`1Bd-fb6yfA))_?zf{SPE5rXiB1 z2KbUP7QEw!B2l$t(?s6)MP>nzAxrlaq@A7J>+a`wGJvSMKN?4fRkZ5JOLQ}c$m0C| z>H}33B1OXI8dmU>Rj``@7#nqfZemuvA5PoCH-h!t&5mvQAmrg`3&8@*IlY=YMJsXe z23_Bn3(Xkm7DqY(jMfGm+iocrG5@9~`pJ0t3E8)KH3#Vtye#uggD;1`D$saMzankW zHJ=?UC;Ym@_O}$Hh0VM!Pd!#XBC9`9O@~$jyQX>Np(Okxk+iwqS>UA7OYujBmeNc? z)vn=e^yKAw;sg&F{IU5As|^*lz*SzAZIdtBes+4hK<|Sg>5nGJF265?a>|PhGBV4P zwET<=-zqaI&`Xt!CF=hevrv>e+$J zi8p!j{2?^w@jh0NHea*twIIMB-P@H(e7k;2N%_ap|Iy~&1lPMq{Cp}@#FsE(GD|AN zoQS*rCeO3-i=I)xOyYn^N$@W%*ze;dKp;Aqgb&?hU#U6G6L5a0lWt&q)<&g^gbYCTed565Iv0=Vau!0kbffkRe@N?ea2- zPLTNkaeYZ`Xa+TsCg)C}faaj59!A=4jHVB1+1X7Wh5R9RG}|GeP((5}+0rT68FfBH zWYvwR+X70MVzOU9)RQj)M{9apPS9;$O9bQ9qJg>h@^{zM`fBgs_FGrbD#w3EeE*Nd z18%YsKzez<;px4OyLGxn`-mX{j#o2YxJt-vb(ouCTAtwYlr|Nm(1jvj3K!{@uL!W> zMU7YjBq{VGyF;+Lf^PX|Z7_^2M|g39oX2V4zUc`GROi#8NPLvm#L=P@L0(3(t|B5e zpenjXN*m93?(Z=^ATl+#hF1`C!_?gC_#Qcre!V6Pg`@hxe_PZ*Hg0=Fmz64un5UJ4 zJJ&N9!_xZ=3m!p2hF(eN70&PB(n19(QK@8+o8p34)I{)go<|K9Z$k3Vga9|pK0G~K zeAy=lDF@QL3R_r}S<_F$YLPj@$>=e%Z1TuP-_E5LSEjWw3Abttzj`;RZGT4}Tyq~K zr}D(a(R{n~Ka3hxG~!@huHUfXJt>k$fD8taUXzMLAtlEa|18b(@x0g?0;E`|iu%nx zPbXO$rrM6!96O=jyE2yB?7g(^o@D1=o zT+syG&=juwAMbxQ^Ks!ZyWm2Bo}98wO;;n(&`YKbPll{8hc>!9`m}ZdCD5c(xtEEe zV9B@X~10oF(5z?+nR&WlDy zKCeq6qmSoRpj*p|7x-IWUtczfl>lYpCKt$E*Xp-DmWVu4{Fk1d{`j*(cqHNDG2!;O zBw^99je&M4j*zD+FqoRr)fZ*2Lm>skxjKz%Drr=lMVa9KMxwY=6+NC8KN1Z>c(O#z zOW9&ZiIixRyxu%H>TrD{U|0SA(?oG1KkYXWuk%;2zPT;I`b_?E@m-1?xhR@zB?W;Td)bo~_rx$ru>sPAu2U1%P` z0v2!JbmvLPCMU`R4DlZTUcqfnXsrHqvi|CBNx7-L9e_j;01Nr%jf;he6q=yxYNNMP z?X4n-R~FAr^?7Qj&kg0na*=93+SjBILI;h{*PN^;wZ(JcbqpaoFs9qoVmXKenhVBA z_y~ip=DN8m9#kRdCl5Y;eR2- zuw&xB`CSl1Hg}ZH>gSCA48`~&vGASzww3yV_YM;pRf#`56vgouQg%kQ|%i$d6 z=r>gUWLuAwQhKN}(jjK_aHt_?B<8ULw z2@X7-(`qwcq35yakdOG5IdJg{@79ddMD=3OAyXoZ87BUdNk`O*pt5wsEMgxH#_nxu z7OfXKXibcjMH9b%J=6AnZ($(fzoNw_cC|@dF%F~Qq060%;ID8FJO7@7SF3A{_*^1f z$;|tM73lN!vz|lrtwxa?%^?hMM2S4^L5y7(=Nz($Yh|TzCk={1iRkmxEO8lseVHVQ zBh8~v;py4sEOK2|KGCJGy_)vBcAE7ai%ulF596JEAV8Q~#K-SSL;;1k8zOOHhrD~o zw1#NOlOc@7xg>lc-yJri(V-iC9a~~4dWL?Bv3mMj@CvS}-P#iZf1pJw=aw$pQfN6X zVT4f5Wo9IIE69e4;>V?-WE=tYl5jQdff@3pR^piRjd7E*9u+^`7N+mC;$gU^nJm3q zRxtP0Y_he4@)~R{Vk_OZI)B#%#f1zQ?|{k5=v}~oHK=~o4A6%EX)(Z}YzS3k`bhYJ z&(M1p`robh3Csfk!~XfK<&B;(GMK+)AIJE;KOH^29*N8;zx`Cc-U&r5PTl)wUj&e1 z#;kb2qQ7cCr#*6TU&%f>2)*P~q@mUIlEVDQEP>x~LnM{&m|RrE^tampE^&m(Ma13* z8HxPYzioh2ga9cqrS~WJfkbFnj=rc3wB@kGA`{C70LWU0(-wy=!X&gSMT0jK7ol@b6%;*pM_SE{OpqN~ix<>eQZm7ewEK5`B^>m_t(m6lI11aJHgX5riKCy< zU-~Xjd`+w08#3*YOse{V#2e+gF%iD98rG{trHTa&=bRDR%gqg+l$w?0G>Tx4zoTg* zu|-KvhAk2_G7j;@u?C}ONK@6~r=qX=rgB~%=gZ;rD`L3J2={$H?@12hI!GOY4@ZvD zu4aVX#cv4jw~cc~2otx8?&a?8=p9YFS8SDorY|$Xdme%qD!vm+UEJV9ci#zfPD81{ ziU#6Bvjp`%aBKdJ%#>m18jbL?N>CGZ>zHrgBvvWOT`z`grZJQJ5>p)S9lSlpp@$% z<)}(+La-n;=`kwT=%xM7a<&hE>H$V(#zE(lkus_H&qAYhvx0;?^^I%yV;m$2Hf<8y zd7iw7^pl%aP$Z3_Glq4#X!z*#u{gU~-%78*87L;?S+b;`3b_*(k~knt)lYhNiLvOa zR`##*S`qHd?>jra$i(UQ8>H}l#uv|Dd${_Q+(>c+PPJK$IjD$T*02OZbVuW8l*-FPGF=|*9E$7Z|=(GEg ziY825F;af^P&r2{=Jdqw6F<2@rDxpPI~SyrVcE;70ZoyCX?Q&n&uvW1Eh173+hO}K zcE}g`sZCmhRJq6gNa5Ixo(vJcODe;FTY~x?-v}$Ce?aDdypPkPz+g;N;o$*KSan-D zJ3J(q*jj3bLw9`#b7s0+vIdw~ZCuY$8a$|`KP7dpk}`D&mgfhS7^}|HSYwJTFx`4~ zlZ`b$9Uba7kow~23p=)1nMLyhw-%gZoF-5L10f^a; zMkfnhB@L|sOlHOY_^p$#wI}{F&rO>sF^({4E5D2drVpT7uE9y1m38J~3JSE%3~aO4 zqv3A`Y9|! zR3Wc0_xug{mrqb^3Op1nHc|&4EE>J~gWz(Vbo^+`^j{$pt)QctK;XdLipIu_H zr@XCeOlLBBy;M!Vre9jLr(+el8}9d20ioC4(@!-HkSK#qb~NIJLxHwoLpsL^qnqt5 za;oV_GE{zATJ8R3d2uZsf=?3B_7-Hcn3w_e9y=?t<5~lqebj6&Ynuf*k`(#$fiL`@ zwh;+JnaOA#9Q$xA?(rAuGkYUkipJjU$tvG6jd2TAi`H!dl!!Kd-bL$;IlTD%d;s^r zIHJ*)$IbnsJFcJQ5GVh%NBh2|(2DUcm?ndIwz^njAl<6xX`FU1S~~I2{kuW^Q25>9 z|15Fw`qgSZ^!GdEkJNu(F~Fiy!GZ={YX4Et9kK}DPIHJLt!)*#XrJX%PQc2Se9V=I z6=+fGSTm6L-9hpN3xl)JKFJQ^!kLGtRzkfRqUwv!f4dOs7c;Z@#m<;Zh zMVFKrz|45eXNeqwT3CS#YMwPw+imKlw>F_u{$yWv$ImvXsi&X>ioMxJH$hx{uH`?C z*D#DmGh}g$o)n?&^zQDxDma8%b{TPNERGPtVE;+}ja_EQ*d>2@1n8aHQHw$@uyf8- z70WMe-k>`vEOcUe8~CPOZz$f(9(!Z!GPKz6vr*9d2I6l$<9ylh8oC;7>5GMInMFW=Y;}`pSI@AzazH`cpeVypl28|4r&G9R6|HpmN3N?R$?Otz zq)ZpB3tFrv{ijdXNX5i6_?mK#9NCyctdkFfoS*(a@{51{%oQh@X%;f7VXn;~XUq|m z0y1U*BC?|Nbj@D;dNAx%vU!%3&C|H)@x)DDy;F6i;dlv2`{)Q$;V~I}L4O!6`sVV( zeYF0bm*ZiZ-y+)of>?t}q0ZFU;{=?Ubj_%a7Rg2Ca#5m{shsx04RX@5fpBbmxTdC# z97AP2J&%(ZxSy(-Pa(NhwQQ2G*Y= zyb2)&4gl-o<6VWw49nXh%eD6Vq|vU~=NG(xsEvQM$Ix$q1;92uKk$_9{guH8SgSrl z{(q5O|9)D31AhPAw+~de0wzj;wm8vP6A}v>pc}2BC2S`;R~2UrE;s zXb+*^4QZvqgkT&h(3*AihX(RU6Sbq}BlR*G$X^V9OSprB2kxG`QaFCqG;(jpem&?5mzNZ9)-om85k`;+%q0VY^!IZ9k30Z zk5JC{PE3hGWb8=R;z3mEB%M&7EWU%oq)RGC7v$XWxDHGppYO%pSa#;!UH)Mj(j53R zxZ#_U`z8nBgV>T!@3tM!b#75|(1Mz2Db(R1O>T=U4edQzrJgb68NJCAy3hwNe zeM_Sy+Zne22jr~Q$9Wl%LD-;!A;3CD%Y+i6fsqzL(#lDpC>Sjo{#6GqI2jcFh&~&> zqtCfheBEt!$Ug152JYEu$%uP!Q%)O4D{7=EtEw7aevXAt8tn{ER7epPV-`;~qSyTu zOJKui)5nnHY%03aE~_fR?hLnMAoZWt+pt}yrmTwUSwDKq{9eE&-1n+8zGZFdy-xd@ z;6M3`lENcq8fp6Av^J!cdN=;- zQ(kJ8BjV1=jAH|wxPaAnMX&Bl*C_aH3w)jMz990?`){s039z#^?SifW!7<1i?6r(K z_IvbqXsh5y*0uE=2Ydu4HlwF23E%a9eqJoe@G|6(l43hAv!$fU7~4yxP?U7xo_I;7 z;l%=d!{LY-_^~p0XHgQdT_amxx&+x|bk)+sY3|@=s%NchT&x|ckP;-5 zp&nBbAyw0bRT2?sFj;o&DI}8X8D-1HQz;ZF_4@HbWwCtnT~GF;-rq9%rcbo{WU{Sz z>F{tVQW>nxbhE&&<_`!_#Ad!iI7j-ahnbw%!t&;JYg*9tQCa!YqZL(J_;b8#-MCVZ z1@2oW-%N`GOByfQTq_D@>Pni=1+Vj(ig3G0*sf}?#RB0ox<;9n8dk!a-K1x z9Ar9`!GyhR0;NbxauelRW96XXox%MQM8$2SALQvVnYmIbUVw3c9-=H6cQfqu&eDG) z%b6`|p;AvSlbU!T5tv;FK%8!)R5884JM$NsEJ zwkni<3BRD81|VLE{jNvTWt1qS3B~;r{_E@vD@b}q7n@KL=(49xw5D1B&F*czl}=3= zY!m$G{8jNIdZUNL%1~fxnP<-JbpZBPj}=RfzK|p}D48gXGznfO4v}{|yn`S~9_E%m=J8{zgN3{Yp(H@94^&471wbHfgV zRnmrlj3a%2oaXR2yRJn``1b=COU(|N*?O1wrMnwUP>3!VTb0?Inx55s@U3TWZb0R< zu?l926QZ)+|DOG0JLp~6=j1Sh78tqaT6Cs&LcxK!G7lHzbn=U&#ddL4QY}TDj8n#b z;mWBRv3mn2ShG*V8YRUVnkxpPyzRco3gl3p(sY_`zL9bp8?|@SdI?Jf7joJe*Cp*8 z;c^E=d`}P3J10&21Ew-sGntH0o~1VrpI&N8LVQe!hs0fN%tAAq3@mi;RFWw*{m&QF z&kO7p=tsvT!+Iqmy1RC-Jj0bQ7>PV5pynZo~V-C1=%G# zrDfdB=QG6+vJ5)B&=r($^W5^9IKSgq!Ei$b29azXY8%@6nhy4vJV#`StEh9HLec(UrVCr9~%!gdJ^5$cE`Ddh!Zw@)YNVLSzeuU-MI>EZc3dcRS%cNjBe}?LrTFWpg>lmGe2$E zm$sJUu+%}4O+|W1y3I16QU!&`VUugFNwT`8=aX=t!DJ95OD=u3cYQyFe!nYoy4{?r zK(8#y5#u{<$dtudgi?=J=`?RhY51833l(LZf+YQ&Nzt@WHb=2c*!mik5HizvT7r8U zcemR=7#^D%9RIDeX)L;s`^e8S787qf;43x;g+DkSB|f@gwy*s97jyS$MKl%n4~LN% z!L3unVL|`aSe61o8u9Sy327zTe5b?qs0hW{nMj4?Nu%%6(j`^e^mC_L4+>#lzxZr= zl3$yX5n$|#mK{vR7<~D%|5}@qijxbI#EWpm>*}0k7Kz%h7QEp7iG~v;*b`DLI#tZH zKP1^uKt?Atwf9^EPoQx{&;tHwt9_cVIwv(s3K^D2N6+%GpJXwKS{{SF8iYNNgr>94 zk*nw+NEbHeD--QP#~_;$sia^z%dpRAkr>Gcd)cw{Y3 zm#;a)-X%V6;lG8MwM8n>R*`f1B7dXcnBMpj5fxTwErd_8IJd!1U*8`NhJrEa8@&QE zYrfzy-FS8!MfOBXR9$nA*ghTxV&K-o+OLBh^X4I{$t0f~_RoFz`K96WeoDbBCD7%L z2ZV3ocA+Oed+!9x_08~&0o#jl@cSj$b$=8YFuXA{0|;yJzU_K?PoI2sENI<7Aou8E zV?h$9v)>yN*L~^{+>UoM0zSUOaso;#Sx-?N4=V0<_TL-PpOc8$K>;fD?31qjF%<>- zKq%MGzUH^NHjRmxj?%f_feeXnVn8jDCK5UxC(H#A%=`*0WwA3J?iz|B(uvgllCJv0 zB4;3h(R+QMQ(Ywdqm{ZcSMM6TBk4Qim;~c%S=4kJ+tFaT){%Dxd0+B-r>{pljEtiMJ09 za*$}f9-x{fZQBe46V^GPyUO!8j4H4UK^7bbyz@3{`OT<}6%dS|uMxk|g5bu;Ny$?^D zdBsyE>Db9)^lAC_DAwE$=u|9?;>`eAq(`2QKVFY7K_B3207tu*TgHeO_7#9O?&ZGF ztkxTS1Z};TzdLW9wc9D{rytP_YzvO^-T;##B!~W0)~SNh_iU)6GFp>0@c#ICW7wfq z#!L~FX0%D4=%0AXldtGlo#w2kB{9kW3d#Ndq=<|HR$x$RNFr!v5lUMpS;J&1c`&x{ z3Z)@6nQ`j4l&`Voezmy2VL4k>SB*fC!H%NL)X@=zjLCw}JirVPSbVa;x$n&No9!W3 zAzFX3Ai8R~C-J3AAfm!*4HH(k+s6mWzSJHVqDoc6cOU&W)dFDpKu*4*U)q0uBN zYnJkk`q8+-(l^F=?(w&W)C^anPEi7bIQ7~VuDVrLt9Ht3cerlT46^P{7y`UuP|uQv znvRT(d}))Q`#D6u|C`53t;u&vX?<=UPAkKp(Dwwv=)5_X*&%7cnch{D=wFtlBFy+* z!;HN9{vWX%0E+{Ki0^B_JEU$lD|fal>&;#{_2Lu_uc}tLr!S}9G7*AgSe#Tjy>?WO zayQK^)AY~SEF{ZfnpIjvPGUJ4v(Cjne#dDthVS9Qvl#~;QLWf ze!*i@!OjfNwz-2tTrsVVzW)B*$&x?Ne_FD7cD-&_e=QSqC-W~w^3VegcpYSUh5Y9{ z($Uo=Q(WrLwFba8K(060A`84=r?ErscBGH5NfkhE{QIOO1()saDeM+!@F8~8kT|#g z-L4iNY0UqjYukc#Z4eYJN7YHY7G(UnR_oFuST0nK6xsP&hiq}QP`vyX%(F0VnMtYg zXuFP2^!b3=)1;GZWmcx>y2}>@%hj9*tJ_!b=&N^IM_@JB$(EnHUjLTJv#J$=EKw`u z2g$61)r{oV%P~V;Iy9~SCY!i3pzY4i3O}{a;{Uz!Vko=&uyS&LW#B4Amdqy$BX zsisGmjVU=F&!aaZqOG7|P4rWseTK*Q1?OxU#QPO7jib-~;>*4odMMzzx_rSo&DN!lSor=4dLv@lZ5w9K5F_n*TRH~(9BQ?HN;a@0PF4GdL4Urp zdA~yaNCav#_j7>`F8(~F&9nD5V9on_|B^rdIo^-J>vvDrh0>H>UlBY+tyYXiRlgEV z61fdlp-@MtlrO#!)k2vx)jJGea?I^;PA6LmL$9WU})ROqAeYadQ9qZA}4 zQB77{6ROopiN2zNn&(!9$KO*K$~0Mats=|{E0zU!MLWlUoo)+})L=#h&xVEEuN0vs z2wRY>A73$6`WrsYM~GTjeheDz(;Sv2*JKl6$RdG>LHN;1@9@}~t}BfyN@KCHA>HT@ z)0W3MX$8tf@y8_7=pwsw@2{782I$p@31S(z?EBH@y9JSKVt2n7gQrF3bO!i zEZtBupJn~2&zKVusypjPvN`03%QQEQ|1OB{^5+@G_m(=Nplbrx(QsGj^D`~j_hUa( zg67}BtV0EBJrPm4!zphxHN>+mlrTfV-ql5T_>HgE$zu~akoy8_b?l@d2D{eMgIVkM z9zfBrt*wpTWpC6Mem*XV9owhBdif_dS#$0I_Y|N!2=O0dtv=}O?EhZIIl)_`&u_;v zPv=%5Q!*mY3#S5J7waRVqcAJJzE`y9g{kCIjl^8E%q>~t}zng6nqV!zcsdg+)`N9^WmflH(9*(4C$O|+XkP?b6u?3TQVken2` zk`@J3)V-Ce&@CAi^JpGE>a_^`Dy5wG&vT!Kgm3U)7EcBDa}n5*1n>OvhBiG~xBkb`IYrmi zc44$>>@>C-voRYtwvEQN)!4S(*tTsnwoYvG-`{_ko0BolIAibkUF&(~Tw#*s4U~}Z ztBW3hpy!7jf`dbl6%SX9P=vG5>doMI)!v|CVjhMGQI7AenXWY)7FYRNWI7>x&Ca`d z+BNo99>A81+o{OuVPI=OV`s|ypca%kA|(Do2rYn+scdGqoJ5K5RS|NA60uw4uJGiu z#wbx{S9$8oj^t9+g$*AR}!re-t+ai$8E$gQvx3C z9MNXXZlUr^UM@k|&eV#X?=qCO25C`xy63l4PRay!*Yxq@C9}$7mv(H!A44q9$;KK? zBESfIYyZDIwA=kZHGn|$@Aa$Eqnw{!$7R^tP7BEXQBsR`EeB#Fzhy~^x}2`)vf!@B z42v0S5z}l>9)Lw8Z5xf{>dD*ObNPPorZ9Al%6qykYP(kbHXn~q++4-gI1Dm<-P+2> zk36!raoC*!CzAkheHLbtL3C0yRI!R(1}d;&BbMQ7b7n)vunfP9V9k?fh>&K34tFy^ z?jLt7+Fe(0bCDf?%5LO?ZOUaRM>!q#VUP*=B+HWwoJarRap2(1sNMqaKN87)=oo@x zd<1c{K{xIA^cjdt3*4LIve3bLt`}xk`c(@^Dm1*pp98Z&+2CO6c@hI>6_Rfo5M%0> ze?s*GEyev3NG#{g%tFL_ZK>Hp1!PHd)>$F{j;RJeDnr)BYQW=F3A=L|qt&74=gvI` z*oi3Uep4q9{sKHY33goZG9kYk|M|I1Lb#}Cp*JaxTD{ZU!aChgDRAE;By}~dWpKweMMJHq9LcG*qWil3d3upEeE^Qchp(*z%k>H3f~T0v_g<#K86ZA~ z-E!Doua7g{d^~!mBKtlSWOBQQtKybX{09WNQAw`kt0dcKnw8E0`IgypEVdrrhj|2>uKd{tXQB=8D_rxi~zR)2F9@vvzu# zxZbcancb<3Bsyq+=>kn@4qKY)f|8bW2VqCFsBwZfJYAWZf5a%F8YtP82lnCYNt6`! z82p%nwhy#mnt~WMQ@m>DM#f@!tm_SLu5Y=Q1#P~awMnuLYP!Yn8L$*)8*g2bn`LyV z)-PV)HCCWGkx=iG_2KX{qILf6ioY!L;q0EqF{EldI6SfTD4;LA4b;4Kzh0}{{?qonuD`tQsw{cW*uwMgbvrL_eT>b6#WqDT$gN6u zcOb?qOZk?Ko5let{S4RDC^XWJ4~o3s%7kX@-YLtar?_?j-T;@MAD6etAHDqB086S0 z#fC{C)p#Wb*U;w%paeg%8}Q@Xny<5&jn7vk6;TJ`@%Eu4DW=Mo+5UE^MM`R>3jbdNLn%Ry*AJ%k4(3j4TF-GfAWZqZKW0rSR z#8OpVUB|@S+t-KOPHHqfeNt3O=G<3eqA0kH7u;8RYB4^hAW+E zrtq>X%lR!se-;Z? zl_q#kfFfV9p|-Ge`zQ~?h2Hh3(v}Hov!!XQ9Ata7eX-GyL@YxNk>i#Tz`EBu8V>QV zG3K(`-(Pe{Ly~LWJ}gIxs!Wo81jX^+RJcQ9!1pi4k(Y?9gf4Mpn6zY?9=mZP{^meT zH!fEqu3;k7YoRzvN|{g2KQE(aGXUD{te<+iV{WJQ7OT|?ssd+n2)MLVmUA5Iw2D60 z%H1BdZwb2^b98QULrHo1M5`9dXv)rMps~UggWxC)BhdGi7+L?XzCD z`&Bb^$&f;=w1H?M%uZ0dXQwWtX(USG({rWjIFHUyEXe&A-_?J0FCbb39BzP}*nh>1 z|LpYtzd|2N>FxFtef;>(H#ZD$?aRhAE7lQ1`onZPd9qIF#E+y?m2O+1*nHm8>W`NS zFtb$SMJQm%HFP>hIl69C*i~&4DC4h(D4;PNU7ER_f^v)K(`LIo%*X)BjI}wJ6!X<+ zyu!r}tg1)5vDL_K$i1PR_`Em*tS<;s`w=2$q~et4r`Y9P3tpi4i`xa}$fUnCTR_{f zK|=5bY#^@g8Uqx#zj)?;UU8Q^I|D&~{{Cd8OhZ@+$~bttOYPk83_BD_<)w_MVjF4^ zL72r|^)a#id_~QmqlWddLD#gV+*>&nr-&It<}#^QNms^+EyFJlg5@L17+kSE%`^zU_2esFN+XDP1OZ^FjHgt4HvVQ=|hZb<)OWhfkH^ zhJMfH(W!OQEB4dnV&kvrGh^+rZV|!0hpoKc*;|1ERS#%iz1Xw7J~vQf=BpDFnemZm z37W_xsKWjKqa*;?#5Xw5bdD)2?tjQ+pg6+?6sQ%)w&D~&8ZUi6Umv}H`fy&@t|x^W zZTY=_HUm{+I#|mQmflgr5U%SU!DK9MP9=}x&1Z+^ex5bi-Q$d3!ECv62x5KZpsCx+ z;v8e|awGF;X8wDYPYmD#p_>T3t_peGwqtoc4-xu-{eQa*6YlGu;)TRtqi&r0CkM3J zZFbpf&9;u`Cm2<_okNpr94o7xP1DU}054h(;jin8tDC8I@sbsx8aO&z`nqB1cgI$t7F0B58Qh_UI@CRu4pb#-JE=kN?i z3Fi2u%cGNz!A0XA-PAtv^$4xShxR?nK&J00i^xh}HYBa?D*Mwn4#&d}w%8?#=1KL= zn@-hxI1Y3q!W3(`B!>_!$Dl*POXdqzBTYdU65eZ62Yf*gBC8A5h>9hdD^%kj2$jW(n&i0;;<-i5zLlIJ_8Rn?ZpPoYUrWpt+|U>p0}h|Id% z4dm4@mYv%-%2J5Ond8YyM}$e~$W`}%#XBw3h`8ttP&jowS)e8aB1Sj7AYgv8Kd$$| z+-$pb2tZ&d$Lqpl3%Rgd9ZJ$cY^S`%peBD<6&X?)J&!rSx2O>8ODPz@V$4f;`?A9F zHt&0fd~98rV2o3W%IoCDPvkY-t}c#YF(XVmBf6)kC+M^o4;O-q*D#-0T8-|hNHFtc z5fznoH}WA;(H97zc5o#&=5CAD)F0=nt2P-fpBWRjb-A-+;&t(0BxiV0?2l>S>hoFQ ztc6w)_)bD*4yz^+E{}|oy`4WG3=9JEY2++aEc)$!9P*?v)G`tJp^~8z^7|wPw5P0` zlMT5`GWhB}>C?kn4p1K(|3Ou`UuJ)lk?1TA|HF;qAJ%2+E7M;a!7X4P^*EbZ9|mD& zc-XW4?*etZ}yW9_GHuD+-eOu zHSr*HU+_8=WA0UBXWfvdvkhd_Pz$DoPF^U)EF7G@tuZXva^_lxb-{3}g}w*(kouRp zU^Y}u*F8w@{h!`}rQRzp@HbF?uWf1?0ydr!@o(ktuaBR4UAI}6SN+!kM6Q($IEIg> zbF=3S@{;w9jS{Ojcolsdo>HY$m4c(_l<2Js*c_rTA!978TzHy}FZV3#N9GIb_=YKt z=ZC9}mz^$$VlItqwtuO+8$Nrg^u*B~TeX^P$lG1ce*+^zRypcfEA>eMm9IU2Jj z@h|W*OEVlss&zF0vkZ1x|8gq18W9U02b8geVIHw+C+k}| z;ZnW8a+@qJr23W&vR`MMPPmD$X`!D-Em+J!ty$RNp4`idha_tw!()zSR;!UBfVx?iQi+Q%5$XgZh)-{$U)s3d4mD-ya! z@%)N<)P+kAYuj?OtVk-pGr`4YHujNd5y+mO+fmcwSW!umwET$IUNgI1E=~L`zde^} zWhvc0LkzjsaR6Q`600s1JjC7m1GqlclF34uu!^ln(sD*Q1rFV|sm*C<#r2njX1Vg= z5dSeM8(d=}gLw1wR-YGXO&8ft456dexIT;H5hm{}dQ&)NMHbA;MZnpFAXCrn7Ea@o%g*reO@m11)w)C!bgbk ztCf+@-n1hV&xO)F1^>+Fw~l5d(VWa{SK}mt6wP5m$t3?Gh|r5x#x@qnvecMG_PAhE zij46%^-hctLJv=C=G59DwtZT>bKrlLWI!qRYIq~F-Zd3glsRs{ZbJnKVoZoB*!}tM zhwpl+``=T~W~*!Rc)qw50BHN}*}ftEf0!-j`!bJ~zn)M`_E)aB%{OCZ=}8;yMk7aG z-SUqEDtx5!AM+4i9~-(kau`HOii#*Gva*O+OdZS$x{2$5{LiRrHoC?>=6)5;W3#eT z&?zqV${NZViWL5S%;F>?Lxyv9I#QI&>ElAq#TqNOE@cfL>*cihC9^PbyL_1`an3XT zC8JCxQ5v`An}7HC5$jh?q>7hc(}z*b`|~IULM>^ajFVDpm9v+BW@fsu=dye$N+q%= ztcTh0Bz3+B@F9m}U>dO(ToZ2^ImYqJ$k@AI)6&L?wx=h%bjQ z?KrfspmzuFC5Z$l|4~#`rE~@!jLS~5Z**VZW?IG`a5DKXJXr;7*?qS}V~LR%<)SFR z?&nhptj7&;DU0HV*PQ&QOeQH-cxA1iE4TJ(60fIol4FK2yLC8r@xI2cbDQF1sZz1H zgP%NHv4AnXQ97xy5YOm{vMj2&Gb#k>9eaChg9C|OX_?vFo?E&l4>65!wLXF--Q=2< z$SYMxst2kwV&(lb7XcQ&Yzr~9aKMWX>fzSeipx&=0M}jXlqkwZ$B@BvJp_~<)@V0{ zbwXrke-vS3D!z-n1SvL_#DOV%XW!A(1Tr9si4`kCZ>uQ!ExGqv z&Xa{CNex6n%o}+vE^^-MeTV+5+s-XqHUibhiRo!2^g(_upSLHy&l9z7kH?I|kUat% zSH#srEG#Vv)RJPUC=`G-L#6a*&eS9nFK}8q`GAuSu9`zplc3D7St7~EuzuQ;|H)?c zdENJu|2@F>%H*lc7yfMx=vsEKaXoTC2lO%?xMu^Z002>4$c+~ySv8U?v8KmEmk@kaUHTzbMbB&*f10yph6Xpo3}grWr^dfAYem5U4*o2es5 zjmHuPT|&mw82jW!wK{>zNbJz9n%Qi&OlDv)I5e~mXj^nnRpgeTc?8&&`3RT^Apgn= za+#_%HBKV~`|dqGB_laTPMB1>O*( zNu@)b!-ory)|PknS_b$~kEN;JUZ3q7eAmkc=y^cSD;(KpTNr=EG1gw;N1J;>kd71` z-TCjv?x{vvwg0+#eG(3^syKnyx8GQf6bidqccAf?rbKPv4jr3o=ke8+wizzFwfN|0tO+kfU& z(z1v-KDrPa%SM=SB9ofevQ^Yl$l>AP5um$>9Bit_Pfe$#gW3=!W|0OT6HINWZ+w^y@j@=Y{{I{+Uuxs#-`10 z<7y>El!u+5Wmf3tEk1o&gVoAXOPWcWl4_$;`Rbm|#1N6bp}rW_>(V~Bx;j}VcfQ{0 z_?Fdu>*@y75z7dVb$}}{p!5jt-`N6qu{T?7z(C5oqL?VUndbt#!TUrhr!O>DoUy_b z3xUp5f>c4)UE3Lfz<=*F^VcXslt6)iWR-6B=w!xE<>cA;@LttRp3jG7pIhIIk138V zuUqo%>U)n)U$UaNoWOV@tg!@6p!h!nAFqTUW>y(+)If;@Fl!tQlOV~`Ze)cy0tR{a zMs7$khfiLt0kKVrtK-v^;6wG<-+AiA11M8TVR5_#I?~28V|@~bgm$Y zhcm%-^_{-+jL4>7?KY19tYpTkE;Q()rWExxZ87zbNk`?8-wpG?ra)cKxwma5^c;sQ z3nldEOk6|IwJGY0A4Jo!KRQ^pDJA!&i1BbE{Fzdp%H(4m7=OJ&HMtevU$7W^q1=iR z^_yR2$(40&Us1J8Q%I%RxOp(=%|0H7wGG?67v&);l9KqS5)|N7p%2OSNGPV<`VXvf zdwvm1_V09hrhR@GN3t{!M8;ZeUf5sUM|f@L=^Ql>q)vY2Re9nTZvOz0*yeQ?MngN*e?lfE5!^h=1WBTMTq( z)~!aiw(k?b1wThQQ~igse^2}HPE0;gGhbE@{Gu5GwHoO`i`_N z=~5&FYg=-oVjiy1O*PgQ5LP9SN-F7a%>QBb>lTypYMd7djc?z2PCgshd`YpMD0^Tf zSffxz<0bvBO4GyhqCe}lf{k(EfftWY@xV)~A-EF6R&o@9Yl|lhR|uOCba=DM-LwE? zR?yT}I;AP46bv}(+U{VG#eQ89g2PhwcimvBzx`G`m33^Kf2GL$43+c7TM{wT^Hc}} zAd}mHed<4kc5c`^uS2_}TmGHalan{BAq9by;KiuSLTrX#UoNM?{Ua`x@=S0Il?>yJiNc4T{h>~@ltV$7vl#Sh^Js{^l^rZN zJ#%}DdYKa)4y0&;#+tIVk+EcEehPqffnb~P0k3KaQY<$$$KnSM)fiJ%LLRSZK4c-wO(dYEI+&-XZt0@r9CCSUHIr znY=W-m4;V2U}6AI4(CI49Mui9Cao=p_adC{Tb_~Tvt^g9zf zxPVEmN+*wQ3j<}}uTye}FGm3EJ6-a+Ue*^L6xAoII&|Q2YP!S>G#hr;gUX396g8XJ zJqxo8jU@bWu>1o1h`K|%-EDhzY3)cY#3!jL!fT3@aTHG`3;r?|x9PIL%HigK_6z$J zZPVEbt#4lY<+N-q`+mWMUnfne^M6APDb;6~S{Q{O!C!q5Sl^X57s?2v%y#ql+Tt4n z=?!SNNynR3X!hb^G2FZi(!M>rW*>B3dJ^%!s(sL0`o6(7EYi6gVk3o6LANJA{?0P_ zf&V+ouZL7ipIT~9L&KCUAcS~Bjia)gpJJ*don&*YWvOiV-RH2v_Yxw?amw;g1al0# z!+RV2a&K0Q$oL1gbfuq$-Rz2A&qJ$`$(3p^k0FYD=OQ$mLb$sH znD$csh%d~}h$fGq0i+0$_cK=240A#{PLB=99mM=cLi+u@Q&rB@!{=muYo+{kaY)ut zx0g7pxqwL!tloJVfvLpu&c`prg{uY|q3)%k1$l70+99{n_g&OB;olkub=1~>>^IB(^|AouqQ0ex-lPsutE&(@HtJttZc?4 zK@^gZ)S3g^Jx`nYPO?+_o}5UZ8i8LRmOr~9vf`R9&;A@5BZN+q4 zT%(4c%Dr>uTP%;4a79^ACns4I%}D+I&jRNeZAV4T9kerJE(!@R0fN`RU`F~lO@IG8 ztL+ng^!tt|$^8sUj0fz0Ky48?ka2=ORseP7$D&qF5pYOg0S*a>P|aEaeJQet$qh+0 zu9uRbH{g+o5oP+GNMQr{c1V%pKg64$468P^$xrvKY$9iWF`e9dd_L$R**QAi@&Jg9 zZnvxL-p9Y@FC6U~7kUbklZdb2l6%af#0DY9L(~5sllvz zM0i;y6xD6l?!8irf)5!Z=RsDo3Kyc%(k~{D^zjU@K7Sb?4;R5j#h1j!%q%!UG$*GP zJ3JJ3xy*0z&idyt@h>VK#GpIen6lE`FmaLmdwEMJIhjsw)j)U^Plw4yiWtgFsULChPAc8E!#|+(cybmATe08l_Ainxh_8v5Gpu zBX@Wlt7D-FG(1D$fNuERn8|M$%Ou!}JD#~TlPS1P0dZ{WlU!KLw$7iKh)J9V@9V_g zvF1*&iw?PKI}3R~wY%KGEiptb>1tY;3i(kYF&%$EiVC3~_eEXNIg)-y^j|81JS8gv zCqdIBHEVa?j)Q)*`ZZyGJ|M^Fdcu% z1<++f#1qS7y>*=YDM{Bh6D$y8$E)6{E;kl%=K_a2>mt8|c)Di_%L8n-SdPo_%fNcC z{uy8qCx5sp=fzEtpci6yGhyON_qNoKMM#}(Z0zh0Z;q#F4ZGhYrMMs5I5#=87_(t9 zW`VTIn_FEj5PdFy>M8Q)NC>CL6P0IlbxmuV=$y@-_O2&q0tCIl=~r6(Fw}d(BcpyW zG?gn9LES2kox8v8rq$=NVB?*Up2pX!$BpwfdB@#@l54xWq`>Qjj*;P;8^9@05-N$%m2|mUD_6Gf8HZ?Dzu37;68LTfEC*!AR!XkNYIdpdUy505oL*beGNK2FteKL0k zUEqe?M?SPFooQg1Y-nK_COw$2d~c(#lIh=|g39jNluLTJ4#GmcZsdYbN(FGXlq$br z7gMPSP4_&n{j`Z@MHNeye4t~jt!ofx5KNC3@k1p{X9yBVw-5sJeaFAa4rrk;ALyLe zX_8z(HWSD5T!&CC37;FqG)s>2k)Yf*|A76?f|%?||-+^|YGcgzS2|Gi!lRje#&@3QQ5VDS;3Toks?y z(Y(o~bJj5LgihI;qztp1sFn(OE_kA2@#2@y$MgZ(=(LA*>t@&={ch5T7|R(+XfSzA zB!)c+Pi%_&FtY<5tdFHv;Z(=+7>b577aBTIQSa6y61iU66#cxv+zIyR{>yHs1A?UF z=S%PBgOun0!o0T&N^)_3sok?Z8A5^1i=%J)-(%ltxY zt~Lz7x)aDym@c&X+l!4l@hZz+yUi^vgTt0IDFvox)$8@&(q#;TBJpltJR4dO3f#pc05eR;&M7d0jgASYfL5!(50zbsRe9|N6=AZd2ffh zP-Tv&$14+=*DA5%U4hdSG$iti;F2{^BxqxG`!nETZmzVT;}qS(B_g}q6KCBfB=zk@TMP4q{?Z3x4pCkzdJT;P z9DGoyaT+L?wCCLhk~4*t>D>LP&v-*s%8|Qeu|gQiT#|P{jBH0IZRiT?dBIpgF#f~f zk3jG-1X*AdwWO$|OSblPPQ7q~v*i;8{+XP8C}0NVj775OAI$7S@Eo^@J`IasM_)Mi z?ICEf{+lF*Kej+wA~;Y%`0)wXYQ(>_=*P*Qqjtxa6f%$%3_%Oet`URFjl_ z_x*tp*N`@3okx+OgmM_k)i>Km$vLtQs@By!?O>16oQO;M<}9MaYljyPPitbbdHLj? z>vr|@ZRqoj(e1c({%rxsz}4_?|BT@kWx{@)FvHGTw1-V>5xAru{ja6 z5SE_PwJp%@ijd==U2e_UENq8#e?(iD^zhb5<^%x@Veb6u$ zsjx-a_=V8phy>6`h{wBq_C?@j;1Y&ic{KEq43H(F@LIJ+pH_Bpew_QWx$d)YDq%R)M8lGA_x$SNCS@iaI5fdxK4BvtkhUAD@#HZ;kXofS(X zedjK{HP0+SZ#YL~Bwx&@9b{2l>64=!@3Piydd_kExa`T#G@Kt?Cp*#^=qV?ensfP^ zzT#*JV zd>Lf)VS)k5O3tU4l~X=*yd)S%fq84F`-^{$f!29~O?i~!M@ObgHW=f!cOzRv<*+qO zl3TGVY-TJrBn{RNs5GHrOmV}bDf$G$NL%}{95bu)kYPp^7sbm| z0sfDdPS14EAhn+p+;?zTX~Vs53=c%yb>X|K5?)0K!Q$}FwDG7#nqXZ9+gSpJOwca&Eo$%GpP+$vppv$Hab%u(YtPRG&WIISQRoK@}^D=4R!Gz&E8hlV1e zw3M!hKJUi0yX>NWX5q6js9TU{a<-(9Q%g99&7`xUQ60ZVS|eopZ-mY!J1@3>DitDN z+~T>)L~|TEdRC}GGD^2s+gf*IeGFw)1(-@^p8d0^idzl4TM~tA-vvW}N(y38Mm-ha zv9+B?p`$`I92$PX4jCYzod<&fJr8sKm+|(AQZ;;F(=4QW$-V; zaO!6NvlwZ-##KV{{-~8x`abcce>2wT;8hiAqDDpgEJo zmr?|HmrMShVZLj7piDocE>ljBH#X0H$@msVQ`&YH*4Zf+i{14k}j`^p@ z2Q>~;k%4zdnjHd$yEE1o#9aIy{@750O1LXbx zd|m)m{^bMV5TtkHCEHEnxbTAmV<1H`cG%TPXX5X|bDdP$1$I#G03}8WF$D+gV-d)N zpoGbUgaa!*CU@xzv&fxeDPa8}elo;r$l7uSHcXZd6Bfu*mt!&1uCDE`PU1N+on1Dl zuXhI88UORdq9tb=Pb&>2_#IK<=x4xe1Z0HJ=#(crOMX9OjKQ&GM3zf8hblKGOpBS& zI?Ypgh|!(|JsB!zL;QisfM2CDD1j}&XI1#in`tJV1&>iM^eS^^98m|JccLl`0d{Oa zxu}^~3ub;&ZXM)oq%qW$^s~hhBrj>;yu()gHF16grjb?TE&Luk(b|%}%F+~a3RgM3 zw!zXb0Q$lHW7qoSoe3C3r!eXq#%<6u8v+8H`Yv4TGC9)tFwOvsvNB#aAoE92`dn4s z8ZNI8)I;GMydaEb8O4K_Sxb=T`4!o~*z<3fdl#CO7xpDti@b=nk)rpZciFH6t9qZ% z;JCS(3Z4orP?E_jBE2lMLV)6oy8*bq2AHn3Plv{`-9Au@-k%o7_&|XHg#59Md=$eL z6p#P&<_jQ;70NzD82)aXfht$6ldc)*)J8O_7E*wwXB+(CWGK9lmDQw?`1mL+;?T?# zVSFC#O{26_Nd;HY2uSm?K~zP6!>E#Nd>p{L6r|;7RHn`B^U)HwYyGNqSfHuQeF~k& zJzO=*WOm}F-{f>ny9IpHj!U@I`0$^;4<9_sb^BeH5d7v-TuZ!KUFGHNs~n+F0)L2q zq2$MrkjImXE5L>4yO^+Z)@xQF7`jG&EMuFq)u%6K=|qvLGA)S1DN0!c@d# zQa}Wl-#wdTos|=HY@S)WOSe1uL&7gezVo^rX#%6*q2tTidKu$leVUCz;>alf2vwN6{lK6B=k(OJAgXx`oZXzJSCY{|#A1-r|) zLa2Q+ID82O_FkoC(uH*loN`D(Q;-)OkI66|0RGEg*XHDS(z z0$=H0P8_jMsHj?bdmTd+Qot1D~ME_MW)QSPs&@|}*<1s5cHmTk3r7H((X=nf$)l8CuP)T&uf9l`>;VeC6+a?-K&&qcj0?=?FDVeOlu! zBYnSfG7WZxp%J&F#fk3eev6O|LL!=#HCP}|Iq|2wYhg9%(Nt4B*HHTW7dx#n;qn-bmI4o z>C&tM<~+BWDV7q;f2sQ}J#mJ!#q zf3JoALR9qaEWFiYUMKd8xNkfXzl1O;{#=KRpmMDoWO!;g~= zYj}5_tt*W=UMtKPHyO#f(L;}lB~A>BZfc_JR;w?YxDD{Yx5gxovv^^*ENQ_r$o=%t zKkj99%*t2|%4~PaQnRtWnoG*ip2}5>=6}2U>^tDl-2o?hrrLV5_Vu^@NS3pE*#|cu zboZ0T-A6BCQ!(DFmS=`>b+VRraTB4O+@SBoIV_BJZ#INCihTvecQ=&<|S11`|e!ozV-8 zc1VnN3=s5*u87tBtt;Ci+fd^adkrPzICfY$?d~JS9N?WWG+dYHbm`!)lhrGHHr;C? zH>TTh_3vAgcYjaU+0=G(XjF?;A%-Q2Q-j!uAsS^Yocxf}H6qyjIFvNt-@(=>4C4>9 zTWSRl@E1K6p~8n{3HF|D;C?Ysdqq{!Heixane=y+?cGb_Sri|`RB!%l237mILj9$! zi#rLRAG>Q&XE5eNV|7Gm7VAD|Qk`twg~Z;OQ8e^YzpaQhS`3g^7O-`XvV94*-QCLM z5%YBnRKb~2k0#<9c=UaAy`4Fp=d?V2eEo)tsR-Qb45~CHn#Q2Qh)cD)T;G%;$D$R z@E453h)mS?W;GmGxrHD%2A3M|C*#SwVy;*zg|2s{8zN#IAYgR<$_rgTCB!H#Vi@bx zF%jO*rKyd6qvLDV z%0!5CH2oeKc7?oJp}!8nqZ4}FwBzK=AxZ+lF1m_mya}-vx`e^Ew}yb9H6gtF8>sR~ zAvcv$qO+4L{lm7rT6H*k;vTLh0KMGfEl9W4+DBP(R9_#JhS)V0=YSH8!(#L9{2z(g z#FrLn;g$#nYj_l6G^JEik^>ZT%1ckvE3>Rc6PSKi#xRi=vHh{C17iDQZTY$FwNMu+ zx0shIf!@vqh4qF}(6n9w8;T-Fvb1i4=`XJLl=*+Z9NivM_t1O68Ys-PK z)bP}>O}H)8!XN}*_&ERXB#xHe9HcNK`jHhxx_j42B*lb2{z_}#lXcyrOs90W`z8S% z5A`(Pqj9$9GX~$IZC6hS|C>SAshl_E|ApNS``A(|sLndfY0~OiJR&M8XI9WinU2vu zwxP|=32fq5m&A$R<4|e3QyphjsMgpR96xmj7*F`BZ_j0kwOtdaxv5?qW?b7JJ!7}W zE?ytuFInz`7P;n+(bdeVv|IatUsJW?&hD=pUQ_L4UbkHsu|?!O5qT>W1+U?iHv0wQ zJanWf^!jb?w$IIXjc$;u3BDiXx7TOv7hcxu+*7~8rq(qx>!?lH!`$11k7OaILaDOEUpCQFv!Gxn)~QNyaxl`4u@#grrSzMc(&x*ona@-HbFbc2NTQu$8Z=eb&Etr*yd)c+lO4yv3mLxfVc&_e?|THdfIsUB;;u0@Kt+hl0J!YU0SPs@wFM;uUraLh?LB zmJ{e??&qZ10LAMFQ)7-F8{MhqI zeNLP)LSLFjPrwi;jj6n{B`UrYlj}l2^3e(fDoSAJCk^E{h{)1wYGx`aClEy6dz2KB zSO$;`dm!J=hD6`~W3j!ui(qstxJ#5?aXTpY+PQJh_I*KqDjLq-W_*YKB-y$xy)?9L zot~OXJ)gzYJOFIm;Qm0L9NjonRugwUJyQy~#(w6cDc1vPNb2XK&v~x& zMpi@fSjO5Z;bkG$PP)FXKjw+36EuzKl_Y86{%WJ8*LB)7%jT*m&vg7^1p}hG4$kgL zxcmtXRhGXn^{*`bdD2J&_5QwfD|HD^0e;h$VaGqc2N}6bq}Ks<)%DS9x*AOZLnhT+ z^I1x+tDkw1W0{7lKXp5%o~PL!_rK;=D`+9!qY25+qH%7&6LJ*ugc`)0*B$Oomcv+% zlHu#=?DZOoVM~TJk&g}MCWUIiev6_0wWt4usupkPQFSf9wY`K9FAql1O14Jq%;e(f z_)FGO!w_f4H@|+`tl=e0kP%fJF-sFWz#4E2xGpq0;YI%W#<_Jj%r~_oT;3v-*Eo<0 zXGjy=52hKoozv6kd>%_6Ls&a=d{cVVWDB1c60_pAW&NkRbW3cNF|?0q<0Ji-u~YM< zNvF8q%7}!mn~MxTw9`yv!2$tZ+8Lb5^akgji0ty@jLS!qdI@KOweL^rSgOP#AY6&G zrqsp=j+0%P2w957o#PRVkarF!B1M756GYkJS$5&rY3yJ;MOgXYp}J*jXY2J1kNx-WO+M?S zpEnIvfTy)6#~5dAZH?|U7OLtLF3EUSfYK=Ym;7kFPAvhWJ?ZuzdRFs1^t1tJQ;{(F z&o#JC^|Zu}@z^|ri~GfCIkG^v5h6-*r5zut~ZphQaUyZSK& zodDLSXU8FPIETTo*G20IxAwT%4KP|Oc9GdJFqAH@yiiwFDfJ}pSFqLM=g=pg)G zo6tN$ums-UB2aKPe~D*Vy8Gxhd}|bSmy}RVPR2nBadU^y6Z-Z{r?k+R$IkZaTfXWP zq+<0V95wo4x_(88o0sJkvy}sbwj@5!62T_YXYEENM%Lty50`7g2Zu#GzTeGT8Hg!R z3`szhVXKb?{H-w|>=Z$d2EdW#Un7OZeP#3)N>&=xjs(>)*rgg7?V^Q0c^uR>=DDCn zUR6|9GV{6+JE(~GB{8kH*awU^bGX0vLGryq=j?sH8GgbNyrFa=s6Ctguf>zpb!$$Nl4-ykyx2N%68qoD5RhA>1upXWwwrpPP(wJ z-ka*6?8Ds`f!{Ol+A*F;rM{svTtnT%q80H(}a_a{cJlG7nm`p zM_`r)*D%drM!MC1@E(#vTbStG=nO@5V07 zLox0bWdF>0?dA7TGlQX9>-2ih``av|PA#N80{oPX91N6t0|O#^(uKH2s)`A9P28K8 zlQ_uz8P$!xaFMiGhHl3BFd1voVR-(p zCO4SO<{>VdIA+#c5mpV7kY7{cmk@wl3OvRo*d~rR=$eWtxc>BZGo+6G__AFG`4K+7 zF(5&k9TzM?gaZaF=gMaFwA1YSo(yp-yhb7)cQYoV=DirD5DI2mW_MzBMw+#w9oVL;_og!RC`K+d>mWbmafNGfKe-ZnyWJ*W-J9u*TboAJc^DRzI&-=db zr}oI}If2b9w>2}KY87GY#a{wA$be+jy@KmA?lNFQU5}5iyy&?1fLT!5J5#`+J+$!f z&vjRnecHOt>uqLhEc~swIj+OtuiXXy{Pnl4>o91X%Sh@#5?cSauGp`6Y~~3Ga|H!z z@?|tVcahLoJMqy?oFxrz2Qv^<5S{87gHmL0aax6??r#hBP^aTpHxV7zs~An}lt9N# zWii-N{tnydPw8kNF%d#ZXUOx%{+nSN^2tlx^8UMU(x5UQyDaKW+Q9^5{*oE8a64|YcU`AvDC z!!eSyNyirq)l zhGAXjOsXBGTN`|^*XIM@@IlTTUE}PdC;8nkJiwK(&iMZ>M zDx7GKzn}}dNQ9Zb_6fS{7DAi?w5?QCf&&z)O(fz$wpYH18J^FN)bGb)=AUR{* zE^(p-lX_$$YURWN6EQghiTmt)0MU4l7ZOqQ*)9!cWtm&b@xtZvSmh}^EFm~L9ME$P zQBBd>qh$<6Ne`LC3xT&WZe<)87vf;nbC3whowP-e)PtU{CV)BT1l>!M-nH;+e5A-p zywb?5;QDC9%jeHCMlw~<8|IvP>@@jG4_kn?4k;wPqQ_t`2*N?$2LZGQ?`-cc?2$y`WMaa;-ZP#Y5MYsb*2ej&NbI+QaEt}y=;t=S6_Yg)u-=n*1zB0t+ubz=IUw42*<3I zl%^@n+O7AHHksrsIBO6xBg@;#Th}#9!)0n!P0NyZq-|fl^)pNBt3Oswrk|K(p39dn zbI4iDqYogB;n2!}$z+VwIvTW{s7AAiLx`BrK+=2^ZU_sd#k+v}7Z{ZAK>)#dtn(lw zTcaHi0yXS&{OEDMjsQ!zaN#l+FK=^Zc!rfn9%1#+8m{zIb%`nhx5kxq*LH6nSRs;1 zADu`}rzmEpA9nF*Vu8mdmm)f!@$51R_EOKPYck;B zecg#7hYcuk3=u(~pxfUzPK!oCbZ-2+QPl*a=|XYm|1CQK94Elq$bI!L2 zvfbRmZSJo->0e|@Xi_|waL1kRuy3Mo>Yt4W0 zpZ#Zi;jet2zxsdp>pcAELrC4DtSh8-U*XI`< z=b*M7qy*VK!+zq{t>Jv8G} zkTliIY6YXL6pXevgFR(Dxs)6}bcp-zyN{zskCH`IWFZ98>6Al<4zaVn-Slb0UlysL zaw-iJovh?w_@-EDC@LdlP^)g{jtJ#f8hiu|kK0GlF)u{e(Y@?IC+GVo2#bZc6GUcr z-*#IwE&c3g+wodFxAzN{%>*W;oca7Ny#*noGr7lt(Hzsq{Yf;j1m_$w29Se2V>=nN z*>yKvPvCP$$au$oI~e|Ww{tOkz9@>oqgMi@GQd%r5-9~*%jmHEV}AbUf04Sbsj7;8 zzfYDGjK|~u=KA%kKYN!ae!sno?ErYB9f!|Iyf;X#LUHKWoU<|O5`;;#2$=2CI*fH# zpCeR;$}&s^S>7Yd^DCR1H{Y=vDvllgahLf&x@mT_s0HWC32)rK$z#jQfkWg4d3*qE zitH)S(qpBKPDco6myzyZ@Dnrc+nOm~j({mQ#Jr$l3$G9jE5_-idLI}3&34T1~ zfno`N--DR#ifcD+^77@Yh_zMB;Wdt|tiW_c;Verc^tQKi^0p0ZBs`S6BfWA4ZsIbS zU-;zwW|)k6*$SC>2zczh>;bVm3j@iy>}2vp3+HKKMltf%)j$fL4H-(`N$=%JF*7L= z#`{$f)nP^0ltT2|hfI_R(Lb3fDsL%kgSVO-&3b=GW+mHOj+@ta80}0Mk2O67*46ZT zA#+q&f%gjI90-L{flH?%uHT3N@snQQUB#f#oIZ7&BWvfFj3#W4r@Z+3>wNN&hv?Z1 zuN_rssdX$WAB${z8x*>xCOQFxOs|ViqnylmqW}#IG7Q74Q8zy(E~9xraeq>#q8Z#o z+U`2b@imj?gh-pyEZhc{vY<5Fp!QgT?S8L$&pWU#*5UI$jA3-R3q){WGYQV&Vo*Xj zh0`Jgh(=?UdId|vf^j`%`|54HRGeNv%<|)>c<}f-FT8ny=gz*#mCUj^ns9k@o8e%Y zpZ%$y;$QvzukcrY?ElD*{FVQhBgc-ix;9{ITw$Nj`U(O^FUuKd z&xzG#jvhILbrse-tTSY}La3w$x7f*NO#sm%DSi$*!Jd+sKK5noG1v*(>|>ytAOOOg<4 zF~&V<>KvuYm{`l1^Jm$qYNQYK)lRRjv7~b{Yw711vYLkRcMhvU@X)2X(78kr;d{!1 zdy7gW`}O%e1hd;qEdt@KId00g<#>GJkV#36V^o*Ca{eNvP;6CWCP3fS+?Z^#?WQOv zI67QMN=aQ+l+!7_Uau*%?Y)m>X=c~)t{RO%g_o0HNra`Q=`38j+vKClrp%BKB5Y28 zaI$mQVEeSt+}YyI4Gg+_?{o7lwY07#io-k-1YsL{A?-smH@S;H+~T3T9I|d}&(Rk6 zZiA0=^d1?`6|3P9x4e*Pj!v>@f)DAsED&qpCTC2 zFPTmaDw9mhk}Q|FszwNj))_Cn@DjiJtN)5${ncN^T1%E?4SFui^3Pnqe&z4n<#d0) zz02(&PTMx%a-C6*N6^n04*SjGol>fJ7gFdknv^NUWMY_3T_aqg5DW$bjM;g6r6cFs z&6i*Q!tnH=-()1d&s)dstu3xzzQ%_R9cE{1o26b6RGDH11KQNK16(Q$?b{!pWY1$X zNeG3sp(Lo35)G8rA(bN28fP72Q=zmX>lavG@ZbaYV?@sOFC`fHbz*4W=bh% zxM=frj1oMetFkt8Ztpw1leWK8G|&1F$rW)?jGt&CdfwWQ8Br1)3C+$2_b|&eLg!fP z(88j~7!=Da4KhZeCM$A|9zDunpcsl(%+Bq=MKlIu4JrGY`YahiG0q+%olrRoQn0kN zOs_XU3x&Wk9#6S;;~Izi1BU&Ki7kWkp(I5#1PdGHRGS_1q42klu&=r+^5dhE-hq^M zBkjJe-Iw~?_*wX5_dV&8vTJL+%sYK)5l1DJ)w#JKT2@h*7ajg5;9PjFOf2@@a35WO zi3%Z5)_*HcHKGo)5{XDQ2)X@tpljrLssMm-VEPP)i*`SAoqu=Ny*6*uj#0Rm*@E4z zo#5_jO){p#xpo2(g|p^4@4Lor!Rrgx>L%FMKBqD%aMPkCS|!Q3XCoamj*oDyjg_1> z)d-cevq;X{L30r1P|9gHBX>@V_}~uQ6_sk?-b6TSo@+*3{m?6h*-})&V?p=n𝔯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Ⓢ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�UDTXv`7=de!oXUx0#ts8Jd#5$(dhlvzVM> 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%0ARonc0~K%N2W7EG%YSG;uuH z2K>N$Y$v$qTH)&MJDCzOQO>5)62G za#}xe6NW;%*0Z=AYHC-}&e_ohu-=D8ht|qMGxD8W|BTLU@8Y90{%JLfh+}~fr8G|R z;FhFRIjnCF>!(i&<~SylMJi`sw{4t5nY3P+f2v!*$TaxBb|j6Oa8Hm>i1gpRRJjB4;I5hxLw}fEpZ=;ZFiq}PYe%jK`Ylb%3l7%|Vsyln!bpi~t!wVP zSI9<3%JT#rY-(UF*znuf1X~b&(y_ez6*BnceZOAmVB2`p0QWq5sLdN9{+x|fM7`)P zJHK~|S>Z7~icpPj+?8uw6?%*$aZpwWJ+Y3Jtf?Gzs2AZSx?0Jo-|V%N{7$DBa`=2u z|DmKyNL|=(%HCqBQGb$;x2S#D?q>K*_>5zMsq0~c(@|4D@@Xo)gTKsfV{&LBN8|tC zf~Tt0WM2PTdi9;TL;2?+XkJUv0{gE@y+cH=GGdyY@POeAp8`&8eF|KuFfMB0rek>tUyDnY+0UHML8&%XOD17kp`|=FG$Cx0mbtz!ANS`^}TcqWf zvEKd<JuPUo{CjidPw zn_k$ZD5ac|>Xb31h4?;v=;ow_IEh7#n4)ImgCfa^4;|vs&v^Dbgf1itWwtIz^p2+i zL#gfsZ06`wR(Pef|3NJO!}2zWJ~ll4RC&f%5xJ)Ys1)Eh2w!%-%k=X}Q&ST}=uhK| z?qUkWH`D|0jKnnLGgpbpkC~{}b~g_^JkUcL2pltDxFf{K4#K%BJ`d0TSuY$t>1N)&nNMquoB2mD+76J0=6k$LhP$r2N-cI76&E4Ht@Gs{f(lAMTzVT*xRC!bVJPZJ=Z=hIOw5GvK_>ZBLbfo!+uvS-MGfxL1#V~ zM>kCcG_^d!)QqjYeMpAc;C&Q?S4>8q)=8cGZVbE!2I6P!n>nXVOY)MLUhlQK3jV8I zbmz}{mLQFu_9)F9DKOWQCj?;CcH(xEO&w&0vyP*rwyT6v3eE8yacou&^suz)O2=lB zA_an?rvA+Xr}JF$^0#3UF`V8k#{Y^({ynSsMP0!7c@k4lU_R}4!Vs?sbX+JfcTvEc3fIL$YZ6PD7l@M9I@JPm$VRuv0(Bsyt|I1#$ zQ~ZApR6rPA&_o|qWUQRDF9cg!0 z$0b1F?aj*JiM(QyLA9ZWvvZ{;+ly#*J?FBslZcsd8Fs$@S+Ov+vV!I4WMB1PH*)VY z6#O#h-}7jgkt0id(c>E_dh^lAx}rF8OfGdORltSTd73J<|4Rf&EN)2K?;n+f;+Id7 zL<}65h*B}{m3D8L^^BZP#(g}SKZgEgVP$vuwd_Jo2Vub7aZ?najpOkSeINB^5{~G@ z8Y~+79VfAShsi2?lDypkHrIaF$KU&UUphW?ISw0+s5ozRjkuH*;ONy)!jomEGUjdr zZij>&4Q}!xtFo^{`1dBqE(sNaHfx=ib!$%-hMGz@;u~x<&WvxxbxXKS`FQvx)zAv@ zT-isfdPlX9Wjy%6%G$%k>Wjnl@KX|9Mmr-rb5wN&<1UY@3fgacdfdt?Z=GJ<1|H+M zhf45G6f~hL@8HYK?1GIHv;;YLQHyT1ru9m3XgIpnH3e+Us-97EYaE|Gi;Y@Vk`le6 zuP0DIjM_#Pgk@-b;TaoLKUq1g)`wKOX}vlW{jJOEB#LbsPUG@u8&@qGR&07ZmO*F! z^TORXu-TXa{DJ0;xzc1w04daEblJ$X2R^TxRp2E_*c<(8L0_z z+OPy5O_S)t{V?S!Dh(xX#?SvWiI@Paa03qBSp*cS6jLWlL0`1w)v~g(Y2^<|++&De zfN_p%euxyYnUc45iToiF{ixUQ(Bh|@+)}a1IGNz4u+O6mEjT9l46s_;LzHuoKztr4-iP+MkT zTuooxdU}a^0`*P2orfSRy% z7s@jo>_Vs^xCrPROpa-aB(JWBkGUYGG@(EOFR*8qd1nom2Z_SiPc;D;^K@wCM_0+nVFX+zXb9K z5+-p_W=|?)&z7R7wWyAdN6>!8^8nwK#3^08+ud2!c?_H<(&Z$kj;jgR-{P9qU8_+b zg+4Z0Zx6ZKs~Ql(>$KOI?{~|+qb{Kcwuu}vmlE!_?W+30xA8Y2jmQ^CWm%&n)OF0#{07U?(Qf>(*3EOXzHo@K|KwjM#(xc|XBoT60YJo5A>K3hH%G50fNbXSPel%_vHS49K2V`+wi6xm z3&_Wag-&o-oifDQ-#;<+K!S&avusqBMz)b2*K+pKCR7zgr@Gqhe3ck`r^?icgEp#2 zCW-MO-6wDBWaH6X@0{m3(ckHktRjg~&Gq_<&(Q)vY&lRzDdYB#cD9(@LZ?xmW( z#wzk2f`qY&sxGxWF|s;~!!C?E6H?2Yf^Hu)N9M}l^YF{9%Hc9FZIX5RhHUG%_G&D= z(<2WxFgi=H6fm+CM!n?4jQl=dtu>^F97H&V8vh69Q-b{&IlxIiaUt_U9$k07YXa5HsVXl7loiX#LjCgreZ1P zApgeWRYlXY#H0#}bAq2oPJ7+)+xVX9Vb8{8wG;A(5r;lLrRsWCauyen!9Y6G_HE-M zr>e$|i?LJk6(tKzi|iA;uFxB!1Mh-EEF*rT>e8`Wbxam>Y(5nEvxa(#V-(S#V7%y0 z5nRWVrP64jS5d1EGV;JTm~`+lq!Db#Q8rUIFfk~&7s`D!RwW+D7X6D1Z6cQ947hH# zAshFno*h+Hq#aeehaIr`w<4I6s~j_6zZpnY><3tf;VK$rF^z1sq7Ks`IJ~BX9K8^0 z4Xp$XFR_S4Zajx5o&vqqXHT#WW3`c1K0$5J(JTJ)DLEi$AeRoe;;M{xo|wR{3Xo|g zWClr&Jo0y1t;^uBCFdXhpr?1E7u!0Yj2Z8woZ!MF6O_c40+yuac}h1NlPy2I9#cGX zMy}W~YJVhV z=zYQaG{^YW*8*IW5rcxf%0mFSywHh7E2OiyPBKlJ==BjQ^lDL4#$BS#py%IPq~rz6 z76&5)(4tkf2-O`n*k$6lzqt_D{^{PM^x$2^(&DTUlhMsi9*xROZXv}JeY*@F<+rne zglPB@xYG-59|=x=<1xGi-=Hw`aXfel;_;O^u*}kV4(&kN-ZNlU;xwXlZdgDHe};pF z_U_}=8)N@lgzi7<^z!{C;GF1C?|r3rHwy2Ke;Y#d7rq=cHFfipq`1s zgK?$u$)#r(7iw74aWW~oqgX!UsPF5&qqroWL@_Zj{n9+~aiEvpPCyH%vU%*r3ycE@ z{u_p>PtVLeP!hl2+h-P;)7{MZwsv$3+ze1O;K&&#OQGoClr{{l^tDh;qpYu!XRxe` zS;u`bIJ@_yt5wMo`W*ki(#@j!oMdDCFf10z%za*t1U9tb+PFET>~xE=@R)|ewf#Ew zp|WO7-rO;1?2;Kt2J-aWM$Ilg{e^QC-uZ{DjyFpwxKHb%PY}J&4Jx-~Rx@m4b&HtEYP-{#f_Ix(o-x&8mPF;LQ4PYi%Yzyn0UO#jck-TpI}U-71)?JEuK%tO5r#Q2Ft}@h5`TDP8bK#WI8yt z!nzd4tWV7>6IOIS%|5G`BeD5?Ww#5*W2eeHc zNt~5L><-)QaO`GlCuN?w-}b)gHTv9ZId82kk}bm`@6p#YNlOpM+XI=PDyJOKY4O6pv`zbwlZcu^JrvHSJA02#CL)1>n>8X1fs^D$fHS&@C$nsFA-iy1vNSZc0u`w-9O`+qd0zBGH4A%#MjRcuOpT{yDST1SYA|oS{7VPaW1wUaTis+ElQj3#upyAuqYMdM!8&*dr;0}r8 zlbMeBK2);~H8M)-iq(lfKV#`}o9?70Zhu5v>>7{;P%641r((br1oK*-kKaOIOQ)rE z52TZrW9!?@;Z$-d*r34=*MGCJN&sTe60WZ|U#SW{7a9$_{NtuC(1>joG?S2-_YOnLw&QBg6o%Ss7)l$^o2+&Wr07K8YpJOfpS;U~j2kwK@oMc-G4vJ#{( z58hwqJZ0Q1IzBxh74l@xM{a#^)C|J7?391Vx)BCGGRiR9^}No*dO}cC`z0 z|HuXr*LAI0+&_(v3dW6W&iO=f^RJ{o85Nkv5aZ~h=|wn`SlHDz72cs-5^0Oais3~> zbE#YZ_(}DcU+nxXX`m`>o#}A7nOgx{&0E^io^9fHd<3a~WZCd@A}WKhfKr3jOeNEx zC+HE0-&D+1P^rEG2t#g)yC8;L*&NM7hBy?GPp$9n9+VY*5&RF9%IUaaSn^Ln&&X)R z?=!#AYWCyO>&0KmPQT9bzlWRdNaXOs_@&kY=4k$cMERecr|}Z0m6VZw3c&%4W4uZz zP%0fZr(kd2$_nx?qt1Tk%sTT9iH6Ph`l} zjo{_97?pUsojUt=6fZrxX}NzjgyJ-XhCo_beLs^P9L>CI1PxaEH^<49NCl+b zDMCVZNY_^|Z?@~jm9yVGa}_mbcB3-%BeB`P)`%?Xr*RaZz-T0Uj7u44=g_B`u^Vx| zr-i8rQpusggh8umuki7&kdaHSGHF#8qG`qg)wwPh5R(IHT3TM>TgX zO5D@OunWIVPmfsQXM?Gv@|G{Ec{wYdmm7q9WSM!nWjVk;VsIW{- zKG+TClF0%;x6SWeIPkPJF!Xaa;hk==&Ukn!nJ>N`)N{Bq9CO+8=kUL4Ri zlE;z34rpbHxjr8>tX6|D{j;janD|_O9{p zc^LJBM(39U5F79S11%6e=H>Xi+a>Mdfrn9%*wT`Vk5Gh{1r;f|4lE1}4xL<$(jo29 za+4cfpWcpFrXfMzkNw@qfr$f@9~A(ZG;c?HXIUcD2yaOT#>U&PE|1cJ6wiG3KK9+* z*v@oc-`5UhEsztZ%5&T$)1#YJ%W!zC&*HOZGkf)6p`by2^}>M4W}Cr#d=c?F?c*a& zWPeB6h}SXj+*97?iSwg_m7gvjuoMvrW?Thx!{8!m4>Q+ znwp2khyPQd3r!bN#yU2(jen}m-61Z}6kT1zw*SI8VY-`Gc{7OAYuyc1P|(kP#jTXV za_+o@aMzEfo^=(=(IYVxlhpfCSUzO z^A#rXJt9v{{{Q~m=l{eLG5?J~8fvD!7GzH2Ld8E4)8SDfoRj@7`DZ2uAeO)<2r14( z^x)IwWWPzzX>l9Cv*wD9rZwkH5J_Sjf2&NZKt;ZBU7>i!u9Ac`d;XIuy%x)@pT6d{ z$np8FRb=R`W&}Hjs;Y3zj|zSW$PFkQ;tP*l!IIg@8kKeUwZZ0p+>ZFR4S2u2MDOuu zM0z1PD3l3??VYdCxkry0xo$@`2xaYeo!jnP@DOf1*^zb) zrkf4sVa}{m&wbb9=_>?Q>}Z32LpeudQR`IGuj`=90Zt+vFdd0NrCmqpVCQY9!<=$o zSP|%-EWX-HY-T2EEM%A)P#fRc!terlw$B{%q!9F}P;i$4;{Y3{LWXoFQ&)wjVkN9^ z6U%O0<_t7Ei74<|D$78}AGf%(4b9!72bKw0?Pan$I4HgK4ebwmcJ7Ekb2jn>rqKF% z%VYeX-M^UF`6v$MGO~#b1j1sm=IaY>n9Ih+W%20oMzV#p$)FU%1T7tKs;|Zt(BxJ1 zQ<@BNd;QaLXlN5K9=ZnMW{sT06kFOZ$ApQ#>XvPr`@WOZDkDzz%Qv1JI$Q+YZ`zUR zgv#47gI4B+S!Zw6Xbg~d09S7xtjHBONzj{1OG^tM;Js9zc+BO$T?2gP?f=zr^?!BT zZkf%@>KrtV4XuDsGtTGf9ljfZYePd~1CPf%3F5-~%hM=|ciCcO9*t(l3Css}G4M>fAjy^iodZaqYr%S`pU~0-YS>*`e zab_ANU2SWO z)*%hrxkeSb|C=Flzt%YG|NCElz@iD8Zd8-3@8tDA7}U)m1m*~-8HxrvdWOfr4rKNw z+8# zw<1MNIII&Bm2CBX-f_4+2;9i$ej9#5mbtCaTC1zy3|1n8Ah%^uOc|8vV;~NZ8sa0( zmC`Nbuqm~|VsnH(%B-G`F8vWC!3H)T-#?LQl{cnutqNpQ$ceBlW)4=A^qHsM9}7nI z0v2>B{o#9RR&M%q(l1D~Ec)~wq|yHtDs3RFIF?Up`1X$2C+Ag%7LSTK^!C(RHbJF) zA7Rg`^gQ+Ji1Hkr(3FmEc4LKElEGQ##>+8zd2qVKnl_LG)qMn+5`l?AgIhtKPnM#u5ucjM|P;bXV#VSJZU zV=MoYf&o7;SpfapJPmODi5_IkgGZ!DDZhuz$#OU}fiT%f{4ISqnyJZY6uk^_rrQWE z74@;Qqik8zu@NR*R#ghV>fiRt1&fYXhx={vg1DPw`ue&^=(3+3*NXDyb_6CV~X~}iav>_s!z?#Pyi!n zMAqDZ$2<{QU}9(YpACj#@n+&Cqxnlv+a{~|X}`eW*&5L(Dyf-Gd+0Q$nawqbqHNT( zIyj3~9VYCs{g#(4yk8)~Ex&15cX^fYivd-yM6-Tg&{=mc>os(PJCR^dUu*Y0x^_km zoG6(Z{zjUV8iKk>Bztjq60znjoS@`1Y2XD>@B29G)nB*MwWW)cWvDE!r4xcMd$vCp zLI6ql(VRm0<1Cy}C&#_3w{yt#U<>hXD_4c;$^2G5n8C5a4V*2KWg7ZAAy*+c` zj6AI_B;j=HIPsCXdr)3U)$()#w66J5MW&i8fj$`~rPsA=W=Uhw*g1-6)w0Cs`nx;i zk5|FoMT(ai4!(pz7aEo&-mli^>0mf{h*Djsjpw`1Rc4$U<>BmBjcI(UuEq+@Uk-Z#}A#Z1PlB_6i>f`yqMy&?GAfjNk8fkt$GC~ zVcc>A?MiK8)=i_>(SN3{dCU$T(f#E9W0{$zYhc!eymOBri-2B|)`9#-J2*X?JjWWl zrl6rfse{{3c}pgUuvw3!>HHCsEQQkCDoRA`K% z1_cH_&uwljpYH|}8x-m@$4{}Q97P`V6{g@ZLeZ$D#zm@!x^Mf*8YDOu=Fxg$oD<2X zW9o$}bB#-cc8e!W3a;wH`@JKb6Yk{%oE2^r`H^m26-l-&slWLDt!_v#tqzQjm2MM` z41I4PyAO+Yi|319UR&D>xb!3aeAxUj_^A;bW1hsbACp+@jMKQoOA6Y$zB{XI|XKHGeoA5tH&Gl%Bm5( zgpzQ&A?jkxeC?RoRS_DN6?s|pf8hj3UdzLN!zQ}P89&;?p$X#d+~S(vgE|{r-bv5S zJVl??e?UEM_+4r&))~F`uV2g<62M1qv(U!R-X8F-h2HRQSXdXK|5~7k+G;z>8>UR; z$ih2fL?gHFaBY`Ww5C!LD%{1xpG|Jjek!5;^=EyVoK)bz5=$0!b}kZ00Z$0SNpf;B z(poIyQAWE)>UFPOq{0GWh0A=C~wV+8w95)3&zsH6ny)cj`P_Acx2 z84yfGyJa{gX27sw)n+}2zI3bjpL`nXXMPT^ujgF?(JA`q%4oD02I+Tx!uHFDAATzH zU$_@==@e4$to5Kn7UKyQ$nDlK$ZWOcm2K%B3hLg$kV{uKUn*ow>_nfcgJ<|U_){0s zHdH#;RoBY(s+a3th+xT(ae~Dd`E4X%!s=}F8^V@}U{@3ng8nR`g zt>1ll?|1HHg|zx&Mv!{7f-tg(v(Ad@(;edbTv)q7%Kn_GO`a{rs9>j>G{6_Sz=3z2 zS?I!9JU-WA?Gf*Cf55W%%f6@tXL2%vMA-Fd>9DTOSn!tM@(8#hW7`E(`<0+ac>;vG zheBhlAd8Sbq>Hj&nzoesw*uwiVXhDMQ+nmqvGA zSD=q&_uob|-ZK6?4VngYkI;SdC9?AZFGnVGuXX^I(CbALuxHo#022^9)x72K-Hlf- zyi2ttt5!j&Cy~cZH`ZyXKanA1OPwbD7rCgx9W7F{$Q*>%Z*HtmjWN1Fz%FquKw=5J z1oYvj%>sFnII&+lJ17~ zUb~RIF8DZ}eH%NVuoHyrvR$xeUC2KdV(+cLgJd=2dsKONS1E%K`HVgTziuda) z9(b0LD5U%YIU7%4gIy8Y($44Aqpdv?Zv(|)qmyu8^7`Rshuez(_-i1gzf@7sIW1_K z7%B@04&u%t^Lc$>%<$cGC(|Y63b{Zy5l1q7G2}ZbWf#XXr0A{^E0&bQ&82?C*ZEs$ ziKKodo^i}ruU)6+9h?pmJQ$U!-o$46!_fU{kTrLoH}Ekgx7#}|TzV1__o{ zf4gFpa?~S+N9hGJLFZM=4iqLykG<0z-lp|Q4Vi+8#A-__Z9v?qk4YjnZy5Hkzk)W{~X-@lc zw};JJ)KV%f5e|1I?XHmagSea^Sb7{o?V_P$jUt(PvF@v>6A=0GA`s5p6J+Ul5CD*) zR%rNZ1&!QdLK6-k?{yw-Tr0s^l*a4Xq-;V#3lQFPwd5d#eZ{5*Yr+oyCTLcw0hrLr zvGAFAIvm}CBnENDxO%-2(7lJg4Q38}VstRG_r==n8IE7qv*2b}Jn_PC>xoIF!Nq97 z1}@?(NFP&r9G5Lqx9F}UGn)b6^kTo7WTbb6|O zlU8S@4?Ed6J6y>zf(1a7K;m+zP*n0ExM;n~0?&kKN|>7pkNmPPwm~Jv?|noZ)}$em;wfTt|)K zAu%b#MK8GRzR=WK=9HB|??}CKW>xSCKkw6u-v4t{(I2{WEIU@V`GI%I7qktbBJz$^ z#@!#k7NL5+y07cQ3)=R^wV&)Jyrt2H&#fbtLu>*~HRTAsT@5znFzeT*)=sk=&U}>` zWfxEuv+aVTp{4w)#odPzYR_xinxamj>AYY*pf7BnGRV)na?lZAAuwn+Ee#S;CJq09{ZO~I^oa~iG zPO({hEcfhH_tE`vZVOv`IG{;`uvV?keLNF-bPCClS04O{Zev`ImI*8WUq)1$HG%7U zm#jY@{ThOLjZQtuntE^)?8Fch>|{9Kz(9BkkRy0Ix|CgeN}D%yPJwqyQfdPeHyqs` zCci|PKJN22dTo>tw|1nhhsTY->go0o1TzyKvV7fHYez(S^d%Qng1h-Y0&)I`XX1W?H*Kcc|=iCSOPQo{<8g?r{XBvRkQ+XfPPSuRB0$<%q}2o#YVVs3T( z{I3>8r|W|DT=ILH6qTnE!C1q#gr!xYkb5%fm}*)eT@DJNiy2)VIu!9xf`^!dRUwgtI=Zm22JDBI_@QNC^x6nf$` z@T^9&IVL3&24bWih?gTDgp%}#cU_ekR+8cuQ%|lUZ*&>CP8n@;xrdlFZkKRW{N{Hs z|Gb}u>nkXz!8>lVe!Vg|Nmxr|dpOe5Z7g9--=Ywj?@}>Y zv<8fbzb7K~oB+Z)BtmpCnLMVtP{)|=LpCMD&cQ#%R7*J<(?a53%;Rf`)8`%^H^y(^ z0_VdfLRv}+K8HX{^XydOAp%H3^2uOSWmM4=vGAC*E<`1%#dHXzsi_v@YimSSv-$h9 zK{vF|0Gb1c0@=&=ySR>%xvc{*Q3F;NcP<5hY$*S~eYM)U+sj235I1i1@ZtxjlZ*<} zM3B!b;?CN0^e2*fAnm&(LzEXr#3;Npea=zoaHG|Xsz5|Ww2YmRU)g6x>uSKqe>a4N zuGXMlrn6(`M%H-@ZB*9Arux2(4HN*Cu%!sli>*X%@s0TW^1F?q{$}$j(bt%J1||)s zQQt5H1(g=}*0;1=hwF4G$!cG;Z{$0Cs(N`LT5+y$=4bufJyJl_LCLa_k%REt&x0gS z;y}2~H=>aGLbiwSCKW?uRHR1N9D#)pN_LywW3YW}1xTBAV1C{46`)H)(W!372?^;{Pwj}Q;t;3(2`nvNXY90q4)8% z;P9rISb;K0Emr%)&8i^igS2a@;ncH}H=a~DuzB6F+s(=?MpxDw$!~R)Z`It#CjI#t z{|gV?0f$7yR5!fkgpP@lX3=Kx2_qi9KU&S^KX9)R^s#KNrOv+sN5s>Of?8&9I2?{CLZgPfU(8cWIWC>30ZIwS|W;Q zb}@SBBN)H3{Sy$2&I8O72q}Q*-5xdUoB1tGHDg5E;otWm7el2jXpIW(Y9O<7$i+I3 zY0`BMOHWWjfnN4(Wne(4+o*s_{>Zbl3jpfv|952o`WLy9Mqlsa^Wp55Ka~KKb6+r#IckevZ^oa6iFELaivh`Hxp>_awCz& zdr?+19AgUKklNLR-o`?wN*%)JRK2@uk?3U{JYf>xeFcaDu_VdRC7@G~jQhb=B7fHh zOREd;;ee)M)zST6V!w>-Gx2tYBm|tq2z}g17auSU(U+@iMxfoW)&a}gJM~c~JHOi~ zE}I&Crw!X&*>a3SWIzmugWEp=HvYZ_O9FrVun-O|jm@CXZi1fN6yo;-Ld`R%od4ijIYm6LP%67)QA z!f3bC6Y}$X>tPdOu4>n3HIP}{1|v06ZKjZoY9aZa*G0t2r(Lo}Xjb@ec*A?c^f z-NSQu*<(O$%^UC4xZVK+VX7Ga)Se{-JLc6KN> z`~VT#m|GayH$9$(@_nCp5CQ$ZTq{i(5jY>*V`gCQY~ANe-7V|qelK7j(>}HLcQ-Y? z`1E~$xfcwm_^)LB{#UZ7B2Qx@FC#Cghs$ir6yP&O()~O3p|F?y!S4JUSajlSfqf;V zeSDB=DNK!Ambg!AhXutM!7d>)Yay%a>(Bqm+v-N6i=SZg+{&Yi#q3nF1YzDu35XxStj>X0HVco{-i_N~d`SyTlaUKN_ui-*#CKpO{%#-+oS?Khjsm z-+OgVdFb;Q)VI43Te)fL;AT3|@M&yyQzkPs(MgoSr%)=sA!x*9a#cEJ1oK$d+iI5w zenf&-6F$>4eJkp&w;in3<>BW5qlem%!2vb7-WHRSOC7lD2e^JUFl93l-@RSFJLCvG{s>!0 zmqRFhpsQ>EaWY}638=Vzz$tS-@3s!0}Q4-Q9RSEO&L5odHe`Cob$*|ENqc8 z-Af5-pVF7G5yzk|*$)$)iz zBAw~_*%rdC{prmCw$ojQD0LEZ#nSJ5N#B;)*ho+;fnx05d`0ReDqWbG^3WD+h#T@ruMyqb>!(^g+DC zNKjAf^UXk1=Q@j;z%6@lz@ZU)E`ZvSm;nNv%R3Dchk$m8I&aTa@2yaff}mjo<}mza zd25PpeW_0}M6*!3j5cNWVIa|BG*Y-)Jgtx8_X%#Aky2Z<3!)`We~lEebz=?3Qfh@n zP3UWhIwni7;M zo>#%!bo5~tJ*r!jHXg1!zLm{agKtr3;Kj$+b&_nXrldm%BiemGXBg`;o*T8@-To1gfwc3z$#&-<6Q=1GrpP z;BQ?!pQr7JZqU53-@~$%QG>gw{-2lK|2U?L_0hkdLggwG6R3;u^jmma6Y0#b(WT)P z=$xB5NpX9d`%^V)01Kt7G1Fh;?u2}%t+g}(MNjPwZu;5h=*~&;@PwDdU=A%h9cy0XE zGO*`X}ZOp7mE38hamH_Z-%98IwI zJpb?yDf>~RK4P2d`{&A#IV+4OP5*Q$=h5Ch$Fr|-XL8pBcY8qVDrnrzqcbRBCgrv@ zC=fb2zypOQ^fe3(Pcv0)In3b#Cdm$r7RK$(89xr`Nm1!GS(tNV|POep$YP z{2wJ%OOy;)4=t)rRP>fW70}lX?;JhA3f-5ny1gGDTk1RfkVcK^x4Tkj<8AFLq`Ml? zCci@o7N-ZIp5!X$71*y}PF=s`yWCwdpo-kEHP+Io+fMx61M;A^tO5_L+FW*F+}zx} zFM&A{Wh1&dk{VW=NDB9^Hs|XMKQQ1?xvKm4*Sj;3#;9v&)4FP*hV{8wp8v8_w{g4+ zxY5?f|1dsn#vZ+Q_RBG%-Ctw01|yAgHHrH8K!Z4=o`%A=|8xMPYh2 z=H{)F9R8}=&K)X2R~A}7J@S0lOrR&YjHlKa-b;n1o`P%N>YzMCy+35q+`-hrT3yEx*vx|E`IV!p4H%lH%Y?3>u zbDl^K6JCDDa8y?h2j0L&M=n;jt%koFCPN>CmW{u!>WR9kZ)#UQnbnAkk0g}D!tZMx zZmvj-(I=RYif#l~16vOS#c@z`ec~r#(7Qt`bCHdl`_06DS0*MVoBJ-IFr7dDJ2OprnZjKvdUcj$=bWt6ORZt3P=VmzmZ0cMZ>+G%pg0 zK`)7tNnrDrCRVEIK~bw+3xQl!LGyr){LwL)9V4(;ZFFze;wcPdubu zE?6sU?d@>zxli-SaGx%*o{L}$+K3EYNmPPCzK#uKKqgXBn)CebAeFQWB(owBJ4b#I znM?(Zf&>=)glUE*ie7dOH7FlgQ(Ac~E*NGir77rucs)h#sz@S%Iip?0e_NiU!2;HZ zWmJ@C^-dXyUOIdVJrQ55N-PI_@~a^Wc}i`pXS6s<90F75jqfQ0b=*EooLj%w9#w1!JnzXO08rbE};_b*sPX1-W))> zs01F%e>{tR9s&V8kmg9C{e8339{Et8tvtq-5hK7oAxJwNfcHt*W!OujHT+k`23RpU^(q?@nW27$ddDFXD+mZUxnTFD_er^nm zLLYn?52qq-h5S(7WO#_|bAOGe>egVRc1@nf7d2zD* zO01@eUzlpL@-VIbY~?RaGXXo3Rs?k zR&Xxr4nw~n+C??(t~A@x@$dbO(y}CEm#+n<;**{bnCtL#s%f(JEim7MGUr0*%{LkJ zx2`Bzv4S&^9+z*aS?H*I5k7gW%;E`7HUFsrDq5!uO(8&12BY70Ng;!(>Ro){b@7|S z5y8HqI8S=b??(L|x4bdlyVxwcX|p!M|J;?0{HBjPs$+)cN_}~(YGUr(bkim`c4m$ujvVqX`_Mf}!sG7VJ_!k~Ly{p-4*bc;@r+ zDOUM{lPfaH3yA<|Yu3@;{_QAlqi)eg82XLo>b4K|J1Tfk=6z$cV7IAk0&{-BDb=SN z@9Ka}++%ia{-IJrAZslMVj#WAUD@7G3Y`xr8jjVz{)bNXf1#>pLK45l1;!eU(Yo~OW^v|qtd34F#-5p|22&imSRFXeLAUOprA zsA6>NvbM6)=6ZKEFUl~vVVoQ_FZdpN5W92Yo6+Q!yDV95ZEeg?#ZatkI-S^_#Ul-( zjfXOVS`Wh(Xv#x~!w|rWBb%#2s2NGPS%E;Shm!O*V7DjvYSke>r>>a*eeeZdr8I;@ zX;U7)_eOnGBeZd}YOLgcd_84aj`yV**x{vZHEQ*Yrx^~_-4T^UU3O~v|H~vXZUgrFU z_~R?AS-1Lmc2hKbiDTnxYuBnmkDbxll4YzXeVm%5Ot?C$`-P&0p1v3++^ZrmN{0MT zX3y=J54a0LI3>P*I&A*a4S~E`VR5QT&lQxmV8=VTzI=-psNMgbVRAHqz1KhTVL)iG zct|h7Eq_cgnXYKeuT(y)WKSn>KjTT|^B8w&9T_ugqtn&Y^x7-@>ho!#to-t>2yjVW zc>nkKHuMtq{+v1XQ~o?Bjm)fl}`5;4^|$yi6_18e7iA!x<3t=eI1v4Bt8iK zBdhug)icABvVWz^?6)SjSS>^XJ@}PTv1V+t8Z->i0AcA^WO3vGov&n+mZJYl1FIUr zv{CEiPVCm0LQ2*U27Pcmm$YfUQHkc9fsa!)NJz^jFvOs=h7XNgsD()kp)`7~BTANi ziWtPFiQ#s=!Xo-v5EzwXM_3w{Iw%`ooc1xv%iG#(neOnr#gk`-nRUjiL@wylnU5@v zvW8jD*m!p-iCVdqM{OW!2whL4G|BpRjY@T%S&cN^SN5>`!mnSDHn8Dv!BQ$h%7-(T z5m@qL#mAu-WxIc!Gz1XvHW##OP=N9CFN`Xkj2Nx0Nk*=iI@m*S_;$_M+=3tYu(H!M z5rFRyU0i9CkBoiv`i zK5Q9oX=B8(1+!;IGA_&4@>6?;GUMa(3#I?GCLI+sQ*W|n%zB0QuKo$q{G|T#>GW86irHT z`r$M?9s1nEdEV|UxbS$2)Clboc@ZMBS_@Ot{QkmO4dcM*g4gzf-OBO;fQ@i)c*yVn zbVZw%9CQjW+%z7WZIogFTjR9d>A^IieD!;%Uix%aSHc6yHpfNxE1oPWGkJJr?zV&p zs|55q0!9%T2znLzh8K^zlfG;BbA&~I%oODjYLk+r z1Y&R?nyS7e6~;bG7+C5J3`a+;N6+(;*%f_~m=9>`ZUo zFk4s3X-ehqw=E==!iO`D#Uy`TeLP+>lD^IAo?7$d0F*2TfJG?;7^|BF1K$2qJShTA zaj~da`*ig+^nVLg511lCTvZB>bo zxnQj;6jlfoG6o*)2|hjwija929BRbWzwWR6F6Q}?vYTQYol}?oXD%?13Cvz{rMpWf zNDWPL$3RJ#hkL-7GpmC_UGy4A1E-pzz@UC_fB!H6Q{V2U;LWFUXl!pE91VO#eL7bW zlIb;uxlN_w?la!lAhz#b8FkA{BjxYn`qZZX8?a1P&Q#dxm!kP?YL;717>KmRucc~| zW58b#LB~K()SPSAVlu0LS_}ryHt8+Z#1=zLu&HDaUU|B74Wa)d5!TXI<)NyrU+J5L zw3R<)5^4~<`XSJV_8jFwSsi<6;*XJtV@+(T7@CMZxlbhD2+r5#rG;p^RTMpti0T%$ zfNh{1?t+N_Jw4Gp&9JNvQ(}h!ya0C#YD@CSkNAeTg@!271WBDUxOj#db+qBPV&2;M zYKJVsXEM{OT*;3rLxYjiG!Oq_2MSE7^Dz1pTWOzvfe3I3SnLoHZq)L{s%@ZJY2M~Gai36L#U&L0v0 z%!XU9XL-UZ@Z|BWqPKn(&@5Sx0@xrM|JGLuvqt2Q0N04iY$c`?!<5*htr$xglvNT# z(=i?`UDzGYhp zJx^Sp`;R16CC;gI#WIk@(x2Nq&C>-wweg2*I}<_b$*om%9ju)3AAPRJH#|ly-n*?` z?PgvGFN4gM0_;EW&`zPEBz@Iv8igd5`lx(4&Bkogm7=z~Dr~`M-p8WU$5-y><7!?) zDtlnQ!$CW+K?kxSW%q$dkmki6c`? z;h6UKuBF+-F)&8lE+S=YB!^6D6xK~R<>u?MOP8LKJLDwI?u_+Db#C0{bw7|oW>stJsf0BD}?v(u~h&7mUhv*=R8cMBb7;;aj zZMxF(K0uAnm(oZ_Ji5YZXt4UNymh+kZz_$fEP7{-L51aj!RDK`04Havw78cQ?4hve zYjDASdC*HhA=UZ?i-npiFrIp)lK=Ps$a+S9eP4V)i$dyFOrGk>L|@B_ga!^X&E&9#r35Hg5mRl! ziX3J;uReY+fc`;!Np8VAfF6O@T-#|sWWBCb5gBPdg@`*u82+#TN7lUa>rIY0cyT2ndNgn5r@UXF551b-qQ%_>h$<;T-w#LYXr7Z_& z6m43dW!I$i@6uaQJDEbHg=rG9@YC9`-=u7iENsx!=f6-c=x!^?^BhsSLoDc=*1G%s z_H4$Y{K0JBRCDKn05>{@$jc#MI-tinXPeA$WU?2nN0_C0o-rE#@Y&LLE}aZb6MXvA zFy8l6g{-kbpv+PwG;pO$v?QVi6JYK!69!lGuz(mlc|BoPBRNXn-6d8P20CMqV0C_8 zfjK>eEKPM1akYLvTx@ypG_IcQ0~(QQ_3AlC(9>1<_-(HvOhB#k6gQWFPTgFzdHSIM zk?l{a@L|E2k?^S6tLtAPs}S~cKUWqO9A`P`kp1+;xj1F(*a!EUxqs*>9~9Q3&|(8J zWer;82H!inNyf=c@sWoa6w&*jH$RrIJql}I#1tsX8bEJRwu zm}+Z2;=0!_LXx*?rN@(DF9fIUtJQ3n?du;~e*H#&QQ+^($pf8i1;{taPC<=aJ7Uu=$>t+v^@bZ6t2w`&<3D?-N!vVk6(GY-g;T zG#xmIzw3+42zIawJGQ&KH+WeFU((iJBsI6JyrNxH(NBOYcBA)0qW-IXIx6X{!g#Ou z?g}Ww=yZ4a{J{MnL$p-tW%2Q)C#&V4y+y>NoR`X>iLc8S3VRsLnoA$%ryPU?RJ{SQ z4HW_XIbAPvSVl}VGD4r$`2Pb*LAJhHyXLEQ$vO`%fNZI1);xf$dR;Z67TOp61h}jH zK5Ec$kOA7CF(wZLq>u8~s$9A>mXt+?iWFW5a-qp=L{5i+X>;zHBXjeR9L30GcFTyJ zp<+M-*;qh`CPFsxB0-24Av9Vl1Tp|D=PDiz6oGM5(x5Qa0yU)$M!-)sFjnAWsjPVH@N zUFj^ZTqS9~vzKKU>)Fe5Hnw+IURl96BZ@SJI6)Xt){z*91_iRn(K<}#_%y~y53xk! zNy?l~p40Y{qn!@hH?C7)h?9h_E4jA4Nz!WZ_V4%(io9f|(d5Iw`CEMK;~!^Za}%A! zIADHumf>*tx3VmIRg9~qHLY)?nu4~kQ$=y|_9D+7F{UIE9!1HwJn{gGofLc-2Hnzu zbHUxsg0TXp64J&ZH`X^ff9V=oA<43W&Rl0@dwc6A@AQ66>#MJ|jhoM=I(b)7W-EV|EU>yfM=biGP0ZOaNDiW(FrWmimq@8AARzk3aF7xqRd9H$-8e`2ZIu?Sa@15pO@wAwq1cs6q^wx7 z8brNVaJ8sdyIOfAMl4?SnhDT$J5uS^_0^AsHcV$d-Z|nVrn|dGnl_2zCcS>i=O2HH z-}o=T%cno{7>|A73tYc>gJwgsyf9C@p#g*R7QC&*bM@~gXe+%PY2a#GeF%)8tAMEU z%_^@TSon*Ip7qX$CIoucb6iHunxAiyBs@qg!}Kk zj|j-=7RDHs+xX(N4XbAA04$HPo(THlB@1#MravYurj)t{>qgtU;v ziRQ$yqeMy|`Tw)`=V6ka*L^1Ve#_lr%f0qO6@UT=g1fkgv@KJTsTM6$ZhExXbjxnJ zXFOw%Wsk?x>Y1MDo>5zmM&p_8vAngUw%w8}t7%H2DN3YBiW@+I#10e)pimoX-*V4b z@A7T^$BkM5f?7#Lk+|>k#FLp3k(qH$L}cD`&pE$eeMVj@g+fr89IX?CuT|`_R>sLw zFS64gqoat$mA%W0i@QI%v9UUH53+_oK2nM~6YTqxlmsl>+Z9VIOWb+qL4=TKr5Q~M zVjWG*iWS~EP;x3qFQ=nh4n!@tE``E?8576TZ7**K!4V}XlSx68v=~f0I$oxf3Foei_{;RH5D<#G$5EC*IpS$I}xCU(*Gv@65>#IUq$0qZ>%hg<9v z6{Uv$&JIbI^1biW(ky)_jrC8ss%YUo^9#_5Q{HgjowPH+6vR)h zTST$OxO(pgfZ*`1mVPga0woi=a|`VBM~JAyv(KI8p@*MgFoYl+`wt!t?QZX{o<4DU zCZjFG;r5r}EPDrpdt;>-_lJ!7nh-BH~!1qyp*jA>h2DGzBWuK4(67oO3wm zrUkM>NQw8a>``|;*I&rX~Fk; z*lv&W{T)`vBUC%3C@q^Cn{+!}o_yqC&R@8M&>Bxb#)?*}b*h(TfA{LutFul|L&I%J zQ?$KGOa_CkBu##-ztdl=_lE+;RP5Qc#G%7?;ElxxSMS+)PZY)A-3?{7B#No3iYr&I zustvorNbd?SxkO)*0DAG@hQuaBudn9l)o?QL`=q>i7_0x`}OSIyNlpz&pv`9R^XjO z3V~JvH?;r=qC`lGm?{-2sSzktP(*s_=q4(h_e4nxF9he#oMUsV#QXZd`t9HOE=J=D ztzx`aR5lPx5W&>{h-EF2@0G+l2~iv4Q}QZieNgfHk37Sfi<=ms*>mVHz^^?2?D2m- z%iN~%@`uy5`0=80^MRPsDmFGoWZgyXzW0rc@|?xxd4`isyodS4MYeagFqI`u5`-|* zk;ic3K>m8sh7i-8Q}Gg+e|f#AleIMd&GE{cqmVD{l-)WKd2>|qhRWd|80PRMNv;SN{wpmVpzox@8i^!l8=A(5q5IP#Lr_ykEp$yf&{0#^hbtkTY_`fEYF=9 z^W?Ejj-J}$sgv7$>G4Z^>4^(Gb7sKE??QC%B3rtLahPZ9<}rGSA}o_LM-q?7O-^1+ z@WK+X7*i4mq*+Yv0y5I5C_<D)51(6WP)I7uOaILR2e)1ixL(g>Q17>A%#Q9Kv9(>v82;Z==M^cc%_9`Nz`ZLzJH^v%bDG*rZyD3KwA0!eMZ*r1Y z5xgf-5!M={if}F^x`BZjqz{h2dTA{0_3nlSYcF9HW&Wj$)jvu+B|?_Eg(XD5PxD9}Y>|%RKP= zkMNa;zluZRrD9=WX**8Be|qiO)=Z75%Cb+VH}_ywls}$Cv5u8wGTg!B+q~}XeH`4| zL---7c63`E#-kBg+9HZFR5TrroQ@yLTRJpS-cq26mr3o5TN`XI9nHJ-V+i#Q+$-I7 zlRv1__R<3F`uE>jun1AqMTklWqRqGnq^(^Xef|uOKY5a2?ih_7QPO2Jo?s^$J0! zcADZUi>)fOj?g-)rw9UgA26lCIEQofxT4lEaU2u8ducXqG03h3S6(42ub9K@`MtS) zcO!dWvll3C>?Q`GZcdzX-4jkAq((`3v#ToVf-g*W37IO01_U~4Q`(57l{?rTmpt|K zMG6Z`EBpWa%3|-y^^KjAuf}_!q2Ui>b$#pF{rBDbs4MdywMDKYsmRip;pR5w`X&cj zZT8R4k*NTa5$HgY#Pulibh}kZ(2)l3(4|8W(U*dv)uz;%^V^$j49AeBcmr40*D#eO z1jVE*>CMm4ZFlm@lposKn(hCOhKAdjrf7Q=nOm3_bG^AA-QF4?yg)~qB#OA}&V#Hh z&QaNtG)@RaXsHO^kw!5xc(kyjX~NFVfEUkQ#Y;`)P?Ov~3hwx1Qd(w4Xo z2mxdSkyadg;TV^%Y*L!Q#8xcsKFEQ)?q)nGDXNNAn$Z#^5;N`Q2(0&%9)xP+Wt-Br zS>Gx7{Fff%;^9nE-4zGC~jcpL`7diu-u{kQZw!XuG zLx<=t?jZz?3l$-hBuPvX3%tv@X{;@gQ>prw7+_DomN&*6Zx&=eynNg-+_>Bnzqo$= z%Rk;K?ePcTUv~HF9_Z7@ri8}^Kz11wDOH%G2yK4r(_iA*t;R&z~V6 z$#xQ2s!#ao7;t!ry61IE11U5@XrTUFO;3n;N)Q%=n@XUE zY0+Mb)d=OmS47f~NXhH&+Q;J$KSD8v{`Mv~eqe67`z!taWHjsdNkap`g)7(29KQR| zquZnWuU{RMGEF<6BG$HtTpf=I-44;pZmQlKB}U@MwZkq-plF($W9+cG^V<);to$t#^3u?zPt7O+kNSmEqPU z-g^H%v=b0+#L_~So$Vdubb(EZsSada2~QOxp*}Ld+1*6m?Am-y;qdaua?6;Xydf|D zvXA_s>u}Q__R=q3Uce|hHOCeyxmCoiC9bRu`0z(R!TF1021QBSnPYxoe&@g)hkkM} zEPh@I@$>Kb&UgOPD_2*4=iG%0FGf+cTomJ7MUfK%#F0j8Rd?eeMOl@Uc?H2!oy2C)IF~op1*=u-px_hWID1sE!b~7N7p}}3YJ{=$(xS5-sseM>XEM? z==7FxA!YaBJ9+fUuQJX97L~?2TizLrK0fRBNJGPQu3XzVbMWwiQ!?a-}H1t0CJbd#Ej2pgrlqD>*d)tZb z{^e>?&`DZs4MxNY_U&1r*NV`gL<&!=6=juAO->zN6t$wEsAcP=h><9q2dPp-tf)dg z!eT>R;B^<~c<$Iq&RrQX0k(EVWZe$CcJErkC6YgOD0z8|yphbeW@HJ;$d$_XwjLjD+Qdj9q(o{oKQ!`@+v| ztzJ60xqkJ+CqMbyYfnG@`03Gb=Yf1O`gxf|AL(Y@Q7cQ{sHILL9V2CicLEWj+Dp($ zln$5>SYLy)7q0Q!pFP2Ef94p!`jH3tcOUrzUp>A~+}=lLVIRi#NLpQ5-Ce{<2kRA; zaYz-TRIREhL-C5S{ZWgyueS>*39oql_3ybp{u)F9!q(4SxIU^G5OUgGlNGLZEyN26 zGOpimA_0*SCkAgzv=XF|p&E~Q;~U<_*5($+k6)rHB@4UuymfikuFtQpuQvy&4Yv&& z8{5y_f8?I9IJf-0*VfjNB#eS*GcQhe0EG7_sr5H{A#jN9RXt>>Jindpo5R?eP|7fB_ zZGamrtt4>xz%CXSI>_MAu|haUE2;O4Vy)0hP&$Q^EiSF^aQyUTN?KA!T1zQ^-MMmm z*0DAG$ru&IQ>`feG3(4Cp~BjdYO=|}eT&?4cn_}FA&w+vKA9E@wQp3wPRIA^eH^Xo z4neG9w1m=?Ov)Uk6i9`uB3hkY6gK7LxpQm{4TQk<)>ZC4bbwYSX$wO)c7))moWpq! zN@KjlSi!YxoBYS${xqB04jC(u30g&u&#f%|RGy z@65a$A+HN*ymHYNe0at0yK$f3YP*9FVMKx#ZGQjJXL#hPQ#hqD!813vWJ$yiZC>5{ zx+i;FRc++?=<_Qp%m2if@JzeiAYz1Xiv9mLz%niB1=phD%q@;fz5?AgH|v<2Zugn2aaH%6_fP|1I&qf;i+$CH|)--Zw1~ zYHk&d`P!mwYVT0%{zE;Se8X^C1PRoITnGU|Ab0^Nl!&Hw8}$P#%N>#=0VybRh_f!E zGUm>E-pa8Tu5#+^6~~mpfL_Zkt zfkTH6V~hN+=c9aM*0DD<-0n0*+pCPVdye(y_=khMNauUY6q9+v$JX#CCu?VUF2Y|fghK0- zonc9<(`MhnePr!~NEs@#LzG2G;c6W`ks@SDASICCaCihxM);tRQjtWU8IptvQtja7 z26rF6mlvKt$@T@07*!NIfmVBg%i+<%s!A;$4n47O5l3pfOToe1w^Lqe)0ZvJoapoXnLevK5mDz5rC7r2C4yeTiFt}J zMOU^A@O(!XU_}?>mc|S`zqaJs5%^P~k&CphSxZ9d&R{GcG(?E1}G{ zNLvNt;vy$cUqK1OeCNQPdk(*0^ZeDbjp=s7ZACWM`(#kOLv=g%4vLCWM-XXt`UUGd zBci!^vV~o^I3t(!PMdIn$XFI~NzyM#EV#*ps&cqUp|qye&WM$!zi|;&T8fFIzp)9{ z|3EK_e|=(1v*+2+@CVQoZLd0g+MVv6@o4<+R+3`!oHTaq-95*?Jqu(Bh)~N(r&>+V z=xdDk1etc&f)_0we)<@n_}l{+U+?cYYsk{<@s+)sKY3d= z*=}Ln-`u(9p8Fp3-u(DrFwnsj1YdCJ{3+7Na^F1%!CTVE(~1<6@fHq4r`@5ba-4It zdrOS+5~M<+2*|o*lePHAb?Iw_zVUlsv6J>PkKQkN-9@yI2`uRAHKN3RN zX|-DWmX?+ljWL+Az#71d`nBro5z;8C#XQD&!PU!GId|p)&pz`aCtmm}N00tKk9_$N z5*?_@30euXRtRBH(v!vkg2h)AA-KBJWj)S&ln|g)y^km(Y&F7KgOCCpP4{h_B+>~| z*Uz04wF+e2r*_kSgmo9xt8}Ui36lDxS}kT$XIlgXLJ@A5roU7LG*TK=&-5uu@Y4O6XS=eXbK@2J3)KjJ~r}-!Gi}cOC>&takvmDCzcBr&(Ui4xbO9EVKgid(jb*a=nR3NEDMq(B6tfS zM=61#Rt=QYzG9N=er(tG(QaOD-D`WLufO)X>%4TdFT8AAQiPXE1>@`uh5xI;W%2Xrm~KFOEi|e-X#=N2O$aaeirjZhmoj zacQsgUX$k@M1t0BN~4f6C3wZ8vQz}NM{s3BaBi*Osbd#7dh|3W&TevT%agk~i1tvj zK&3&5dv1=?=g(3UfuI$WvOwtM z(7t{9H+Ocno6d7X!`GoH+Gaqjb;t3Ax%9so^#?MR3MmT~7cyRVWFNh*LIg*o07 z!;!mLUWlP8h?JWijD~5aVA^pLsy2O=3L$i@Zl`OBa;SIL>Vr}b(HtsTBu)17mZCpZ{wKvi6#+iCHp*WX2`hA0tGT2hr`1devrW~aYHoMiYQ z@uKc<-Z0Jv5CR_p@)eqAFW;fMu7>#X`@F(;-u!;TOD-?{-kV-yw#td)dOR{|FL3lRy9oPTkRXP-UJiIdNB?(A{SoH@pey_6+o$rw-Ba9oT#TE>Jinc?*fKM$9V54twAV3>t zNA6o*nETAy#<#}FkA~L{=g*&Kr{Dkha<6wF(ds*FJ|>fa#0Si<&vFuTV9zp%wS?g4 z#ERe?i5DRJ^!*?Z9<0(-NyL^7$gD$^#9Ub4VBjmP7g%6?l}CsgPoU-FprZR2wH3 znD~T#8TkDtp5>Y6uOZ@$D4VC(o!d^*_WRe?)@GfuKQ{9V3m>a0^ZtHW96(8uj^x@} z#n!~oo7+okzK!vc!Yd-(0zfMVOi)4)$$-S7L_L05AK*&@s`kXgecj)Et91PzbluI{ zzP^pX%kFu@v|Gv+HYiL;+3ZUmd+q{Xc<>ZPq-32%gp|M1AM8Bv&CGGX-)C!Q>&$pE zdSEme{=AN|Un{EoRJ%Jr=`GCN(Oupb86GyZh-Z9zw*66gHs>ZG!AEDFsP;0ij}w%914s0gH3x zbY#=j16)9S4?CqC!hgECNv+ z)w>m16Nr$iO<4s_oW8=Tb88H8q>}^!p9HSVI=GR8sosZe*>9XhMXSF)m~rICz8$ z7te6!bRQLYc18pCEYET1;C_OyJ4nwTJI-TI9%XH-!eHnvEYa=s{;RKk^_g$Uf2{p} zpH{c?`AGO5R>Ae8w3I_h@FUKgdV!^R%{_PSLq`G?3_==AnbT=y-~vJVS}TO>I~%ID z;}F+fyUf?@t-XBj={jGdjQBPDVYk=!x%GD_E%68p5#vhmsV_dtSD)KpJP8~;d?&Gv z|NiR6>Z@99PbPz{vdkaLCxhP@4Bqmyt!(qxyyt~9OGfQhb~sIwSSii+)+Smzymwez zVT?rpX&PgUCJiQ~`m+_O(|>HJxW$q+3)Q6z}9B91|7Pd*-Fs*+Zkpe5jK z-KCb-l|aFVy5r}j@}NP8sq$o~J8j~6SM%ncp}(pB2|~W?cF;OtTs>l|qKKj_NLwwo z2SeK36&!6^-9=25a{Byb`hzhps6C4d3!8({_*?Ql)$r}jd7!tr_+ek=?+D)BYbFyS z=@4a(+1g}Rx6Qs?E3~D=+XC$!N(6KORj-fU*r~39V5Bvo-6dOG;>y+z=hwDT?U?nA z9h~F4_8&O#Ti32#Yt$qg8eT?Iw9SY#Y2PiS{@!3bQRI=ra_1d;xZ~h1V(C%Bp|QkK zeP&o|fyJX$LRlKFu5NMZ+A3bAj4N9aso%$i7iJw-!=Il0`}c1qN&MH!($P*A*xnwK z7nZlZ?VXG!6;UMWT{aPrqTXZELJ_MNg~o}x>mtSVPMbiA2q82|CRjU0$sAK`vTxrq zHaLzxdlFl;821aD&e(O}Zd7ZL&0)o-zwju>&s?QKqtlEyPEU0^oxky|Ji;;_k9QW9 zc0FvI|FM4Vv`iCLw+c*1xwN{?@}2{97WQLZt$OCHKm?Cc^_k;Zh9m$UfxIz#>hUNn zaNVN~ZrxR@i?sSucW!RoX@lzp*vomPJ-C~G2m%pZI^xHv8up47Rt%qwOaqqy9%n!?*tI{7UuFvMknx3d=&PUE|#1qypV#TnPq6 zz)Fo18Dpy%6@ts_6)&7#=h3HLy^D+-$i0==b|9@7ecZ z;`qD6!ouPHxTHd`ofnJ>i;CJT?b**rIz$jS0a7bsB~ihn2}Hr;@(GKvCL4~KZ?zyV zS-W_dEJ;|~8iG!eQGfXEmEF7kr|s>=$*7^>R+^%1MyxFDisLl>o8@?d4-=&D9KK_T z!-sd1>VOg!jUkR|Nopwt#!HmSh|(S#n?sJBzR1Q-L1~!a!n44`vyQ9bPtV52=J8e* z|FE}i*Kn|dZ~zl*vBlfp_9iORM}(ptJDIB2X(UQ%q)@ftv#d>?H8)Lz5ds+mXi+O5 zs)X^_(p}igQ%@h`>g57!4S7}3n`^Uo?_O3{*Le7mhuIkvSTAUG+Ndc0spH3vf6I3S zhQr~dg~jDZoiRU}k8|Da#Z-C8_C}wb&C}d@*CCeX=g1OIBpqemC)U1J6chr2udNqQ z(~r|m7w+pDU;h!V|HBj~|FEAGB?&IXl-~2m6EE=VAAg+OdKQ=VptbsijqUzzCR%i! z!S=>lnU5c^RrT{#X+IJIJFQmQ>-FZA_aE3RtI|-E6Y!E)du(N~#v$=IvY0Tx(55VNq%^eBm{uyMyN4d{Og$o*?( zR~K!73-vfGH}ZeID_4K*eLzWx_W|!6Ns>^O745k>#uLY+Fl3$GNIj3I!}$wW85fS7 zkx8Q{UM$PvcV_*JG&H;t{eGV;&3;8|@$OWsBcdwFf~Oh{sro~9FV3@TrAsOT%6n3% zN6A9~A8^Ky_1f$dhB$7qUX&Cn;>Gn%&Tnj!v|9`&70%hEUUz=&4R3nGljqN$pLN&` z4Yv(V(KaIv-f^T_T-x=I&YwF&tUXZ_*t2_{`|mwOsvWH)kZK82`Tu|;Qi{SDbQEKZ zr1G5yz82bC&kofJmmfQ0WJ45s0IheC)XA^>5(FeRt!mM+%8?76k~e zNK=h198se2LV<87F%`%N@`mAXkP_So=L_Pb1Ti4^h+Vr6@Yq+ricC68#uF-+u)KR8 zU;6TsoW8Ki$U0C8fp~WH>iXZBb$-5442Q!rOUt_+HO~KNZqUXAg4C>S<-B-dhsBi@ z7MAv7Tp(@D6MUd3N<0psBcu>mS0QAr*6D49l3}_p#Z;W4zJzH9r*`6=cH5YC+oreQ zxNq<;`QBT*b7DH)_mbOhxs*1=Crb?SgpYjuK~9`oXE2c*xbuFbi2pCw)~?;|W7HeK z7pv0#;^N}sPtPsPJ%hkTvA%0C>Bp7#WbF_tBH zz|(y8p%WZEG2pT1uJQZ@LulR2{O)@x#1iNQauZQi5kk(PMS>S8sR_*k8=gyROm>svU{Fqo;$(C3u}}j zMhJ2LT(9>tlS%Vuyy5m_GMSLZ>96S|djC#Y9dJQYddazApYbTCx3Wx}w24|>3M|`$ zk|d6(RK24st)(qBY!B#EmOV==%*&Y7jVt7nik&JDLXb(U#VF4|ytTD8>yR57ZX24S zZAPrET^_8g?Ek6FwRN&I;GN~b{(0`d?@l@`NVP)+hZG>C0}nw+92!I$VZaYQEcx|rmr}muJ|TpcT&~|oDTO6r zSOiRn`PKjMF|Mq7+Px*xR=e05Z2!G}zd!4g|B>nU`&_+z_4v;A_Q$l;KWjq}S)3kj zx7zdL$p|GBX%gYArK${(6col22p9)tCE4zeIDhdPN1wjHv12dr)T0ma=)=Fy`nAg} z^g6^zL=q{IR1rdfjzV20BM>+OmMBtaRd>;}rtV(LsiLRwXr&O-Vmpq~X{Y=8b13mP zV64Ntz})f*&Lxb>ia48R*WSB%;^=V-tC>u4ErjToW%Xkzsxpkn6*`J>p+1Pbbyq3?p>_&J0a6+i9;lGQ(9RY~;+XNyI%m&b#7l*7 z1)Dn~&^;=zutw1Cbe`Q>-}t+;&dWE0@o;!1O;Zu@Vq#B>Iz(lVPtuv!I5A7LCQ)gn?czF&{suN>G<-05-Z z%vsJ2N}?#j`ye(aMi zh=eEB5+OX^IfRUmI>k9fk}Y!j;s(#WaF)#i5iB|IG2l<{nA>n$Feyf-m*FO@vt zO^Fbet&MedEoJQ4wSZtktQ-nYx1A74iLD%3)n?2HkV;NFFao6nQcHrWriSfc0w8+` z)g@~$vpw>hJarL|LTHVQS{UzXwOe$1y`SjsY#p0*UcPDh9*@7&%CdK8DeiTZ!+JxO zNhaeFC!c?gGT-El_Z=pSK~R7zND>9X;+!LnW9s5b-jLsaIWI5FUa5=rk}jQhTo)a= zwaW(e-lCi;Rx&LVy$@8O#lr6W96NE5Pk-SFyvoQrb2!JZezT?iUoG-Ueyq&RhjkYJ zMy&K^yWO4dw!6#oa|<$*j!9AAoFz&WS(=e#8A4i$QAwQ0x(N0j5dvEqh8Lc_#tSE2 zU^uyoH3j|QHc~j^Bt~mVqy=%LNRkMtY8n54LkW+P0lY^^MV6*0Rcp99@26w3wU?ii zl1SB^@E`(#2xB$RJb#YoPF!MjqeQ3}f>f^w*S?9T-;SXYSJl@&Avhta-nEY5- zmFZF|qcAz4%$aO&6RL`K647aAwA(2ug2)H#aELSp9VJYJU?L?|no+efj$hfv6Y$QF zTYDM!?5yK#Xt-TyinbZDu(0cgM}y&EQw~Vtz+H#-aR2KL(MbcDwq#0>#sW!+vn5gs zq|z7zY1-q|`OCa;>MX+o;VcgLIB;>+aW#D7Sl+$&$;!FEGt3J_tTEOQMUs2&c|ELsK>gzC3ue_Mr(n0l3*)#FSbz1a`N;s2BW~F za_o!^a|;JKaPZFcxwiaU-^z-Se-w7@+Wlc$+V=~o4;Tc6t^L{trRVg;bxN;!>)XE* zon-WPhTsKpRPQ;pB83(L=RE{Jb#1=+xd5aP)9b^H>u&wJHfE~}FcJJK7I}#2UfV4l zy}AQe7jczZrf_Y({P;<}^p)qx%{;sIA4Em*&una5J2UGXe+k+iOlMzwW#yUrkF?r#rLWCh=P#Q#Avy;QKCpP)S z11EUwxl5d1w``75)`l%s`-*FQ!RjDltD3__dvVbo%CLlC0k2lbU6*l{;^{K+ZF1Y9 zaBV8z!_dWxE;eMuI>K2&S>?1_F)Hrx*yGPI85_!xV_aE82rqj0%B=I-(C~+`*6(jD zEiFA+gz%#mM`InwDMO*Snirg0-QfAPEl&5xtV_kFZn1RWPPPh5J}B_54!+f8lBNt= zDdRNb?A2{HHaF2qGqGj>{4cYPv7zC1LC^XDyp}1;s&K&*MG-<9#^bzJ0@RWyiinj! zNe@AzwMNJYhe9bu2m&3)NF5`TK`O7kW8bV}YWT)->eR8Lt=8PXwATDYmc=+1xOC|f zFP=Ta-i3QnQH9hc+nWPAt)#YyaGoFperob8aG(Uj)thXQ6d3Q3+9E{+LExMt%_Mi< zc^7xxd6;vT&XL5Du1M&#GY;Kx=;OcpJKwUCb$?{epFhv;x!oTUiT`3*xWiEtfvRv- zU^sMq;qwo2a3$pf-}g=y7nd=kRjdzaA*fv4VYxo4czxIGI)9Ks_N^n7@)UD<@#Rca<~|2}ZneM4 zd*AzBF0QV=(YWxw<&{0}UFa>mRZ0%TNvwyPJFKs-p;QH4Q%yVpPcPMYVYzUAoAs*? z^YB9t(@F!KR!Xnirq|XSK75#a?mWoS@*I&C>|X4!du0KoG$Cjl6>%I9B@w{`NbmuI zBRGc>ruKgmiZZX*yJtUd|BiQZ{_-OXV~Z{&hYsHHhLh*c%{rwG4SysqtgU_S&O7e- z!KmN=9q)^DFezC`6wU>%T)M*M)vH{(@FI8aKEU?eG2VIL4h}BP#r?L4fCg=kos6&#iKn`|p1*X^;22cKh86Zt?GJ4+hI) zkr1^aEKqoljs=y-+3|+WK8P&~p+toO;Ut~zDtlK>lch0PB00E!nY->d#J*iC>{@DZ zVDCI>8Y3c!wHAV)l_qqum@Ji;Vhx=*@MDUiLdB9F`rr?7{Mj+Xt&;cOS`o zkGynTc4N-(Y_YSmRokl6e;3WH<7#NQooI@-8Iq6lO@YFBgYbcVzt6bH386l@8RbbUI=1Z90ZA0F+qYWJwbuin#vTk(l#IXzz68( zbNuEfzRX})Fw7l;!Qkz$fBkpf^TG>{o|<(&UJX`PSI@27fB);3qUd*omLGWW)LBer z>FU6EWcm1SeUg>T@Llh^mqY}V5`+LsD{!xTobmM(C11|<4qXTVv;63<9HAE-FN?Cul4o9 zv!UTzg7fFE9NoQp?t3z+KAfoZ-PR1qVuco((w1aeadmr(rv_VW3OLwXBFfqfflIxV z)BP>-$%Iy?MP3?DebS|&p6moX?$N2RUfd`1SJJayXSe^o8H77yE?>jh;S34 zBm(MPw%h=j9N`egaxLp7dEJ>Pz^9VFbXta)NKPe_i3RD=Ar4h?Z zE!M7{VrOlKL`Zhl)IsxLWNs%NUL)L|F?PJYEExa5y|R2pkgY>mm#)0v;#Y zSR~F@ST6`Fz_ha#=@jG$M8%|(;u%5EA_z;0i2{NWIlENvxipiZ~ zr<80Lg4L1WazEz$S;4W>B`3}lym+bL)P)I8y>OK;J$aS~9y`M$&u?>fE8)y~#%jL8 z#wg?BYQdvVKgNkOr>JzBo#B8B7cM+G9*pYVt`mX>83 zz*hn16qU|c_c70n9AB+GPY(;8?N2y!ZJW`^P_$cErLfMGr8PeVH166B4O5z;ZH8nz zp4XB3n^ieMD`<5z?|ADy9NOI`)dk8|XeA*8q?DK-kTS+vj}R#(Egpa7H0Q1iv0lln z)y>N?|D{>S)$q;Xz=7RQ8e@MvO=FF5j79@uS+cT}amVfz`u$Z}aUdd0#VRyDIQ;c4 zjWZx5a;k91bZ-g~YR@TA3zrz5&|5q}QAnOTdW!zYW1U7s;tlQI@-GjE+k;u>;?<(x z?=#nKe?}?w!(~y-CraZ(Ni}xl`4+`^lLPzaS(-};zCa33x2-9Q5}_sSc8BflZIX62 zmFNd>(_J)yK;EJ*C{RKoghNV65Ybf3KTNx8^@yvGs&)|b1xhH4b09QAwuxJN`1F^a zdUP9DVu; z9{$RsJo@M(eC`Wh;Q1GhGZ_VB*5%5TD+~sM3&7`RotlP*KMCXU6 z1uZG%A|UfTClHXLw({_{{+nlwrBo8<9C=wj976c1S;x`Pa68eAN6ru>WKoNX)Lu_{ zUNXr`LJ$ZPN<}DDkK_aaQi9U85P`$dn_HmWZV`f`-R_bk@!{9Z(e!JOQ>V@!ou6O) znaSAxv@)JfC*t`Nmsp%T%8@(w&|Nr$h{jaa7!fQH0_SVBNHHCk^v2?J{hCuD5bC+F zdi-uGMyQ3rxDns+<~w=kcO2qV51hv^YbgPdOTfgthG~bDbG+_vN$22 z@z#(#hc^}hq^TxKVY~9IPYmAmQEG^(040gzm|{4@dk^sLS?8gl;TyoG1AMyhp2Alw z>g<3dbdDT3^2R$49{Njpo_}y{ZFTbO*tbNaO0@S7ELsZ!j({W53Z$QQ za&F4V0|Z|y=LH`K-XY_8hGWOv!Y&q;_H*joIo5XyoQ%kN^Y`!FS8Z->Y&T<79~7!9ARxmx7)@URDFe8m0R<+ zBEklwQ@Yudgb17NmXt=iMH&R8OS)6Kk(TZ*>F#dHO*icC;hghc*Y{h0!CKF=X4cF- zGxr^OjfWw{CoCmI;Sbk8MLS!s;XWV_8Xst9(}tnpo{HCD!r@$*sT^? z+^)+2(-r|~SngfyfLv%dDMol>Af5N#4mE#23S5EUD1?%0=|2X9PzMPmJAwy8tn0L$ zTf%S6sv=r|UnOmQl<6C8sjb}?k->X^`Y7`h`zetHY4^dSw^4rS0N(NlR#8;6nsRAm z1^l8YuRF5w6T0oQ?ZHH?(vfgXA|p80biXkv%pBt|_3mvk33)0~c5(=_z-3cw*SMVy{hJv}|INuZla82T7E!SR?l=ypw< zrkzwLdw)Hyt1D8ws6m1mgEKDk$Af(T;+I$oyRl03$(M+U;n)^z~P&NJK8W=irKUd>a&Uz z!`r;duLQr9L#bBO+q&39ZFmxj^6@TtE$uH3FL-H=%>~1A3BB%~OZZfv zVDIDNEQx7)Sc_P>V_TcK%h5CJ)yUhh}>Sh6o(=!#2=39ICrE*SFzNe0QuIu0Nr%iGoDa%u>3M-tp)1T$+ZEk=TydSjV z)@nz~2l7(;D~2n&2>DB|P$aIYZ!c$2xabcYEGVV|beNTu_2@vE9Hc&|gnpENNCTk1 zxktFanWmL``kYRv&)6`td~0j06-ad&(EYUZAtTT5#?JZL?RoQU>-`g=(a`7*@}Tz} z0VoIV+l=U^S6mMtO0HeVgl23`F^GK$KJa7Oc1_JAK2(E$^`KLeg4m;sL;4|fgLpUT z*PdLX8TGx>z=##ci1g-H&y~=O*H2d^4s0mZQi8cukK*OS2HUS0GNJ0!F% zZ|*&UL=KXrr6skrhycATI{w|}{s(oH)dW6JaY_2Ck(Y1oJ$^hsF|O3ua~~XpkC}-A zt4r3Rm&)v;X7VIl_zWRm7%g{)a6*k8cwZhQl(`)8`y0GQ^;M0W5TkgAcO9CUee+;S zUhORCkq-vTU0wlAo3fsL{5&+-S9i0Ks4$ky8}v!`&E0F=FJk6w=_;988gxhkl<=%0 z)2#*_8zTO8a3a2Ogwg2z7qo@EqlVwYGaeAVe)2Q_70Z+ha9r0{KCoxFuNA*p;>&XK?#~UF<@fzUW&C4`9!r`P zcV$uQtr|OtCa%*uG2DozJGevB_=OJ~bwJ^Xd{#EM{bJ7_Ts1`BLCYVP!!ZM_@af>e zN?5_0nyi1;I%g(;j)!5~iNzSdfS?y29K@MOgKBJom67vauV|N)UX!JA1-&Tckf0pX zQn;q%Q~m8`O7f|zxa;X012&$0m_S+B+E2V_( zsrI*PcQ-o2m)Y$Ip*a1HFl2YIMGc2V_RR|P9<7mCSb%{ZC8Kw|6J|5xnuJ?iygRxl zf5qRTd%c88BX1Uj4RNX*#V32fqEkrkCW@5D?sa9u^+Eyz{OXi7MXHE?ecXZ9v(8KV zORGf}uA9#Al6007svfK0U~Z2)jttn+tCBLCXmLp4w(1&1=FbXC^XGN*D==A?n; z3-eoLQ&JW$5RFn3x%{Q7XUvhXWMddA#jtx~sDoKK``kj&aYx=Z*PK^H6s}L_OdmWH zQ(ZTwE1|K=5XiwtlD2d^(5V%EMlak2<@!fq?BR*jo|vcN-@Fh2`M!wk{sfh|`z=(G z5KW13i&UX)LetV+_LmYiulImLjLN%c`9$ zi>D4~p}5tP0j*vx1))`XM^F~`FZkGblQ=0+ys6i)6)Rhoj9RS(Y0Bs+cMxU zTrL5H_qn~J(qSO~cd{+J@=XqdL-*Kbz!Uo=y}s~5-q z@K(f%axr3CA@6onVF6vOR75(r-Ro3qBgKyU2a1YlIiUza%}w;bvuemzRFI!OrV#_Ye*W)HJm%zBz+sTj|*%n=B@9fCy z-Ui`9fUfJ(GxYj${@c23s}Msd`s*1SAlRhPpvI%%(~Kec>GTn`{GD#07p01!(+q_* z^Lk8JK}J)%db)dr5(R4+-dnc3ZbD)5UW9Ig9xO3C3m>b zE3@&xxvVy!pO@7!Mg5W!aSd2sTUZ48DPY|t=d7|L=`ANI%eqiAKuPozVc;`cWsD_u z4~yp(g~0h=8qOnuvTc%H3MyyCpT27{j=#n_GyR2-uuoJ+>z+Hk7esWa<8?X89DXxtsf+3m}pN8oiP^fM>c|&4mo~GSz{==A>gf z#IrTw~xVrPV#H>aDUsxuEFnjhOyKJO7+%BH(q z(BCccLX3j5{W;`LTi>nuUPEjSu;Dte^UFU-x2I@DtI$hhkV5(M=rN)tK|L+kD}$>K zM6lOTm)V2pkngz)f4tm{N2B94cMTJs%hmcs3qPH}TilZKC&dpD6bmBe*#vo74l zK@L_kxBu8b-~oEafkxW`^3ii5%2zVNF}3-+R_ioy@XE+>2{=9Q{yFja&D(M|`mT1Q zdGNjPQ4DELVyNW;y>VWGvQ^=B0$CgwVZ9-BN$(Avkaanw1UI7ODrB%=j{M_SM#9C%x&Vb|lV3+!+ zPn|6(kwd(Rvlts!G3~y7TKsVCbx#IywhD@D`~v|hesO5*Hlf;^_fDOi}D-TJ(qfXU^6*%C0)R7g64KwM1|5iJ4U4ljY(I3}PD zCr#?I_VVsg;MV-3s;UcO$1I&`u9}#VqfrJbIf(n{_w=&DCb0;k#4U)3i2^c`&L7H# zfayPOcc5HQ<$~~Tm2BE)RkyZIE1s!Ksyv5p;Sx7^x_))1L8IM1gSaPP`cKyPJ zXCt^LDKO)%vcCD-@oWUKt5u}*)%~MGu)G&m0YNx2*3azE5}K}_kMAXKTl&glMq9$q zw}m;kb-#XlaiCS5&AEb8BNcWIZp{7eBPIzJ6n>vQ`Hn5W`-iJob$k3JTjH$580y?aW;vCXWZ57Q+ZAU)gyxykH2#K7!XR^L)h`XG0(vfD5 zFv?W>c`+%!G%_+$BKjqXLK^FFXyOb`8Qe<+al2w7gQZ0R7`EBz)EEC1Szi^5{YOqq z<`+sOm@+F-NoEVdu~}Dm?|T&!Xo4d^Xt{5Qx$V(m{dzYjB}`6Z%9wq7Y4Hx_w`~2F zPj@U-H~!Z(cb409QuG=vUvH)g1p4vYZphh%aybAM^}paOraqvJNrvA4%_N#Ovk0~S z3!Bp!16kSPX~R?n$Wtd$Z!FRanZHU(TcbZPfHvi)|8vk0y+%jCu;ub_WEci2jbkED zX1srUzg~|<&cz~!69MlR$SWt{uE?go@-^gRFe6d!dc*B>`oMx;>uznx%pPB2?Lz8t zMyU5N@F}$cLMet4M-7SI5c9`Q9)NuQC3%+W0FQ$gAZqAWB6y9p$(Q#*3R7_+T0G$u zBgs=|6LgnZa$zVKmG*!R+?yiUYB@U*In0A!MJqjpPAlzbp{!!Ohur1!qR?f-e1zq3 z0snT6E>d|x=g$_oQKIqBoW)lukbqbg>)%SVAJs=HE0%htJ%M>O>LWL^E7GQ z-2XbWT1ayE)16iy&$Xa8JPRwXO0UCHCoX4QUDB2jJhFFD(Y_7FQ^J!O2}l zAo=T7-3k#D&EmKaA&&`iGYf-=>*ihl!+Cy0N&{A6Q4q|1G@ZZvl1=*RaN&_kGGeLE zB{kLwyTmy+dUs+TQ}3Y@dOr1DfeL^&*D^FYD=B zUe`?4m%O+QpcQi4xjhYI3}5p0*OBKhBLN`@4cpf|I6;Q-0p_7luUP}nLzb039a$s1 zMc)Kv6qKV8OV5vIH+!3rZ|)vpx*aLun8w zvM;Q{6fh`-E#*M9o@jBVSR8qCU3sPLMl7kD8{Y#IGVQ~BPZV6CEF(NS|GUF{&&Eo9 z!C@@p6Bk7dkmN`l+$2OG`jhX6szCJct$Acvd-cO!W-`fUYYg+`{#f*$n1hYXRjSdT zvcBEsbGUGQr`sf^NvM*GsXU{zgwZbi`{i5RySqYh8#q!Q^x4pQ?`t0^hW1aIqD_nI zh@NYRE(hAHA_BE4YEh;wu!l4o_eD!BIjpt)h3$+fr86Wk|B%Nf!qoLb|DH6 z$8@gye<#gLI~a0RAjE%OD1XG|d#xqqd!AI(t~J_&qE63%0q3q?Sri~2e!hPn;+$5lib5Y@gqWNrqA2HZG8}+MN+)I zL2PO{Zs_zKNvOE6{T7{8v0>D94+q;wY#oAPbhly??G*z`_FoZhDV`z>><85{)%qK0T_xh9^rzEPM3ecA-1*0 zwU_rE&FCN+i@L_Sfe(n*s4uFU078ceM^S(8s)PxRblxjUI{8#Xv!+Og;~zHKa?IA) z@HdJnJSdn^%Gs*^idYKEkJDP8KGM2C`zyn!rROwSI<;5M@K(;#pjp!cC+}DZ!j6d} zLiJLd-kHj^d8KFeTvzj^$zp4>4WMj5BtSk+Kvnqs;eq9ePl4}>`fm^egaLZe`H4|c z#!$zZv&9LhhL-8JgcP=G(ZZ*{`esVShFx(3=7DATI^i_;pmnr;JB2GtG`e7ylWExnq z%@#ZOFx_^8hIWkS&;Hv?{fEl_{c+RzNJf&Afa&o5{;k7c6TVk&d?BSZlEg~Rg4-N% z`>!ELUGgbP+a0+AUxU2OsT`ysa$nG2g^otQ zJ#M%!U!K-|#^;0AgZ6OagGQ>{X5T$kwcX_qFX6KhghfSnqnt*r-Vt&wBS1VK3MCT~ zc05_-D?4w5yjVqgkQN`2dR<4-o4N&IWL`dW>EBb+JTuDq&LDnv8S(`^=Ik$t(;Cq5 z%hLPeV!V;;fW&cQ3m8cJHzvN>c@BwtHJT)4f}LNU94^#^s>NU}IlaBQYu6lD0BO>z zF2491)M5KkNd-hPrX;4Sx^?R^L-AqB^iS@?Jr5pHH=Cx7PmMu$LE8-zQEXU+wy!a| z&`Z@XF%U92WEyg|~7@Q4vZ7 zI0QTxQ&bdW6t0M6Nh|(uN<4S{OwvEYWP3M# zKNXapq>w!ll*$tsp~NLf(FKh`;IhhQ@=cH7khu6?mER+UEHujn6(Z+FNs(Kb7)NiF zKmOG-xnAKsDl>)b!+BKXD}xJN+G+yraJ#p?HLUM?Z$O*4XmS{sx(p90!~pDqj=MsX zI%+4-caIsWDNT^RHC(Q}KH73lip8WVuyvcGV0-$|p!a<+OSDS9&#Gho(+->BYBr#{!gwC)e$^|?Z}P33GVINzh7zrm+T zHJW{sWB;s^;T!<48rqnUL7w8Zn#7bU+|M3z8&mREMm8EkRh2$?rTiruEP@=aT*Gb8 zjBM~6-dTl&Jp{&ZEQ}9Oh_$PvE4Xje(TBhp5o`I-7dMiho9?6IkwZ>iViGfW<6j=A zB6w|@F~a*cVymdoXwG!pwcoE?w;TcrP!@nReKQz3SaY8T)PldQ1FV{!@8lPtpS3=J zFPoO3mkSuf?COBG_w{xl{lGkz8>e9ztt>BCZY}k6zn7j8Lr4K{NBr{A=farX zx|fPbH~Ji?U&?Buz^oRy#dJq1-m9fTs(R=I&RNyDXmNydv}NNc@~C_K@o0fQb|b%D z%HN>s8L%q8x$^6{3#oO?i5!3N1npdVF*^GBHugS5*u-@OYa)ht$oh!4Zr$VN>S_Lc zGFw0WGa~m|sU>(#@Tj>1UaIl@ja;`H?s=#9V4r|n*r>-XF}i?#G&bE2jl{KZ!U$A_ z7ur=3{~j%C{hzbN_*OFw?$uL4w4v2HEHkNquGZASGY>IKZc$c(l9=}UC7FTK<@zgt^JMt(iw z*F?ZtqOkDEWw0E#{`C3u*RRE9+;Fs=pungS-{}as;-;E_*V*))W+L1E&re$VUBJXA zjb((aq3iAMt3mKlp!0@}mVh#C9ZsXXkQnlF{2H^^k0MXCTDqb^^m}0OP%zMsVCzP8 zLZ)27pNL{>5z6@LW-3u{%OTQ+)ev1(IX#04%|{hA-G<LuX_8ts;ZHmqaIg;+nH6+ zOC>_;C4ra~71nU(X| zgl{Yl@$yZoZvu@(yvKiUoa;TnEoyr-I`D^N14aE$T3z}OYIvZ_3WU4-x9lbPeoO5E zRg1EW|3v*7Cb(Bz9^^CIa_r>3_DWDF<_m4CFIfIpnA_^gqciNHX(!Im}TwzYyZC)F1 z-N6e92DLuh{p9T`bP^+vIjV!@jv1**oh9+@SG^2j4sZIfQ`P69HatSEtx6P-y2)|= z2vMUZr!p1&{Tq`&SQ#bfvg)SWm*I6!u@K#}U5t~X3V+o$-*mkunCc{8%>}?3+K=n2 zsI}hc# zh$r^G-9ABKo>#|vm=-=bKEUA3*R_mVw?QueN2@2%gepWt$eyPwDLwtUvOQa`DLpbf z8}N90li@vB<>~$({8EngWd?n%W@w{xK#=(OBGR}bCk6iL&~2r&X~o|Eb}052oPmRu zl0PK|J(57D#_;c`&&@pRb%Ku=bfZ^n6e4wQ^=q)iN^mRWpq{YR2^_NVK1~<;xT#D6 zPNinSEzoTiQ>8y{g?TbinL8_E_>5E21%dK-$=^q@NrlG|(!T~p;-l>`YaxShJBj>R zq^J~$KA>NL1!mc3>Hzf2VLJZ)ePy#ojg5je> zFpIkO7rdpBIK2}wOLg(zPB}aeTxv%??e90NY#g`67%yk%_9*71&V$^yVioN7D-EpX zsPLOA?p`z;8M^yl0&-VUe%LhWSdo2L-Z;j-F_=8aVvRyKa=F?x>x6@6SYDefEx~PP z_$%$lLzSsQ+Ny7Axh79%?rfIq`qJdqXa0TH$81r6j}*>_-jA}Bn;uOY-$OfpfX&vl|$Lw;aSrgII#P<7B z_e;eFsW9dObZNB~PCK_*WOo!RmuWT>mePQ|rd<((qIYh$)X_(|W=ZEXKs?YCET5#QWUBvoQs8cH(lX$}s!Ef=|ELj&ikU@u%>>w@AK zSgn1pu`9)t)wn^SwZ93f_JV-!n~c z=j+5O(44tHZfF=`0pLc>0-&$*pJlMakyiV5Jnf+QT?mH^N*tDBlg0nzB4j8(L?IoJzEEN>(rZ)Bi36JHqxL1# z`xbR0%il<+`i|vV?3NCOGPZFXxc}<{fjTzY^B(Q=VXa=qa<1wO%)#vLyV}py=v8U1 zf$?lH1P9T5y){hAdvo`GJVgU~SS{C{la`H+u9l{?(?Z4<9_#-(J(Fbl#W0YM zvl}|6%Y;g0b8}coPNN$pp9nZNXq-Vp2Gtu2i#S!#E4uVZAVMgC223wd(a^ws5CvqL z8an)5)~l_&0vC7ps~!a7{D~tpF~h?uy<9tlc-g zM0_s;U+lk2iODnyo}j0c`lza36vDLvGZi?&_ws%fsI}!9F@>2}_Bo&9iH?$4!0qo? z*8g`AOrJmU@5;(bI(V2D+2Pm8V3NRV#tZ5h6{)?Tn(l-j4c5K3_5JDb zq|$F#(p!aWX*%K{yqP6uP5$U!)&{P#<(wpS|Dj5=lz`)SRy_L9`-DG|P;J$Bi#XJcbNk1huj`rlPVB20#PF&jrQw zjuBEMhl>w@SWq~2k3b5zW(^En1=DU{@wpv}pLHtZrDxkyTkO93r0QjDVUkC`^e?DD zsO!M78C@lGd++!AC5|bB~ynj3D}|!mLHl{_GIV?46vg!Ow}IPswN|m~3y=t0(-$;>C){ zm1$(O{DFQG5r*a{DO3vhlELVK%9fUa&eR{Qrma-BKyK5i@YK>a!XJneY8616nUk8< zadj^dtMr~>{=bYX`8nhAMmclpI)#5xikqgJ_^l~a8F}}Wua+GPC;;P9Aj;Y2)MaR4 z+VXRQeZ1PtM!1bTmD@EGnC{%PxVhQ}2>1JV9ZJdezG;aQ(S}4@zKIL|t z>#E7 zznDA<8+c_>=3ihI^S;&{wVRE08@C9WO4?nzAaxjd&TsQzpQlzH3%^uz|8(;F z?_v^wtSS764v?g|j^bynH-*RU+p5x8!vbuJM8rnl>{+h%sl z;@q|zek}0{l>_AcLdutt<`ZX)MlQ~xvhhGZ<$MBm_|fSv_J}@J`ROT@voGwiA9alr z26EZ^KQe4qVD*hdBA5s9nEQiGBr4TnW2B-fk$hhWe+^%OUbVrFQs1+@E3wN@4K{i= zynTyf?UdDYaK*2n?lcWecSsLh9+q`nWSwRIrHFCxqD-uC9>aPJ0<|MOLDGu#=-yXiBk7>PTvgOo#Wes;G2u1C7vCer2@^|} z4WiZ7?uJm&vxTvJG9jY#`A6MIm(9P|yD%-yX>>Ey%wJTT&WxV0&|Q};ZFK@X5{9a7 z-TAC}0pD?ldY;Z{1aV)rgiNe2l*r|UHCdo^eYkic>;pE`7SYjTr9`UlsG?Mac9|)s zo9M0akq>|Phv>LN!+m>ld!b*Wt!zjg@?d`3+cie_Iz+(6MS>Dz{cF`zo1FPb4ND~~ zG&BHuD2vynA7r#0bCGy3-Ujq|pRe(0Yip+hWJpf0!*he#_V=KG&3U$|Q>{SW=`_Oo zWPI)JKc9CI$+18pB!yk&0Km~ta4i_BsEfH&G7mDS{aOA(q&?HtDf16wNiaP?&lmy- zSU_Zy7RJmu{+tsVy_vM|nNEJzwS*(P8*|g-p31u-C87^$Hswhpt(tl0SXkHtHca;K zhC_i3&wVbxrzqtMcB-CeFN_^5VX8av{HSAQjt%k*s|;a>(3Z(%sn^@K>@4N^7FLQa zzs{~cExvETJM9HDO&eO>Pu37q(!L24VlgbP74Gu4U2mVKHqGRINi}SeO8S9A;xxu7 zfvE6{(YLrzH4`~x$$ zD)&*l9(xu0U#c+)uP0kN*t8LmtVMtu;<6mWYgL{Jq%$IGn_cU=Usd%@3WpIr|B@`( z-GLV(?JlF{zPw5Jqmduqb$}*rb?dD6M62hvMd@l{UgzfeIUd?VabDCdFgHK1smJgB z_ej#qR`GIbTKxH6lWv35P|-Dx}& zLV3R2PU0eqi~}o?yXE^)u}LA=lDvF3?bmkZ^prJJ9B-qQ!F*bXGL8N>7>PFU0=IjT zu2*L-KKfOU-yObBOow%QmP<6-9UUHf7Egb@3=f8u`gHu-Xuz-inIhO-p{-^&^ur1} zKbMm(_?3j4Z+Uw+IHyoVXfbKPj}5imJLJSkJU!C9J6Tb#Biup^tz0>)yZ%PH0wqLxzA4Qr)YUD%Xcy=YL;B9pC+cwmy~@g^ zEyR^e)8t^fa2emx(-DI>xb4&Ya4rYF%R>`j@1yUBIKtIX@}^e_(E=3?ufKN_E$TyV zJ{LqU>21Xt4XcUiBsvm?D;c6rqLu=_@zJk9P?wX(RK1I&?0I!IU zJ*OPcZ86c#4IUJ#Zx)hWT>dpV}gKJC#ZQ%mbs)g?#8yaFYIXh+}bQR9? ztLomb+F&>5Fy&9>b^QfdW==U8a*y?6&0Ro+uOJ+u%~N>`yi8mO@`uD=FaRRsF6~Ats50;G&v9~sO#K>h&9~dlF5K`) z?0rWy1m+B^oPsqJ=5<#y)n4jF{H;UnXz)8yJMXxi=`29Ne%s0X?8;|pk*vz28mxE8 z0D*(N9~B%E?JcVgKdxJMbsguZ*dxH^)in@xBgZg00~^CzGFYOXfhDMGY22JML$}TI z2>;rRS>(YBn8I|UizxFjfuwml+CVA+0^&=?St)|k1l~xt)yTqun4)({c`WD*YkpL} zybZ7Vq+UnD#VNyE326%D#7XRVv#*Jgg9FhM^bJ*}cK<|wTC#t@S#B_ysViqE#lbO= z!zoY+*yqAJUpwnuDyT)oS_1YCI$fa3I>H5LBJJ}REnr9P?38Nho|ETBg7z#n9_N(S zjRDgUMalYBVTKU~^I05&4>M+Moo$a@4sngb#j`--)y7Nwrmb=H0CNM-xG>rr@Cn#9 zYp(O^50qlmsqi!kY%%EHALj*s&ZwTxqkUb|%wYJhwk%=|=dz z9rb>Ai$wmDYxfVAk=J6D03uGjCM^nFxZfCIhQh@ za>ol+0E@Yl3w+NO{>_qQGz4Ad71rq&P6E_cqmS=D(#BpBd7HQ{Ssj zfmhG|HC9Bah;c$tst9g&27j=W!eRxRm^FQck3*{xUe2Pm$ORiR?9#UY!%k{0Hh;h2 zb35%Y;j;)1h7%cSrE&7QeJWbWpLcwS&hwM}=6qe-+PYeU_54KLRK<*4Atz-(pSt>u z>s4@Rj^Ljr5LY^QSH3gWJ7q`3iur&;6si5ka)J_WdndIYj@xc&6S>}}?nQBE!4B?f zird;&EipF)^NT83I8r1?cpzS|W3W+N*YqWG0(yp_wH+$HVO%9a4r>SIy3}v60ewY% zIIu!HwurPMjbc`uBqL}ft0s?uBwEdpS8k(TB9J1PE&R`jsk} zR#$<{pWdD8Eh(sRbgWKz1)qG!Gnv%-z)TdmVzixm2tEX4re3X%wz*$quP{nZ~X^&AYPc+v4{y@@Oy86Jl~RuLgcu~wXwUUFu{c5si2?I%K+&N8({{^2pI zIqlJVt=ns-`+iYBVa^rk_kS;t1rRTU0h7|Czbs)@J=L8*lKsxv$R5|O9bDM-_fZ`Q zvg7g`k+@~~yn4p>9LKOG z%~bh5V}%8(>6%UFkZa!_aw1Hv-zHzKHZ|KtR)2SdrTuHkTbff7^#d7iv*T99QAe3$ z0uErq-Nh09wTVgvlXp#SO-*%?fku?5*Y2Zf4*~bb`9$$zVJDYC5>)y238rQNeHr!L z1%lVOGmAt!5(gS+rFm|@#2MR%yG@qAaBe2JE{yO$!adiv=6=T-e%@7<|>Bb-UA z65qA_kF-Y3&islam5eFLV zttmy2^7rUhcGVoDY<(JD>g03N?y8>Ed;0siX%aegDL~gp6Y%^9e4zv^pt831TM5IX z;0SGlHBE@<`3Evrn07t%Sd`;X6$|^9&M*goglDi)LpOe_XLMis{PzB_f0ty$IMmtm z@a}RL|JekZaq=}Ue^0Bu=DDs*mTL~b$>~ead0*GLx40rRp<~EH|MwdPR+OU^nQK7B0%~J5}E)9T=i}YclMGm8z88ZHUmlHTTg?F(GLDNZJ*$tCTz@-%nSfAT(y~DBL2=j0l zmWC|htCg#yUs4F!LezoQ5C)lMUx}*la{gFKMV$1f2ZQ701Gm3Jm-X(Kgl>MwonPCm zjm)kyk=>b;d*jQVIz4AUKF32MB8!9iR#vF{cU)Cp={^{Iq$_k?dy0KHX_C+E8#U~= zQx&%6iQ6Ghye53u*=_AtZ}W_Av5>1;)|8|ni^GF$T$)3Ex*Kedjmy$n2t+j{q~C-& zj9QJQ2?9c)v6+hyt-rql<&wcUw%h(|bQ;(}j=@`{?GQsL`4atej7~1aCOen7hpdeSu<12ou=X=4@MX0hH>%To0pu^DL5YI^)L4hcym}UI-*~^N^7DvueOxlb*z%DtFfnE5 zTFY?}_00XFro4F`o84D%2(lHQi#TXVfFWQ8AzXq{4wOzk6KCS1tnZWwCVn}FzU6G>4Cu!T7342uidv(+7W%){jH;7gH|eu_E0G$>#EoidN@*lj z4b?H0ebqIx@MCgyx_5 z(stc3{R+#l6OHIFdMm#@8xw z6ZN^|q0^&tqoJ3f{rf{8ER&Rz`;lDRz0%m%&r(veGhH zti98FGdIOas3W)ip<0;l@bF9E9=YAu_E`aBYu7PJLYA-1^esNKv8r+A$ z3Xl5|5Bfn1v82duJBVSGTJ6;iAx&{HoAat?k|_@b8JhLTFK!RFLS`C+$@x_%h<>kHw-m#-ch6!svlzrVGTX?NXC%YH4*$XS(x zI6Z({i6>kgUqc)8Y1k zoMP){eMK*0XI{^$j#5jhAAnWf37ougu9z#bkc?>j7B+yh*qlbkqHuc|*xFB}z}L6O zdz>|H?s;;q=eaX^--1AKH3_6_2N#VQ?3}KwqoQpkB%5~qm%}80D>2<$lP33we(O2QwHvBPGa-;aw8$4+(pygXi3{xA7ts=-H#k1=x{odwaN7+MCi_y7tsh|J z5KFuUk%spzB(34D?+jT@4Npl3(VqtjXFBNuY9u0s26Oyul!;3`KJH_yRrSc zP1qg`;X^G-#%h5^eZ6YZyX_Qe5z7O*c;niiX&5Q=gGvzeFtj6!Wes1c`KUeZj7uqy zV@w^cBL_MIz;4*MdFhyzK`BK9lN`3~BoZpod%@Y>X+tGQ|jd6zKTr~Mn7qw53wjcIds`YSxVLA?;UgLJT3L3aI) ztk($+(Sd?eqR}zL0i`HiB*{=j*p@qdN-+^vO}mpUBUB92Tj+(81LBB;d8wkrC$%aP z&e9KB^hHIT{z6}|HY8$~`w5^%+10Y^Om)jwrV&f_aV_KT63dTSe@yWYoIJ*PSu>cVvbruZs#$VjXZmttn!6f#tU_OyBrKQ^aB(Vv6%qJC8n4XrSQ|yA zsTRil6PAsfvUXabO#!?Ljs!=aDT!v`1!+e=uM%Cqr;{gk{rcFubNg79@f=j?3IZWI z1f5z7_OaL`nCfOw@K$)m1GWgtaoV8X>Fo_#UQa6LKV$a1-E8xbBh(;zTpSw`BsWU; zzlkJzgW$NEAi$RP1}a?mX;`guWhO{=R8Wp{!C+fIF@773nFt($8saX~v}ndYJch8r zkyV0RJ*#2W9}1VlA>$`9eB6?ysO6_@o_7L)3(0&_wGz9tW1R+v&J!U_B%j(GJ8e|Q zp7e2^XeX>UV%;&7R_=a0pyAfDU@_M9Ul3Rtun9X)>fj@E3!TXf%QGKzckiAalL?8$ zmZ6iSX$mq}x2XH^y5v0JG}xq7mG+kRoz4O@k-6KlPoGuauJFO%xzvqz@=T?8k}?J7 zbF*XGaGKoCoh%beDaI6EEw`k=0(mfzxz#V7a5cP~H!|ER@Dgu1zR%=P!<#b8npO-U zM$*BV2>(p!Kpi86TDqT5+ldjr6#;AVKt}tq5$VqRi27Vu;4WIWs~;M4s~-QF z^SFApS%UvD1x~;eM$mgCR-v+^d++IPI>;IU=x8{0^@c~!YwO@)#^w=i)pLVzagj#p zYdYs&bUx?lqoeCkyp0-DR_~s?4Q!`0Vvd=mPbS>GosN8lkNcC3T0i^(@y1%ImEI1k zg*KL*X3pSDwe51cnRkTKH6O^{ZsMUzrd)BNyYLpOruSTiSnxQ57vrCmKLL1Z` zF+vqJQ)1In)T-8~_1y7){x6;v&l~X~_b2DRuIrrZd%kD2pO*+8p^Tyu?{CQ7_jIG? zi)cyNWKaqc_H=T6@NrXiaKJYBAzQ}v)2Q+Q!X z%XFuK$do5&cw>Fkd7#q$h3f2pbIn62bsQr#uIu$r*n2ZF4`ZGZ#pfW5$b*f9ly_Bl zP@eAxETWH!%PiB`&GSE3S`4dj+`3`K;1iK6mSUpSpvcI;z`*wVLKn-X(O6K5d z@krJPqCfDf;-F01u0er()U&RN_{8*|FJXyFezt6c8HYv*%&A|=^R z?hL5qHy!<5Frl=gbH7(2?3pxLw8UI;BL~F;O#c9aQEK(`&Yl}ov8{PndM(?)upA>^ zFczCK_Q!K?wI-B%&de{b8awJi|d z?(mkeTE?>4z8x#o{)bCIj|XI+K3@cm=$GM;Me}&@}Ky$ zmS~C(vTu1WeNovl%ru|9WcV7Y#$s({h<$=>8G7#o>^F(>BkP#QV4 zUNJhl5O?0*?61rG(vsuFn_{sNOS9mcW$ey5`uXoHXyEX}6SnpqLFH3(9^?`guJn7h znvA^0$F{bHh7+Ks_xDGD+2W)nVVnu05F!v3u#Uw&ojZwk!1VuiG2YEZk9yIG6v!zr zHhNC~d~4w5lYMqVF;z?X0|$=Sbn`w_SIN$Tc&Hk)W6IRYuGw?6e4~WeaG*!6q?@m} zW!@R(jO|*Glw;&dGnf6yb0gk6Cvo*vR|QBBqWxB6Kjc$i@(s%$DOms21)RFFbd;le z-M7t%U^h4Ikl72E!h5^OfOpAFbrDQzEn+FyUlsZLo$8u{$Jp|bdb%%;c6;4pY?H)XEcS7_q3_G`)T~#= z;V3DZN#>JZVJ_*=vL}~<^o%8eUQymA{e{Eek`WxhNiGie7nwb?kk-qzdiY|UDp41q zAxXGWD{h_R>F)wht(#Dq&Sm5Hl_F8@66maAo{8!U;z7KfkZ+o8{F>T}-4GxtaMbHK zC|~ym9?XU5)&0#-3q7z14j=t?XYl#b(mhM|U-_%4_j=drxX@!XxYgQp^z9^?syD*- zgN(yi2KOh_REmtlAUrGzTF-KCQ84QXQ6xCip=(1rM)qL>WyRK0LnSl9qN{lSX`!)>5KD>hDWIlgpC<{oeIrHJ^xl(X5RgUK{(Le~oG%ziyTSZH3BSus^~#Nk^&)!ePXt7g@C@FXpfU->uYTVmrS!vGE!Q7kUT2kxwlX%-FD6;JJ+cch)A%$RAaJK$; zWAv>CY2%91FE*|@^7J2Jy|c5r#6Q4@GuJs>$q5-4FY;A6nU*6zwIo{w6g|Q7WpFty zY+*&>zVrsqdo-@xTKxu#>j5$EAnaz`(}RKtxwo@EZ_uteKiK7S&n+ya{fTj4SE)fA z+raTUwf9u@1#PI}ii%o!ZfJ;N7mkceGo`mt(&VHqBJ1=grhevMSj}`Bwx*_L{tnB9 zOR2Ax3xtb(10~q&S0111<8&ygD*7DpB@KqyWLt$!7=$EhH32G%|1Z%Q~o-d zt5ynXM8-$@c5b@mfMg-smE5^pTwsRas`mk{F<4#?nP$|F#uB%$GvTg5YojHnVigzQ zk&m>YzSMvj&0pNfxWD%hUW;@yUwsUT%g4}s%|oVoS`PVt-{J2~{e#em7R@y_c=v~B z5Rre6Gxg^|^Ee!3!MROgV{4p4JhHy>nNcU0h7z>kyPu=Mp*BnjziCCm$y?agn9KH* z(Y1bs9;DFq-ZXS%qu$D4-f0<6mWJ0rK}7cFOs*Vcn3t4l6X=!swFQ8KpCCg;;|=^p z&n_M=7RS>^!fhIOa|lJVn+HH|6E^X*$+WlQT z-ta$}R8aaQ1^Q*u@K7uE<9pg!RM1AWaD5c~N5@@0AMUe%e&ANU3+Qq0LbNMqwPQKRG~pc=v*o?!%2ac~mq20E4IafmoD9qxg1tyXpx%%d2+diZe+)cnIj zARI?nNmzXAXv5iJdC16G-W1keeGn;Wz=f(>{OoVt@90Ci7lU<3$Bho?sqAktey+b( zwjCT`^!@?23>#Q2L+~qI*zZm{o+nFEMh-K$PZkN&@f@zN3gsQ;%-rxDIeLrM}iYgF1O`4j>kpX0zg6# z_T+J=G#a8_TU#xO^AAq>U|3sfBEaz9vP9n@}rgKFy))cVMM(@PLkbl?Z6Ti zazg)#q3Wf9XOf*H59=Xg-gF-DnxVJN@hLJE#oa-rBz`-QTzY! ze=(<3)g&Wx7C~Ck?$^fGsDLyBhJ0A$ztE)e2#2ko49`;=M)>p|&rA-%qTF-E6@NpF z6#O$c`Aa9{wE`Cn(bg)S>gi&I3A$b>6RAx(g{ark0wb)v6q5p~(vqdmEIwCaqDH$n zsD84l*3*=tP%B_ha79w>^$ZAznCc!%;Tyiku@(O1;Q)}e(*H8;fc?iEKG>+4DN9{} zVRAi~`#h$zVDpn@X~wUiz?=8xF4X%5px)HG?;8A%c zyT5>Q2!Q+fSAmSLg{33k2hBqHrEjjwkAl^Xp3>7}6!k=EQPllT=}>9oaLq*uTFo1G z*qx2-qp6%li8g2~xI~gFg$}{3a(PrM_D%T?f?kwo>C^PwK%(uoi;qwCY%AUGwYZj@ zEO2Lh2cEzK&zzsz<~{tfiQm#DZnoLLhL8U_N=$A`U8Es>RRPpp?c~Zes!Q0 zB1XcS^UJ{#14$_@56b$CC+MigIx;<@1;3_+Ez&}e?-2W`qh4Y!ewbndxR^w|9to{W zJ~LH`g{?D9i3vkodj*3vwm|d@ddt#gL(3Fkre*V(t_LhRu+3ugj6SG)H%%*0(T@}B zP20Ct3`j6+RxQ|;;7k$POVaWf2A3A+xo7iIC)b zYRXb;U6m295G$o1w7d_J$+v04AgEb!pX!pp6!UhM=*eXR-_r1gpJ|w-c2{ z*C8uJ%+OUPce@K|*L6tJD#zj+JHjB5WmAY#!(pe)!Zi8UKU*qk_)31~U?uFDm6kex z+%0gJcQ)?5J!?KX=OjGA&)`ihLSH}4E2EK*$P!k7Bb_mP&fur z%JG#YFkTBwevmX7#=mUL=jId{?OG1QV*9414FkS$3tCxkrK4mT{DB+HNOwtyKtOi9J1yUl9XE;H1=J zq9;&q;r#;7=`8|$wem6DsB(>*=>i(&0PGXU;!l2frn9iT_kbGc>XmB-;{_p4n9~6M38qi$G*@<>t2dtx@T@k3#Mg_F+?+ zsyI+uX!BrjVg^Gq@%(S&nVPcqz7 zSLM2o;)zu5L^p`J!f_{$k==~T1E{r)?EM)96$>+s(ozl~V&XHM41->@QgL?ehnL#& zsEHRjR0ItxZq*NzqN*HrJ1`bMTDvkDGw=`Na{h7ImtwjY^=6u8E=#MeP;1-l<)P`+ z-LCB?G6yX`px8wLwyzHSsUmWBV;`{0zS5jZz-)SBTXb42x4r6Ym3@oxU+?(ur39{E z4|@JZezPZ?k=BbdB3@L!Gmof0IXC;ZUnsfnvzpI?(h74J*{BnG=UfoJA{0{ZP&hj( zDi)+ehyFob0_keG>0Rqym$SfPzUJD?!@{hIvScJHfqW}*Zv z91tCLPg7tlISA0sm5KYPp1< zzhN>ZRU#=CE@L#o51+=|MkO$PajaFh{t>U{#~adimYZ*Xyk+0+ys}EK^OT8%d(659Ygw>lA~} z@k{I|sO}?m===9cFw4DlkeYGp7#h*Caw?M#eft(HP!_iP~-ssZ`X~)d-2&j$v{I1^r zwY48kq^p-Rgx9Kl=%tc`i-J; zv>Qg`&Va@@smX!Xj~E(&+ElrH_3p+ReDX7$7UEe$NqJV~c_T+;w5HaRuv3hP^W zIN1D(LF_!Dxn}2MZQV|*oX>my>Eofub3+pLv%FU~FRnt(H}~_^e+*AF!q1ZjEjH#n z0`kiTz=4{#Cf=e{I%g^bxe=C}HcLc)kK$SdDFQjU3j({gFvty*& zs%U^lmrq`g7DjYrO(8?VB{j5P!9I-#8*Ju+S)%_kj|D(C?s`ZL(AK&ad24Jc9F5o1 z-CM{GWD7g{%|h_?@;^HXZ#z0P{T`lJaVeM^oSbuei?NqxS(hBzi8LD5G0`f%m0Yp6 zH$E}{1>bzW8hI{u@d~#t;H$8|$L2T(6C1Bk#rnHFtP+&-S2RP}0^;_o#v?JeA<*jV zc2inSJ-2gYRnLhXuK_BMrPU|db+oYW9Xr?L^z5U^A_Fg$VCfQJyj8_VsHU@DRfDX+ zO-=W!dskX5ETGSneZluApu|i0TGA8^!cAQsJz7EaL2-y^pPJ z2Oe@DY+O?c<^%d234=X&fBt#QTvA*{@l%C}xE}G>-?R8Uf<){`UGOCpNf(z@X5+R} zL)h+4ynl~4loJ!W#6nzPleMOGE*MwG6vJZ}`k*LHe<6@nL;adNFPQ zlzc`c?8&-&>FReJUt4vZ<#%fi{5**CsX_YTL~1dv@_n{vt0 z=ubl7_D;oAM7+xsbI<^RAcjODfBkY**q?NcSo`9zJmn^yZa|xN6~-?BH*B6L*^hQN ze`64bQt%&-5KhfGpZdEoQSA5Rf8QbI;vHCQt*v0D=*4&D!^&(4$9k(N&*f{0k{>TS zg9sk~TA_f|6WtC!22$yQ6B~WvGwOMIC)7*FxJ6S#Dpc>G%F2asNapL5u~!{~G(hhT zJs2FJR8N)tWyC1go3uQW)uTtj{CcaB#5V>pf%Is>q-*_%^?p;dDOFd@9-EloR`zQ6 zug`*j$GYdWX~=%fBjgjet|!z;%$qAD6r#8GMf2sU!p_=hRXa(CJxD|&pZzVHk(nvj ziXgh2_rt52DHoSwc7A7YfHrYBtG}ie?RU6Cu^qLIJZeME53JcTsDu6gqRY#5_|=&; zH3YPrc6sc${eV$QGTR;QDCqMAoUF`od)+vkMlL6~%7t}7jheE0m$JMGul3Irm`i5D zW;J$G?-2z}od`$jRk1d|*gZD!ooC8GOU9^85xYjI4$i^aln(^6G57sD3+6)?zL)DASvQ8y_B&Oim)vaHF;eKtm3qKGRVtS9CK2Y%+ z52mp%s?*{yVwRDF8Y90z#Em~aGkw)Cawpd!;@>@nz4p`PsP4W0d|nt_!xQCRv!;1s)7nR)){zVBUc4c5DeFpCS$?Z+!eaQK1 z%YPXUHHtju;Cb@LQ(=Eod1-w@q_Wzfq+Fy@5{wy^drDPt%L|fQUy8yvPtDfDQjNj= z*iO7;^wiC5MvBFi7TC6;mU}r?K=(#E1P`D}b1?VfLr2I^LI!&p{&LsEhHa3X;tK1l z!{3%|-939;-R?>J*UI}(1$HDP=C*A}-sG_XDW8xfxY%TnQcwbupb?YiE7uy`91V77pgN_4eAw`= z62MgM5R1$j!p{eb>+qAT)~|}p6`OCZdamcnKgP2Qgd0fEWTqj4k#-T0$@7Qb-HtHx zXUyVuHdp!tGnOiFd-D_yGuv{ccuzIe#U&54o3EdX^m|}=AmjDBnEarvPwRB!TY>ho z^m5V90tH&8{$cO}^>_<+2xny#&(b83)5L|H?mzwdR55oJfQfk+=`nui^eZ9HO{p?c1Yb9GY@cnhk*07q$Icr6r(ffK8W(!P z+abOkN(%NQSYExbvs8O4(?)rDti{Ljm?3We;@~KAf5Q8|m2g973ob}b)6<}Mp zy3*Hf^(a~Vg{8EOr*eVIv?dSFWhMF(@(&;9gGu@6_IEFc6|R{l2bhnIQ*s>T>sBwf z3#6Zq<@+4CR8K3j_Uv1=E4$T*Is>C$4fLpD$(x&-fg0q%nr3eaE{0)5FZlA1`FP1K zLs5X^fu+*$eWwHf8 zQR3Nb%nLHYI#0PgCbB;%E#C+Y<6Q{}`IPKslVaTM@oJu5g~MPNAExFvhve-cYJSRw z+6;=+Rq)O>wCy1?S93e0T|xoN6L)AzA_u5<--wDIq@q4ix!vn^U!eYQZQ#=Ng{Pkf zdj)n57rRciTYVLvp{H{5Q`>CffFvzcBqtq63bmok0pgq#6g3Mg(J9t`Bt+XC6?1+? zeczC>y3@6rjEw9~kfv3Ti&GF>&N%=Me2_^=NJ-w6l)5V^VZe;)Amb#ZqM|NlR5`vJ}Ycz~Vs3#PuVL7`3oa56t9cb_0JA9&DBh?sRcekg{|8AWoB;p; literal 0 HcmV?d00001 diff --git a/docs/source/_templates/autosummary.rst b/docs/source/_templates/autosummary.rst new file mode 100644 index 0000000..0f54333 --- /dev/null +++ b/docs/source/_templates/autosummary.rst @@ -0,0 +1,25 @@ +{{ fullname | escape | underline }} + + +.. currentmodule:: {{ module }} + + +{% if objtype in ['class'] %} + +Inheritance diagram + +.. inheritance-diagram:: {{ objname }} + :parts: 1 + +{{ objtype }} + +.. auto{{ objtype }}:: {{ objname }} + :special-members: __call__ + :members: + :undoc-members: + +{% else %} +.. auto{{ objtype }}:: {{ objname }} + +{% endif %} + diff --git a/docs/source/api/data.rst b/docs/source/api/data.rst new file mode 100644 index 0000000..616a808 --- /dev/null +++ b/docs/source/api/data.rst @@ -0,0 +1,23 @@ +************** +Data container +************** + +.. automodule:: nmreval.data + :no-members: + :no-undoc-members: + + +.. contents:: Table of Contents + :depth: 3 + :local: + :backlinks: entry + + +.. autosummary:: + :toctree: generated/ + :template: autosummary.rst + :nosignatures: + + nmreval.data.Points + nmreval.data.Signal + nmreval.data.BDS diff --git a/docs/source/api/distributions/index.rst b/docs/source/api/distributions/index.rst new file mode 100644 index 0000000..cf1fadd --- /dev/null +++ b/docs/source/api/distributions/index.rst @@ -0,0 +1,29 @@ +********************************* +Distribution of correlation times +********************************* + +List of all implemented distributions and the associated correlation functions, spectral densities, susceptibilies + +.. contents:: Table of Contents + :depth: 3 + :local: + :backlinks: entry + +Cole-Cole +--------- + +.. automodule:: nmreval.distributions.colecole + :members: + + +Cole-Davidson +------------- + +.. automodule:: nmreval.distributions.coledavidson + :members: + +Havriliak-Negami +---------------- + +.. automodule:: nmreval.distributions.havriliaknegami + :members: diff --git a/docs/source/api/generated/nmreval.data.BDS.rst b/docs/source/api/generated/nmreval.data.BDS.rst new file mode 100644 index 0000000..0818b3b --- /dev/null +++ b/docs/source/api/generated/nmreval.data.BDS.rst @@ -0,0 +1,23 @@ +nmreval.data.BDS +================ + + +.. currentmodule:: nmreval.data + + + + +Inheritance diagram + +.. inheritance-diagram:: BDS + :parts: 1 + +class + +.. autoclass:: BDS + :special-members: __call__ + :members: + :undoc-members: + + + \ No newline at end of file diff --git a/docs/source/api/generated/nmreval.data.Points.rst b/docs/source/api/generated/nmreval.data.Points.rst new file mode 100644 index 0000000..16d7d60 --- /dev/null +++ b/docs/source/api/generated/nmreval.data.Points.rst @@ -0,0 +1,23 @@ +nmreval.data.Points +=================== + + +.. currentmodule:: nmreval.data + + + + +Inheritance diagram + +.. inheritance-diagram:: Points + :parts: 1 + +class + +.. autoclass:: Points + :special-members: __call__ + :members: + :undoc-members: + + + \ No newline at end of file diff --git a/docs/source/api/generated/nmreval.data.Signal.rst b/docs/source/api/generated/nmreval.data.Signal.rst new file mode 100644 index 0000000..c913f1d --- /dev/null +++ b/docs/source/api/generated/nmreval.data.Signal.rst @@ -0,0 +1,23 @@ +nmreval.data.Signal +=================== + + +.. currentmodule:: nmreval.data + + + + +Inheritance diagram + +.. inheritance-diagram:: Signal + :parts: 1 + +class + +.. autoclass:: Signal + :special-members: __call__ + :members: + :undoc-members: + + + \ No newline at end of file diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst new file mode 100644 index 0000000..acbad32 --- /dev/null +++ b/docs/source/api/index.rst @@ -0,0 +1,12 @@ +========== +References +========== + +.. toctree:: + :caption: Table of contents + :maxdepth: 2 + :glob: + + data.rst + models/index.rst + distributions/index.rst diff --git a/docs/source/api/models/basic.rst b/docs/source/api/models/basic.rst new file mode 100644 index 0000000..e87e570 --- /dev/null +++ b/docs/source/api/models/basic.rst @@ -0,0 +1,6 @@ +************************ +``nmreval.models.basic`` +************************ + +.. automodule:: nmreval.models.basic + :members: \ No newline at end of file diff --git a/docs/source/api/models/index.rst b/docs/source/api/models/index.rst new file mode 100644 index 0000000..006cb95 --- /dev/null +++ b/docs/source/api/models/index.rst @@ -0,0 +1,31 @@ +************** +Model function +************** + +List of all implemented functions + +.. contents:: Table of Contents + :depth: 3 + :local: + :backlinks: entry + +Basic functions +--------------- + +.. currentmodule:: nmreval + +.. autosummary:: + :toctree: + + nmreval.models.basic + + + + +NMR relaxation functions +------------------------ + +.. automodule:: nmreval.models.relaxation + :members: + + diff --git a/docs/source/api/models/nmreval.models.basic.rst b/docs/source/api/models/nmreval.models.basic.rst new file mode 100644 index 0000000..17cfa50 --- /dev/null +++ b/docs/source/api/models/nmreval.models.basic.rst @@ -0,0 +1,37 @@ +nmreval.models.basic +==================== + +.. automodule:: nmreval.models.basic + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Constant + ExpFunc + Linear + Log + MittagLeffler + Parabola + PowerLaw + PowerLawCross + Sine + + + + + + + + + diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..3d36539 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,424 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import sys +import sphinx_bootstrap_theme +sys.path.append('/autohome/dominik/nmreval') +import nmreval + + +# -- Project information ----------------------------------------------------- +project = 'NMREval' +author = 'Dominik Demuth' +copyright = '2022, Dominik Demuth' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version +release = nmreval.__version__ +# The full version, including alpha/beta/rc tags. +version = release + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.inheritance_diagram', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', +] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +add_module_names = False + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'bootstrap' +html_theme = 'pydata_sphinx_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = { +# # 'source_link_position': "footer", +# 'bootswatch_theme': "cosmo", +# 'navbar_title': "Welcome to hell", +# 'navbar_sidebarrel': True, +# 'nosidebar': False, +# 'body_max_width': '100%', +# 'navbar_links': [ +# ('User guide', 'user_guide/index'), +# ('References', 'api/index'), +# ], +# } +html_theme_options = { + 'collapse_navigation': False, + 'show_prev_next': False, + 'navbar_end': ['navbar-icon-links.html', 'search-field.html'], + 'show_toc_level': 3 +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'NMREval v0.0.1' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# (Optional) Logo. Should be small enough to fit the navbar (ideally 24x24). +# Path should be relative to the ``_static`` files directory. +html_logo = '_static/logo.png' + +# Custom sidebar templates, maps document names to template names. +html_sidebars = {'**': ['sidebar-nav-bs.html']} +# html_sidebars = { +# '**': ['localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'], +# } + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '' + +# Additional templates that should be rendered to pages, maps page names to +# template names. +html_additional_pages = {} + +# If false, no module index is generated. +html_domain_indices = False + +# If false, no index is generated. +html_use_index = True + +# If true, the index is split into individual pages for each letter. +html_split_index = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +html_file_suffix = None + +# Output file base name for HTML help builder. +# htmlhelp_basename = 'pydoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'NMREval.tex', u'NMREval Documentation', + u'Dominik Demuth', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'nmreval', u'NMREval Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'NMREval', u'NMREval Documentation', + author, 'NMREval', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The basename for the epub file. It defaults to the project name. +# epub_basename = project + +# The HTML theme for the epub output. Since the default themes are not +# optimized for small screen space, using the same theme for HTML and epub +# output is usually not wise. This defaults to 'epub', a theme designed to save +# visual space. +# +# epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or 'en' if the language is not set. +# +# epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +# epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +# +# epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +# +# epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +# +# epub_pre_files = [] + +# HTML files that should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +# +# epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +# +# epub_tocdepth = 3 + +# Allow duplicate toc entries. +# +# epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +# +# epub_tocscope = 'default' + +# Fix unsupported image types using the Pillow. +# +# epub_fix_images = False + +# Scale large images. +# +# epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# epub_show_urls = 'inline' + +# If false, no index is generated. +# +# epub_use_index = True + + +# configuration for intersphinx +intersphinx_mapping = { + 'Pillow': ('https://pillow.readthedocs.io/en/stable/', None), + 'cycler': ('https://matplotlib.org/cycler/', None), + 'dateutil': ('https://dateutil.readthedocs.io/en/stable/', None), + 'ipykernel': ('https://ipykernel.readthedocs.io/en/latest/', None), + 'numpy': ('https://numpy.org/doc/stable/', None), + 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), + 'pytest': ('https://pytest.org/en/stable/', None), + 'python': ('https://docs.python.org/3/', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/', None), + 'tornado': ('https://www.tornadoweb.org/en/stable/', None), + 'xarray': ('https://xarray.pydata.org/en/stable/', None), + 'PyQt': ('https://www.riverbankcomputing.com/static/Docs/PyQt5/', None), +} + +# autodoc options +autodoc_typehints = 'none' +autodoc_class_signature = 'separated' +autoclass_content = 'class' + +# autosummay options +autosummary_generate = True + diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..98a6339 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,26 @@ +.. NMREval documentation master file, created by + sphinx-quickstart on Sun Feb 20 17:26:03 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +################################### +NMREval documentation +################################### + +.. toctree:: + :maxdepth: 1 + + user_guide/index + + nmr/index + + api/index + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/nmr/depake.png b/docs/source/nmr/depake.png new file mode 100644 index 0000000000000000000000000000000000000000..feafb282368f233f6077d27e363c641ff6790628 GIT binary patch literal 60395 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

`;rB*YIZuz$skY#AJdO?jNU|hw?cz<`4wo0gl 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

pFb=C1B--UHe^SAX(zX8^0x{lj7 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 0` and :math:`1-i` for :math:`\omega > 0`. + +.. figure:: depake.png + :scale: 50 % + + +.. [mccabe97] M.A. McCabe, S.R. Wassail: Rapid deconvolution of NMR powder spectra by weighted fast Fourier transformation, Solid State Nuclear Magnetic Resonance (1997). https://doi.org/10.1016/S0926-2040(97)00024-6 \ No newline at end of file diff --git a/docs/source/user_guide/fit.rst b/docs/source/user_guide/fit.rst new file mode 100644 index 0000000..959e836 --- /dev/null +++ b/docs/source/user_guide/fit.rst @@ -0,0 +1,43 @@ +.. _user_guide.fit: + +============ +Fitting data +============ + +.. image:: ../_static/fit_dialog.png + :scale: 80% + :align: center + +The picture gives an example of dialog to setup and start fits. +First, there is the possibiity to fit different functions, called models to differentiate from the functions inside each +model, to different data simultaneously. +In the given example, two models were would be used during fit: +test1 and test3 use the default model, in this case model a, +while test2 uses model b. + +In the middle column, the functions of model a are currently given in a tree structure: +It is comprised of three functions: a constant, a sine curve, which is a child of the constant, and free diffusion. +Functions can be dragged by mouse to each position, including a child position, and it is possible to switch between the +four basic arithmetic operations (+, -, \*, /) by striking the corresponding key. +Function at the same level, e.g., constant and free diffusion, are evaluated in the given order. +Children of a function take precedence over the following functions. +This means for the given example, that model a is something like + +.. math:: + f(x) = [\text{constant} / \text{sine}] + \text{diffusion} + +It is possible to skip functions by de-selecting it. +This choice alse affects children, even if they are selected. + +The right column gives access to the parameter of selected function, in this case of the free diffusion function. +Here, the parameter :math:`M_0` is linked to the parameter :math:`C` of another constant function of model b +(identifiable by the number behind the name). +Linkage links not only parameter between function but all settings, e.g., if :math:`C` is fixed, :math:`M_0` is fixed. +Some parameters like gradient :math:`g` are predefined as fixed parameters and are lacking any options besides their value. +Upper and lower bounds may be given. +If one or two of the bounds are not given, this parameter is unbounded on the respective side. + +If a parameter has one entry, this value is initial parameter for all data sets. +If the number of entries is greater than the number of data sets, the first :math:`n` values are used. +Here, test1 uses 1 as parameter for :math:`t_{ev}` and test3 uses 3. +If the number of entries is less than the number of data sets, the last value will be used for each additional set. \ No newline at end of file diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst new file mode 100644 index 0000000..2ec252f --- /dev/null +++ b/docs/source/user_guide/index.rst @@ -0,0 +1,11 @@ +========== +User Guide +========== + + +.. toctree:: + :maxdepth: 2 + + read + fit + shift_scale diff --git a/docs/source/user_guide/read.rst b/docs/source/user_guide/read.rst new file mode 100644 index 0000000..a423962 --- /dev/null +++ b/docs/source/user_guide/read.rst @@ -0,0 +1,19 @@ +.. _user_guide.read: + +************* +Reading files +************* + +Supported filetypes are + + * Text files, + * DAMARIS HDF5 files, + * Grace images. + * HP alpha-analyzer EPS files + * NTNMR .tnt files + +DAMARIS HDF files +================= + +After scanning the selected file the program shows a list of the available data. + diff --git a/docs/source/user_guide/shift_scale.rst b/docs/source/user_guide/shift_scale.rst new file mode 100644 index 0000000..45e4b4e --- /dev/null +++ b/docs/source/user_guide/shift_scale.rst @@ -0,0 +1,16 @@ +.. _usage.shift_scale: + +Moving and scaling +^^^^^^^^^^^^^^^^^^ + +Values are always used and displayed in scientific notation *x.yyyyez* where x is a signed single digit larger than 0. +The relative stepsize (by default 0.0001) increases or decreases is always of the same magnitude as the current value, e.g., +1.2345e0 changes by 0.0001, but 1.2345e20 changes by 0.0001e20. +If the step results in a new order of magnitude the value will be adjusted accordingly, i.e., a step from 9.9999e1 to 10.0000e1 will become 1.0000e2. + +.. note:: + This behaviour leads almost always to values different from zero when using the arrow buttons. Please enter it directly to get a value of zero. + +Boxes :guilabel:`log x` and :guilabel:`log y` change the respective axis from a linear to logarithmic scaling and vice versa. + +Every selected set will be recalculated according to :math:`new = scale \cdot old + offset`. \ No newline at end of file diff --git a/nmreval/__init__.py b/nmreval/__init__.py new file mode 100644 index 0000000..d18f409 --- /dev/null +++ b/nmreval/__init__.py @@ -0,0 +1 @@ +__version__ = '0.0.2' diff --git a/nmreval/bds/__init__.py b/nmreval/bds/__init__.py new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/nmreval/bds/__init__.py @@ -0,0 +1 @@ + diff --git a/nmreval/configs.py b/nmreval/configs.py new file mode 100644 index 0000000..84f1a83 --- /dev/null +++ b/nmreval/configs.py @@ -0,0 +1,69 @@ +import configparser +import pathlib +import pickle +import logging.handlers + + +__all__ = ['config_paths', 'read_configuration', 'write_configuration', 'allowed_values', 'write_state', 'read_state'] + + +def config_paths() -> pathlib.Path: + # TODO adjust for different OS + searchpaths = ['~/.local/share/auswerten', '~/.auswerten', '/usr/share/nmreval'] + path = None + for p in searchpaths: + path = pathlib.Path(p).expanduser() + if path.exists(): + break + + if path is None: + raise FileNotFoundError('No valid configuration path found') + + return path + + +def read_configuration() -> configparser.ConfigParser: + try: + config_file = config_paths() / 'nmreval.cfg' + if not config_file.exists(): + write_configuration({'GUI': {'theme': 'normal', 'color': 'light'}}) + # raise FileNotFoundError('Configuration file not found') + # + except FileNotFoundError as e: + raise e + + parser = configparser.ConfigParser() + parser.read(config_file) + + return parser + + +def write_configuration(opts: dict): + config_file = config_paths() / 'nmreval.cfg' + + parser = configparser.ConfigParser() + parser.read_dict(opts) + + with config_file.open('w') as f: + parser.write(f) + + +allowed_values = { + ('GUI', 'theme'): ['normal', 'pokemon'], + ('GUI', 'color'): ['light', 'dark'], +} + + +def write_state(opts: dict): + config_file = config_paths() / 'guistate.ini' + with config_file.open('wb') as f: + pickle.dump(opts, f) + + +def read_state() -> dict: + config_file = config_paths() / 'guistate.ini' + if not config_file.exists(): + return {} + + with config_file.open('rb') as f: + return pickle.load(f) diff --git a/nmreval/data/__init__.py b/nmreval/data/__init__.py new file mode 100755 index 0000000..7b73355 --- /dev/null +++ b/nmreval/data/__init__.py @@ -0,0 +1,5 @@ +from .points import Points +from .signals import Signal +from .bds import BDS +from .dsc import DSC +from .nmr import FID, Spectrum diff --git a/nmreval/data/bds.py b/nmreval/data/bds.py new file mode 100644 index 0000000..086313a --- /dev/null +++ b/nmreval/data/bds.py @@ -0,0 +1,47 @@ +from typing import Any + +import numpy as np +from scipy.signal import savgol_filter + +from . import Points +from .signals import Signal + + +class BDS(Signal): + """ + Extension of Signal for dielectric spectroscopy purposes. + """ + + def __init__(self, x, y, **kwargs: Any): + super().__init__(x, y, **kwargs) + + if np.all(self._x > 0) and not np.allclose(np.diff(self._x), self._x[1]-self._x[0]): + self.dx = self.x[1] / self.x[0] + else: + self.dx = self._x[1] - self._x[0] + + def __repr__(self) -> str: + return f"{self.meta['mode']}: {self.name}" + + def derivative(self, window_length=9) -> Points: + """ + Calculates the derivative :math:`-\\frac{\\pi}{2}\\frac{d\\epsilon'}{d\\ln\\omega}`. + To reduce :func:`scipy.signal.savgol_filter` + + Args: + window_length (int) + + Returns: + Points + New Points instance with + + References: + Wübbenhorst, M.; van Turnhout, J.: Analysis of complex dielectric spectra. + I. One-dimensional derivative techniques and three-dimensional modelling. + J. Non-Cryst. Solid. 305 (2002) https://doi.org/10.1016/s0022-3093(02)01086-4 + + """ + y = -savgol_filter(self.y[self.mask].real, window_length, 2, deriv=1) * np.pi / 2 + data = Points(x=self.x[self.mask], y=y) + data.update(self.meta) + return data diff --git a/nmreval/data/dsc.py b/nmreval/data/dsc.py new file mode 100644 index 0000000..429ddcd --- /dev/null +++ b/nmreval/data/dsc.py @@ -0,0 +1,6 @@ +from .points import Points + + +class DSC(Points): + def __init__(self, x, y, **kwargs): + super().__init__(x, y, **kwargs) diff --git a/nmreval/data/nmr.py b/nmreval/data/nmr.py new file mode 100644 index 0000000..79dc663 --- /dev/null +++ b/nmreval/data/nmr.py @@ -0,0 +1,179 @@ +import warnings +from typing import Union + +import numpy as np + +from .signals import Signal + + +class FID(Signal): + def __init__(self, x, y, **kwargs): + super().__init__(x, y, **kwargs) + + self.meta['apod'] = [] + self.meta['zf'] = 0 + + def __repr__(self): + return f"FID: {self.name}" + + def baseline(self, percentage=0.12): + self._y -= self._y[int(-percentage * self.length):].mean() + + return self + + def apod(self, p, method=None): + try: + self._y *= method.apod(self._x, *p) + self.meta['apod'].append((str(method), p)) + except KeyError: + print(f'Unknown apodization {method}') + + def zerofill(self, pad: int = 1): + _max_x = self._x.max() + + factor = 2**pad + if factor < 1: + self._x = self._x[:int(self.length*factor)] + self._y = self._y[:int(self.length*factor)] + self._y_err = self._y_err[:int(self.length*factor)] + self.mask = self.mask[:int(self.length*factor)] + + elif factor > 1: + add_length = int((factor-1) * self.length) + self._y = np.concatenate((self._y, np.zeros(add_length))) + self._y_err = np.concatenate((self._y_err, np.zeros(add_length))) + self.mask = np.concatenate((self.mask, np.ones(add_length, dtype=bool))) + + _temp_x = np.arange(1, add_length+1) * self.dx + np.max(self._x) + self._x = np.concatenate((self._x, _temp_x)) + + self.meta['zf'] += pad + + return self + + def fourier(self): + ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx + freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx)) + + spec = Spectrum(freq, ft, dx=self.dx) + spec.update(self.meta) + + return spec + + def fft_depake(self, scale: bool = False): + """ + Calculate deconvoluted Pake spectra + M.A. McCabe, S.R. Wassail: + Rapid deconvolution of NMR powder spectra by weighted fast Fourier transformation, + SSNMR (1997), Vol.10, Nr.1-2, pp. 53-61, + """ + _y = self._y + 0 + # Perform FFT depaking + _y *= np.sqrt(self._x) + + # built halves + right = np.fft.fftshift(np.fft.fft(_y)) * (1 + 1j) + left = np.fft.fftshift(np.fft.fft(_y)) * (1 - 1j) + + freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx)) + + if scale: + # fourier transform should be multiplied with \sqrt(|omega|) + # but lines at omega=0 are suppressed, so sometimes not a good idea + right *= np.sqrt(np.abs(freq)) + left *= np.sqrt(np.abs(freq)) + + # built depaked spectrum + new_spc = np.concatenate([left[:(self.length//2)], right[(self.length//2):]]) + + spec = Spectrum(2*freq, new_spc, dx=self.dx) + spec.update(self.meta) + + return spec + + def convert(self): + spec = Spectrum(self._x, self._y, dx=self.dx) + self.update(self.meta) + + return spec + + def _calc_noise(self): + # noise per channel, so divide by sqrt(2) + self._y_err = np.std(self._y[-int(0.1*self.length):])/1.41 + return self._y_err * np.ones(self.length) + + def shift(self, points: Union[str, float], mode: str = 'pts'): + """ + + Args: + points : shift value, + mode (str): + + Returns: + + """ + if mode == 'pts': + super().shift(points) + else: + pts = int(points//self.dx) + super().shift(pts) + + +class Spectrum(Signal): + def __init__(self, x, y, dx=None, **kwargs): + super().__init__(x, y, **kwargs) + if dx is None: + if 2 * abs(self._x[0])/len(self._x) - abs(self._x[1]-self._x[0]) < 1e-9: + self.dx = 1/(2 * abs(self._x[0])) + else: + self.dx = 1 + else: + self.dx = dx + + def __repr__(self): + return f"Spectrum: {self.name}" + + def baseline(self, percentage: float = 0.12): + corr = np.mean([self._y[int(-percentage*self.length):], self._y[:int(percentage*self.length)]]) + self._y -= corr + + return self + + def fourier(self): + ft = np.fft.ifft(np.fft.ifftshift(self._y * self.dx)) + t = np.arange(len(ft))*self.dx + shifted = np.fft.ifftshift(self._x)[0] + if shifted != 0: + warnings.warn(f'Shift of {shifted}: Spectrum is not zero-centered and FFT may give wrong results!') + ft *= np.exp(1j * shifted * 2*np.pi*t) + + fid = FID(t, ft) + fid.update(self.meta) + + return fid + + def convert(self): + fid = FID(self._x, self._y) + fid.update(self.meta) + + return fid + + def _calc_noise(self): + step = int(0.05 * self.length) + start = np.argmin(abs(self._x - max(self._x.min(), -2e5))) + stop = np.argmin(abs(self._x - min(self._x.max(), 2e5))) + if start > stop: + stop, start = start, stop + stop -= step + self._y_err = np.inf + for i in range(start, stop): + # scan regions to find minimum noise (undisturbed by signals) + self._y_err = min(np.std(self._y[i:i + step])/np.sqrt(2), self._y_err) + + return self._y_err * np.ones(self.length) + + def shift(self, points, mode='pts'): + if mode == 'pts': + super().shift(points) + else: + return self diff --git a/nmreval/data/points.py b/nmreval/data/points.py new file mode 100644 index 0000000..c143151 --- /dev/null +++ b/nmreval/data/points.py @@ -0,0 +1,665 @@ +import copy +from numbers import Number, Real +from pathlib import Path +from typing import Any, List, Optional, Tuple, TypeVar, Union + +import numpy as np +try: + from scipy.integrate import simpson, cumulative_trapezoid +except ImportError: + from scipy.integrate import simps as simpson, cumtrapz as cumulative_trapezoid + +from ..lib.utils import ArrayLike +from ..utils import NUMBER_RE + +PointLike = TypeVar('PointLike', bound='Points') + +""" +This is a test for all and everything +""" + + +class Points: + """ + Base class for all datatypes + + Args: + x (array_like): x values + y (array_like): y values + y_err (array_like, optional): errors + + """ + def __init__(self, x: ArrayLike, y: ArrayLike, y_err: Optional[ArrayLike] = None, **kwargs: Any): + + self._x, self._y, self._y_err, self.mask = self._prepare_xy(x, y, y_err=y_err) + + name = str(kwargs.pop('name', '')) + value = kwargs.pop('value', None) + self.meta = { + 'group': '', + 'name': name.split('/')[-1] + } + self.meta.update(kwargs) + + if isinstance(value, str): + for m in NUMBER_RE.finditer(value): + value = float(m.group().replace('p', '.')) + + try: + value = float(value) + except ValueError: + value = None + + if value is None: + for m in NUMBER_RE.finditer(name): + value = float(m.group().replace('p', '.')) + if not value: + value = 0.0 + + self.meta['value'] = value + if self.name == '': + self.name = str(value) + + @staticmethod + def _prepare_xy(x: ArrayLike, y: ArrayLike, y_err: Optional[ArrayLike] = None): + x = np.atleast_1d(x).astype(float) + if x.ndim > 1: + raise TypeError('x axis cannot be multidimensional') + + y = np.atleast_1d(y) + if not np.iscomplexobj(y): + y = y.astype(float) + + if x.shape != y.shape: + # remove ugly 1-length dims + x = np.squeeze(x) + y = np.squeeze(y) + if x.shape != y.shape: + raise ValueError(f'x ({x.shape}) and y ({y.shape}) have different shapes') + + mask = np.ones_like(x, dtype=bool) + + if y_err is None: + y_err = np.zeros(y.shape, dtype=float) + + elif isinstance(y_err, Real): + y_err = np.ones(y.shape, dtype=float) * y_err + else: + y_err = np.atleast_1d(y_err) + if y_err.shape != y.shape: + raise ValueError(f'y_err ({y_err.shape}) and y ({y.shape}) have different shapes') + + return x, y, y_err, mask + + @property + def x(self) -> np.ndarray: + return self._x + + @x.setter + def x(self, value: ArrayLike): + value = np.asarray(value, dtype=float) + if value.shape == self._x.shape: + self._x = value + elif value.shape == self._x[self.mask].shape: + self._x = value + self._y = self._y[self.mask] + self._y_err = self._y_err[self.mask] + else: + raise ValueError('Shape mismatch in setting x') + + @property + def y(self) -> np.ndarray: + return self._y + + @y.setter + def y(self, value: ArrayLike): + """ + Set new y values. If the length of the new array equals that of original y including masked points + this is equivalent to a simple assignment. If the length equals the masked points then masked points are + discarded. + + Args: + value: array of the same length as full or masked y + + Returns: + + """ + value = np.asarray(value, dtype=self._y.dtype) + if value.shape == self._y.shape: + self._y = value + elif value.shape == self._y[self.mask].shape: + self._y[self.mask] = value + self._x = self._x[self.mask] + self._y_err = self._y_err[self.mask] + else: + raise ValueError('Shape mismatch in setting y') + + @property + def y_err(self) -> np.ndarray: + return self._y_err + + @y_err.setter + def y_err(self, value: ArrayLike): + value = np.asarray(value, dtype=float) + if value.shape == self._y_err.shape: + self._y_err = value + elif value.shape == self._y_err[self.mask].shape: + self._y[self.mask] = value + else: + raise ValueError('Shape mismatch in setting y_err') + + def __len__(self) -> int: + return len(self._x[self.mask]) + + def __repr__(self) -> str: + return f'Point: {self.name}' + + def __getitem__(self, item: str) -> Any: + try: + return self.meta[item] + except KeyError: + KeyError(f'{item} not found') + + def __setitem__(self, key: str, value: Any): + self.meta[key] = value + + def __delitem__(self, key: str): + del self.meta[key] + + def update(self, opts: dict) -> None: + """ + Update metadata + + Args: + opts: + + Returns: + + """ + self.meta.update(opts) + + def __lshift__(self, other): + if isinstance(other, Real): + _tempx = self._x - other + ret = type(self)(_tempx, self._y) + self.update(self.meta) + + return ret + else: + raise TypeError(f'{other} is not a number') + + def __rshift__(self, other): + return self.__lshift__(-other) + + def __ilshift__(self, other): + if isinstance(other, Real): + self._x -= other + return self + else: + raise TypeError(f'{other} is not a number') + + def __irshift__(self, other): + return self.__ilshift__(-other) + + def __add__(self, other): + """ + Implements self + other + """ + if isinstance(other, Number): + _tempy = self._y + other + ret = type(self)(self._x, _tempy) + ret.update(self.meta) + return ret + elif isinstance(other, self.__class__): + if np.equal(self._x, other.x).all(): + _tempy = self._y + other.y + ret = type(self)(self._x, _tempy) + ret.update(self.meta) + + return ret + else: + raise ValueError('Objects have different x values') + + def __radd__(self, other): + """ + Implements other + self + """ + return self.__add__(other) + + def __iadd__(self, other): + """ + Implements self += other + """ + if isinstance(other, Number): + self._y += other + return self + elif isinstance(other, self.__class__): + # restrict to same x values + if np.equal(self._x, other.x).all(): + self._y += other.y + else: + raise ValueError('Objects have different x values') + return self + + def __neg__(self): + """ + Implements -self + """ + ret = type(self)(self._x, self._y * (-1)) + ret.update(self.meta) + + return ret + + @property + def value(self): + return self.meta['value'] + + @property + def group(self): + return self.meta['group'] + + @group.setter + def group(self, value: str): + self.meta['group'] = str(value) + + @value.setter + def value(self, value: float): + self.meta['value'] = float(value) + + @property + def name(self): + return self.meta['name'] + + @name.setter + def name(self, value): + self.meta['name'] = str(value) + + @property + def length(self): + return len(self._x) + + def points(self, idx: Optional[list] = None, special: Optional[str] = None, + avg_range: Tuple[int, int] = (0, 0), avg_mode: str = 'mean', + pts: Optional[list] = None) -> List[tuple]: + """ + Return (x, y) values at specific positions. + + Args: + idx (list, optional) : List of indexes to evaluate. + special (str, {'max', 'min', 'absmax', 'absmin'}, optional) : + Special positions to extract. + + `max` : Selects position of the maximum of y, or real part if y is complex. + + 'min' : Selects position of the minimum of y, or real part if y is complex. + + 'absmax' : Selects position at the maximum of the absolute value of y. + + 'absmin' : Selects position at the minimum of the absolute value of y. + + avg_range (tuple of int) : + Region for average of y values. Tuple (a, b) uses ``y[i-a:i+b+1]`` around index `i`. Default is (0, 0). + avg_mode (str {'mean', 'sum', 'integral'} , optional) : + Averaging type + + `mean` : Arithmetic average + + `sum`: Sum over y values + + 'integral`: Integration over range using Simpson's rule + + pts (list, optional) : + If given, points will be appended. + + Returns : + list of tuples (x, y, y_err) + """ + + if (idx is None) and (special is None): + raise ValueError('Either `idx` or `special` must be given') + + if avg_mode not in ['mean', 'sum', 'integral']: + raise ValueError(f'Parameter `avg_mode` is `mean`, `sum`, `integral`, not `{avg_mode}`' ) + + if pts is None: + pts = [] + + for x in idx: + if isinstance(x, tuple): + x_idx = np.argmin(np.abs(self._x[self.mask] - (x[0]+x[1])/2)) + left_b = np.argmin(np.abs(self._x[self.mask] - x[0])) + right_b = np.argmin(np.abs(self._x[self.mask] - x[1])) + else: + x_idx = np.argmin(np.abs(self._x[self.mask]-x)) + 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: + pts.append([self._x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)]) + else: + pts.append([self._x[x_idx], self._y[x_idx], self._y_err[x_idx]]) + + if special is not None: + if special not in ['max', 'min', 'absmax', 'absmin']: + raise ValueError('Parameter "special" must be "max", "min", "absmax", "absmin".') + + if special == 'max': + x_idx = np.argmax(self._y.real[self.mask]) + elif special == 'min': + x_idx = np.argmax(self._y.real[self.mask]) + elif special == 'absmax': + x_idx = np.argmax(np.abs(self._y[self.mask])) + else: + x_idx = np.argmin(np.abs(self._y[self.mask])) + + left_b = int(max(0, x_idx - avg_range[0])) + right_b = int(min(len(self), x_idx + avg_range[1] + 1)) + + pts.append([self._x[self.mask][x_idx], *self._average(avg_mode, x_idx, left_b, right_b)]) + + return pts + + def _average(self, mode: str, idx, left: int, right: int) -> Tuple[float, float]: + if mode == 'mean': + y_mean = np.mean(self._y[self.mask][left:right].real) + y_err = np.linalg.norm(self._y_err[self.mask][left:right]) / (right - left) + + elif mode == 'sum': + y_mean = np.sum(self._y[self.mask][left:right].real) + y_err = np.linalg.norm(self._y_err[self.mask][left:right]) + + elif mode == 'integral': + y_mean = simpson(self._y[self.mask][left:right].real, x=self._x[left:right]) + y_err = np.linalg.norm(cumulative_trapezoid(self._y_err[self.mask][left:right].real, x=self._x[left:right])) + + else: + y_mean = self._y[self.mask][idx].real + y_err = self._y_err[self.mask][idx] + + return y_mean, y_err + + def concatenate(self, other): + """ + Add the values of an object of the same + + Args: + other: Dataset of same type + + """ + if isinstance(other, self.__class__): + self._x = np.r_[self._x, other.x] + self._y = np.r_[self._y, other.y] + self.mask = np.r_[self.mask, other.mask] + self._y_err = np.r_[self._y_err, other.y_err] + else: + raise TypeError(f'{other} is of type {type(other)}') + + return self + + def integrate(self, log: bool = False, limits: Tuple[float, float] = None) -> PointLike: + new_data = self.copy() + + if limits is not None: + new_data.cut(*limits) + + if log: + new_data.y = cumulative_trapezoid(y=new_data.y, x=np.log(new_data.x), initial=0) + else: + new_data.y = cumulative_trapezoid(y=new_data.y, x=new_data.x, initial=0) + + return new_data + + def diff(self, log: bool = False, limits: Optional[Tuple[float, float]] = None) -> PointLike: + """ + + Args: + log: + limits: + + Returns: + + """ + + new_data = self.copy() + + if limits is not None: + new_data.cut(*limits) + + if log: + new_data.y = np.gradient(new_data.y, np.log(new_data.x)) + else: + new_data.y = np.gradient(new_data.y, new_data.x) + + return new_data + + def magnitude(self) -> PointLike: + new_data = self.copy() + new_data.y.real = np.abs(self.y) + if np.iscomplexobj(new_data.y): + new_data.y.imag = 0. + + return new_data + + def copy(self) -> PointLike: + """ + Copy the object + + Returns: + A copy of the object + """ + return copy.deepcopy(self) + + def get_values(self, idx: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Return values at a given index + + Args: + idx (int) : Index value + + Returns: + Tuple of (`x[idx]`, `y[idx]` , `y_err[idx]`) + """ + return self._x[idx], self._y[idx], self._y_err[idx] + + def set_values(self, idx: int, value: Union[list, tuple, np.ndarray]) -> PointLike: + if not (isinstance(value, (list, tuple, np.ndarray)) and (len(value) in [2, 3])): + raise TypeError('Values should be list type of length 2 or 3') + + self._x[idx] = value[0] + self._y[idx] = value[1] + + if len(value) == 3: + self._y_err[idx] = value[2] + else: + self._y_err[idx] = 0 + + return self + + def set_data(self, + x: Optional[np.ndarray] = None, + y: Optional[np.ndarray] = None, + y_err: Optional[np.ndarray] = None) -> PointLike: + if x is None: + x = self._x + if y is None: + y = self._y + if y_err is not None: + y_err = self._y_err + + self._x, self._y, self._y_err, self.mask = self._prepare_xy(x, y, y_err) + + return self + + def append(self, x: ArrayLike, y: ArrayLike, y_err: Optional[ArrayLike] = None): + x, y, y_err, mask = self._prepare_xy(x, y, y_err) + + self._x = np.r_[self._x, x] + self._y = np.r_[self._y, y] + self._y_err = np.r_[self._y_err, y_err] + self.mask = np.r_[self.mask, mask] + + return self + + def remove(self, idx): + self._x = np.delete(self._x, idx) + self._y = np.delete(self._y, idx) + self.mask = np.delete(self.mask, idx) + self._y_err = np.delete(self._y_err, idx) + + return self + + def cut(self, *limits): + if len(limits) != 2: + raise ValueError('Two arguments are needed') + + low_lim, high_lim = limits + if low_lim is None: + low_lim = np.min(self._x) + + if high_lim is None: + high_lim = np.max(self._x) + + _mask = np.ma.masked_inside(self._x, low_lim, high_lim).mask + + self._x = self._x[_mask] + self._y = self._y[_mask] + self._y_err = self._y_err[_mask] + self.mask = self.mask[_mask] + + return self + + def shift(self, points: int) -> PointLike: + """ + Move y values `points` indexes, remove invalid indexes and fill remaining with zeros + Negative `points` shift to the left, positive `points` shift to the right + + Args: + points (int) : shift value + + Example: + >>> pts = Points(x=[0., 1., 2., 3.], y=[1., 2., 3., 4.]) + >>> pts.shift(2) + >>> pts.x + array([0., 1., 2., 3.]) + >>> pts.y + array([3., 4., 0., 0.]) + + Returns: + The original object with shifted values. + """ + if points > 0: + self._y = np.roll(self._y, -int(points)) + self._y[-int(points)-1:] = 0 + else: + self._y = np.roll(self._y, int(points)) + self._y[:-int(points)] = 0 + + self.meta['shift'] += points + + return self + + def sort(self, axis=0): + if axis == 0: + sort_order = np.argsort(self._x) + else: + sort_order = np.argsort(self._y) + self._x = self._x[sort_order] + self._y = self._y[sort_order] + self._y_err = self._y_err[sort_order] + self.mask = self.mask[sort_order] + + return self + + def normalize(self, mode): + if mode == 'first': + scale = self._y[0].real + elif mode == 'last': + scale = self._y[-1].real + elif mode == 'max': + scale = np.max(self._y.real) + elif mode == 'maxabs': + scale = np.max(np.abs(self._y)) + elif mode == 'area': + try: + from scipy.integrate import simpson + except ImportError: + from scipy.integrate import simps as simpson + scale = simpson(self._y.real, x=self._x) + else: + raise ValueError(f'Unknown normalize mode {mode}') + + self._y /= scale + self._y_err /= scale + + return scale + + def toarray(self, err=True): + if np.count_nonzero(self._y_err) == 0 or not err: + return np.c_[self._x, self._y] + else: + return np.c_[self._x, self._y, self._y_err] + + def tohdf(self, hdffile): + grp = hdffile + grp2_name = self.name + + try: + dset = grp.create_dataset(grp2_name, shape=(len(self._x),), + dtype=np.dtype([('x', 'f'), ('y', 'f')]), + compression='gzip') + except RuntimeError: + dset = grp[grp2_name] + + dset['x'] = self._x + dset['y'] = self._y + dset.attrs['TITLE'] = self.group + '/' + self.name + dset.attrs['damaris_type'] = 'MeasurementResult' + + def savetxt(self, fname, err=False): + path = Path(fname) + + if not path.parent.exists(): + path.parent.mkdir(parents=True) + + header = [] + for k, v in self.meta.items(): + header.append('%s: %s' % (k, str(v))) + header = '\n'.join(header) + + if np.all(self.mask): + np.savetxt(path, self.toarray(err=err), header=header, fmt='%.10f') + else: + with path.open('w') as f: + f.write(header) + for i, l in enumerate(self.toarray(err=err)): + if self.mask[i]: + f.write('\t'.join(map(str, l.tolist())) + '\n') + else: + f.write('#' + '\t'.join(map(str, l.tolist())) + '\n') + + def get_state(self) -> dict: + ret_dic = {'x': self._x.tolist(), + 'y': self._y.tolist(), + 'mask': (np.where(~self.mask)[0]).tolist()} + + if np.all(self._y_err == 0): + ret_dic['y_err'] = 0.0 + else: + ret_dic['y_err'] = self._y_err.tolist() + + ret_dic.update(self.meta) + + return ret_dic + + @classmethod + def set_state(cls, state: dict): + _x = state.pop('x') + _y = state.pop('y') + _yerr = state.pop('y_err') + data = cls(x=_x, y=_y, y_err=_yerr) + _m = state.pop('mask') + data.mask[_m] = False + data.meta.update(state) + + return data + diff --git a/nmreval/data/signals.py b/nmreval/data/signals.py new file mode 100644 index 0000000..222d03e --- /dev/null +++ b/nmreval/data/signals.py @@ -0,0 +1,100 @@ +import numpy as np + + +from .points import Points + + +class Signal(Points): + """ + Extension of Points for complex y values. + """ + + def __init__(self, x, y, **kwargs): + y = np.atleast_1d(y) + if (y.ndim == 1) or y.shape[0] == 1: + y = y.astype(complex) + else: + y = y[0, :] + 1j * y[1, :] + + super().__init__(x, y, **kwargs) + + self.dx = kwargs.get('dx', abs(self._x[1] - self._x[0])) + self.meta.update({'phase': [], 'shift': 0}) + + def __repr__(self): + return 'Signal: ' + self.name + + def swap(self): + """ + + Swaps real and imaginary part of y + + """ + self._y.real, self._y.imag = self._y.imag, self._y.real + + return self + + def phase(self, method: str = 'manual', **kwargs): + if method == 'manual': + self.manual_phase(**kwargs) + elif method == 'simple': + self.simple_phase(**kwargs) + else: + raise KeyError(f'Not implemented method {method}') + + return self + + def manual_phase(self, ph0: float = 0., ph1: float = 0., pvt: float = 0): + self._y *= np.exp(1j * ph0 * np.pi / 180.) + if ph1 != 0: + x = (self._x - pvt) / max(self._x) + self._y *= np.exp(1j * x * ph1 * np.pi / 180.) + self.meta['phase'].append((ph0, ph1, pvt)) + + return self + + def simple_phase(self, pos: int = 0, avg: int = 0): + ph = np.mean(np.angle(self._y)[pos-avg:pos+avg+1]) + self._y *= np.exp(-1j*ph) + + self.meta['phase'].append((ph, 0, 0)) + + return self + + def baseline_spline(self, line): + self._y -= line + + return self + + def divide(self, part): + if part: + self.cut(0.01*self._x[0], self._x[int(self.length//2)+1]) + else: + self.cut(self._x[int(self.length//2)], 10*self._x[-1]) + + return self + + def toarray(self, err=False): + if not err or np.all(self._y_err == 0.): + return np.c_[self._x, self._y.real, self._y.imag] + else: + return np.c_[self._x, self._y.real, self._y.imag, self._y_err] + + def tohdf(self, hdffile): + grp = hdffile + try: + grp2 = grp.create_group(self.name) + is_empty = True + except ValueError: + is_empty = False + grp2 = grp[self.name] + grp2.attrs['damaris_type'] = 'Accumulation' + grp2.attrs['TITLE'] = self.group + '/' + self.name + if is_empty: + grp2.create_dataset('accu_data', data=np.c_[self._y.real, self._y.imag], + compression='gzip') + idx = grp2.create_dataset('indices', (1,), dtype=np.dtype([('dwelltime', 'f')])) + idx['dwelltime'] = self.dx + else: + grp2['accu_data'][...] = np.c_[self._y.real, self._y.imag] + grp2['indices']['dwelltime'] = self.dx diff --git a/nmreval/distributions/__init__.py b/nmreval/distributions/__init__.py new file mode 100644 index 0000000..55d58aa --- /dev/null +++ b/nmreval/distributions/__init__.py @@ -0,0 +1,7 @@ + +from .havriliaknegami import HavriliakNegami +from .colecole import ColeCole +from .coledavidson import ColeDavidson +from .debye import Debye +from .kww import KWW +from .loggaussian import LogGaussian diff --git a/nmreval/distributions/base.py b/nmreval/distributions/base.py new file mode 100644 index 0000000..d89a326 --- /dev/null +++ b/nmreval/distributions/base.py @@ -0,0 +1,85 @@ +import abc +from warnings import warn + +import numpy as np + + +class Distribution(abc.ABC): + name = 'Abstract distribution' + parameter = None + bounds = None + + @classmethod + def __repr__(cls): + return cls.name + + @staticmethod + @abc.abstractmethod + def distribution(taus, tau0, *args): + pass + + @staticmethod + @abc.abstractmethod + def susceptibility(omega, tau, *args): + pass + + @classmethod + def specdens(cls, omega, tau, *args): + return -cls.susceptibility(omega, tau, *args).imag / omega + + @staticmethod + @abc.abstractmethod + def correlation(t, tau0, *args): + pass + + @classmethod + def mean_value(cls, *args, mode='mean'): + if mode == 'mean': + return cls.mean(*args) + if mode == 'logmean': + return np.exp(cls.logmean(*args)) + if mode in ['max', 'peak']: + return cls.max(*args) + + return args[0] + + @classmethod + def convert(cls, *args, from_='raw', to_='mean'): + if from_ == to_: + return args[0] + + if from_ == 'raw': + return cls.mean_value(*args, mode=to_) + else: + # makes only sense as long as mean/peak is given by = factor * x + factor = cls.mean_value(1, *args[1:], mode=from_) + try: + raw = args[0] / factor + except ZeroDivisionError: + raise ZeroDivisionError('Conversion between mean values impossible') + + if to_ == 'raw': + return raw + else: + return cls.mean_value(raw, *args[1:], mode=to_) + + @staticmethod + def logmean(*args): + """ + Return mean logarithmic tau + + Args: + *args: + + Returns: + + """ + return np.log(args[0]) + + @staticmethod + def mean(*args): + return args[0] + + @staticmethod + def max(*args): + return args[0] diff --git a/nmreval/distributions/colecole.py b/nmreval/distributions/colecole.py new file mode 100644 index 0000000..e85c58a --- /dev/null +++ b/nmreval/distributions/colecole.py @@ -0,0 +1,105 @@ +import numpy as np + +from .base import Distribution +from ..math.mittagleffler import mlf + + +class ColeCole(Distribution): + """ + Functions based on Cole-Cole distribution + """ + name = 'Cole-Cole' + parameter = [r'\alpha'] + bounds = [(0, 1)] + + @staticmethod + def distribution(taus, tau0, alpha): + """ + Distribution of correlation times + + .. math:: + G(\ln\\tau) = \\frac{1}{2\pi} \\frac{\\sin(\\pi\\alpha)}{\cosh[\\alpha\ln(\\tau/\\tau_0)] + \cos(\\alpha \pi))} + + Args: + taus: + tau0: + alpha: + """ + z = np.log(taus/tau0) + return np.sin(np.pi*alpha)/(np.cosh(z*alpha) + np.cos(alpha*np.pi))/(2*np.pi) + + @staticmethod + def susceptibility(omega, tau, alpha): + """ + Susceptibility + + .. math:: + \chi(\omega, \\tau, \\alpha) = \\frac{1}{1-(i\omega\\tau_0)^\\alpha} + + Args: + omega: + tau: + alpha: + """ + _tau = np.atleast_1d(tau) + _omega = np.atleast_1d(omega) + val = 1/(1+(1j*_omega[:, None]*_tau[None, :])**alpha) + return np.conjugate(val).squeeze() + + @staticmethod + def specdens(omega, tau, alpha): + """ + Spectral density + + .. math:: + J(\omega, \\tau, \\alpha) = \\frac{\sin(\\alpha\pi/2)(\omega\\tau)^\\alpha}{\omega[1+(\omega\\tau)^{2\\alpha} + 2\\pi\cos(\\alpha\pi/2)(\omega\\tau)^\\alpha]} + + Args: + omega: + tau: + alpha: + """ + _tau = np.atleast_1d(tau) + _omega = np.atleast_1d(omega) + omtau = (_omega*_tau)**alpha + return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega + + @staticmethod + def correlation(t, tau0, alpha): + """ + Correlation function :math:`C(t)` + + .. math:: + C(t) = E_\\alpha[-(t/\\tau_0)^\\alpha] + + with Mittag-Leffler function :math:`E_a(x)` + + Args: + t: + tau0: + alpha: + """ + return mlf(-(t/tau0)**alpha, alpha) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-3, 3, num=61) + + fig, ax = plt.subplots(2, 2) + ax[0, 0].set_title('Distribution') + ax[0, 1].set_title('Correlation func.') + ax[1, 0].set_title('Spectral density') + ax[1, 1].set_title('Susceptibility') + + for a in [0.4, 0.6, 0.8]: + ax[0, 0].loglog(x, ColeCole.distribution(x, 1, a)) + ax[0, 1].semilogx(x, ColeCole.correlation(x, 1, a)) + ax[1, 0].loglog(x, ColeCole.specdens(x, 1, a)) + g = ax[1, 1].loglog(x, ColeCole.susceptibility(x, 1, a).imag) + ax[1, 1].loglog(x, ColeCole.susceptibility(x, 1, a).real, '--', color=g[0].get_color()) + + fig.tight_layout() + + plt.show() diff --git a/nmreval/distributions/coledavidson.py b/nmreval/distributions/coledavidson.py new file mode 100644 index 0000000..8d2fab6 --- /dev/null +++ b/nmreval/distributions/coledavidson.py @@ -0,0 +1,179 @@ +import numbers + +import numpy as np +from scipy.special import psi, gammaincc + +from .base import Distribution +from ..utils.constants import Eu + + +class ColeDavidson(Distribution): + """ + Functions based on Cole-Davidson distribution + """ + name = 'Cole-Davidson' + parameter = [r'\gamma'] + bounds = [(0, 1)] + + @staticmethod + def susceptibility(omega, tau, gamma): + """ + Susceptibility + + .. math:: + \chi(\omega, \\tau, \\gamma) = \\frac{1}{(1-i\omega\\tau_0)^\\gamma} + + Args: + omega: + tau: + gamma: + """ + return (1/(1+1j*omega*tau)**gamma).conjugate() + + @staticmethod + def specdens(omega, tau,gamma): + """ + Spectral density + + .. math:: + J(\omega, \\tau, \\gamma) = \\frac{\sin[\\gamma\\arctan(\\omega\\tau)]}{\omega[1+(\omega\\tau)^2]^{\\gamma/2}} + + Args: + omega: + tau: + gamma: + """ + gam = gamma + _w = np.atleast_1d(omega) + _t = np.atleast_1d(tau) + omtau = _w[:, None] * _t[None, :] + + ret_val = np.sin(gam*np.arctan2(omtau, 1)) / (1 + omtau**2)**(gam/2.) / _w[:, None] + ret_val[_w == 0, :] = gam*_t + + return np.squeeze(ret_val) + + @staticmethod + def mean(tau, gamma): + """ + Mean tau + + .. math:: + \\langle \\tau \\rangle = \\gamma \\tau + + Args: + tau: + gamma: + + """ + + return tau*gamma + + @staticmethod + def logmean(tau, gamma): + """ + Mean logarithm of tau + + .. math:: + \\langle \ln \\tau \\rangle = \ln\\tau + \psi(\gamma) + \mathrm{Eu} + + with the Euler's constant :math:`\mathrm{Eu} \\approx 0.57721567\ldots` and the digamma function + + .. math:: + \psi(x) = \\frac{d}{dx} \ln(\Gamma(x)) + + Args: + tau: + gamma: + + References: + R. Zorn: Logartihmic moments of relaxation time distribution. J. Chem. Phys. (2002). http://dx.doi.org/10.1063/1.1446035 + + """ + return np.log(tau) + psi(gamma) + Eu + + @staticmethod + def max(tau, gamma): + """ + Calculate :math:`\\tau_\\text{peak}`, i.e. the inverse of the maximum frequency of the imaginary part of the susceptibility + + .. math:: + \\tau_\\text{peak} = \\frac{\\tau}{\\tan[\\pi/(2\gamma+2)]} + + Args: + tau: + gamma: + + References: + R. Dı́az-Calleja: Comment on the Maximum in the Loss Permittivity for the Havriliak-Negami Equation. + Macromolecules (2000). https://doi.org/10.1021/ma991082i + + """ + + return tau/np.tan(np.pi/(2*gamma+2)) + + @staticmethod + def distribution(tau, tau0, gamma): + """ + Distribution of correlation times + + .. math:: + G(\ln\\tau) = + \\begin{cases} + \\frac{\sin(\pi\gamma)}{\pi} \\Big(\\frac{\\tau}{\\tau_0 - \\tau}\\Big)^\gamma & \\tau < \\tau_0 \\\\ + 0 & \\text{else} + \end{cases} + + Args: + tau: + tau0: + gamma: + """ + + if isinstance(tau, numbers.Number): + return np.sin(np.pi*gamma) / np.pi * (1 / (tau0/tau - 1))**gamma if tau < tau0 else 0 + + ret_val = np.zeros_like(tau) + ret_val[tau < tau0] = np.sin(np.pi*gamma) / np.pi * (1 / (tau0/tau[tau < tau0] - 1))**gamma + + return ret_val + + @staticmethod + def correlation(t, tau0, gamma): + r""" + Correlation function + + .. math:: + C(t, \tau_0, \gamma) = \frac{\Gamma(\gamma, t/\tau_0)}{\Gamma(\gamma)} + + with incomplete Gamma function + + .. math:: + \Gamma(\gamma, t/\tau_0) = \frac{1}{\Gamma(\gamma)}\int_{t/\tau_0}^\infty x^{\gamma-1}\text{e}^{-x}\,\mathrm{d}x + + Args: + t: + tau0: + gamma: + + References: + R. Hilfer: H-function representations for stretched exponential relaxation and non-Debye susceptibilities in glassy systems. Phys. Rev. E (2002) https://doi.org/10.1103/PhysRevE.65.061510 + + """ + return gammaincc(gamma, t / tau0) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-3, 3, num=61) + + fig, ax = plt.subplots(2, 2) + + for g in [0.4, 0.6, 0.8]: + ax[0, 0].loglog(x, ColeDavidson.distribution(x, 1, g)) + ax[0, 1].semilogx(x, ColeDavidson.correlation(x, 1, g)) + ax[1, 0].loglog(x, ColeDavidson.specdens(x, 1, g)) + gr = ax[1, 1].loglog(x, ColeDavidson.susceptibility(x, 1, g).imag) + ax[1, 1].loglog(x, ColeDavidson.susceptibility(x, 1, g).real, '--', color=gr[0].get_color()) + plt.show() diff --git a/nmreval/distributions/debye.py b/nmreval/distributions/debye.py new file mode 100644 index 0000000..dc76bd4 --- /dev/null +++ b/nmreval/distributions/debye.py @@ -0,0 +1,30 @@ +import numbers + +import numpy as np + +from .base import Distribution + + +class Debye(Distribution): + name = 'Debye' + + @staticmethod + def correlation(t, tau0, *args): + return np.exp(-t/tau0) + + @staticmethod + def susceptibility(omega, tau0, *args): + return 1/(1 + 1j*omega*tau0) + + @staticmethod + def specdens(omega, tau0, *args): + return tau0 / (1 + (omega*tau0)**2) + + @staticmethod + def distribution(taus, tau0, *args): + if isinstance(taus, numbers.Number): + return 1 if taus == tau0 else 0 + + ret_val = np.zeros_like(taus) + ret_val[np.argmin(abs(taus - tau0))] = 1 + return ret_val diff --git a/nmreval/distributions/energy.py b/nmreval/distributions/energy.py new file mode 100644 index 0000000..41e9d53 --- /dev/null +++ b/nmreval/distributions/energy.py @@ -0,0 +1,124 @@ +from itertools import product + +import numpy as np +from scipy.integrate import simps as simpson + +from .base import Distribution +from ..utils.constants import kB + + +class EnergyBarriers(Distribution): + name = 'Energy barriers' + parameter = [r'\tau_{0}', r'E_{m}', r'\Delta E'] + + @staticmethod + def distribution(tau, temperature, *args): + t0, em, sigma = args + m = np.exp(em / (kB * temperature)) * t0 + return temperature * np.exp(-0.5 * (np.log(tau/m)*kB*temperature/sigma)**2) / np.sqrt(2*np.pi)/sigma + + @staticmethod + def rate(t0, e_a, te): + return np.exp(-e_a / (kB * te)) / t0 + + @staticmethod + def energydistribution(e_a, mu, sigma): + return np.exp(-0.5 * ((mu-e_a) / sigma) ** 2) / (np.sqrt(2 * np.pi) * sigma) + + @staticmethod + def correlation(t, temperature, *args): + 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) + 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 t for tt in temperature]) + + return ret_val + + @staticmethod + def susceptibility(omega, temperature, *args): + # in contrast to other spectral densities, it's omega and temperature + tau0, e_m, e_b = args + + def integrand_real(e_a, w, t0, mu, sigma, t): + r = EnergyBarriers.rate(t0, e_a, t) + return 1/(r**2 + w**2) * EnergyBarriers.energydistribution(e_a, mu, sigma) + + def integrand_imag(e_a, w, t0, mu, sigma, t): + r = EnergyBarriers.rate(t0, e_a, t) + return w*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 = [] + for o, tt in product(omega, temperature): + ret_val.append(simpson(integrand_real(e_axis, o, tau0, e_m, e_b, tt), e_axis) - + 1j * simpson(integrand_imag(e_axis, o, tau0, e_m, e_b, tt), e_axis)) + + 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 + + @staticmethod + def mean(*args): + return args[1]*np.exp(args[2]/(kB*args[0])) + + @staticmethod + def logmean(*args): + return args[1] + args[2] / (kB * args[0]) + + @staticmethod + def max(*args): + return args[1] * np.exp(args[2] / (kB * args[0])) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-12, 12, num=601) + + fig, ax = plt.subplots(2, 2) + ax[0, 0].set_title('Distribution') + ax[0, 1].set_title('Correlation func.') + ax[1, 0].set_title('Spectral density') + ax[1, 1].set_title('Susceptibility') + + p = [1e-12, 0.3, 0.02] + + for a in [100, 150, 300]: + ax[0, 0].semilogx(x, EnergyBarriers.distribution(x, a, *p)) + ax[0, 1].semilogx(x, EnergyBarriers.correlation(x, a, *p)) + ax[1, 0].loglog(x, EnergyBarriers.specdens(x, a, *p)) + g = ax[1, 1].loglog(x, -EnergyBarriers.susceptibility(x, a, *p).imag) + ax[1, 1].loglog(x, EnergyBarriers.susceptibility(x, a, *p).real, '--', color=g[0].get_color()) + + fig.tight_layout() + + plt.show() diff --git a/nmreval/distributions/gengamma.py b/nmreval/distributions/gengamma.py new file mode 100644 index 0000000..3c96063 --- /dev/null +++ b/nmreval/distributions/gengamma.py @@ -0,0 +1,111 @@ +from typing import Union + +import numpy as np +from scipy.integrate import simps +from scipy.special import gammaln + +from .base import Distribution + + +class GGAlpha(Distribution): + name = r'General \Gamma (\alpha-process)' + parameter = [r'\alpha', r'\beta'] + + @staticmethod + def correlation(t, tau0, *args): + logtau0 = np.log(tau0) + logtau = np.linspace(logtau0-20, logtau0+20, num=4001) + taus = np.exp(logtau) + gtau = GGAlpha.distribution(taus, tau0, *args) + ret_val = np.array([simps(np.exp(-xvals/taus)*gtau, logtau) for xvals in t]) + return ret_val + + @staticmethod + def susceptibility(omega, tau0, *args): + logtau0 = np.log(tau0) + logtau = np.linspace(logtau0 - 20, logtau0 + 20, num=4001) + taus = np.exp(logtau) + gtau = GGAlpha.distribution(taus, tau0, *args) + ret_val = np.array([simps(1/(1+1j*xvals*taus) * gtau, logtau) for xvals in omega]) + return ret_val + + @staticmethod + def distribution(taus: Union[float, np.ndarray], tau: float, *args) -> Union[float, np.ndarray]: + alpha, beta = args + b_to_a = beta / alpha + norm = np.exp(gammaln(b_to_a) - b_to_a * np.log(b_to_a)) / alpha + t_to_t0 = taus / tau + ret_val = np.exp(-b_to_a * t_to_t0 ** alpha) * t_to_t0 ** beta + + return ret_val / norm + + +class GGAlphaEW(Distribution): + name = r'General \Gamma (\alpha-process + EW)' + parameter = [r'\alpha', r'\beta'] + + @staticmethod + def correlation(t, tau0, *args): + logtau0 = np.log(tau0) + logtau = np.linspace(logtau0-20, logtau0+20, num=4001) + taus = np.exp(logtau) + gtau = GGAlphaEW.distribution(taus, tau0, *args) + # wir integrieren ueber lntau, nicht tau + ret_val = np.array([simps(np.exp(-xvals/taus)*gtau, logtau) for xvals in t]) + return ret_val + + @staticmethod + def susceptibility(omega, tau0, *args): + logtau0 = np.log(tau0) + logtau = np.linspace(logtau0 - 20, logtau0 + 20, num=4001) + taus = np.exp(logtau) + gtau = GGAlphaEW.distribution(taus, tau0, *args) + ret_val = np.array([simps(1/(1+1j*xvals*taus) * gtau, logtau) for xvals in omega]) + return ret_val + + @staticmethod + def distribution(tau: Union[float, np.ndarray], tau0: float, *args) -> Union[float, np.ndarray]: + alpha, beta, sigma, gamma = args + if gamma == beta: + return GGAlpha.distribution(tau, tau0, alpha, beta) + b_to_a = beta / alpha + g_to_a = gamma / alpha + t_to_t0 = tau / tau0 + norm = (np.exp(gammaln(b_to_a)) + sigma**(gamma-beta) * + np.exp(gammaln(g_to_a) + (b_to_a-g_to_a)*np.log(b_to_a))) / np.exp(b_to_a*np.log(b_to_a)) / alpha + + ret_val = np.exp(-b_to_a * t_to_t0**alpha) * t_to_t0**beta * (1 + (t_to_t0*sigma)**(gamma-beta)) + + return ret_val / norm + + +class GGBeta(Distribution): + name = r'General \Gamma (\beta-process)' + parameter = [r'\alpha', r'\beta'] + + @staticmethod + def correlation(t, tau0, *args): + logtau0 = np.log(tau0) + logtau = np.linspace(logtau0-20, logtau0+20, num=4001) + taus = np.exp(logtau) + gtau = GGBeta.distribution(taus, tau0, *args) + # wir integrieren ueber lntau, nicht tau + ret_val = np.array([simps(np.exp(-xvals/taus)*gtau, logtau) for xvals in t]) + return ret_val + + @staticmethod + def susceptibility(omega, tau0, *args): + logtau0 = np.log(tau0) + logtau = np.linspace(logtau0 - 20, logtau0 + 20, num=4001) + taus = np.exp(logtau) + gtau = GGBeta.distribution(taus, tau0, *args) + ret_val = np.array([simps(1/(1+1j*xvals*taus) * gtau, logtau) for xvals in omega]) + return ret_val + + @staticmethod + def distribution(tau: Union[float, np.ndarray], tau0: float, *args) -> Union[float, np.ndarray]: + a, b = args + norm = a * (1+b) * np.sin(np.pi*b/(1+b)) * b**(b/(1+b)) / np.pi + ret_val = b * (tau/tau0)**a + (tau/tau0)**(-a*b) + + return norm / ret_val diff --git a/nmreval/distributions/havriliaknegami.py b/nmreval/distributions/havriliaknegami.py new file mode 100644 index 0000000..5e771a3 --- /dev/null +++ b/nmreval/distributions/havriliaknegami.py @@ -0,0 +1,106 @@ +import numpy as np +from scipy.special import psi + +from .base import Distribution +from ..math.mittagleffler import mlf +from ..utils import Eu + + +class HavriliakNegami(Distribution): + """ + Functions based on Cole-Davidson distribution + """ + + name = 'Havriliak-Negami' + parameter = [r'\alpha', r'\gamma'] + bounds = [(0, 1), (0, 1)] + + @staticmethod + def correlation(t, tau0, alpha, gamma): + r""" + Correlation function + + .. math:: + C(t, \tau_0, \alpha, \gamma) = 1 - \left(\frac{t}{\tau_0}\right)^{\alpha\gamma} \text{E}_{\alpha,\alpha\gamma+1}^\gamma\left[-\left(\frac{t}{\tau_0}\right)^\alpha\right] + + with incomplete Gamma function + + .. math:: + \Gamma(\gamma, t/\tau_0) = \frac{1}{\Gamma(\gamma)}\int_{t/\tau_0}^\infty x^{\gamma-1}\text{e}^{-x}\,\mathrm{d}x + + Args: + t: + tau0: + alpha: + gamma: + + References: + R. Hilfer: H-function representations for stretched exponential relaxation and non-Debye susceptibilities in glassy systems. Phys. Rev. E (2002) https://doi.org/10.1103/PhysRevE.65.061510 + + """ + return 1 - (t/tau0)**(alpha*gamma) * mlf(-(t/tau0)**alpha, alpha, alpha*gamma+1, gamma) + + @staticmethod + def susceptibility(omega, tau, alpha, gamma): + return np.conjugate(1/(1 + (1j*omega[:, None]*tau[None, :])**alpha)**gamma).squeeze() + + @staticmethod + def specdens(omega, tau, alpha, gamma): + omtau = (omega[:, None]*tau[None, :])**alpha + + zaehler = np.sin(gamma * np.arctan2(omtau*np.sin(0.5*alpha*np.pi), 1 + omtau*np.cos(0.5*alpha*np.pi))) + nenner = (1 + 2*omtau * np.cos(0.5*alpha*np.pi) + omtau**2)**(0.5*gamma) + + return ((1 / omega) * (zaehler/nenner)).squeeze() + + @staticmethod + def distribution(tau, tau0, alpha, gamma): + if alpha == 1: + from .coledavidson import ColeDavidson + return ColeDavidson.distribution(tau, tau0, gamma) + elif gamma == 1: + from .colecole import ColeCole + return ColeCole.distribution(tau, tau0, alpha) + else: + _y = tau/tau0 + om_y = (1 + 2*np.cos(np.pi*alpha)*(_y**alpha) + _y**(2*alpha))**(-gamma/2) + theta_y = np.arctan2(np.sin(np.pi * alpha), _y**alpha + np.cos(np.pi*alpha)) + + return np.sin(gamma*theta_y) * om_y * (_y**(alpha*gamma)) / np.pi + + @staticmethod + def max(tau, alpha, gamma): + return tau*(np.sin(0.5*np.pi*alpha*gamma/(gamma+1)) / np.sin(0.5*np.pi*alpha/(gamma+1)))**(1/alpha) + + @staticmethod + def logmean(tau, alpha, gamma): + r""" + Calculate mean logarithm of tau + + .. math:: + \langle \ln \tau \rangle = \ln \tau + \frac{\Psi(\gamma) + \text{Eu}}{\alpha} + + Args: + tau: + alpha: + gamma: + + Returns: + + """ + return np.log(tau) + (psi(gamma)+Eu)/alpha + + @staticmethod + def mean(tau, alpha, gamma): + # approximation according to Th. Bauer et al., J. Chem. Phys. 133, 144509 (2010). + return tau * alpha*gamma + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-7, 3) + y = HavriliakNegami.correlation(x, 1, 0.8, 0.26) + + plt.semilogx(x, y) + plt.show() diff --git a/nmreval/distributions/intermolecular.py b/nmreval/distributions/intermolecular.py new file mode 100644 index 0000000..8a20ce4 --- /dev/null +++ b/nmreval/distributions/intermolecular.py @@ -0,0 +1,99 @@ +import numpy as np +from scipy.integrate import quad + +from .base import Distribution + + +class FFHS(Distribution): + name = 'Intermolecular (FFHS)' + parameter = None + + @staticmethod + def distribution(taus, tau0, *args): + # Distribution over tau, not log(tau), like in other cases + z = taus / tau0 + return 54 * np.sqrt(z) / (162*z**3 + 18*z**2 - 4*z + 2) / np.pi / tau0 + + @staticmethod + def correlation(t, tau0, *args): + def integrand(u, tt, tau0): + 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]) + + return ret_val + + @staticmethod + def specdens(omega, tau0, *args): + def integrand(u, o, tau0): + 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]) + + return ret_val + + @staticmethod + def susceptibility(omega, tau0, *args): + def integrand_real(u, o, tau0): + return FFHS.distribution(u, tau0) / (1+o**2 * u**2) + + def integrand_imag(u, o, tau0): + 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), + 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), + epsabs=1e-12, epsrel=1e-12)[0] for o in omega]) + + return ret_val + + +# class Bessel(Distribution): +# name = 'Intermolecular (Bessel)' +# parameter = None +# +# @staticmethod +# def distribution(taus, tau0, *args): +# x = np.sqrt(tau0 / taus) +# return special.jv(1.5, x)**2 / tau0 / 2 +# +# @staticmethod +# def correlation(t, tau0, *args): +# def integrand(lx, tt): +# x = np.exp(lx) +# return Bessel.distribution(x, tau0)*np.exp(-tt/lx) +# +# logaxis = np.linspace(-20+np.log(tau0), 20+np.log(tau0), num=5001) +# +# ret_val = np.array([simps(integrand(logaxis, tt), logaxis) for tt in t]) +# +# return ret_val +# +# @staticmethod +# def susceptibility(omega, tau0, *args): +# def integrand(lx, o): +# x = np.exp(lx) +# return Bessel.distribution(x, tau0) * 1/(1+1j*omega*x) +# +# logaxis = np.linspace(-20 + np.log(tau0), 20 + np.log(tau0), num=5001) +# +# ret_val = np.array([simps(integrand(logaxis, o), logaxis) for o in omega]) +# +# return ret_val + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-3, 3, num=61) + + fig, ax = plt.subplots(2, 2) + + ax[0, 0].loglog(x, FFHS.distribution(x, 1)) + ax[0, 1].semilogx(x, FFHS.correlation(x, 1)) + ax[1, 0].loglog(x, FFHS.specdens(x, 1)) + ax[1, 1].loglog(x, FFHS.susceptibility(x, 1).real, + x, -FFHS.susceptibility(x, 1).imag) + fig.tight_layout() + plt.show() diff --git a/nmreval/distributions/kww.py b/nmreval/distributions/kww.py new file mode 100644 index 0000000..87ee238 --- /dev/null +++ b/nmreval/distributions/kww.py @@ -0,0 +1,84 @@ +import numpy as np +from scipy.interpolate import interp1d +from scipy.special import gamma + +from .base import Distribution +from ..math.kww import kww_cos, kww_sin +from ..utils.constants import Eu + + +class KWW(Distribution): + name = 'KWW' + parameter = [r'\beta'] + boounds = [(0, 1)] + + @staticmethod + def distribution(taus, tau, *args): + b = args[0] + assert 0.1 <= b <= 0.9 + b_supp = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] + B_supp = [0.145, 0.197, 0.243, 0.285, 0.382, 0.306, 0.360, 0.435, 0.700] + C_supp = [0.89, 0.50, 0.35, 0.25, 0, 0.13, 0.22, 0.4015, 0.32] + + B = interp1d(b_supp, B_supp)(b) + C = interp1d(b_supp, C_supp)(b) + + tt = tau/taus + + delta = b * abs(b-0.5) / (1-b) + if b > 0.5: + f = 1 + C * tt**delta + else: + f = 1 / (1 + C * tt**delta) + ret_val = tau * B * np.exp(-(1-b) * b**(b/(1-b)) / tt**(b/(1-b))) / tt**((1-0.5*b)/(1-b)) + + return ret_val * f / taus + + @staticmethod + def correlation(t, tau, *args): + return np.exp(-(t/tau)**args[0]) + + @staticmethod + def susceptibility(omega, tau, *args): + return 1-omega*kww_sin(omega, tau, args[0]) + 1j*omega*kww_cos(omega, tau, args[0]) + + @staticmethod + def specdens(omega, tau, *args): + return kww_cos(omega, tau, args[0]) + + @staticmethod + def mean(*args): + tau, beta = args + return tau/beta * gamma(1 / beta) + + @staticmethod + def logmean(*args): + tau, beta = args + return (1-1/beta) * Eu + np.log(tau) + + @staticmethod + def max(*args): + tau, beta = args + return (1.7851 - 0.87052*beta - 0.028836*beta**2 + 0.11391*beta**3) * tau + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-3, 3, num=61) + + fig, ax = plt.subplots(2, 2) + ax[0, 0].set_title('Distribution') + ax[0, 1].set_title('Correlation func.') + ax[1, 0].set_title('Spectral density') + ax[1, 1].set_title('Susceptibility') + + for g in [0.3, 0.5, 0.7, 0.9]: + ax[0, 0].semilogx(x, KWW.distribution(x, 1, g)) + ax[0, 1].semilogx(x, KWW.correlation(x, 1, g)) + ax[1, 0].loglog(x, KWW.specdens(x, 1, g)) + a = ax[1, 1].loglog(x, KWW.susceptibility(x, 1, g).imag) + ax[1, 1].loglog(x, KWW.susceptibility(x, 1, g).real, '--', color=a[0].get_color()) + + fig.tight_layout() + plt.show() diff --git a/nmreval/distributions/loggaussian.py b/nmreval/distributions/loggaussian.py new file mode 100644 index 0000000..065eacc --- /dev/null +++ b/nmreval/distributions/loggaussian.py @@ -0,0 +1,138 @@ +from multiprocessing import Pool, cpu_count +from itertools import product + +import numpy as np +try: + from scipy.integrate import simpson +except ImportError: + from scipy.integrate import simps as simpson +from scipy.integrate import quad + +from .base import Distribution + + +__all__ = ['LogGaussian'] + + +class LogGaussian(Distribution): + name = 'Log-Gaussian' + parameter = [r'\sigma'] + bounds = [(0, 10)] + + @staticmethod + def distribution(tau, tau0, *args): + sigma = args[0] + return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma + + @staticmethod + def correlation(t, tau0, *args): + sigma = args[0] + _t = np.atleast_1d(t) + _tau = np.atleast_1d(tau0) + + pool = Pool(processes=min(cpu_count(), 4)) + integration_ranges = [(omega_i, tau_j, sigma) for (omega_i, tau_j) in product(_t, _tau)] + + with np.errstate(divide='ignore'): + res = np.array(pool.map(_integrate_process_3, integration_ranges)) + ret_val = res.reshape((_t.shape[0], _tau.shape[0])) + + return ret_val.squeeze() + + @staticmethod + def susceptibility(omega, tau0, *args): + sigma = args[0] + _omega = np.atleast_1d(omega) + _tau = np.atleast_1d(tau0) + + pool = Pool(processes=min(cpu_count(), 4)) + integration_ranges = [(omega_i, tau_j, sigma) for (omega_i, tau_j) in product(_omega, _tau)] + + with np.errstate(divide='ignore'): + res_real = np.array(pool.map(_integrate_process_1, integration_ranges)) + res_imag = np.array(pool.map(_integrate_process_2, integration_ranges)) + ret_val = (res_real+1j*res_imag).reshape((_omega.shape[0], _tau.shape[0])) + + return ret_val.squeeze() + + @staticmethod + def specdens(omega, tau0, *args): + sigma = args[0] + _omega = np.atleast_1d(omega) + _tau = np.atleast_1d(tau0) + + pool = Pool(processes=min(cpu_count(), 4)) + integration_ranges = [(omega_i, tau_j, sigma) for (omega_i, tau_j) in product(_omega, _tau)] + + with np.errstate(divide='ignore'): + res = np.array(pool.map(_integrate_process_1, integration_ranges)) + ret_val = res.reshape((_omega.shape[0], _tau.shape[0])) + + ret_val /= _omega[:, None] + + return ret_val.squeeze() + + def mean(*args): + return args[0]*np.exp(args[1]**2 / 2) + + +def _integrate_process_1(args): + omega_i, tau_j, sigma = args + area = quad(_integrand_freq_imag_high, 0, 50, args=(omega_i, tau_j, sigma))[0] + area += quad(_integrand_freq_imag_low, -50, 0, args=(omega_i, tau_j, sigma))[0] + + return area + + +def _integrate_process_2(args): + omega_i, tau_j, sigma = args + 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_i, tau_j, sigma))[0] + + return area + + +def _integrate_process_3(args): + omega_i, tau_j, sigma = args + return quad(_integrand_time, -50, 50, args=(omega_i, tau_j, sigma))[0] + + +def _integrand_time(u, t, tau, sigma): + uu = np.exp(u) + return LogGaussian.distribution(uu, tau, sigma) * np.exp(-t/uu) + + +def _integrand_freq_imag_low(u, omega, tau, sigma): + uu = np.exp(u) + return LogGaussian.distribution(uu, tau, sigma) * omega * uu / (1 + (omega*uu)**2) + + +def _integrand_freq_imag_high(u, omega, tau, sigma): + uu = np.exp(-u) + return LogGaussian.distribution(1/uu, tau, sigma) * omega * uu / (uu**2 + omega**2) + + +def _integrand_freq_real_low(u, omega, tau, sigma): + uu = np.exp(u) + return LogGaussian.distribution(uu, tau, sigma) / (1 + (omega*uu)**2) + + +def _integrand_freq_real_high(u, omega, tau, sigma): + uu = np.exp(-2*u) + return LogGaussian.distribution(np.exp(u), tau, sigma) * uu / (uu + omega**2) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + + x = np.logspace(-3, 3, num=61) + + fig, ax = plt.subplots(2, 2) + + for g in [1, 2, 5]: + ax[0, 0].loglog(x, LogGaussian.distribution(x, 1, g)) + ax[0, 1].semilogx(x, LogGaussian.correlation(x, 1, g)) + ax[1, 0].loglog(x, LogGaussian.specdens(1, x, g)) + gr = ax[1, 1].loglog(x, -LogGaussian.susceptibility(x, 1, g).imag) + ax[1, 1].loglog(x, LogGaussian.susceptibility(x, 1, g).real, '--', color=gr[0].get_color()) + plt.show() diff --git a/nmreval/dsc/calibration.py b/nmreval/dsc/calibration.py new file mode 100644 index 0000000..965605f --- /dev/null +++ b/nmreval/dsc/calibration.py @@ -0,0 +1,290 @@ +__version__ = '0.1.3' + +import os +import argparse +import sys + +import numpy as np +import matplotlib.pyplot as plt +import scipy.interpolate +from scipy.integrate import simps + +from nmreval.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.heating.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) diff --git a/nmreval/dsc/dsc_calibration_fast_neu.py b/nmreval/dsc/dsc_calibration_fast_neu.py new file mode 100644 index 0000000..0575213 --- /dev/null +++ b/nmreval/dsc/dsc_calibration_fast_neu.py @@ -0,0 +1,292 @@ +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) diff --git a/nmreval/fit/__init__.py b/nmreval/fit/__init__.py new file mode 100644 index 0000000..d311aa7 --- /dev/null +++ b/nmreval/fit/__init__.py @@ -0,0 +1,7 @@ +import numpy as np + +EPS = np.finfo(float).eps + + +class FitException(BaseException): + pass diff --git a/nmreval/fit/_meta.py b/nmreval/fit/_meta.py new file mode 100644 index 0000000..42e1d3f --- /dev/null +++ b/nmreval/fit/_meta.py @@ -0,0 +1,161 @@ +from typing import Union, Callable, Any +import operator + +from inspect import signature, Parameter + + +class ModelFactory: + + @staticmethod + def create_from_list(funcs: list, left=None, func_order=None, param_len=None, left_cnt=None): + if func_order is None: + func_order = [] + + if param_len is None: + param_len = [] + + for func in funcs: + if not func['active']: + continue + + func_order.append(func['cnt']) + param_len.append(len(func['func'].params)) + + if func['children']: + right, _, _ = ModelFactory.create_from_list(func['children'], left=func['func'], left_cnt=func['pos'], + func_order=func_order, param_len=param_len) + right_cnt = None + else: + right = func['func'] + right_cnt = func['pos'] + + if left is None: + left = right + left_cnt = right_cnt + else: + left = MultiModel(left, right, func['op'], + left_idx=left_cnt, right_idx=right_cnt) + + return left, func_order, param_len + + +class MultiModel: + op_repr = {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} + + def __init__(self, left: Any, right: Any, op: Union[str, Callable, int] = '+', left_idx=0, right_idx=1): + self._left = left + self._right = right + + self._op = None + + if isinstance(op, str): + self._op = MultiModel.str_op.get(op, None) + elif isinstance(op, int): + self._op = MultiModel.int_op.get(op, None) + elif isinstance(op, Callable): + self._op = op + + if self._op is None: + raise ValueError('Invalid binary operator.') + + self.name = '(' + self.params = [] + self.bounds = [] + self._kwargs_right = {} + self._kwargs_left = {} + self._fun_kwargs = {} + + # mapping kwargs to kwargs of underlying functions + self._ext_int_kw = {} + + self._get_parameter(left, 'l', left_idx) + self._param_left = len(left.params) + + try: + self.name += MultiModel.op_repr[self._op] + except KeyError: + self.name += str(op) + + self._get_parameter(right, 'r', right_idx) + self.name += ')' + + self._param_len = len(self.params) + + def __str__(self): + return self.name + + def _get_parameter(self, func, pos, idx): + kw_dict = {'l': self._kwargs_left, 'r': self._kwargs_right}[pos] + + if isinstance(func, MultiModel): + strcnt = '' + kw_dict.update(func.fun_kwargs) + self._fun_kwargs.update({k: v for k, v in kw_dict.items()}) + self._ext_int_kw.update({k: k for k in kw_dict.keys()}) + + else: + temp_dic = {k: v.default for k, v in signature(func.func).parameters.items() + if v.default is not Parameter.empty} + + for k, v in temp_dic.items(): + key_ = '%s_%d' % (k, idx) + kw_dict[key_] = v + self._fun_kwargs[key_] = v + self._ext_int_kw[key_] = k + + strcnt = '(%d)' % idx + + self.params += [pp+strcnt for pp in func.params] + self.name += func.name + strcnt + + try: + self.bounds.extend(func.bounds) + except AttributeError: + self.bounds.extend([(None, None)]*len(func.params)) + + def _left_arguments(self, *args, **kwargs): + kw_left = {k_int: kwargs[k_ext] for k_ext, k_int in self._ext_int_kw.items() if k_ext in self._kwargs_left} + pl = args[:self._param_left] + + return pl, kw_left + + def _right_arguments(self, *args, **kwargs): + kw_right = {k_int: kwargs[k_ext] for k_ext, k_int in self._ext_int_kw.items() if k_ext in self._kwargs_right} + pr = args[self._param_left:self._param_len] + + return pr, kw_right + + def func(self, x, *args, **kwargs): + pl, kw_left = self._left_arguments(*args, **kwargs) + l_func = self._left.func(x, *pl, **kw_left) + + pr, kw_right = self._right_arguments(*args, **kwargs) + r_func = self._right.func(x, *pr, **kw_right) + + return self._op(l_func, r_func) + + def left_func(self, x, *args, **kwargs): + return self._left.func(x, *args, **kwargs) + + def right_func(self, x, *args, **kwargs): + return self._right.func(x, *args, **kwargs) + + @property + def fun_kwargs(self): + return self._fun_kwargs + + def subs(self, x, *args, **kwargs): + """ Iterator over all sub-functions (depth-first and left-to-right) """ + pl, kw_left = self._left_arguments(*args, **kwargs) + if isinstance(self._left, MultiModel): + yield from self._left.subs(x, *pl, **kw_left) + else: + yield self._left.func(x, *pl, **kw_left) + + pr, kw_right = self._right_arguments(*args, **kwargs) + if isinstance(self._right, MultiModel): + yield from self._right.subs(x, *pr, **kw_right) + else: + yield self._right.func(x, *pr, **kw_right) diff --git a/nmreval/fit/data.py b/nmreval/fit/data.py new file mode 100644 index 0000000..42b95bb --- /dev/null +++ b/nmreval/fit/data.py @@ -0,0 +1,149 @@ +import numpy as np + +from .model import Model +from .parameter import Parameters + + +class Data(object): + def __init__(self, x, y, we=None, idx=None): + self.x = np.asarray(x) + self.y = np.asarray(y) + 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]}') + + self.we = self._calc_weights(we) + self.idx = idx + self.model = None + self.minimizer = None + self.parameter = Parameters() + self.para_keys = None + self.fun_kwargs = {} + + def __len__(self): + return self.y.shape[0] + + def _calc_weights(self, we): + if we is None: + return 1. + + 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)) + else: + we_func = lambda yy: 1. / np.abs(yy) + + if np.iscomplexobj(self.y): + weights = we_func(np.r_[self.y.real, self.y.imag]) + else: + weights = we_func(self.y) + + else: + we = 1. / np.asarray(we) + if np.iscomplexobj(self.y): + if np.iscomplexobj(we): + weights = np.r_[we.real, we.imag] + else: + weights = np.tile(we) + else: + weights = we + + weights[weights == np.inf] = np.finfo(float).max + + return weights + + def set_model(self, func, *args, **kwargs): + if isinstance(func, Model): + self.model = func + else: + self.model = Model(func, *args) + + self.fun_kwargs.update(self.model.fun_kwargs) + self.fun_kwargs.update(kwargs) + self.parameter = Parameters() + + return self.model + + def get_model(self): + return self.model + + def set_parameter(self, parameter, var=None, ub=None, lb=None, + default_bounds=False, fun_kwargs=None): + """ + Creates parameter for this data. + If no Model is available, it falls back to the model + :param parameter: list of parameters + :param var: list of boolean or boolean; False fixes parameter at given list index. + Single value is broadcast to all parameter + :param ub: list of upper boundaries or float; Single value is broadcast to all parameter. + None means no bound. + :param lb: list of lower bounds or float; Single value is broadcast to all parameter. + None means no bound. + :param default_bounds: bool; If True, uses default_bounds of a Model if lb or ub are None + :param fun_kwargs: dict; key-word arguments for model, + :return: Parameters + """ + model = self.model + if model is None: + # Data has no unique + if self.minimizer is None: + model = None + else: + model = self.minimizer.fit_model + self.fun_kwargs.update(model.fun_kwargs) + + if model is None: + raise ValueError('No model found, please set model before parameters') + + if default_bounds: + if lb is None: + lb = model.lb + if ub is None: + ub = model.ub + + self.para_keys = self.parameter.add_parameter(parameter, var=var, lb=lb, ub=ub) + + if fun_kwargs is not None: + self.fun_kwargs.update(fun_kwargs) + + return self.para_keys + + def get_parameter(self, scaled: bool = False): + """ + Returns list of parameters if set. + :param scaled: If True, returns scaled values, otherwise unscaled + :return: parameter array + """ + if self.parameter is None: + return + + if scaled: + return [p.scaled_value for p in self.minimizer.parameters[self.parameter]] + else: + return [p.value for p in self.minimizer.parameters[self.parameter]] + + def cost(self, p): + """ + Cost function :math:`y-f(p, x)` + :param p: list of parameters + :return: + """ + y_pred = self.model.func(p, self.x, **self.fun_kwargs) + resid = y_pred - self.y + + if np.iscomplexobj(resid): + resid = np.r_[resid.real, resid.imag] + + resid *= self.we + + return resid + + def func(self, p, x): + """ + Function :math:`f(p, x)` + :param x: + :param p: list of parameters + :return: + """ + return self.model.func(p, x, **self.fun_kwargs) diff --git a/nmreval/fit/minimizer.py b/nmreval/fit/minimizer.py new file mode 100644 index 0000000..4d5dc05 --- /dev/null +++ b/nmreval/fit/minimizer.py @@ -0,0 +1,489 @@ +import warnings +from itertools import product + +import numpy as np +import scipy.linalg as la +from scipy import optimize +import scipy.odr as odr + +from .data import Data +from .model import Model +from . import EPS +from .parameter import Parameters +from .result import FitResultCreator + +__all__ = ['FitRoutine', 'FitAbortException'] + + +class FitAbortException(Exception): + pass + + +class FitRoutine(object): + def __init__(self, mode='lsq'): + self._fitmethod = mode + self.data = [] + self.fit_model = None + self._no_own_model = [] + self.parameter = Parameters() + self.result = [] + self.linked = [] + self._abort = False + + def add_data(self, x, y=None, we=None, idx=None): + if isinstance(x, Data): + d = x + + else: + _x = np.asarray(x) + if _x.ndim == 2 and _x.shape[1] == 2: + d = Data(*x, we=we) + + elif y is None: + raise ValueError('First argument must be of type Data, 2d-array, ' + 'or second argument must be given') + + else: + d = Data(x, y, we=we, idx=idx) + + if idx is not None: + d.idx = idx + d.minimizer = self + self.data.append(d) + self.result.append(None) + + return d + + def remove_data(self, data): + try: + idx = self.data.index(data) + _ = self.data.pop(idx) + self.result.pop(idx) + + except ValueError: + raise IndexError('Data {} not found'.format(data)) + + def set_model(self, func, *args, idx=None, **kwargs): + if isinstance(func, Model): + model = func + else: + model = Model(func, *args, **kwargs) + + if idx is not None: + for i, data in enumerate(self.data): + if data.idx == idx: + data.set_model(model) + else: + self.fit_model = model + + return self.fit_model + + def set_link_parameter(self, parameter: tuple, replacement: tuple): + if isinstance(replacement[0], Model): + if replacement[1] not in replacement[0].global_parameter: + raise KeyError(f'Parameter at pos {replacement[1]} of ' + f'model {str(replacement[0])} is not global') + + if isinstance(parameter[0], Model): + warnings.warn(f'Replaced parameter at pos {parameter[1]} in {str(parameter[0])} ' + f'becomes global with linkage.') + + self.linked.append((*parameter, *replacement)) + + def prepare_links(self): + self._no_own_model = [] + self.parameter = Parameters() + _found_models = {} + linked_sender = {} + + for v in self.data: + linked_sender[v] = set() + self.parameter.update(v.parameter.copy()) + + # set temporaray model + if v.model is None: + v.model = self.fit_model + self._no_own_model.append(v) + + # register model + if v.model not in _found_models: + _found_models[v.model] = [] + m_param = v.model.parameter.copy() + self.parameter.update(m_param) + + _found_models[v.model].append(v) + + if v.model not in linked_sender: + linked_sender[v.model] = set() + + linked_parameter = {} + for par, par_parm, repl, repl_par in self.linked: + if isinstance(par, Data): + if isinstance(repl, Data): + 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] + + else: + if isinstance(repl, Data): + par.global_parameter[par_parm] = repl.para_keys[repl_par] + 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(): + if mm.global_parameter: + for dd in m_data: + linked_sender[mm].add(dd) + linked_sender[dd].add(mm) + + coupled_data = [] + visited_data = [] + for s in linked_sender.keys(): + if s in visited_data: + continue + sub_graph = [] + self.find_paths(s, linked_sender, sub_graph, visited_data) + if sub_graph: + coupled_data.append(sub_graph) + + return coupled_data, linked_parameter + + def find_paths(self, start, graph, coupled_nodes=None, visited_nodes=None): + visited_nodes.append(start) + if isinstance(start, Data): + coupled_nodes.append(start) + + for neighbor in graph[start]: + if neighbor in visited_nodes: + continue + + self.find_paths(neighbor, graph, coupled_nodes, visited_nodes) + + def abort(self): + print('ABORT ???') + self._abort = True + + def run(self, mode='lsq'): + self._abort = False + self.parameter = Parameters() + + fit_groups, linked_parameter = self.prepare_links() + + for data_groups in fit_groups: + if len(data_groups) == 1 and not self.linked: + data = data_groups[0] + # get variable parameter for fitter + p0_k, lb_k, ub_k, var_pars_k = self._prep_data(data) + + if mode == 'lsq': + self._least_squares_single(data, p0_k, lb_k, ub_k, var_pars_k) + + elif mode == 'nm': + self._nm_single(data, p0_k, lb_k, ub_k, var_pars_k) + + elif mode == 'odr': + # ODR takes no bounds + self._odr_single(data, p0_k, var_pars_k) + + else: + data_pars, p0, lb, ub, var_pars = self._prep_global(data_groups, linked_parameter) + + if mode == 'lsq': + self._least_squares_global(data_groups, p0, lb, ub, var_pars, data_pars) + + elif mode == 'nm': + self._nm_global(data_groups, p0, lb, ub, var_pars, data_pars) + + elif mode == 'odr': + self._odr_global(data_groups, p0, var_pars, data_pars) + + self.unprep_run() + + return self.result + + def _prep_data(self, data): + if data.get_model() is None: + data._model = self.fit_model + self._no_own_model.append(data) + + return self._prep_parameter(data.parameter) + + @staticmethod + def _prep_parameter(parameter): + vals = [] + var_pars = [] + for p_k, v_k in parameter.items(): + if v_k.var: + vals.append([v_k.scaled_value, v_k.lb / v_k.scale, v_k.ub / v_k.scale]) + var_pars.append(p_k) + + pp, lb, ub = zip(*vals) + + return pp, lb, ub, var_pars + + def _prep_global(self, data_group, linked): + p0 = [] + lb = [] + ub = [] + var = [] + data_pars = [] + + # loopyloop over data that belong to one fit (linked or global) + for data in data_group: + actual_pars = [] + for i, (p_k, v_k) in enumerate(data.parameter.items()): + p_k_used = p_k + v_k_used = v_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) + # parameter is variable and was not found before as shared parameter + if v_k_used.var and p_k_used not in var: + p0.append(v_k_used.scaled_value) + lb.append(v_k_used.lb / v_k_used.scale) + ub.append(v_k_used.ub / v_k_used.scale) + var.append(p_k_used) + + data_pars.append(actual_pars) + + return data_pars, p0, lb, ub, var + + def unprep_run(self): + for d in self._no_own_model: + d._model = None + + self._no_own_model = [] + + # 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 cost(p): + if self._abort: + raise FitAbortException(f'Fit aborted by user') + + return self.__cost_scipy(p, data, var, data.para_keys) + + with np.errstate(all='ignore'): + res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=1000 * len(p0)) + + 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, + err=err, corr=corr, partial_corr=partial_corr) + + def _least_squares_global(self, data, p0, lb, ub, var, data_pars): + def cost(p): + if self._abort: + raise FitAbortException(f'Fit aborted by user') + return self.__cost_scipy_glob(p, data, var, data_pars) + + with np.errstate(all='ignore'): + res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=1000 * len(p0)) + + 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): + self.make_results(v, res.x, var, var_pars_k, res.jac.shape, + err=err, corr=corr, partial_corr=partial_corr) + + def _nm_single(self, data, p0, lb, ub, var): + def cost(p): + if self._abort: + raise FitAbortException(f'Fit aborted by user') + return (self.__cost_scipy(p, data, var, data.para_keys)**2).sum() + + with np.errstate(all='ignore'): + res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)], + method='Nelder-Mead', options={'maxiter': 1000 * 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 cost(p): + if self._abort: + raise FitAbortException(f'Fit aborted by user') + return (self.__cost_scipy_glob(p, data, var, data_pars)**2).sum() + + with np.errstate(all='ignore'): + res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)], + method='Nelder-Mead', options={'maxiter': 1000 * len(p0)}) + + 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))) + + def _odr_single(self, data, p0, var_pars): + odr_data = odr.Data(data.x, data.y) + + def func(p, _): + if self._abort: + raise FitAbortException(f'Fit aborted by user') + return self.__cost_odr(p, data, var_pars, data.para_keys) + + odr_model = odr.Model(func) + + o = odr.ODR(odr_data, odr_model, beta0=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 + + 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 func(p, _): + if self._abort: + raise FitAbortException(f'Fit aborted by user') + return self.__cost_odr_glob(p, data, var, data_pars) + + x = [] + y = [] + for d in data: + x = np.r_[x, d.x] + y = np.r_[y, d.y] + + odr_data = odr.Data(x, y) + odr_model = odr.Model(func) + + o = odr.ODR(odr_data, odr_model, beta0=p0, ifixb=var) + 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): + self.make_results(v, res.beta, var, var_pars_k, (sum(len(d) for d in data), len(p0)), + err=res.sd_beta, corr=corr, partial_corr=partial_corr) + + def make_results(self, data, p, var_pars, used_pars, shape, + err=None, corr=None, partial_corr=None): + + if err is None: + err = [0] * len(p) + + # update parameter values + for keys, p_value, err_value in zip(var_pars, p, err): + self.parameter[keys].scaled_value = p_value + self.parameter[keys].scaled_error = err_value + + combinations = list(product(var_pars, var_pars)) + actual_parameters = [] + corr_idx = [] + + for i, p_i in enumerate(used_pars): + actual_parameters.append(self.parameter[p_i]) + for j, p_j in enumerate(used_pars): + try: + # find the position of the parameter combinations + corr_idx.append(combinations.index((p_i, p_j))) + except ValueError: + pass + + # reshape the correlation matrices + if corr is None: + actual_corr = None + actual_pcorr = None + else: + indexes = np.unravel_index(corr_idx, corr.shape) + dim_one = int(np.sqrt(len(corr_idx))) + actual_corr = corr[indexes].reshape((dim_one, -1)) + actual_pcorr = partial_corr[indexes].reshape((dim_one, -1)) + + idx = self.data.index(data) + model = data.get_model() + + self.result[idx] = FitResultCreator.make_with_model(model, data.x, data.y, + actual_parameters, data.fun_kwargs, data.idx, + *shape, corr=actual_corr, pcorr=actual_pcorr) + + return self.result + + @staticmethod + def _calc_error(jac, chi, nobs, nvars): + # copy of scipy.curve_fit to calculate covariance + # noinspection PyTupleAssignmentBalance + _, s, vt = la.svd(jac, full_matrices=False) + 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: + _err = np.zeros(nvars) + corr = np.zeros((nvars, nvars)) + else: + _err = np.sqrt(np.diag(pcov)) + corr = pcov / (_err[:, None] * _err[None, :]) + + corr = corr.astype(np.float64) + 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 + + return _err, corr, partial_corr diff --git a/nmreval/fit/model.py b/nmreval/fit/model.py new file mode 100644 index 0000000..1df8fbf --- /dev/null +++ b/nmreval/fit/model.py @@ -0,0 +1,153 @@ +import inspect +from typing import Sized + +from numpy import inf + +from ._meta import MultiModel +from .parameter import Parameters + + +class Model(object): + def __init__(self, model, *args, **kwargs): + self.idx = kwargs.pop('idx', None) + + self.is_multi = False + if inspect.isclass(model) or isinstance(model, MultiModel): + self._init_from_class(model) + elif inspect.isfunction(model): + self._init_from_function(model) + else: + raise ValueError(f'No idea how to use datatype {model}.') + + self.lb = [i if i is not None else -inf for i in self.lb] + self.ub = [i if i is not None else inf for i in self.ub] + + self.parameter = Parameters() + self.global_parameter = {} + self.is_complex = None + self._complex_part = False + + if 'complex' in kwargs: + self.set_complex(kwargs.pop('complex')) + + if args: + self.fun_args = args + else: + self.fun_args = [] + + self.fun_kwargs.update(kwargs) + + def _init_from_function(self, func): + self.name = str(func) + self._int_func = func + self._int_iter = func + + self.params = [] + self.fun_kwargs = {} + for k, v in inspect.signature(func).parameters.items(): + if v.default != inspect.Parameter.empty: + self.fun_kwargs[k] = v.default + else: + self.params.append(k) + # first parameter is x + self.params.pop(0) + + self.lb = [None] * len(self.params) + self.ub = [None] * len(self.params) + + def _init_from_class(self, model): + self.name = model.name + self.params = model.params + self._int_func = model.func + if hasattr(model, 'subs'): + self._int_iter = model.subs + self.is_multi = True + else: + self._int_iter = model.func + + + try: + self.lb, self.ub = list(zip(*model.bounds)) + except AttributeError: + self.lb = [None] * len(self.params) + self.ub = [None] * len(self.params) + + if isinstance(model, MultiModel): + self.fun_kwargs = model.fun_kwargs + else: + self.fun_kwargs = {k: v.default for k, v in inspect.signature(model.func).parameters.items() + if v.default is not inspect.Parameter.empty} + + def set_complex(self, state): + if state not in [None, 'complex', 'real', 'imag']: + raise ValueError('"complex" argument is not None, "complex", "real", "imag"') + + self.is_complex = state + if state in ['real', 'imag']: + self._complex_part = state + else: + self._complex_part = False + + def set_global_parameter(self, idx, p, var=None, lb=None, ub=None, default_bounds=False): + if idx is None: + self.parameter = Parameters() + self.global_parameter = {} + return + + 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] + + gp = self.parameter.add_parameter(p, var=var, lb=lb, ub=ub) + for k, v in zip(idx, gp): + self.global_parameter[k] = v + + return gp + + @staticmethod + def _prep(param_len, val): + if isinstance(val, Sized): + if len(val) != param_len: + raise ValueError('Length mismatch for global parameter') + else: + return [val] * param_len + + def __str__(self): + msg = 'Model: ' + self.name + return msg + + def __len__(self): + return len(self.params) + + def func(self, p, x, **kwargs): + if not kwargs: + kwargs = self.fun_kwargs + + f = self._int_func(x, *p, *self.fun_args, **kwargs) + + if self._complex_part: + if self._complex_part == 'real': + return f.real + else: + return f.imag + + return f + + def sub(self, p, x, **kwargs): + if not self.is_multi: + return [self.func(p, x, **kwargs)] + + else: + print('multi model') + if not kwargs: + kwargs = self.fun_kwargs + + if self._complex_part: + if self._complex_part == 'real': + return [f.real for f in self._int_iter(x, *p, *self.fun_args, **kwargs)] + else: + return [f.imag for f in self._int_iter(x, *p, *self.fun_args, **kwargs)] + + return list(self._int_iter(x, *p, *self.fun_args, **kwargs)) diff --git a/nmreval/fit/parameter.py b/nmreval/fit/parameter.py new file mode 100644 index 0000000..c23cff1 --- /dev/null +++ b/nmreval/fit/parameter.py @@ -0,0 +1,155 @@ +from numbers import Number +from itertools import count + +import numpy as np + + +class Parameters(dict): + count = count() + + def __str__(self): + return 'Parameters:\n' + '\n'.join([str(k)+': '+str(v) for k, v in self.items()]) + + def __getitem__(self, item): + if isinstance(item, (list, tuple, np.ndarray)): + values = [] + for item_i in item: + values.append(super().__getitem__(item_i)) + return values + else: + return super().__getitem__(item) + + @staticmethod + def _prep_bounds(val, p_len: int) -> list: + # helper function to ensure that bounds and variable are of parameter shape + if isinstance(val, (Number, bool)) or val is None: + return [val] * p_len + + elif len(val) == p_len: + 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(): + p[k] = Parameter(v.value, var=v.var, lb=v.lb, ub=v.ub) + + if len(p) == 0: + 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): + return {k: v.get_state() for k, v in self.items()} + + +class 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): + self.lb = lb if lb is not None else -np.inf + self.ub = ub if ub is not None else np.inf + + if self.lb <= value <= self.ub: + self.value = value + else: + raise ValueError('Value of parameter is outside bounds') + + self.init_val = value + + with np.errstate(divide='ignore'): + # throws RuntimeWarning for zeros + self.scale = 10**(np.floor(np.log10(np.abs(self.value)))) + + if self.scale == 0: + self.scale = 1. + + self.var = bool(var) if var is not None else True + self.error = None if self.var is False else 0.0 + self.name = '' + self.function = '' + + def __str__(self): + start = '' + if self.name: + if self.function: + start = f'{self.name} ({self.function}): ' + else: + start = self.name + ': ' + + if self.var: + return start + f'{self.value:.4g} +/- {self.error:.4g}, init={self.init_val}' + else: + return start + f'{self.value:} (fixed)' + + @property + def scaled_value(self): + return self.value / self.scale + + @scaled_value.setter + def scaled_value(self, value): + self.value = value * self.scale + + @property + def scaled_error(self): + if self.error is None: + return self.error + else: + return self.error / self.scale + + @scaled_error.setter + def scaled_error(self, value): + self.error = value * self.scale + + def get_state(self): + + return {slot: getattr(self, slot) for slot in self.__slots__} + + @staticmethod + def set_state(state: dict): + par = Parameter(state.pop('value')) + for k, v in state.items(): + setattr(par, k, v) + + return par + + @property + def full_name(self): + name = self.name + if self.function: + name += ' (' + self.function + ')' + + return name diff --git a/nmreval/fit/result.py b/nmreval/fit/result.py new file mode 100644 index 0000000..97a8a5f --- /dev/null +++ b/nmreval/fit/result.py @@ -0,0 +1,339 @@ +import pathlib +import re +from collections import OrderedDict + +import numpy as np +from scipy.stats import f as fdist +from scipy.interpolate import interp1d + +from ..data.points import Points +from .parameter import Parameter +from ..data.signals import Signal +from ..utils.text import convert + + +class FitResultCreator: + @staticmethod + def make_from_session(x_orig, y_orig, idx, kwargs) -> (dict, list): + params = OrderedDict() + + for key, pbest, err in zip(kwargs['pnames'], kwargs['parameter'], kwargs['error']): + params[key] = Parameter(pbest) + params[key].error = err + + _x = kwargs['x'] + _y = kwargs['y'] + + if len(_x) != len(x_orig): + f = interp1d(_x, _y) + rng = (x_orig >= np.min(_x)) * (x_orig <= np.max(_x)) + resid = f(x_orig[rng]) - y_orig[rng] + else: + resid = kwargs['y'] - y_orig + + stats = FitResultCreator.calc_statistics(resid, _y) + + return FitResult(kwargs['x'], kwargs['y'], x_orig, y_orig, params, dict(kwargs['choice']), resid, 0, 0, + kwargs['name'], stats, idx), [] + + @staticmethod + def make_with_model(model, x_orig, y_orig, p, fun_kwargs, idx, nobs, nvar, corr, pcorr) -> (dict, list): + if np.all(x_orig > 0) and (np.max(x_orig) > 100 * np.min(x_orig)): + islog = True + else: + islog = False + + if len(x_orig) < 51: + if islog: + _x = np.logspace(np.log10(np.min(x_orig)), np.log10(np.max(x_orig)), num=10*x_orig.size-9) + else: + _x = np.linspace(np.min(x_orig), np.max(x_orig), num=10*x_orig.size-9) + else: + _x = x_orig + + try: + pnames = model.pnames + except AttributeError: + pnames = model.params + + parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)]) + p_final = [p.value for p in parameters.values()] + + part_functions = [] + + if model.is_multi: + for sub_y in model.sub(p_final, _x, **fun_kwargs): + if np.iscomplexobj(sub_y): + part_functions.append(Signal(_x, sub_y)) + else: + part_functions.append(Points(_x, sub_y)) + + _y = model.func(p_final, _x, **fun_kwargs) + resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig + + stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar) + varied = [p.var for p in parameters.values()] + + if corr is None: + correlation = np.eye(len(p_final), len(p_final)) + partial_correlation = np.eye(len(p_final), len(p_final)) + + else: + if len(corr) < len(p_final): + correlation = np.eye(len(p_final), len(p_final)) + partial_correlation = np.eye(len(p_final), len(p_final)) + # probably some fixed variables + j = 0 + for i in range(len(corr)): + while not varied[j]: + j += 1 + correlation[j, varied] = corr[i] + partial_correlation[j, varied] = pcorr[i] + j += 1 + else: + correlation = corr + partial_correlation = pcorr + + return FitResult(_x, _y, x_orig, y_orig, parameters, fun_kwargs, resid, nobs, nvar, model.name, stats, + idx=idx, corr=correlation, pcorr=partial_correlation, islog=islog, iscomplex=model.is_complex), part_functions + + @staticmethod + def calc_statistics(y, residual, nobs=None, nvar=None): + chi = (residual**2).sum() + try: + r = 1 - chi/((y-np.mean(y))**2).sum() + except RuntimeWarning: + r = -9999 + + if nobs is None: + nobs = 1 + + if nvar is None: + nvar = 0 + + dof = nobs - nvar + loglikehood = nobs * np.log(chi / nobs) + + stats = { + 'chi^2': chi, + 'R^2': r, + 'AIC': loglikehood + 2 * nvar, + 'BIC': loglikehood + np.log(nobs) * nvar + } + + if dof != 0: + stats['adj. R^2'] = 1 - (nobs-1)/dof * (1-r) + stats['red. chi^2'] = chi / dof if dof != 0 else 0 + + if dof != 1: + stats['AICc'] = stats['AIC'] + 2*(nvar+1)*nvar / (dof-1) + + return stats + + +class FitResult(Points): + + def __init__(self, x, y, x_data, y_data, params, fun_kwargs, resid, nobs, nvar, name, stats, + idx=None, corr=None, pcorr=None, islog=False, iscomplex=None, + **kwargs): + + self.parameter, name = self._prepare_names(params, name) + + super().__init__(x=x, y=y, name=name, **kwargs) + + self.residual = resid + self.idx = idx + self.statistics = stats + self.nobs = nobs + self.nvar = nvar + self.fun_kwargs = fun_kwargs + self.correlation = corr + self.partial_correlation = pcorr + self.islog = islog + self.iscomplex = iscomplex + self.x_data = x_data + self.y_data = y_data + self._model_name = name + + @staticmethod + def _prepare_names(parameter: dict, modelname: str): + pattern = re.compile(r'(\\?\w[\\\w .-]*(?:_{[\w\\ .-]})?)(\(\d+\))') + + split_funcs = {g2: g1 for (g1, g2) in pattern.findall(modelname)} + + parameter_dic = {} + for pname, pvalue in parameter.items(): + nice_name = pname + nice_func = '' + m = pattern.match(pname) + if m: + func_number = m.group(2) + nice_name = m.group(1) + if func_number in split_funcs: + nice_func = split_funcs[func_number] + + pvalue.name = nice_name + pvalue.function = nice_func + parameter_dic[pname] = pvalue + + modelname = re.sub(r'\(\d+\)', '', modelname) + if modelname[0] == '(' and modelname[-1] == ')': + modelname = modelname[1:-1] + + return parameter_dic, modelname + + @property + def model_name(self): + return self._model_name + + def __len__(self): + return len(self.parameter) + + def __repr__(self): + try: + return 'Fit: ' + self.name + except AttributeError: + return 'FitObject' + + @property + def p_final(self): + return [pp.value for pp in self.parameter.values()] + + @property + def dof(self): + return self.nobs-self.nvar + + def pprint(self, statistics=True, correlations=True): + print('Fit result:') + print(' model :', self.name) + print(' #data :', self.nobs) + print(' #var :', self.nvar) + print('\nParameter') + print(self._parameter_string()) + if statistics: + print('Statistics') + for k, v in self.statistics.items(): + print(f' {k} : {v:.4f}') + + if correlations and self.correlation is not None: + print('\nCorrelation (partial corr.)') + print(self._correlation_string()) + print() + + def _parameter_string(self): + ret_val = '' + + for pval in self.parameter.values(): + ret_val += convert(str(pval), old='tex', new='str') + '\n' + + if self.fun_kwargs: + for k, v in self.fun_kwargs.items(): + ret_val += f' {k}: {v}\n' + + return ret_val + + def _correlation_string(self): + ret_val = '' + 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'), + convert(p_j, old='tex', new='str'), + corr_ij, pcorr_ij) + return ret_val + + def correlation_list(self, limit=0.1): + correlations = [] + + if self.correlation is not None: + pnames = list(self.parameter.keys()) + + corr = np.triu(self.correlation, k=1) + + for i, j in zip(*np.unravel_index(np.argsort(np.abs(corr), axis=None)[::-1], + self.correlation.shape)): + if i == j: + continue + + if abs(corr[i, j]) < limit: + break + + correlations.append((pnames[i], pnames[j], self.correlation[i, j], self.partial_correlation[i, j])) + + return correlations + + def savetxt(self, fname, err=True): + header = self.name + '\n' + for pval in self.parameter.values(): + header += '{}\t{}\t{}\n'.format(convert(pval.name, old='tex', new='str'), pval.value, pval.error) + if self.fun_kwargs: + for k, v in self.fun_kwargs.items(): + header += '{}\t{}\n'.format(convert(k, old='tex', new='str'), + convert(str(v), old='tex', new='str')) + + if self.iscomplex == 'complex': + np.savetxt(fname, np.c_[self.x, self.y.real, self.y.imag], header=header) + else: + np.savetxt(fname, np.c_[self.x, self.y], header=header) + + def save_parameter(self, fname: str, label: str = None, overwrite: bool = False): + path = pathlib.Path(fname) + if not path.is_file(): + overwrite = True + + writemode = 'w' if overwrite else 'a' + + if label is None: + label = self.value + + with path.open(writemode) as f: + if overwrite or not path.exists(): + f.write('# label(1)\t') + for i, pname in enumerate(self.parameter.keys()): + 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('\n') + + f.write(str(label).replace(' ', '') + '\t') + for p in self.parameter.values(): + if p.error is not None: + err = p.error + else: + err = 0. + f.write(f'{p.value:.8e}\t{err:.8e}\t') + + if self.fun_kwargs: + f.write('# ') + 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('\n') + + def f_test(self, chi2: float, dof: float): + if 'red. chi^2' not in self.statistics or dof == self.dof: + f_value = 1e318 + else: + 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) + + def get_state(self): + state = super().get_state() + + for attr in ['idx', 'fun_kwargs', 'nobs', 'nvar', + 'islog', 'iscomplex', 'x_data', 'y_data']: + state[attr] = getattr(self, attr) + + state['name'] = self._model_name + state['corr'] = self.correlation + state['pcorr'] = self.partial_correlation + state['stats'] = self.statistics + state['resid'] = self.residual + state['params'] = {k: v.get_state() for k, v in self.parameter.items()} + + state['mode'] = 'fit' + + return state + + @staticmethod + def set_state(state, **kwargs): + state['params'] = {k: Parameter.set_state(v) for k, v in state.pop('params').items()} + data = FitResult(**state) + + return data diff --git a/nmreval/gui_qt/Qt.py b/nmreval/gui_qt/Qt.py new file mode 100644 index 0000000..1a85709 --- /dev/null +++ b/nmreval/gui_qt/Qt.py @@ -0,0 +1,12 @@ + +from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport + +# from PySide2 import QtCore, QtGui, QtWidgets +# QtCore.pyqtSignal = QtCore.Signal +# QtCore.pyqtProperty = QtCore.Property +# QtCore.pyqtSlot = QtCore.Slot + +# import pyqtgraph as pg +# +# pg.setConfigOption('background', 'w') +# pg.setConfigOption('foreground', 'k') diff --git a/nmreval/gui_qt/__init__.py b/nmreval/gui_qt/__init__.py new file mode 100644 index 0000000..cd017b5 --- /dev/null +++ b/nmreval/gui_qt/__init__.py @@ -0,0 +1,17 @@ +from .Qt import QtWidgets +from .lib.styles import MyProxyStyle +from ..configs import read_configuration + + +class App(QtWidgets.QApplication): + color = 'light' + theme = 'normal' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + configs = read_configuration() + App.theme = configs.get('GUI', 'Theme') + App.color = configs.get('GUI', 'Color') + + self.setStyle(MyProxyStyle(App.color)) diff --git a/nmreval/gui_qt/_py/__init__.py b/nmreval/gui_qt/_py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmreval/gui_qt/_py/agroptiondialog.py b/nmreval/gui_qt/_py/agroptiondialog.py new file mode 100644 index 0000000..7adeaf4 --- /dev/null +++ b/nmreval/gui_qt/_py/agroptiondialog.py @@ -0,0 +1,323 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '_ui/agroptiondialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(513, 466) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(Dialog) + self.tabWidget.setTabShape(QtWidgets.QTabWidget.Rounded) + self.tabWidget.setObjectName("tabWidget") + self.tabWidgetPage1 = QtWidgets.QWidget() + self.tabWidgetPage1.setObjectName("tabWidgetPage1") + self.gridLayout_2 = QtWidgets.QGridLayout(self.tabWidgetPage1) + self.gridLayout_2.setObjectName("gridLayout_2") + self.frame_2 = QtWidgets.QFrame(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) + self.frame_2.setSizePolicy(sizePolicy) + self.frame_2.setFrameShape(QtWidgets.QFrame.Panel) + self.frame_2.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame_2.setLineWidth(3) + self.frame_2.setMidLineWidth(5) + self.frame_2.setObjectName("frame_2") + self.gridLayout_4 = QtWidgets.QGridLayout(self.frame_2) + self.gridLayout_4.setSpacing(3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.bottomMarginDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.bottomMarginDoubleSpinBox.sizePolicy().hasHeightForWidth()) + self.bottomMarginDoubleSpinBox.setSizePolicy(sizePolicy) + self.bottomMarginDoubleSpinBox.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.bottomMarginDoubleSpinBox.setObjectName("bottomMarginDoubleSpinBox") + self.gridLayout_4.addWidget(self.bottomMarginDoubleSpinBox, 5, 2, 1, 1) + self.bottomMarginLabel = QtWidgets.QLabel(self.frame_2) + self.bottomMarginLabel.setObjectName("bottomMarginLabel") + self.gridLayout_4.addWidget(self.bottomMarginLabel, 5, 1, 1, 1) + self.rightMarginLabel = QtWidgets.QLabel(self.frame_2) + self.rightMarginLabel.setAlignment(QtCore.Qt.AlignCenter) + self.rightMarginLabel.setObjectName("rightMarginLabel") + self.gridLayout_4.addWidget(self.rightMarginLabel, 2, 3, 1, 1) + self.rightMarginDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.rightMarginDoubleSpinBox.sizePolicy().hasHeightForWidth()) + self.rightMarginDoubleSpinBox.setSizePolicy(sizePolicy) + self.rightMarginDoubleSpinBox.setObjectName("rightMarginDoubleSpinBox") + self.gridLayout_4.addWidget(self.rightMarginDoubleSpinBox, 3, 3, 1, 1) + self.leftMarginDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.leftMarginDoubleSpinBox.sizePolicy().hasHeightForWidth()) + self.leftMarginDoubleSpinBox.setSizePolicy(sizePolicy) + self.leftMarginDoubleSpinBox.setObjectName("leftMarginDoubleSpinBox") + self.gridLayout_4.addWidget(self.leftMarginDoubleSpinBox, 3, 0, 1, 1) + self.leftMarginLabel = QtWidgets.QLabel(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.leftMarginLabel.sizePolicy().hasHeightForWidth()) + self.leftMarginLabel.setSizePolicy(sizePolicy) + self.leftMarginLabel.setAlignment(QtCore.Qt.AlignCenter) + self.leftMarginLabel.setObjectName("leftMarginLabel") + self.gridLayout_4.addWidget(self.leftMarginLabel, 2, 0, 1, 1) + self.topMarginLabel = QtWidgets.QLabel(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.topMarginLabel.sizePolicy().hasHeightForWidth()) + self.topMarginLabel.setSizePolicy(sizePolicy) + self.topMarginLabel.setObjectName("topMarginLabel") + self.gridLayout_4.addWidget(self.topMarginLabel, 0, 1, 1, 1) + self.topMarginDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.topMarginDoubleSpinBox.sizePolicy().hasHeightForWidth()) + self.topMarginDoubleSpinBox.setSizePolicy(sizePolicy) + self.topMarginDoubleSpinBox.setObjectName("topMarginDoubleSpinBox") + self.gridLayout_4.addWidget(self.topMarginDoubleSpinBox, 0, 2, 1, 1) + self.frame = QtWidgets.QFrame(self.frame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setFrameShape(QtWidgets.QFrame.WinPanel) + self.frame.setMidLineWidth(0) + self.frame.setObjectName("frame") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.gridLayout_4.addWidget(self.frame, 1, 1, 4, 2) + self.gridLayout_2.addWidget(self.frame_2, 1, 0, 2, 2) + self.verticalLayout_3 = QtWidgets.QVBoxLayout() + self.verticalLayout_3.setObjectName("verticalLayout_3") + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_3.addItem(spacerItem) + self.heightLabel = QtWidgets.QLabel(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.heightLabel.sizePolicy().hasHeightForWidth()) + self.heightLabel.setSizePolicy(sizePolicy) + self.heightLabel.setObjectName("heightLabel") + self.verticalLayout_3.addWidget(self.heightLabel) + self.heightDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.heightDoubleSpinBox.sizePolicy().hasHeightForWidth()) + self.heightDoubleSpinBox.setSizePolicy(sizePolicy) + self.heightDoubleSpinBox.setObjectName("heightDoubleSpinBox") + self.verticalLayout_3.addWidget(self.heightDoubleSpinBox) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_3.addItem(spacerItem1) + self.gridLayout_2.addLayout(self.verticalLayout_3, 1, 2, 2, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem2) + self.widthLabel = QtWidgets.QLabel(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widthLabel.sizePolicy().hasHeightForWidth()) + self.widthLabel.setSizePolicy(sizePolicy) + self.widthLabel.setObjectName("widthLabel") + self.horizontalLayout.addWidget(self.widthLabel) + self.widthDoubleSpinBox = QtWidgets.QDoubleSpinBox(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widthDoubleSpinBox.sizePolicy().hasHeightForWidth()) + self.widthDoubleSpinBox.setSizePolicy(sizePolicy) + self.widthDoubleSpinBox.setObjectName("widthDoubleSpinBox") + self.horizontalLayout.addWidget(self.widthDoubleSpinBox) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem3) + self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 2) + self.tabWidget.addTab(self.tabWidgetPage1, "") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.label_11 = QtWidgets.QLabel(self.tab) + self.label_11.setObjectName("label_11") + self.verticalLayout_4.addWidget(self.label_11) + self.spinBox_2 = QtWidgets.QSpinBox(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBox_2.sizePolicy().hasHeightForWidth()) + self.spinBox_2.setSizePolicy(sizePolicy) + self.spinBox_2.setMaximum(1000) + self.spinBox_2.setProperty("value", 100) + self.spinBox_2.setObjectName("spinBox_2") + self.verticalLayout_4.addWidget(self.spinBox_2) + self.legendLabel = QtWidgets.QLabel(self.tab) + self.legendLabel.setObjectName("legendLabel") + self.verticalLayout_4.addWidget(self.legendLabel) + self.spinBox = QtWidgets.QSpinBox(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBox.sizePolicy().hasHeightForWidth()) + self.spinBox.setSizePolicy(sizePolicy) + self.spinBox.setMaximum(1000) + self.spinBox.setProperty("value", 100) + self.spinBox.setObjectName("spinBox") + self.verticalLayout_4.addWidget(self.spinBox) + self.groupBox_4 = QtWidgets.QGroupBox(self.tab) + self.groupBox_4.setObjectName("groupBox_4") + self.formLayout_2 = QtWidgets.QFormLayout(self.groupBox_4) + self.formLayout_2.setObjectName("formLayout_2") + self.verticalAxisTickLabel = QtWidgets.QLabel(self.groupBox_4) + self.verticalAxisTickLabel.setObjectName("verticalAxisTickLabel") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.verticalAxisTickLabel) + self.spinBox_5 = QtWidgets.QSpinBox(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBox_5.sizePolicy().hasHeightForWidth()) + self.spinBox_5.setSizePolicy(sizePolicy) + self.spinBox_5.setMaximum(1000) + self.spinBox_5.setProperty("value", 100) + self.spinBox_5.setObjectName("spinBox_5") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.spinBox_5) + self.verticalAxisLabelSpinBox = QtWidgets.QSpinBox(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.verticalAxisLabelSpinBox.sizePolicy().hasHeightForWidth()) + self.verticalAxisLabelSpinBox.setSizePolicy(sizePolicy) + self.verticalAxisLabelSpinBox.setMaximum(1000) + self.verticalAxisLabelSpinBox.setProperty("value", 100) + self.verticalAxisLabelSpinBox.setObjectName("verticalAxisLabelSpinBox") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.verticalAxisLabelSpinBox) + self.verticalAxisLabelLabel = QtWidgets.QLabel(self.groupBox_4) + self.verticalAxisLabelLabel.setObjectName("verticalAxisLabelLabel") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.verticalAxisLabelLabel) + self.verticalLayout_4.addWidget(self.groupBox_4) + self.groupBox_3 = QtWidgets.QGroupBox(self.tab) + self.groupBox_3.setObjectName("groupBox_3") + self.formLayout = QtWidgets.QFormLayout(self.groupBox_3) + self.formLayout.setObjectName("formLayout") + self.spinBox_4 = QtWidgets.QSpinBox(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBox_4.sizePolicy().hasHeightForWidth()) + self.spinBox_4.setSizePolicy(sizePolicy) + self.spinBox_4.setMaximum(1000) + self.spinBox_4.setProperty("value", 100) + self.spinBox_4.setObjectName("spinBox_4") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.spinBox_4) + self.spinBox_3 = QtWidgets.QSpinBox(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.spinBox_3.sizePolicy().hasHeightForWidth()) + self.spinBox_3.setSizePolicy(sizePolicy) + self.spinBox_3.setMaximum(1000) + self.spinBox_3.setProperty("value", 100) + self.spinBox_3.setObjectName("spinBox_3") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.spinBox_3) + self.label_10 = QtWidgets.QLabel(self.groupBox_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth()) + self.label_10.setSizePolicy(sizePolicy) + self.label_10.setObjectName("label_10") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_10) + self.label_12 = QtWidgets.QLabel(self.groupBox_3) + self.label_12.setObjectName("label_12") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_12) + self.verticalLayout_4.addWidget(self.groupBox_3) + spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_4.addItem(spacerItem4) + self.tabWidget.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.spinBox_6 = QtWidgets.QSpinBox(self.tab_2) + self.spinBox_6.setMaximum(1000) + self.spinBox_6.setProperty("value", 100) + self.spinBox_6.setObjectName("spinBox_6") + self.gridLayout_3.addWidget(self.spinBox_6, 0, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.tab_2) + self.label_3.setObjectName("label_3") + self.gridLayout_3.addWidget(self.label_3, 0, 0, 1, 1) + spacerItem5 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_3.addItem(spacerItem5, 2, 1, 1, 1) + self.doubleSpinBox_7 = QtWidgets.QDoubleSpinBox(self.tab_2) + self.doubleSpinBox_7.setDecimals(1) + self.doubleSpinBox_7.setMaximum(20.0) + self.doubleSpinBox_7.setSingleStep(0.5) + self.doubleSpinBox_7.setProperty("value", 1.0) + self.doubleSpinBox_7.setObjectName("doubleSpinBox_7") + self.gridLayout_3.addWidget(self.doubleSpinBox_7, 1, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.tab_2) + self.label_4.setObjectName("label_4") + self.gridLayout_3.addWidget(self.label_4, 1, 0, 1, 1) + self.tabWidget.addTab(self.tab_2, "") + self.verticalLayout.addWidget(self.tabWidget) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog) + self.tabWidget.setCurrentIndex(0) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Grace settings")) + self.bottomMarginDoubleSpinBox.setSuffix(_translate("Dialog", " cm")) + self.bottomMarginLabel.setText(_translate("Dialog", "Bottom margin")) + self.rightMarginLabel.setText(_translate("Dialog", "Right margin")) + self.rightMarginDoubleSpinBox.setSuffix(_translate("Dialog", " cm")) + self.leftMarginDoubleSpinBox.setSuffix(_translate("Dialog", " cm")) + self.leftMarginLabel.setText(_translate("Dialog", "Left margin")) + self.topMarginLabel.setText(_translate("Dialog", "Top margin")) + self.topMarginDoubleSpinBox.setSuffix(_translate("Dialog", " cm")) + self.heightLabel.setText(_translate("Dialog", "Paper height")) + self.heightDoubleSpinBox.setSuffix(_translate("Dialog", " cm")) + self.widthLabel.setText(_translate("Dialog", "Paper width")) + self.widthDoubleSpinBox.setSuffix(_translate("Dialog", " cm")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("Dialog", "Dimensions")) + self.label_11.setText(_translate("Dialog", "Title")) + self.legendLabel.setText(_translate("Dialog", "Legend")) + self.groupBox_4.setTitle(_translate("Dialog", "Vertical axis")) + self.verticalAxisTickLabel.setText(_translate("Dialog", "tick")) + self.verticalAxisLabelLabel.setText(_translate("Dialog", "label")) + self.groupBox_3.setTitle(_translate("Dialog", "Horizontal axis")) + self.label_10.setText(_translate("Dialog", "tick")) + self.label_12.setText(_translate("Dialog", "label")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Dialog", "Fonts")) + self.label_3.setText(_translate("Dialog", "Default symbol size")) + self.label_4.setText(_translate("Dialog", "Default line size")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Dialog", "Symbol/Line")) diff --git a/nmreval/gui_qt/_py/apod_dialog.py b/nmreval/gui_qt/_py/apod_dialog.py new file mode 100644 index 0000000..3c985d9 --- /dev/null +++ b/nmreval/gui_qt/_py/apod_dialog.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '_ui/apod_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ApodEdit(object): + def setupUi(self, ApodEdit): + ApodEdit.setObjectName("ApodEdit") + ApodEdit.resize(784, 484) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(ApodEdit.sizePolicy().hasHeightForWidth()) + ApodEdit.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(ApodEdit) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.graphicsView = PlotWidget(ApodEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) + self.graphicsView.setSizePolicy(sizePolicy) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout.addWidget(self.graphicsView, 2, 0, 1, 1) + self.graphicsView_2 = PlotWidget(ApodEdit) + self.graphicsView_2.setObjectName("graphicsView_2") + 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.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.apodcombobox.sizePolicy().hasHeightForWidth()) + self.apodcombobox.setSizePolicy(sizePolicy) + self.apodcombobox.setObjectName("apodcombobox") + self.gridLayout.addWidget(self.apodcombobox, 0, 0, 1, 1) + self.widget_layout = QtWidgets.QHBoxLayout() + self.widget_layout.setContentsMargins(-1, 6, -1, -1) + self.widget_layout.setSpacing(20) + self.widget_layout.setObjectName("widget_layout") + self.gridLayout.addLayout(self.widget_layout, 1, 0, 1, 2) + self.buttonBox = QtWidgets.QDialogButtonBox(ApodEdit) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 2) + self.eqn_label = QtWidgets.QLabel(ApodEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.eqn_label.sizePolicy().hasHeightForWidth()) + self.eqn_label.setSizePolicy(sizePolicy) + self.eqn_label.setIndent(3) + self.eqn_label.setObjectName("eqn_label") + self.gridLayout.addWidget(self.eqn_label, 0, 1, 1, 1) + + self.retranslateUi(ApodEdit) + self.buttonBox.accepted.connect(ApodEdit.accept) + self.buttonBox.rejected.connect(ApodEdit.close) + QtCore.QMetaObject.connectSlotsByName(ApodEdit) + + def retranslateUi(self, ApodEdit): + _translate = QtCore.QCoreApplication.translate + ApodEdit.setWindowTitle(_translate("ApodEdit", "Apodization")) + self.eqn_label.setText(_translate("ApodEdit", "TextLabel")) +from pyqtgraph import PlotWidget diff --git a/nmreval/gui_qt/_py/asciidialog.py b/nmreval/gui_qt/_py/asciidialog.py new file mode 100644 index 0000000..523ad06 --- /dev/null +++ b/nmreval/gui_qt/_py/asciidialog.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '_ui/asciidialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ascii_reader(object): + def setupUi(self, ascii_reader): + ascii_reader.setObjectName("ascii_reader") + ascii_reader.resize(667, 509) + self.verticalLayout = QtWidgets.QVBoxLayout(ascii_reader) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(ascii_reader) + self.tabWidget.setObjectName("tabWidget") + self.tabWidgetPage1 = QtWidgets.QWidget() + self.tabWidgetPage1.setObjectName("tabWidgetPage1") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tabWidgetPage1) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.plainTextEdit_2 = QtWidgets.QPlainTextEdit(self.tabWidgetPage1) + self.plainTextEdit_2.setEnabled(False) + self.plainTextEdit_2.setMaximumSize(QtCore.QSize(16777215, 180)) + self.plainTextEdit_2.setObjectName("plainTextEdit_2") + self.verticalLayout_3.addWidget(self.plainTextEdit_2) + self.ascii_table = QtWidgets.QTableWidget(self.tabWidgetPage1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ascii_table.sizePolicy().hasHeightForWidth()) + self.ascii_table.setSizePolicy(sizePolicy) + self.ascii_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.ascii_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.ascii_table.setAlternatingRowColors(True) + self.ascii_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.ascii_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectColumns) + self.ascii_table.setObjectName("ascii_table") + self.ascii_table.setColumnCount(0) + self.ascii_table.setRowCount(0) + self.ascii_table.horizontalHeader().setMinimumSectionSize(1) + self.verticalLayout_3.addWidget(self.ascii_table) + self.tabWidget.addTab(self.tabWidgetPage1, "") + self.tabWidgetPage2 = QtWidgets.QWidget() + self.tabWidgetPage2.setObjectName("tabWidgetPage2") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.tabWidgetPage2) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.plainTextEdit = QtWidgets.QPlainTextEdit(self.tabWidgetPage2) + self.plainTextEdit.setObjectName("plainTextEdit") + self.horizontalLayout_3.addWidget(self.plainTextEdit) + self.formLayout = QtWidgets.QFormLayout() + self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.ExpandingFieldsGrow) + self.formLayout.setContentsMargins(0, -1, -1, -1) + self.formLayout.setObjectName("formLayout") + self.label_2 = QtWidgets.QLabel(self.tabWidgetPage2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy) + self.label_2.setObjectName("label_2") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_2) + self.delay_lineedit = QtWidgets.QLineEdit(self.tabWidgetPage2) + self.delay_lineedit.setObjectName("delay_lineedit") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.delay_lineedit) + self.label_3 = QtWidgets.QLabel(self.tabWidgetPage2) + self.label_3.setObjectName("label_3") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_3) + self.start_lineedit = QtWidgets.QLineEdit(self.tabWidgetPage2) + self.start_lineedit.setObjectName("start_lineedit") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.start_lineedit) + self.label_4 = QtWidgets.QLabel(self.tabWidgetPage2) + self.label_4.setObjectName("label_4") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_4) + self.end_lineedit = QtWidgets.QLineEdit(self.tabWidgetPage2) + self.end_lineedit.setObjectName("end_lineedit") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.end_lineedit) + self.checkBox = QtWidgets.QCheckBox(self.tabWidgetPage2) + self.checkBox.setLayoutDirection(QtCore.Qt.LeftToRight) + self.checkBox.setObjectName("checkBox") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.checkBox) + self.checkBox_2 = QtWidgets.QCheckBox(self.tabWidgetPage2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_2.sizePolicy().hasHeightForWidth()) + self.checkBox_2.setSizePolicy(sizePolicy) + self.checkBox_2.setObjectName("checkBox_2") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.checkBox_2) + self.stag_lineEdit = QtWidgets.QLineEdit(self.tabWidgetPage2) + self.stag_lineEdit.setEnabled(True) + self.stag_lineEdit.setObjectName("stag_lineEdit") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.stag_lineEdit) + self.pushButton = QtWidgets.QPushButton(self.tabWidgetPage2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth()) + self.pushButton.setSizePolicy(sizePolicy) + icon = QtGui.QIcon.fromTheme("accessories-calculator") + self.pushButton.setIcon(icon) + self.pushButton.setObjectName("pushButton") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.pushButton) + self.horizontalLayout_3.addLayout(self.formLayout) + self.tabWidget.addTab(self.tabWidgetPage2, "") + self.verticalLayout.addWidget(self.tabWidget) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.x_label = QtWidgets.QLabel(ascii_reader) + self.x_label.setObjectName("x_label") + self.horizontalLayout.addWidget(self.x_label) + self.x_lineedit = QtWidgets.QLineEdit(ascii_reader) + self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) + self.x_lineedit.setObjectName("x_lineedit") + self.horizontalLayout.addWidget(self.x_lineedit) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.y_label = QtWidgets.QLabel(ascii_reader) + self.y_label.setObjectName("y_label") + self.horizontalLayout.addWidget(self.y_label) + self.y_lineedit = QtWidgets.QLineEdit(ascii_reader) + self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) + self.y_lineedit.setObjectName("y_lineedit") + self.horizontalLayout.addWidget(self.y_lineedit) + self.widget = QtWidgets.QWidget(ascii_reader) + self.widget.setObjectName("widget") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_4.setSpacing(0) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_4.addItem(spacerItem2) + self.label_5 = QtWidgets.QLabel(self.widget) + self.label_5.setObjectName("label_5") + self.horizontalLayout_4.addWidget(self.label_5) + self.lineEdit = QtWidgets.QLineEdit(self.widget) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout_4.addWidget(self.lineEdit) + self.horizontalLayout.addWidget(self.widget) + self.verticalLayout.addLayout(self.horizontalLayout) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.skippy_checkbox = QtWidgets.QCheckBox(ascii_reader) + self.skippy_checkbox.setObjectName("skippy_checkbox") + self.horizontalLayout_2.addWidget(self.skippy_checkbox) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem3) + self.radioButton = QtWidgets.QRadioButton(ascii_reader) + self.radioButton.setChecked(True) + self.radioButton.setAutoExclusive(True) + self.radioButton.setObjectName("radioButton") + self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.radioButton) + self.horizontalLayout_2.addWidget(self.radioButton) + self.radioButton_2 = QtWidgets.QRadioButton(ascii_reader) + self.radioButton_2.setAutoExclusive(True) + self.radioButton_2.setObjectName("radioButton_2") + self.buttonGroup.addButton(self.radioButton_2) + self.horizontalLayout_2.addWidget(self.radioButton_2) + self.radioButton_3 = QtWidgets.QRadioButton(ascii_reader) + self.radioButton_3.setObjectName("radioButton_3") + self.buttonGroup.addButton(self.radioButton_3) + self.horizontalLayout_2.addWidget(self.radioButton_3) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.buttonbox = QtWidgets.QDialogButtonBox(ascii_reader) + self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonbox.setObjectName("buttonbox") + self.verticalLayout.addWidget(self.buttonbox) + + self.retranslateUi(ascii_reader) + self.tabWidget.setCurrentIndex(0) + self.buttonbox.rejected.connect(ascii_reader.close) + QtCore.QMetaObject.connectSlotsByName(ascii_reader) + + def retranslateUi(self, ascii_reader): + _translate = QtCore.QCoreApplication.translate + ascii_reader.setWindowTitle(_translate("ascii_reader", "Read text file")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("ascii_reader", "Data")) + self.label_2.setText(_translate("ascii_reader", "Number of delays")) + self.label_3.setText(_translate("ascii_reader", "Start value")) + self.label_4.setText(_translate("ascii_reader", "End value")) + self.checkBox.setText(_translate("ascii_reader", "Logarithmic scale")) + self.checkBox_2.setText(_translate("ascii_reader", "Staggered range")) + self.pushButton.setText(_translate("ascii_reader", "Calculate delays")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage2), _translate("ascii_reader", "Delays")) + self.x_label.setText(_translate("ascii_reader", "x")) + self.x_lineedit.setToolTip(_translate("ascii_reader", "

Specify which column is used as x-value.

")) + self.y_label.setText(_translate("ascii_reader", "y")) + self.y_lineedit.setToolTip(_translate("ascii_reader", "

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.)

")) + self.label_5.setText(_translate("ascii_reader", "

Δy

")) + self.skippy_checkbox.setToolTip(_translate("ascii_reader", "Use selection for next files. Deletes possible delay values.")) + self.skippy_checkbox.setText(_translate("ascii_reader", "Skip next dialogues?")) + self.radioButton.setText(_translate("ascii_reader", "Points")) + self.radioButton_2.setText(_translate("ascii_reader", "FID")) + self.radioButton_3.setText(_translate("ascii_reader", "Spectrum")) diff --git a/nmreval/gui_qt/_py/axisConfigTemplate.py b/nmreval/gui_qt/_py/axisConfigTemplate.py new file mode 100644 index 0000000..04e360e --- /dev/null +++ b/nmreval/gui_qt/_py/axisConfigTemplate.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '_ui/axisConfigTemplate.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(186, 154) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 7, 0, 1, 2) + self.linkCombo = QtWidgets.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + self.linkCombo.setObjectName("linkCombo") + self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) + self.autoPercentSpin = QtWidgets.QSpinBox(Form) + self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty("value", 100) + self.autoPercentSpin.setObjectName("autoPercentSpin") + self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) + self.autoRadio = QtWidgets.QRadioButton(Form) + self.autoRadio.setChecked(True) + self.autoRadio.setObjectName("autoRadio") + self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) + self.manualRadio = QtWidgets.QRadioButton(Form) + self.manualRadio.setObjectName("manualRadio") + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) + self.minText = QtWidgets.QLineEdit(Form) + self.minText.setObjectName("minText") + self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) + self.maxText = QtWidgets.QLineEdit(Form) + self.maxText.setObjectName("maxText") + self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) + self.invertCheck = QtWidgets.QCheckBox(Form) + self.invertCheck.setObjectName("invertCheck") + self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) + self.mouseCheck = QtWidgets.QCheckBox(Form) + self.mouseCheck.setChecked(True) + self.mouseCheck.setObjectName("mouseCheck") + self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) + self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) + self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") + self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) + self.autoPanCheck = QtWidgets.QCheckBox(Form) + self.autoPanCheck.setObjectName("autoPanCheck") + self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.label.setText(_translate("Form", "Link Axis:")) + self.linkCombo.setToolTip(_translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

")) + self.autoPercentSpin.setToolTip(_translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

")) + self.autoPercentSpin.setSuffix(_translate("Form", "%")) + self.autoRadio.setToolTip(_translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

")) + self.autoRadio.setText(_translate("Form", "Auto")) + self.manualRadio.setToolTip(_translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

")) + self.manualRadio.setText(_translate("Form", "Manual")) + self.minText.setToolTip(_translate("Form", "

Minimum value to display for this axis.

")) + self.minText.setText(_translate("Form", "0")) + self.maxText.setToolTip(_translate("Form", "

Maximum value to display for this axis.

")) + self.maxText.setText(_translate("Form", "0")) + self.invertCheck.setToolTip(_translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

")) + self.invertCheck.setText(_translate("Form", "Invert Axis")) + self.mouseCheck.setToolTip(_translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

")) + self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) + self.visibleOnlyCheck.setToolTip(_translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

")) + self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) + self.autoPanCheck.setToolTip(_translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

")) + self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) diff --git a/nmreval/gui_qt/_py/baseline_dialog.py b/nmreval/gui_qt/_py/baseline_dialog.py new file mode 100644 index 0000000..ca67fe7 --- /dev/null +++ b/nmreval/gui_qt/_py/baseline_dialog.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '_ui/baseline_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SignalEdit(object): + def setupUi(self, SignalEdit): + SignalEdit.setObjectName("SignalEdit") + SignalEdit.resize(919, 595) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(SignalEdit.sizePolicy().hasHeightForWidth()) + SignalEdit.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(SignalEdit) + self.gridLayout.setObjectName("gridLayout") + self.groupBox = QtWidgets.QWidget(SignalEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) + self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setObjectName("groupBox") + self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout.setObjectName("verticalLayout") + self.listWidget = QtWidgets.QListWidget(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setObjectName("listWidget") + self.verticalLayout.addWidget(self.listWidget) + self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(SignalEdit) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 3) + self.graphicsView = PlotWidget(SignalEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) + self.graphicsView.setSizePolicy(sizePolicy) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout.addWidget(self.graphicsView, 0, 2, 1, 1) + + self.retranslateUi(SignalEdit) + self.buttonBox.accepted.connect(SignalEdit.accept) + self.buttonBox.rejected.connect(SignalEdit.close) + QtCore.QMetaObject.connectSlotsByName(SignalEdit) + + def retranslateUi(self, SignalEdit): + _translate = QtCore.QCoreApplication.translate + SignalEdit.setWindowTitle(_translate("SignalEdit", "Dialog")) +from pyqtgraph import PlotWidget diff --git a/nmreval/gui_qt/_py/basewindow.py b/nmreval/gui_qt/_py/basewindow.py new file mode 100644 index 0000000..4e8a29b --- /dev/null +++ b/nmreval/gui_qt/_py/basewindow.py @@ -0,0 +1,606 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/basewindow.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_BaseWindow(object): + def setupUi(self, BaseWindow): + BaseWindow.setObjectName("BaseWindow") + BaseWindow.resize(1388, 735) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/logo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + BaseWindow.setWindowIcon(icon) + BaseWindow.setDockOptions(QtWidgets.QMainWindow.AllowTabbedDocks|QtWidgets.QMainWindow.AnimatedDocks|QtWidgets.QMainWindow.ForceTabbedDocks|QtWidgets.QMainWindow.VerticalTabs) + self.centralwidget = QtWidgets.QWidget(BaseWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.splitter = QtWidgets.QSplitter(self.centralwidget) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.tabWidget = QtWidgets.QTabWidget(self.splitter) + self.tabWidget.setTabPosition(QtWidgets.QTabWidget.West) + self.tabWidget.setTabShape(QtWidgets.QTabWidget.Rounded) + self.tabWidget.setElideMode(QtCore.Qt.ElideRight) + self.tabWidget.setTabsClosable(True) + self.tabWidget.setMovable(False) + self.tabWidget.setTabBarAutoHide(True) + self.tabWidget.setObjectName("tabWidget") + self.datawidget = DataWidget() + self.datawidget.setObjectName("datawidget") + self.tabWidget.addTab(self.datawidget, "") + self.valuewidget = ValueEditWidget() + self.valuewidget.setObjectName("valuewidget") + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(":/value_dock"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.valuewidget, icon1, "") + self.fit_dialog = QFitDialog() + self.fit_dialog.setObjectName("fit_dialog") + icon2 = QtGui.QIcon() + icon2.addPixmap(QtGui.QPixmap(":/fit_dock"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.fit_dialog, icon2, "") + self.editsignalwidget = EditSignalWidget() + self.editsignalwidget.setObjectName("editsignalwidget") + icon3 = QtGui.QIcon() + icon3.addPixmap(QtGui.QPixmap(":/signal_dock"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.editsignalwidget, icon3, "") + self.ptsselectwidget = PointSelectWidget() + self.ptsselectwidget.setObjectName("ptsselectwidget") + icon4 = QtGui.QIcon() + icon4.addPixmap(QtGui.QPixmap(":/peakpick_dock"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.ptsselectwidget, icon4, "") + self.t1tauwidget = QT1Widget() + self.t1tauwidget.setObjectName("t1tauwidget") + icon5 = QtGui.QIcon() + icon5.addPixmap(QtGui.QPixmap(":/eval_t1_dock"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.tabWidget.addTab(self.t1tauwidget, icon5, "") + self.area = QtWidgets.QMdiArea(self.splitter) + self.area.setObjectName("area") + self.verticalLayout.addWidget(self.splitter) + BaseWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(BaseWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1388, 30)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + self.menuSave = QtWidgets.QMenu(self.menuFile) + icon6 = QtGui.QIcon() + icon6.addPixmap(QtGui.QPixmap(":/Daleks.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.menuSave.setIcon(icon6) + self.menuSave.setSeparatorsCollapsible(True) + self.menuSave.setObjectName("menuSave") + self.menuData = QtWidgets.QMenu(self.menubar) + self.menuData.setObjectName("menuData") + self.menuHelp = QtWidgets.QMenu(self.menubar) + self.menuHelp.setObjectName("menuHelp") + self.menuExtra = QtWidgets.QMenu(self.menubar) + self.menuExtra.setObjectName("menuExtra") + self.menuNormalize = QtWidgets.QMenu(self.menuExtra) + icon7 = QtGui.QIcon() + icon7.addPixmap(QtGui.QPixmap(":/normal.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.menuNormalize.setIcon(icon7) + self.menuNormalize.setObjectName("menuNormalize") + self.menuFit = QtWidgets.QMenu(self.menubar) + self.menuFit.setObjectName("menuFit") + self.menuMethod = QtWidgets.QMenu(self.menuFit) + self.menuMethod.setObjectName("menuMethod") + self.menuLimits = QtWidgets.QMenu(self.menuFit) + self.menuLimits.setObjectName("menuLimits") + self.menuOptions = QtWidgets.QMenu(self.menubar) + self.menuOptions.setObjectName("menuOptions") + self.menuWindow = QtWidgets.QMenu(self.menubar) + self.menuWindow.setObjectName("menuWindow") + self.menuView = QtWidgets.QMenu(self.menuWindow) + self.menuView.setObjectName("menuView") + self.menuNMR = QtWidgets.QMenu(self.menubar) + self.menuNMR.setObjectName("menuNMR") + self.menuBDS = QtWidgets.QMenu(self.menubar) + self.menuBDS.setObjectName("menuBDS") + self.menuSpectrum = QtWidgets.QMenu(self.menubar) + self.menuSpectrum.setObjectName("menuSpectrum") + self.menuStuff = QtWidgets.QMenu(self.menubar) + self.menuStuff.setTitle("") + self.menuStuff.setObjectName("menuStuff") + BaseWindow.setMenuBar(self.menubar) + self.toolBar = QtWidgets.QToolBar(BaseWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.toolBar.sizePolicy().hasHeightForWidth()) + self.toolBar.setSizePolicy(sizePolicy) + self.toolBar.setAllowedAreas(QtCore.Qt.AllToolBarAreas) + self.toolBar.setIconSize(QtCore.QSize(24, 24)) + self.toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.toolBar.setObjectName("toolBar") + BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) + self.toolbar_edit = QtWidgets.QToolBar(BaseWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.toolbar_edit.sizePolicy().hasHeightForWidth()) + self.toolbar_edit.setSizePolicy(sizePolicy) + self.toolbar_edit.setIconSize(QtCore.QSize(24, 24)) + self.toolbar_edit.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.toolbar_edit.setObjectName("toolbar_edit") + BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar_edit) + self.statusBar = QtWidgets.QStatusBar(BaseWindow) + self.statusBar.setObjectName("statusBar") + BaseWindow.setStatusBar(self.statusBar) + self.toolBar_nmr = QtWidgets.QToolBar(BaseWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.toolBar_nmr.sizePolicy().hasHeightForWidth()) + self.toolBar_nmr.setSizePolicy(sizePolicy) + self.toolBar_nmr.setIconSize(QtCore.QSize(24, 24)) + self.toolBar_nmr.setObjectName("toolBar_nmr") + BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_nmr) + self.toolBar_fit = QtWidgets.QToolBar(BaseWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.toolBar_fit.sizePolicy().hasHeightForWidth()) + self.toolBar_fit.setSizePolicy(sizePolicy) + self.toolBar_fit.setIconSize(QtCore.QSize(24, 24)) + self.toolBar_fit.setObjectName("toolBar_fit") + BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_fit) + self.toolBar_spectrum = QtWidgets.QToolBar(BaseWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.toolBar_spectrum.sizePolicy().hasHeightForWidth()) + self.toolBar_spectrum.setSizePolicy(sizePolicy) + self.toolBar_spectrum.setIconSize(QtCore.QSize(24, 24)) + self.toolBar_spectrum.setObjectName("toolBar_spectrum") + BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_spectrum) + self.toolBar_data = QtWidgets.QToolBar(BaseWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.toolBar_data.sizePolicy().hasHeightForWidth()) + self.toolBar_data.setSizePolicy(sizePolicy) + self.toolBar_data.setIconSize(QtCore.QSize(24, 24)) + self.toolBar_data.setObjectName("toolBar_data") + BaseWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar_data) + self.action_close = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("window-close") + self.action_close.setIcon(icon) + self.action_close.setObjectName("action_close") + self.actionExportGraphic = QtWidgets.QAction(BaseWindow) + self.actionExportGraphic.setObjectName("actionExportGraphic") + self.action_open = QtWidgets.QAction(BaseWindow) + self.action_open.setObjectName("action_open") + self.actionExportData = QtWidgets.QAction(BaseWindow) + self.actionExportData.setObjectName("actionExportData") + self.action_calc = QtWidgets.QAction(BaseWindow) + self.action_calc.setObjectName("action_calc") + self.action_delete_sets = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("edit-delete") + self.action_delete_sets.setIcon(icon) + self.action_delete_sets.setObjectName("action_delete_sets") + self.action_save_fit_parameter = QtWidgets.QAction(BaseWindow) + self.action_save_fit_parameter.setObjectName("action_save_fit_parameter") + self.action_sort_pts = QtWidgets.QAction(BaseWindow) + self.action_sort_pts.setObjectName("action_sort_pts") + self.action_reset = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("edit-clear") + self.action_reset.setIcon(icon) + self.action_reset.setObjectName("action_reset") + self.actionDocumentation = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("help-about") + self.actionDocumentation.setIcon(icon) + self.actionDocumentation.setObjectName("actionDocumentation") + self.action_FitWidget = QtWidgets.QAction(BaseWindow) + self.action_FitWidget.setObjectName("action_FitWidget") + self.action_norm_max = QtWidgets.QAction(BaseWindow) + self.action_norm_max.setObjectName("action_norm_max") + self.action_norm_first = QtWidgets.QAction(BaseWindow) + self.action_norm_first.setObjectName("action_norm_first") + self.action_norm_area = QtWidgets.QAction(BaseWindow) + self.action_norm_area.setObjectName("action_norm_area") + self.action_norm_max_abs = QtWidgets.QAction(BaseWindow) + self.action_norm_max_abs.setObjectName("action_norm_max_abs") + self.action_norm_last = QtWidgets.QAction(BaseWindow) + self.action_norm_last.setObjectName("action_norm_last") + self.actionSave = QtWidgets.QAction(BaseWindow) + self.actionSave.setObjectName("actionSave") + self.actiontoolbar_display = QtWidgets.QAction(BaseWindow) + self.actiontoolbar_display.setCheckable(True) + self.actiontoolbar_display.setObjectName("actiontoolbar_display") + self.actionEdit_toolbars = QtWidgets.QAction(BaseWindow) + self.actionEdit_toolbars.setCheckable(True) + self.actionEdit_toolbars.setObjectName("actionEdit_toolbars") + self.actionAddlines = QtWidgets.QAction(BaseWindow) + self.actionAddlines.setObjectName("actionAddlines") + self.actionColors = QtWidgets.QAction(BaseWindow) + self.actionColors.setObjectName("actionColors") + self.actionConcatenate_sets = QtWidgets.QAction(BaseWindow) + self.actionConcatenate_sets.setObjectName("actionConcatenate_sets") + self.actionShift = QtWidgets.QAction(BaseWindow) + self.actionShift.setObjectName("actionShift") + self.actionShow_log = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("dialog-information") + self.actionShow_log.setIcon(icon) + self.actionShow_log.setObjectName("actionShow_log") + self.action_create_fit_function = QtWidgets.QAction(BaseWindow) + self.action_create_fit_function.setObjectName("action_create_fit_function") + self.actionGrace_preferences = QtWidgets.QAction(BaseWindow) + self.actionGrace_preferences.setObjectName("actionGrace_preferences") + self.actionSave_session = QtWidgets.QAction(BaseWindow) + self.actionSave_session.setObjectName("actionSave_session") + self.actionMouse_behaviour = QtWidgets.QAction(BaseWindow) + self.actionMouse_behaviour.setCheckable(True) + self.actionMouse_behaviour.setObjectName("actionMouse_behaviour") + self.actionConfiguration = QtWidgets.QAction(BaseWindow) + self.actionConfiguration.setObjectName("actionConfiguration") + self.actionRefresh = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("view-refresh") + self.actionRefresh.setIcon(icon) + self.actionRefresh.setObjectName("actionRefresh") + self.actionInterpolation = QtWidgets.QAction(BaseWindow) + self.actionInterpolation.setObjectName("actionInterpolation") + self.actionRunning_values = QtWidgets.QAction(BaseWindow) + self.actionRunning_values.setObjectName("actionRunning_values") + self.actionFit_parameter_saving = QtWidgets.QAction(BaseWindow) + self.actionFit_parameter_saving.setObjectName("actionFit_parameter_saving") + self.actionShow_fit_parameter = QtWidgets.QAction(BaseWindow) + self.actionShow_fit_parameter.setObjectName("actionShow_fit_parameter") + self.actionSkip_points = QtWidgets.QAction(BaseWindow) + self.actionSkip_points.setObjectName("actionSkip_points") + self.actionGuide_lines = QtWidgets.QAction(BaseWindow) + self.actionGuide_lines.setObjectName("actionGuide_lines") + self.actionMaximize = QtWidgets.QAction(BaseWindow) + self.actionMaximize.setCheckable(True) + self.actionMaximize.setVisible(False) + self.actionMaximize.setObjectName("actionMaximize") + self.actionTile = QtWidgets.QAction(BaseWindow) + self.actionTile.setObjectName("actionTile") + self.actionMinimize = QtWidgets.QAction(BaseWindow) + self.actionMinimize.setCheckable(True) + self.actionMinimize.setVisible(False) + self.actionMinimize.setObjectName("actionMinimize") + self.actionNew_window = QtWidgets.QAction(BaseWindow) + self.actionNew_window.setObjectName("actionNew_window") + self.actionDelete_window = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("edit-delete") + self.actionDelete_window.setIcon(icon) + self.actionDelete_window.setObjectName("actionDelete_window") + self.actionCascade_windows = QtWidgets.QAction(BaseWindow) + self.actionCascade_windows.setObjectName("actionCascade_windows") + self.actionNext_window = QtWidgets.QAction(BaseWindow) + self.actionNext_window.setObjectName("actionNext_window") + self.actionPrevious = QtWidgets.QAction(BaseWindow) + self.actionPrevious.setObjectName("actionPrevious") + self.t1action = QtWidgets.QAction(BaseWindow) + self.t1action.setObjectName("t1action") + self.t1tau = QtWidgets.QAction(BaseWindow) + self.t1tau.setObjectName("t1tau") + self.action_coup_calc = QtWidgets.QAction(BaseWindow) + self.action_coup_calc.setObjectName("action_coup_calc") + self.action_calc_eps_derivative = QtWidgets.QAction(BaseWindow) + self.action_calc_eps_derivative.setObjectName("action_calc_eps_derivative") + self.actionOpen_FC = QtWidgets.QAction(BaseWindow) + self.actionOpen_FC.setObjectName("actionOpen_FC") + self.action_mean_t1 = QtWidgets.QAction(BaseWindow) + self.action_mean_t1.setObjectName("action_mean_t1") + self.actionFilon = QtWidgets.QAction(BaseWindow) + self.actionFilon.setObjectName("actionFilon") + self.action_new_set = QtWidgets.QAction(BaseWindow) + self.action_new_set.setObjectName("action_new_set") + self.action_magnitude = QtWidgets.QAction(BaseWindow) + self.action_magnitude.setObjectName("action_magnitude") + self.actionCenterMax = QtWidgets.QAction(BaseWindow) + self.actionCenterMax.setObjectName("actionCenterMax") + self.action_depake = QtWidgets.QAction(BaseWindow) + self.action_depake.setObjectName("action_depake") + self.action_edit = QtWidgets.QAction(BaseWindow) + self.action_edit.setObjectName("action_edit") + self.actionPick_position = QtWidgets.QAction(BaseWindow) + self.actionPick_position.setObjectName("actionPick_position") + self.actionIntegrate = QtWidgets.QAction(BaseWindow) + self.actionIntegrate.setObjectName("actionIntegrate") + self.actionDerivation = QtWidgets.QAction(BaseWindow) + self.actionDerivation.setObjectName("actionDerivation") + self.actionIntegration = QtWidgets.QAction(BaseWindow) + self.actionIntegration.setObjectName("actionIntegration") + self.action_cut = QtWidgets.QAction(BaseWindow) + self.action_cut.setObjectName("action_cut") + self.actionMove_between_plots = QtWidgets.QAction(BaseWindow) + self.actionMove_between_plots.setObjectName("actionMove_between_plots") + self.actionBaseline = QtWidgets.QAction(BaseWindow) + self.actionBaseline.setObjectName("actionBaseline") + self.actionCalculateT1 = QtWidgets.QAction(BaseWindow) + self.actionCalculateT1.setObjectName("actionCalculateT1") + self.actionChange_datatypes = QtWidgets.QAction(BaseWindow) + self.actionChange_datatypes.setObjectName("actionChange_datatypes") + self.actionPrint = QtWidgets.QAction(BaseWindow) + icon = QtGui.QIcon.fromTheme("document-print") + self.actionPrint.setIcon(icon) + self.actionPrint.setObjectName("actionPrint") + self.action_lm_fit = QtWidgets.QAction(BaseWindow) + self.action_lm_fit.setCheckable(True) + self.action_lm_fit.setChecked(True) + self.action_lm_fit.setObjectName("action_lm_fit") + self.action_nm_fit = QtWidgets.QAction(BaseWindow) + self.action_nm_fit.setCheckable(True) + self.action_nm_fit.setObjectName("action_nm_fit") + self.action_odr_fit = QtWidgets.QAction(BaseWindow) + self.action_odr_fit.setCheckable(True) + self.action_odr_fit.setObjectName("action_odr_fit") + self.action_no_range = QtWidgets.QAction(BaseWindow) + self.action_no_range.setCheckable(True) + self.action_no_range.setChecked(False) + self.action_no_range.setObjectName("action_no_range") + self.action_x_range = QtWidgets.QAction(BaseWindow) + self.action_x_range.setCheckable(True) + self.action_x_range.setChecked(True) + self.action_x_range.setObjectName("action_x_range") + self.action_custom_range = QtWidgets.QAction(BaseWindow) + self.action_custom_range.setCheckable(True) + self.action_custom_range.setObjectName("action_custom_range") + self.actionSnake = QtWidgets.QAction(BaseWindow) + self.actionSnake.setObjectName("actionSnake") + self.actionFunction_editor = QtWidgets.QAction(BaseWindow) + self.actionFunction_editor.setObjectName("actionFunction_editor") + self.actionLife = QtWidgets.QAction(BaseWindow) + self.actionLife.setObjectName("actionLife") + self.actionTetris = QtWidgets.QAction(BaseWindow) + self.actionTetris.setObjectName("actionTetris") + self.actionUpdate = QtWidgets.QAction(BaseWindow) + self.actionUpdate.setObjectName("actionUpdate") + self.menuSave.addAction(self.actionSave) + self.menuSave.addAction(self.actionExportGraphic) + self.menuSave.addAction(self.action_save_fit_parameter) + self.menuFile.addAction(self.action_open) + self.menuFile.addAction(self.actionOpen_FC) + self.menuFile.addAction(self.menuSave.menuAction()) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionPrint) + self.menuFile.addAction(self.action_reset) + self.menuFile.addSeparator() + self.menuFile.addAction(self.action_close) + self.menuFile.addSeparator() + self.menuData.addAction(self.action_new_set) + self.menuData.addAction(self.action_delete_sets) + self.menuData.addAction(self.actionMove_between_plots) + self.menuData.addAction(self.actionConcatenate_sets) + self.menuData.addAction(self.actionAddlines) + self.menuData.addSeparator() + self.menuData.addAction(self.action_sort_pts) + self.menuData.addAction(self.actionSkip_points) + self.menuData.addSeparator() + self.menuData.addAction(self.action_cut) + self.menuData.addSeparator() + self.menuData.addAction(self.actionChange_datatypes) + self.menuHelp.addAction(self.actionDocumentation) + self.menuHelp.addAction(self.actionUpdate) + self.menuNormalize.addAction(self.action_norm_max) + self.menuNormalize.addAction(self.action_norm_max_abs) + self.menuNormalize.addSeparator() + self.menuNormalize.addAction(self.action_norm_first) + self.menuNormalize.addAction(self.action_norm_last) + self.menuNormalize.addSeparator() + self.menuNormalize.addAction(self.action_norm_area) + self.menuExtra.addAction(self.action_mean_t1) + self.menuExtra.addSeparator() + self.menuExtra.addAction(self.actionFilon) + self.menuExtra.addAction(self.actionDerivation) + self.menuExtra.addAction(self.actionIntegration) + self.menuExtra.addSeparator() + self.menuExtra.addAction(self.menuNormalize.menuAction()) + self.menuExtra.addAction(self.actionInterpolation) + self.menuExtra.addAction(self.actionRunning_values) + self.menuExtra.addAction(self.actionShift) + self.menuExtra.addSeparator() + self.menuExtra.addAction(self.action_calc) + self.menuMethod.addAction(self.action_lm_fit) + self.menuMethod.addAction(self.action_nm_fit) + self.menuMethod.addAction(self.action_odr_fit) + self.menuLimits.addAction(self.action_no_range) + self.menuLimits.addAction(self.action_x_range) + self.menuLimits.addAction(self.action_custom_range) + self.menuFit.addAction(self.action_FitWidget) + self.menuFit.addSeparator() + self.menuFit.addAction(self.action_create_fit_function) + self.menuFit.addAction(self.actionFunction_editor) + self.menuFit.addSeparator() + self.menuFit.addAction(self.actionShow_fit_parameter) + self.menuFit.addAction(self.menuMethod.menuAction()) + self.menuFit.addAction(self.menuLimits.menuAction()) + self.menuOptions.addAction(self.actionMouse_behaviour) + self.menuOptions.addSeparator() + self.menuOptions.addAction(self.actionGrace_preferences) + self.menuOptions.addAction(self.actionConfiguration) + self.menuView.addAction(self.actionTile) + self.menuView.addAction(self.actionCascade_windows) + self.menuWindow.addAction(self.actionNew_window) + self.menuWindow.addAction(self.actionDelete_window) + self.menuWindow.addSeparator() + self.menuWindow.addAction(self.actionNext_window) + self.menuWindow.addAction(self.actionPrevious) + self.menuWindow.addAction(self.actionMaximize) + self.menuWindow.addAction(self.actionMinimize) + self.menuWindow.addAction(self.menuView.menuAction()) + self.menuWindow.addSeparator() + self.menuWindow.addAction(self.actionRefresh) + self.menuNMR.addAction(self.t1action) + self.menuNMR.addAction(self.actionCalculateT1) + self.menuNMR.addAction(self.action_coup_calc) + self.menuBDS.addAction(self.action_calc_eps_derivative) + self.menuSpectrum.addAction(self.action_magnitude) + self.menuSpectrum.addAction(self.actionCenterMax) + self.menuSpectrum.addAction(self.action_depake) + self.menuSpectrum.addSeparator() + self.menuSpectrum.addAction(self.action_edit) + self.menuSpectrum.addAction(self.actionBaseline) + self.menuSpectrum.addAction(self.actionPick_position) + self.menuStuff.addAction(self.actionSnake) + self.menuStuff.addAction(self.actionLife) + self.menuStuff.addAction(self.actionTetris) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuWindow.menuAction()) + self.menubar.addAction(self.menuData.menuAction()) + self.menubar.addAction(self.menuExtra.menuAction()) + self.menubar.addAction(self.menuSpectrum.menuAction()) + self.menubar.addAction(self.menuFit.menuAction()) + self.menubar.addAction(self.menuNMR.menuAction()) + self.menubar.addAction(self.menuBDS.menuAction()) + self.menubar.addAction(self.menuOptions.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + self.menubar.addAction(self.menuStuff.menuAction()) + self.toolBar.addAction(self.action_open) + self.toolBar.addAction(self.actionSave) + self.toolBar.addSeparator() + self.toolBar.addAction(self.actionMouse_behaviour) + self.toolbar_edit.addAction(self.action_calc) + self.toolbar_edit.addAction(self.action_mean_t1) + self.toolbar_edit.addAction(self.actionShift) + self.toolBar_nmr.addAction(self.t1action) + self.toolBar_nmr.addAction(self.actionCalculateT1) + self.toolBar_fit.addAction(self.action_FitWidget) + self.toolBar_spectrum.addAction(self.action_edit) + self.toolBar_spectrum.addAction(self.actionPick_position) + self.toolBar_data.addAction(self.actionConcatenate_sets) + self.toolBar_data.addAction(self.action_sort_pts) + + self.retranslateUi(BaseWindow) + self.tabWidget.setCurrentIndex(0) + self.action_close.triggered.connect(BaseWindow.close) + QtCore.QMetaObject.connectSlotsByName(BaseWindow) + + def retranslateUi(self, BaseWindow): + _translate = QtCore.QCoreApplication.translate + BaseWindow.setWindowTitle(_translate("BaseWindow", "Mr. Godot told me to tell you he won\'t come this evening but surely tomorrow.")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.datawidget), _translate("BaseWindow", "Data")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.valuewidget), _translate("BaseWindow", "Values")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.fit_dialog), _translate("BaseWindow", "Fit")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.editsignalwidget), _translate("BaseWindow", "Signals")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.ptsselectwidget), _translate("BaseWindow", "Pick points")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.t1tauwidget), _translate("BaseWindow", "SLR")) + self.menuFile.setTitle(_translate("BaseWindow", "&File")) + self.menuSave.setTitle(_translate("BaseWindow", "&Save...")) + self.menuData.setTitle(_translate("BaseWindow", "&Data")) + self.menuHelp.setTitle(_translate("BaseWindow", "&Help")) + self.menuExtra.setTitle(_translate("BaseWindow", "Math")) + self.menuNormalize.setTitle(_translate("BaseWindow", "&Normalize")) + self.menuFit.setTitle(_translate("BaseWindow", "F&it")) + self.menuMethod.setTitle(_translate("BaseWindow", "Method")) + self.menuLimits.setTitle(_translate("BaseWindow", "Limits")) + self.menuOptions.setTitle(_translate("BaseWindow", "Options")) + self.menuWindow.setTitle(_translate("BaseWindow", "Plots")) + self.menuView.setTitle(_translate("BaseWindow", "View")) + self.menuNMR.setTitle(_translate("BaseWindow", "NMR")) + self.menuBDS.setTitle(_translate("BaseWindow", "BDS")) + self.menuSpectrum.setTitle(_translate("BaseWindow", "Spectrum")) + self.toolBar.setWindowTitle(_translate("BaseWindow", "Main")) + self.toolbar_edit.setWindowTitle(_translate("BaseWindow", "Edit")) + self.toolBar_nmr.setWindowTitle(_translate("BaseWindow", "NMR")) + self.toolBar_fit.setWindowTitle(_translate("BaseWindow", "Fit")) + self.toolBar_spectrum.setWindowTitle(_translate("BaseWindow", "Spectrum")) + self.toolBar_data.setWindowTitle(_translate("BaseWindow", "toolBar_2")) + self.action_close.setText(_translate("BaseWindow", "&Quit")) + self.action_close.setShortcut(_translate("BaseWindow", "Ctrl+Q")) + self.actionExportGraphic.setText(_translate("BaseWindow", "Export graphic...")) + self.actionExportGraphic.setShortcut(_translate("BaseWindow", "Ctrl+Shift+S")) + self.action_open.setText(_translate("BaseWindow", "&Open...")) + self.action_open.setShortcut(_translate("BaseWindow", "Ctrl+O")) + self.actionExportData.setText(_translate("BaseWindow", "Export data...")) + self.actionExportData.setShortcut(_translate("BaseWindow", "Ctrl+Shift+S")) + self.action_calc.setText(_translate("BaseWindow", "&Evaluate expression...")) + self.action_delete_sets.setText(_translate("BaseWindow", "&Delete Set")) + self.action_delete_sets.setShortcut(_translate("BaseWindow", "Ctrl+Del")) + self.action_save_fit_parameter.setText(_translate("BaseWindow", "Save fit ¶meter...")) + self.action_sort_pts.setText(_translate("BaseWindow", "Sort &points")) + self.action_reset.setText(_translate("BaseWindow", "&Reset")) + self.action_reset.setShortcut(_translate("BaseWindow", "Ctrl+R")) + self.actionDocumentation.setText(_translate("BaseWindow", "&Documentation")) + self.actionDocumentation.setShortcut(_translate("BaseWindow", "F1")) + self.action_FitWidget.setText(_translate("BaseWindow", "Open &Fit")) + self.action_FitWidget.setShortcut(_translate("BaseWindow", "Ctrl+F")) + self.action_norm_max.setText(_translate("BaseWindow", "&Max")) + self.action_norm_first.setText(_translate("BaseWindow", "&First point")) + self.action_norm_area.setText(_translate("BaseWindow", "&Area")) + self.action_norm_max_abs.setText(_translate("BaseWindow", "Ma&x(Abs)")) + self.action_norm_last.setText(_translate("BaseWindow", "&Last point")) + self.actionSave.setText(_translate("BaseWindow", "S&ave...")) + self.actionSave.setShortcut(_translate("BaseWindow", "Ctrl+S")) + self.actiontoolbar_display.setText(_translate("BaseWindow", "&Views")) + self.actionEdit_toolbars.setText(_translate("BaseWindow", "&Edit")) + self.actionAddlines.setText(_translate("BaseWindow", "Set by function...")) + self.actionColors.setText(_translate("BaseWindow", "&Reset color")) + self.actionConcatenate_sets.setText(_translate("BaseWindow", "Join sets")) + self.actionShift.setText(_translate("BaseWindow", "&Shift/scale...")) + self.actionShow_log.setText(_translate("BaseWindow", "&Show log...")) + self.action_create_fit_function.setText(_translate("BaseWindow", "&Create fit function...")) + self.actionGrace_preferences.setText(_translate("BaseWindow", "&Grace preferences...")) + self.actionSave_session.setText(_translate("BaseWindow", "Update session")) + self.actionMouse_behaviour.setText(_translate("BaseWindow", "Mouse behaviour")) + self.actionMouse_behaviour.setToolTip(_translate("BaseWindow", "Switch between zoom and pan in graph.")) + self.actionConfiguration.setText(_translate("BaseWindow", "Configuration...")) + self.actionRefresh.setText(_translate("BaseWindow", "Refresh")) + self.actionRefresh.setShortcut(_translate("BaseWindow", "F5")) + self.actionInterpolation.setText(_translate("BaseWindow", "Interpolation...")) + self.actionRunning_values.setText(_translate("BaseWindow", "Smoothing...")) + self.actionFit_parameter_saving.setText(_translate("BaseWindow", "Fit parameter saving...")) + self.actionShow_fit_parameter.setText(_translate("BaseWindow", "Parameter...")) + self.actionSkip_points.setText(_translate("BaseWindow", "Skip points...")) + self.actionGuide_lines.setText(_translate("BaseWindow", "Draw lines...")) + self.actionMaximize.setText(_translate("BaseWindow", "Maximize")) + self.actionTile.setText(_translate("BaseWindow", "Tile windows")) + self.actionMinimize.setText(_translate("BaseWindow", "Minimize")) + self.actionNew_window.setText(_translate("BaseWindow", "New plot")) + self.actionDelete_window.setText(_translate("BaseWindow", "Delete plot")) + self.actionCascade_windows.setText(_translate("BaseWindow", "Cascade windows")) + self.actionNext_window.setText(_translate("BaseWindow", "Next")) + self.actionNext_window.setShortcut(_translate("BaseWindow", "Alt+Right")) + self.actionPrevious.setText(_translate("BaseWindow", "Previous")) + self.actionPrevious.setShortcut(_translate("BaseWindow", "Alt+Left")) + self.t1action.setText(_translate("BaseWindow", "Evaluate T1 minimum...")) + self.t1tau.setText(_translate("BaseWindow", "Calculate T1...")) + self.action_coup_calc.setText(_translate("BaseWindow", "Coupling values...")) + self.action_calc_eps_derivative.setText(_translate("BaseWindow", "Calculate derivative loss")) + self.actionOpen_FC.setText(_translate("BaseWindow", "Read FC data...")) + self.action_mean_t1.setText(_translate("BaseWindow", "Convert mean values...")) + self.actionFilon.setText(_translate("BaseWindow", "Log FT...")) + self.action_new_set.setText(_translate("BaseWindow", "New set")) + self.action_magnitude.setText(_translate("BaseWindow", "Calculate magnitude")) + self.actionCenterMax.setText(_translate("BaseWindow", "Center on max")) + self.action_depake.setText(_translate("BaseWindow", "De-paked spectrum")) + self.action_edit.setText(_translate("BaseWindow", "Edit signals...")) + self.actionPick_position.setText(_translate("BaseWindow", "Pick points...")) + self.actionIntegrate.setText(_translate("BaseWindow", "Integrate")) + self.actionDerivation.setText(_translate("BaseWindow", "Differentiation...")) + self.actionIntegration.setText(_translate("BaseWindow", "Integration...")) + self.action_cut.setText(_translate("BaseWindow", "Cut to visible range")) + self.actionMove_between_plots.setText(_translate("BaseWindow", "Move sets...")) + self.actionBaseline.setText(_translate("BaseWindow", "Baseline...")) + self.actionCalculateT1.setText(_translate("BaseWindow", "Calculate relaxation...")) + self.actionChange_datatypes.setText(_translate("BaseWindow", "Change datatypes...")) + self.actionPrint.setText(_translate("BaseWindow", "Print...")) + self.actionPrint.setShortcut(_translate("BaseWindow", "Ctrl+P")) + self.action_lm_fit.setText(_translate("BaseWindow", "Default stuff")) + self.action_nm_fit.setText(_translate("BaseWindow", "Nelder-Mead")) + self.action_odr_fit.setText(_translate("BaseWindow", "ODR")) + self.action_no_range.setText(_translate("BaseWindow", "None")) + self.action_x_range.setText(_translate("BaseWindow", "Visible x range")) + self.action_custom_range.setText(_translate("BaseWindow", "Custom")) + self.actionSnake.setText(_translate("BaseWindow", "Worms")) + self.actionFunction_editor.setText(_translate("BaseWindow", "Function editor...")) + self.actionLife.setText(_translate("BaseWindow", "Life...")) + self.actionTetris.setText(_translate("BaseWindow", "Not Tetris")) + self.actionUpdate.setText(_translate("BaseWindow", "Look for updates")) +from ..data.datawidget.datawidget import DataWidget +from ..data.point_select import PointSelectWidget +from ..data.signaledit.editsignalwidget import EditSignalWidget +from ..data.valueeditwidget import ValueEditWidget +from ..fit.fitwindow import QFitDialog +from ..nmr.t1widget import QT1Widget diff --git a/nmreval/gui_qt/_py/bdsdialog.py b/nmreval/gui_qt/_py/bdsdialog.py new file mode 100644 index 0000000..909eb76 --- /dev/null +++ b/nmreval/gui_qt/_py/bdsdialog.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 319) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.listWidget = QtWidgets.QListWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setObjectName("listWidget") + self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 1) + self.label = QtWidgets.QLabel(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + 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, 0, 1, 1) + 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.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.eps_checkBox = QtWidgets.QCheckBox(Dialog) + self.eps_checkBox.setChecked(True) + self.eps_checkBox.setObjectName("eps_checkBox") + self.verticalLayout.addWidget(self.eps_checkBox) + self.modul_checkBox = QtWidgets.QCheckBox(Dialog) + self.modul_checkBox.setObjectName("modul_checkBox") + self.verticalLayout.addWidget(self.modul_checkBox) + self.cond_checkBox = QtWidgets.QCheckBox(Dialog) + self.cond_checkBox.setObjectName("cond_checkBox") + self.verticalLayout.addWidget(self.cond_checkBox) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.gridLayout.addLayout(self.verticalLayout, 1, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 2) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Read BDS data")) + self.label.setText(_translate("Dialog", "Found temperatures")) + self.label_2.setText(_translate("Dialog", "Read as:")) + self.eps_checkBox.setText(_translate("Dialog", "Permittivity ε")) + self.modul_checkBox.setText(_translate("Dialog", "Modulus 1/ε")) + self.cond_checkBox.setText(_translate("Dialog", "Conductivity iεω")) diff --git a/nmreval/gui_qt/_py/color_palette.py b/nmreval/gui_qt/_py/color_palette.py new file mode 100644 index 0000000..7d7e186 --- /dev/null +++ b/nmreval/gui_qt/_py/color_palette.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/color_palette.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(390, 409) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.append_palette_button = QtWidgets.QPushButton(Dialog) + self.append_palette_button.setObjectName("append_palette_button") + self.gridLayout.addWidget(self.append_palette_button, 2, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 4, 0, 1, 1) + self.palette_combobox = QtWidgets.QComboBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.palette_combobox.sizePolicy().hasHeightForWidth()) + self.palette_combobox.setSizePolicy(sizePolicy) + self.palette_combobox.setObjectName("palette_combobox") + self.gridLayout.addWidget(self.palette_combobox, 0, 0, 1, 1) + self.add_color_button = QtWidgets.QPushButton(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.add_color_button.sizePolicy().hasHeightForWidth()) + self.add_color_button.setSizePolicy(sizePolicy) + self.add_color_button.setObjectName("add_color_button") + self.gridLayout.addWidget(self.add_color_button, 6, 0, 1, 1) + self.color_combobox = ColorListEditor(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.color_combobox.sizePolicy().hasHeightForWidth()) + self.color_combobox.setSizePolicy(sizePolicy) + self.color_combobox.setObjectName("color_combobox") + self.gridLayout.addWidget(self.color_combobox, 5, 0, 1, 1) + self.save_button = QtWidgets.QPushButton(Dialog) + self.save_button.setObjectName("save_button") + self.gridLayout.addWidget(self.save_button, 9, 1, 1, 1) + self.add_palette_button = QtWidgets.QPushButton(Dialog) + self.add_palette_button.setObjectName("add_palette_button") + self.gridLayout.addWidget(self.add_palette_button, 1, 0, 1, 1) + self.new_name_edit = QtWidgets.QLineEdit(Dialog) + self.new_name_edit.setObjectName("new_name_edit") + self.gridLayout.addWidget(self.new_name_edit, 9, 2, 1, 1) + self.colorlist = QtWidgets.QListWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.colorlist.sizePolicy().hasHeightForWidth()) + self.colorlist.setSizePolicy(sizePolicy) + self.colorlist.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) + self.colorlist.setObjectName("colorlist") + self.gridLayout.addWidget(self.colorlist, 0, 1, 9, 2) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 10, 0, 1, 3) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Color Palette")) + self.append_palette_button.setText(_translate("Dialog", "Append")) + self.add_color_button.setText(_translate("Dialog", "Add color")) + self.save_button.setText(_translate("Dialog", "Save as new list")) + self.add_palette_button.setText(_translate("Dialog", "Load")) +from ..lib.delegates import ColorListEditor diff --git a/nmreval/gui_qt/_py/coupling_calculator.py b/nmreval/gui_qt/_py/coupling_calculator.py new file mode 100644 index 0000000..72ac914 --- /dev/null +++ b/nmreval/gui_qt/_py/coupling_calculator.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/coupling_calculator.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_coupling_calc_dialog(object): + def setupUi(self, coupling_calc_dialog): + coupling_calc_dialog.setObjectName("coupling_calc_dialog") + coupling_calc_dialog.resize(400, 280) + self.verticalLayout = QtWidgets.QVBoxLayout(coupling_calc_dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.comboBox = QtWidgets.QComboBox(coupling_calc_dialog) + self.comboBox.setObjectName("comboBox") + self.verticalLayout.addWidget(self.comboBox) + self.label_2 = QtWidgets.QLabel(coupling_calc_dialog) + self.label_2.setObjectName("label_2") + self.verticalLayout.addWidget(self.label_2) + self.verticalFrame = QtWidgets.QFrame(coupling_calc_dialog) + self.verticalFrame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.verticalFrame.setObjectName("verticalFrame") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalFrame) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout.addWidget(self.verticalFrame) + self.label = QtWidgets.QLabel(coupling_calc_dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setStyleSheet("font-weight: bold") + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.buttonBox = QtWidgets.QDialogButtonBox(coupling_calc_dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(coupling_calc_dialog) + self.buttonBox.accepted.connect(coupling_calc_dialog.accept) + self.buttonBox.rejected.connect(coupling_calc_dialog.reject) + QtCore.QMetaObject.connectSlotsByName(coupling_calc_dialog) + + def retranslateUi(self, coupling_calc_dialog): + _translate = QtCore.QCoreApplication.translate + coupling_calc_dialog.setWindowTitle(_translate("coupling_calc_dialog", "Calculate BPP coupling constants")) + self.label_2.setText(_translate("coupling_calc_dialog", "TextLabel")) + self.label.setText(_translate("coupling_calc_dialog", "TextLabel")) diff --git a/nmreval/gui_qt/_py/coupling_t1_from_tau.py b/nmreval/gui_qt/_py/coupling_t1_from_tau.py new file mode 100644 index 0000000..4a18eea --- /dev/null +++ b/nmreval/gui_qt/_py/coupling_t1_from_tau.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/coupling_t1_from_tau.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_couplingForm(object): + def setupUi(self, couplingForm): + couplingForm.setObjectName("couplingForm") + couplingForm.resize(400, 300) + self.horizontalLayout = QtWidgets.QHBoxLayout(couplingForm) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(couplingForm) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.lineEdit = LineEdit(couplingForm) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout.addWidget(self.lineEdit) + self.radioButton_2 = QtWidgets.QRadioButton(couplingForm) + self.radioButton_2.setChecked(True) + self.radioButton_2.setObjectName("radioButton_2") + self.buttonGroup = QtWidgets.QButtonGroup(couplingForm) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.radioButton_2) + self.horizontalLayout.addWidget(self.radioButton_2) + self.radioButton = QtWidgets.QRadioButton(couplingForm) + self.radioButton.setObjectName("radioButton") + self.buttonGroup.addButton(self.radioButton) + self.horizontalLayout.addWidget(self.radioButton) + + self.retranslateUi(couplingForm) + QtCore.QMetaObject.connectSlotsByName(couplingForm) + + def retranslateUi(self, couplingForm): + _translate = QtCore.QCoreApplication.translate + couplingForm.setWindowTitle(_translate("couplingForm", "Form")) + self.label.setText(_translate("couplingForm", "parameter_name")) + self.radioButton_2.setText(_translate("couplingForm", "Value")) + self.radioButton.setText(_translate("couplingForm", "Index")) +from nmrevalgui.lib.forms import LineEdit diff --git a/nmreval/gui_qt/_py/datawidget.py b/nmreval/gui_qt/_py/datawidget.py new file mode 100644 index 0000000..498dbfe --- /dev/null +++ b/nmreval/gui_qt/_py/datawidget.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/datawidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DataWidget(object): + def setupUi(self, DataWidget): + DataWidget.setObjectName("DataWidget") + DataWidget.resize(307, 847) + self.verticalLayout_2 = QtWidgets.QVBoxLayout(DataWidget) + self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout_2.addLayout(self.verticalLayout) + self.propwidget = ExpandableWidget(DataWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.propwidget.sizePolicy().hasHeightForWidth()) + self.propwidget.setSizePolicy(sizePolicy) + self.propwidget.setObjectName("propwidget") + self.verticalLayout_2.addWidget(self.propwidget) + self.frame = QtWidgets.QFrame(DataWidget) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame.setObjectName("frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.graph_toolButton = QtWidgets.QToolButton(self.frame) + self.graph_toolButton.setObjectName("graph_toolButton") + self.horizontalLayout.addWidget(self.graph_toolButton) + self.empty_toolButton = QtWidgets.QToolButton(self.frame) + self.empty_toolButton.setObjectName("empty_toolButton") + self.horizontalLayout.addWidget(self.empty_toolButton) + self.func_toolButton = QtWidgets.QToolButton(self.frame) + self.func_toolButton.setText("") + self.func_toolButton.setObjectName("func_toolButton") + self.horizontalLayout.addWidget(self.func_toolButton) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.verticalLayout_2.addWidget(self.frame) + + self.retranslateUi(DataWidget) + QtCore.QMetaObject.connectSlotsByName(DataWidget) + + def retranslateUi(self, DataWidget): + _translate = QtCore.QCoreApplication.translate + DataWidget.setWindowTitle(_translate("DataWidget", "Data")) + self.graph_toolButton.setToolTip(_translate("DataWidget", "New graph")) + self.graph_toolButton.setText(_translate("DataWidget", "New graph")) + self.empty_toolButton.setToolTip(_translate("DataWidget", "New set")) + self.empty_toolButton.setText(_translate("DataWidget", "Empty set")) +from ..lib.expandablewidget import ExpandableWidget diff --git a/nmreval/gui_qt/_py/dscfile_dialog.py b/nmreval/gui_qt/_py/dscfile_dialog.py new file mode 100644 index 0000000..12cb6d5 --- /dev/null +++ b/nmreval/gui_qt/_py/dscfile_dialog.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/dscfile_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.9.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(962, 662) + self.gridLayout_2 = QtWidgets.QGridLayout(Dialog) + self.gridLayout_2.setObjectName("gridLayout_2") + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Save) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout_2.addWidget(self.buttonBox, 1, 1, 1, 1) + self.gridLayout_4 = QtWidgets.QGridLayout() + self.gridLayout_4.setContentsMargins(-1, 0, 0, -1) + self.gridLayout_4.setSpacing(3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.cp_checkBox = QtWidgets.QCheckBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.cp_checkBox.sizePolicy().hasHeightForWidth()) + self.cp_checkBox.setSizePolicy(sizePolicy) + self.cp_checkBox.setChecked(True) + self.cp_checkBox.setObjectName("cp_checkBox") + self.gridLayout_4.addWidget(self.cp_checkBox, 11, 0, 1, 4) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + 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.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.reference_tableWidget.sizePolicy().hasHeightForWidth()) + self.reference_tableWidget.setSizePolicy(sizePolicy) + self.reference_tableWidget.setMinimumSize(QtCore.QSize(0, 0)) + self.reference_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.reference_tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.reference_tableWidget.setColumnCount(2) + self.reference_tableWidget.setObjectName("reference_tableWidget") + self.reference_tableWidget.setRowCount(0) + self.reference_tableWidget.horizontalHeader().setVisible(False) + self.reference_tableWidget.horizontalHeader().setStretchLastSection(True) + self.reference_tableWidget.verticalHeader().setVisible(False) + self.gridLayout_4.addWidget(self.reference_tableWidget, 9, 0, 1, 4) + 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.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.ref_add_pushButton = QtWidgets.QPushButton(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ref_add_pushButton.sizePolicy().hasHeightForWidth()) + self.ref_add_pushButton.setSizePolicy(sizePolicy) + self.ref_add_pushButton.setObjectName("ref_add_pushButton") + self.horizontalLayout.addWidget(self.ref_add_pushButton) + self.ref_remove_pushButton = QtWidgets.QPushButton(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ref_remove_pushButton.sizePolicy().hasHeightForWidth()) + self.ref_remove_pushButton.setSizePolicy(sizePolicy) + self.ref_remove_pushButton.setObjectName("ref_remove_pushButton") + self.horizontalLayout.addWidget(self.ref_remove_pushButton) + self.gridLayout_4.addLayout(self.horizontalLayout, 10, 0, 1, 4) + self.line_2 = QtWidgets.QFrame(Dialog) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout_4.addWidget(self.line_2, 2, 0, 1, 4) + self.gridLayout_2.addLayout(self.gridLayout_4, 0, 0, 1, 1) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.raw_graph = PlotWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.raw_graph.sizePolicy().hasHeightForWidth()) + self.raw_graph.setSizePolicy(sizePolicy) + self.raw_graph.setMinimumSize(QtCore.QSize(300, 200)) + self.raw_graph.setObjectName("raw_graph") + self.gridLayout.addWidget(self.raw_graph, 0, 0, 1, 1) + self.calib_graph = PlotWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.calib_graph.sizePolicy().hasHeightForWidth()) + self.calib_graph.setSizePolicy(sizePolicy) + self.calib_graph.setMinimumSize(QtCore.QSize(300, 200)) + self.calib_graph.setObjectName("calib_graph") + self.gridLayout.addWidget(self.calib_graph, 1, 0, 1, 1) + self.baseline_graph = PlotWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.baseline_graph.sizePolicy().hasHeightForWidth()) + self.baseline_graph.setSizePolicy(sizePolicy) + self.baseline_graph.setMinimumSize(QtCore.QSize(300, 200)) + self.baseline_graph.setObjectName("baseline_graph") + self.gridLayout.addWidget(self.baseline_graph, 0, 1, 1, 1) + self.end_graph = PlotWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.end_graph.sizePolicy().hasHeightForWidth()) + self.end_graph.setSizePolicy(sizePolicy) + self.end_graph.setMinimumSize(QtCore.QSize(0, 0)) + self.end_graph.setObjectName("end_graph") + self.gridLayout.addWidget(self.end_graph, 1, 1, 1, 1) + self.gridLayout_2.addLayout(self.gridLayout, 0, 1, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Read DSC file")) + self.cp_checkBox.setText(_translate("Dialog", "Convert to heat capacity")) + self.loadempty_button.setText(_translate("Dialog", "Load empty")) + self.delempty_button.setText(_translate("Dialog", "Remove empty")) + self.isotherm_radioButton.setText(_translate("Dialog", "Isotherms")) + 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.ref_add_pushButton.setText(_translate("Dialog", "Add reference")) + self.ref_remove_pushButton.setText(_translate("Dialog", "Remove reference")) + +from pyqtgraph import PlotWidget diff --git a/nmreval/gui_qt/_py/editsignalwidget.py b/nmreval/gui_qt/_py/editsignalwidget.py new file mode 100644 index 0000000..1372ab5 --- /dev/null +++ b/nmreval/gui_qt/_py/editsignalwidget.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/editsignalwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(328, 732) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.groupBox_4 = QtWidgets.QGroupBox(Form) + self.groupBox_4.setObjectName("groupBox_4") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox_4) + self.horizontalLayout_3.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.baselinebutton = QtWidgets.QPushButton(self.groupBox_4) + self.baselinebutton.setObjectName("baselinebutton") + self.horizontalLayout_3.addWidget(self.baselinebutton) + self.verticalLayout.addWidget(self.groupBox_4) + self.groupBox = QtWidgets.QGroupBox(Form) + self.groupBox.setObjectName("groupBox") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_3.setContentsMargins(3, 3, 3, 3) + self.gridLayout_3.setObjectName("gridLayout_3") + self.leftshiftbutton = QtWidgets.QPushButton(self.groupBox) + self.leftshiftbutton.setObjectName("leftshiftbutton") + self.gridLayout_3.addWidget(self.leftshiftbutton, 1, 0, 1, 2) + self.comboBox = QtWidgets.QComboBox(self.groupBox) + self.comboBox.setObjectName("comboBox") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.gridLayout_3.addWidget(self.comboBox, 0, 0, 1, 1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.lsspinBox = QtWidgets.QSpinBox(self.groupBox) + self.lsspinBox.setMaximum(9999) + self.lsspinBox.setObjectName("lsspinBox") + self.verticalLayout_2.addWidget(self.lsspinBox) + self.lineEdit = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit.setObjectName("lineEdit") + self.verticalLayout_2.addWidget(self.lineEdit) + self.gridLayout_3.addLayout(self.verticalLayout_2, 0, 1, 1, 1) + self.verticalLayout.addWidget(self.groupBox) + self.groupBox_6 = QtWidgets.QGroupBox(Form) + self.groupBox_6.setFlat(False) + self.groupBox_6.setObjectName("groupBox_6") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox_6) + self.horizontalLayout_4.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.zfbutton = QtWidgets.QPushButton(self.groupBox_6) + self.zfbutton.setObjectName("zfbutton") + self.horizontalLayout_4.addWidget(self.zfbutton) + self.verticalLayout.addWidget(self.groupBox_6) + self.groupBox_2 = QtWidgets.QGroupBox(Form) + self.groupBox_2.setFlat(False) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_4.setContentsMargins(3, 3, 3, 3) + self.gridLayout_4.setVerticalSpacing(0) + self.gridLayout_4.setObjectName("gridLayout_4") + self.ph1slider = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.ph1slider.setMinimum(-360.0) + self.ph1slider.setMaximum(360.0) + self.ph1slider.setObjectName("ph1slider") + self.gridLayout_4.addWidget(self.ph1slider, 5, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.groupBox_2) + self.label_6.setObjectName("label_6") + self.gridLayout_4.addWidget(self.label_6, 4, 0, 3, 1) + self.pushButton_2 = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_2.setObjectName("pushButton_2") + self.gridLayout_4.addWidget(self.pushButton_2, 8, 0, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setObjectName("label") + self.gridLayout_4.addWidget(self.label, 1, 0, 3, 1) + self.pivot_lineedit = QtWidgets.QLineEdit(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pivot_lineedit.sizePolicy().hasHeightForWidth()) + self.pivot_lineedit.setSizePolicy(sizePolicy) + self.pivot_lineedit.setInputMethodHints(QtCore.Qt.ImhDigitsOnly) + self.pivot_lineedit.setObjectName("pivot_lineedit") + self.gridLayout_4.addWidget(self.pivot_lineedit, 7, 1, 1, 1) + self.ph0slider = QtWidgets.QDoubleSpinBox(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ph0slider.sizePolicy().hasHeightForWidth()) + self.ph0slider.setSizePolicy(sizePolicy) + self.ph0slider.setMinimum(-180.0) + self.ph0slider.setMaximum(180.0) + self.ph0slider.setObjectName("ph0slider") + self.gridLayout_4.addWidget(self.ph0slider, 2, 1, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox_2) + self.label_8.setObjectName("label_8") + self.gridLayout_4.addWidget(self.label_8, 7, 0, 1, 1) + self.phasebutton = QtWidgets.QPushButton(self.groupBox_2) + self.phasebutton.setObjectName("phasebutton") + self.gridLayout_4.addWidget(self.phasebutton, 8, 1, 1, 1) + self.verticalLayout.addWidget(self.groupBox_2) + self.groupBox_3 = QtWidgets.QGroupBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy) + self.groupBox_3.setObjectName("groupBox_3") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_3) + self.verticalLayout_7.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.apodcombobox = QtWidgets.QComboBox(self.groupBox_3) + self.apodcombobox.setObjectName("apodcombobox") + self.verticalLayout_7.addWidget(self.apodcombobox) + self.label_2 = QtWidgets.QLabel(self.groupBox_3) + self.label_2.setIndent(3) + self.label_2.setObjectName("label_2") + self.verticalLayout_7.addWidget(self.label_2) + self.verticalLayout_8 = QtWidgets.QVBoxLayout() + self.verticalLayout_8.setSpacing(0) + self.verticalLayout_8.setObjectName("verticalLayout_8") + self.verticalLayout_7.addLayout(self.verticalLayout_8) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.pushButton = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton.setObjectName("pushButton") + self.horizontalLayout.addWidget(self.pushButton) + self.apodbutton = QtWidgets.QPushButton(self.groupBox_3) + self.apodbutton.setObjectName("apodbutton") + self.horizontalLayout.addWidget(self.apodbutton) + self.verticalLayout_7.addLayout(self.horizontalLayout) + self.verticalLayout.addWidget(self.groupBox_3) + self.groupBox_5 = QtWidgets.QGroupBox(Form) + self.groupBox_5.setObjectName("groupBox_5") + self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.groupBox_5) + self.horizontalLayout_5.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_5.setObjectName("horizontalLayout_5") + self.fourierutton = QtWidgets.QPushButton(self.groupBox_5) + self.fourierutton.setObjectName("fourierutton") + self.horizontalLayout_5.addWidget(self.fourierutton) + self.verticalLayout.addWidget(self.groupBox_5) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.groupBox_4.setTitle(_translate("Form", "Baseline correction")) + self.baselinebutton.setText(_translate("Form", "Apply")) + self.groupBox.setTitle(_translate("Form", "Left shift")) + self.leftshiftbutton.setText(_translate("Form", "Apply")) + self.comboBox.setItemText(0, _translate("Form", "Points")) + self.comboBox.setItemText(1, _translate("Form", "Seconds")) + self.groupBox_6.setTitle(_translate("Form", "Zerofilling")) + self.zfbutton.setText(_translate("Form", "Apply")) + self.groupBox_2.setTitle(_translate("Form", "Phase correction")) + self.label_6.setText(_translate("Form", "Phase 1")) + self.pushButton_2.setText(_translate("Form", "Preview")) + self.label.setText(_translate("Form", "Phase 0")) + self.pivot_lineedit.setText(_translate("Form", "0")) + self.label_8.setText(_translate("Form", "Pivot")) + self.phasebutton.setText(_translate("Form", "Apply")) + self.groupBox_3.setTitle(_translate("Form", "Apodization")) + self.label_2.setText(_translate("Form", "TextLabel")) + self.pushButton.setText(_translate("Form", "Preview")) + self.apodbutton.setText(_translate("Form", "Apply")) + self.groupBox_5.setTitle(_translate("Form", "FFT")) + self.fourierutton.setText(_translate("Form", "Apply")) diff --git a/nmreval/gui_qt/_py/eval_expr_dialog.py b/nmreval/gui_qt/_py/eval_expr_dialog.py new file mode 100644 index 0000000..d70a5ed --- /dev/null +++ b/nmreval/gui_qt/_py/eval_expr_dialog.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/eval_expr_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_CalcDialog(object): + def setupUi(self, CalcDialog): + CalcDialog.setObjectName("CalcDialog") + CalcDialog.setWindowModality(QtCore.Qt.WindowModal) + CalcDialog.resize(804, 627) + self.verticalLayout_5 = QtWidgets.QVBoxLayout(CalcDialog) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.splitter_2 = QtWidgets.QSplitter(CalcDialog) + self.splitter_2.setOrientation(QtCore.Qt.Horizontal) + self.splitter_2.setObjectName("splitter_2") + self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter_2) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.stackedWidget = QtWidgets.QStackedWidget(self.verticalLayoutWidget) + self.stackedWidget.setObjectName("stackedWidget") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.page) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.label_2 = QtWidgets.QLabel(self.page) + self.label_2.setObjectName("label_2") + self.verticalLayout_2.addWidget(self.label_2) + self.listWidget = QtWidgets.QListWidget(self.page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.listWidget.setObjectName("listWidget") + self.verticalLayout_2.addWidget(self.listWidget) + self.overwrite_checkbox = QtWidgets.QCheckBox(self.page) + self.overwrite_checkbox.setLayoutDirection(QtCore.Qt.LeftToRight) + self.overwrite_checkbox.setObjectName("overwrite_checkbox") + self.verticalLayout_2.addWidget(self.overwrite_checkbox) + self.stackedWidget.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.page_2) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.groupBox_2 = QtWidgets.QGroupBox(self.page_2) + self.groupBox_2.setObjectName("groupBox_2") + self.formLayout_3 = QtWidgets.QFormLayout(self.groupBox_2) + self.formLayout_3.setContentsMargins(3, 3, 3, 3) + self.formLayout_3.setHorizontalSpacing(3) + self.formLayout_3.setObjectName("formLayout_3") + self.label_3 = QtWidgets.QLabel(self.groupBox_2) + self.label_3.setObjectName("label_3") + self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_3) + self.label_lineEdit = QtWidgets.QLineEdit(self.groupBox_2) + self.label_lineEdit.setObjectName("label_lineEdit") + self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.label_lineEdit) + self.label_9 = QtWidgets.QLabel(self.groupBox_2) + self.label_9.setObjectName("label_9") + self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_9) + self.value_lineEdit = QtWidgets.QLineEdit(self.groupBox_2) + self.value_lineEdit.setObjectName("value_lineEdit") + self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.value_lineEdit) + self.label_6 = QtWidgets.QLabel(self.groupBox_2) + self.label_6.setObjectName("label_6") + self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_6) + self.dtype_comboBox = QtWidgets.QComboBox(self.groupBox_2) + self.dtype_comboBox.setObjectName("dtype_comboBox") + self.dtype_comboBox.addItem("") + self.dtype_comboBox.addItem("") + self.dtype_comboBox.addItem("") + self.dtype_comboBox.addItem("") + self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.dtype_comboBox) + self.verticalLayout_4.addWidget(self.groupBox_2) + self.groupBox = QtWidgets.QGroupBox(self.page_2) + self.groupBox.setObjectName("groupBox") + self.formLayout_2 = QtWidgets.QFormLayout(self.groupBox) + self.formLayout_2.setContentsMargins(3, 3, 3, 3) + self.formLayout_2.setSpacing(3) + self.formLayout_2.setObjectName("formLayout_2") + self.label_4 = QtWidgets.QLabel(self.groupBox) + self.label_4.setObjectName("label_4") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_4) + self.symcolor_comboBox = ColorListEditor(self.groupBox) + self.symcolor_comboBox.setObjectName("symcolor_comboBox") + self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.symcolor_comboBox) + self.label_5 = QtWidgets.QLabel(self.groupBox) + self.label_5.setObjectName("label_5") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_5) + self.symbol_spinBox = QtWidgets.QSpinBox(self.groupBox) + self.symbol_spinBox.setProperty("value", 10) + self.symbol_spinBox.setObjectName("symbol_spinBox") + self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.symbol_spinBox) + self.symbol_comboBox = SymbolStyleEditor(self.groupBox) + self.symbol_comboBox.setObjectName("symbol_comboBox") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.symbol_comboBox) + self.label_10 = QtWidgets.QLabel(self.groupBox) + self.label_10.setObjectName("label_10") + self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_10) + self.verticalLayout_4.addWidget(self.groupBox) + self.groupBox_3 = QtWidgets.QGroupBox(self.page_2) + self.groupBox_3.setObjectName("groupBox_3") + self.formLayout = QtWidgets.QFormLayout(self.groupBox_3) + self.formLayout.setContentsMargins(3, 3, 3, 3) + self.formLayout.setSpacing(3) + self.formLayout.setObjectName("formLayout") + self.label_7 = QtWidgets.QLabel(self.groupBox_3) + self.label_7.setObjectName("label_7") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_7) + self.linecolor_comboBox = ColorListEditor(self.groupBox_3) + self.linecolor_comboBox.setObjectName("linecolor_comboBox") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.linecolor_comboBox) + self.label_8 = QtWidgets.QLabel(self.groupBox_3) + self.label_8.setObjectName("label_8") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_8) + self.line_doubleSpinBox = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.line_doubleSpinBox.setProperty("value", 1.0) + self.line_doubleSpinBox.setObjectName("line_doubleSpinBox") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.line_doubleSpinBox) + self.linestyle_comboBox = LineStyleEditor(self.groupBox_3) + self.linestyle_comboBox.setObjectName("linestyle_comboBox") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.linestyle_comboBox) + self.label_11 = QtWidgets.QLabel(self.groupBox_3) + self.label_11.setObjectName("label_11") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_11) + self.verticalLayout_4.addWidget(self.groupBox_3) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_4.addItem(spacerItem) + self.graph_comboBox = QtWidgets.QComboBox(self.page_2) + self.graph_comboBox.setObjectName("graph_comboBox") + self.verticalLayout_4.addWidget(self.graph_comboBox) + self.groupBox.raise_() + self.groupBox_2.raise_() + self.groupBox_3.raise_() + self.graph_comboBox.raise_() + self.stackedWidget.addWidget(self.page_2) + self.page_3 = QtWidgets.QWidget() + self.page_3.setObjectName("page_3") + self.stackedWidget.addWidget(self.page_3) + self.verticalLayout.addWidget(self.stackedWidget) + self.splitter = QtWidgets.QSplitter(self.splitter_2) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setChildrenCollapsible(False) + self.splitter.setObjectName("splitter") + self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.splitter) + self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2) + self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.namespace_widget = QNamespaceWidget(self.verticalLayoutWidget_2) + self.namespace_widget.setObjectName("namespace_widget") + self.verticalLayout_6.addWidget(self.namespace_widget) + self.verticalLayoutWidget_3 = QtWidgets.QWidget(self.splitter) + self.verticalLayoutWidget_3.setObjectName("verticalLayoutWidget_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_3) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.line_2 = QtWidgets.QFrame(self.verticalLayoutWidget_3) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.verticalLayout_3.addWidget(self.line_2) + self.label = QtWidgets.QLabel(self.verticalLayoutWidget_3) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label.setFont(font) + self.label.setObjectName("label") + self.verticalLayout_3.addWidget(self.label) + self.calc_edit = QtWidgets.QPlainTextEdit(self.verticalLayoutWidget_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.calc_edit.sizePolicy().hasHeightForWidth()) + self.calc_edit.setSizePolicy(sizePolicy) + self.calc_edit.setObjectName("calc_edit") + self.verticalLayout_3.addWidget(self.calc_edit) + self.verticalLayout_5.addWidget(self.splitter_2) + self.buttonBox = QtWidgets.QDialogButtonBox(CalcDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout_5.addWidget(self.buttonBox) + self.label_3.setBuddy(self.label_lineEdit) + self.label_9.setBuddy(self.value_lineEdit) + self.label_6.setBuddy(self.dtype_comboBox) + self.label_4.setBuddy(self.symcolor_comboBox) + self.label_5.setBuddy(self.symbol_spinBox) + self.label_7.setBuddy(self.linecolor_comboBox) + self.label_8.setBuddy(self.line_doubleSpinBox) + + self.retranslateUi(CalcDialog) + self.stackedWidget.setCurrentIndex(2) + QtCore.QMetaObject.connectSlotsByName(CalcDialog) + CalcDialog.setTabOrder(self.calc_edit, self.listWidget) + CalcDialog.setTabOrder(self.listWidget, self.overwrite_checkbox) + CalcDialog.setTabOrder(self.overwrite_checkbox, self.label_lineEdit) + CalcDialog.setTabOrder(self.label_lineEdit, self.value_lineEdit) + CalcDialog.setTabOrder(self.value_lineEdit, self.dtype_comboBox) + CalcDialog.setTabOrder(self.dtype_comboBox, self.symcolor_comboBox) + CalcDialog.setTabOrder(self.symcolor_comboBox, self.symbol_spinBox) + CalcDialog.setTabOrder(self.symbol_spinBox, self.linecolor_comboBox) + CalcDialog.setTabOrder(self.linecolor_comboBox, self.line_doubleSpinBox) + + def retranslateUi(self, CalcDialog): + _translate = QtCore.QCoreApplication.translate + CalcDialog.setWindowTitle(_translate("CalcDialog", "Evaluate stuff")) + self.label_2.setText(_translate("CalcDialog", "Select sets for evaluation")) + self.overwrite_checkbox.setText(_translate("CalcDialog", "Overwrite values")) + self.groupBox_2.setTitle(_translate("CalcDialog", "GroupBox")) + self.label_3.setText(_translate("CalcDialog", "Label")) + self.label_9.setText(_translate("CalcDialog", "Value")) + self.label_6.setText(_translate("CalcDialog", "Datatype")) + self.dtype_comboBox.setItemText(0, _translate("CalcDialog", "Points")) + self.dtype_comboBox.setItemText(1, _translate("CalcDialog", "Timesignal")) + self.dtype_comboBox.setItemText(2, _translate("CalcDialog", "Spectrum")) + self.dtype_comboBox.setItemText(3, _translate("CalcDialog", "BDS")) + self.groupBox.setTitle(_translate("CalcDialog", "Symbol")) + self.label_4.setText(_translate("CalcDialog", "Color")) + self.label_5.setText(_translate("CalcDialog", "Size")) + self.label_10.setText(_translate("CalcDialog", "Style")) + self.groupBox_3.setTitle(_translate("CalcDialog", "Line")) + self.label_7.setText(_translate("CalcDialog", "Color")) + self.label_8.setText(_translate("CalcDialog", "Width")) + self.label_11.setText(_translate("CalcDialog", "Style")) + self.label.setText(_translate("CalcDialog", "Expressions are evaluated line by line and change previous values")) +from ..lib.delegates import ColorListEditor, LineStyleEditor, SymbolStyleEditor +from ..lib.namespace import QNamespaceWidget diff --git a/nmreval/gui_qt/_py/evalexpression.py b/nmreval/gui_qt/_py/evalexpression.py new file mode 100644 index 0000000..43e0f9c --- /dev/null +++ b/nmreval/gui_qt/_py/evalexpression.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/evalexpression.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_CalcDialog(object): + def setupUi(self, CalcDialog): + CalcDialog.setObjectName("CalcDialog") + CalcDialog.resize(895, 547) + self.gridLayout = QtWidgets.QGridLayout(CalcDialog) + self.gridLayout.setObjectName("gridLayout") + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.tabWidget = QtWidgets.QTabWidget(CalcDialog) + self.tabWidget.setObjectName("tabWidget") + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.verticalLayout = QtWidgets.QVBoxLayout(self.tab) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(0) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.textEdit = QtWidgets.QTextEdit(self.tab) + self.textEdit.setEnabled(True) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.textEdit.sizePolicy().hasHeightForWidth()) + self.textEdit.setSizePolicy(sizePolicy) + self.textEdit.setFrameShape(QtWidgets.QFrame.NoFrame) + self.textEdit.setFrameShadow(QtWidgets.QFrame.Plain) + self.textEdit.setAutoFormatting(QtWidgets.QTextEdit.AutoNone) + self.textEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.textEdit.setObjectName("textEdit") + self.horizontalLayout_2.addWidget(self.textEdit) + self.textEdit_3 = QtWidgets.QTextEdit(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.textEdit_3.sizePolicy().hasHeightForWidth()) + self.textEdit_3.setSizePolicy(sizePolicy) + self.textEdit_3.setFrameShape(QtWidgets.QFrame.NoFrame) + self.textEdit_3.setFrameShadow(QtWidgets.QFrame.Plain) + self.textEdit_3.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.textEdit_3.setObjectName("textEdit_3") + self.horizontalLayout_2.addWidget(self.textEdit_3) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.tabWidget.addTab(self.tab, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tab_2) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_3.setSpacing(0) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.textEdit_2 = QtWidgets.QTextEdit(self.tab_2) + self.textEdit_2.setEnabled(True) + self.textEdit_2.setFrameShape(QtWidgets.QFrame.NoFrame) + self.textEdit_2.setFrameShadow(QtWidgets.QFrame.Plain) + self.textEdit_2.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) + self.textEdit_2.setObjectName("textEdit_2") + self.horizontalLayout_3.addWidget(self.textEdit_2) + self.textEdit_4 = QtWidgets.QTextEdit(self.tab_2) + self.textEdit_4.setFrameShape(QtWidgets.QFrame.NoFrame) + self.textEdit_4.setFrameShadow(QtWidgets.QFrame.Plain) + self.textEdit_4.setReadOnly(True) + self.textEdit_4.setObjectName("textEdit_4") + self.horizontalLayout_3.addWidget(self.textEdit_4) + self.verticalLayout_3.addLayout(self.horizontalLayout_3) + self.tabWidget.addTab(self.tab_2, "") + self.tab_3 = QtWidgets.QWidget() + self.tab_3.setObjectName("tab_3") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.tab_3) + self.verticalLayout_4.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_4.setSpacing(0) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.treeWidget = QtWidgets.QTreeWidget(self.tab_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.treeWidget.sizePolicy().hasHeightForWidth()) + self.treeWidget.setSizePolicy(sizePolicy) + self.treeWidget.setFrameShape(QtWidgets.QFrame.NoFrame) + self.treeWidget.setFrameShadow(QtWidgets.QFrame.Plain) + self.treeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.treeWidget.setObjectName("treeWidget") + self.treeWidget.header().setVisible(False) + self.verticalLayout_4.addWidget(self.treeWidget) + self.tabWidget.addTab(self.tab_3, "") + self.verticalLayout_2.addWidget(self.tabWidget) + self.label = QtWidgets.QLabel(CalcDialog) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label.setFont(font) + self.label.setObjectName("label") + self.verticalLayout_2.addWidget(self.label) + self.calc_edit = QtWidgets.QPlainTextEdit(CalcDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.calc_edit.sizePolicy().hasHeightForWidth()) + self.calc_edit.setSizePolicy(sizePolicy) + self.calc_edit.setObjectName("calc_edit") + self.verticalLayout_2.addWidget(self.calc_edit) + self.gridLayout.addLayout(self.verticalLayout_2, 0, 3, 1, 1) + self.verticalLayout_5 = QtWidgets.QVBoxLayout() + self.verticalLayout_5.setSpacing(0) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.label_2 = QtWidgets.QLabel(CalcDialog) + self.label_2.setObjectName("label_2") + self.verticalLayout_5.addWidget(self.label_2) + self.listWidget = QtWidgets.QListWidget(CalcDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.listWidget.setObjectName("listWidget") + self.verticalLayout_5.addWidget(self.listWidget) + self.overwrite_checkbox = QtWidgets.QCheckBox(CalcDialog) + self.overwrite_checkbox.setLayoutDirection(QtCore.Qt.LeftToRight) + self.overwrite_checkbox.setObjectName("overwrite_checkbox") + self.verticalLayout_5.addWidget(self.overwrite_checkbox) + self.gridLayout.addLayout(self.verticalLayout_5, 0, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(CalcDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 1, 3, 1, 1) + self.line = QtWidgets.QFrame(CalcDialog) + self.line.setFrameShape(QtWidgets.QFrame.VLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 0, 2, 1, 1) + + self.retranslateUi(CalcDialog) + self.tabWidget.setCurrentIndex(0) + self.buttonBox.accepted.connect(CalcDialog.accept) + self.buttonBox.rejected.connect(CalcDialog.reject) + QtCore.QMetaObject.connectSlotsByName(CalcDialog) + + def retranslateUi(self, CalcDialog): + _translate = QtCore.QCoreApplication.translate + CalcDialog.setWindowTitle(_translate("CalcDialog", "Evaluate stuff")) + self.textEdit.setHtml(_translate("CalcDialog", "\n" +"\n" +"

- X, y, and Δy values

\n" +"

- Values of dataset on position i in list

\n" +"

(s[i].x and x return the same values)

\n" +"

- Numpy functions

\n" +"

- If available, fit parameters

\n" +"

(see namespace for available parameters)

\n" +"

- Fit functions:

\n" +"

(meaning of p and extra arguments

\n" +"

depend on function)

\n" +"

- Constants:

\n" +"

(nuclei are accessed by const[\'gamma\'][\'1H\'])

")) + self.textEdit_3.setHtml(_translate("CalcDialog", "\n" +"\n" +"

x y y_err

\n" +"

s[i].x s[i+2].y s[i-1].y_err

\n" +"


\n" +"

np.function

\n" +"

fit[\'NAME\']

\n" +"


\n" +"


\n" +"

Name.func(p, x, *args)

\n" +"


\n" +"


\n" +"

const[\'NAME\']

")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("CalcDialog", "Parameter")) + self.textEdit_2.setHtml(_translate("CalcDialog", "\n" +"\n" +"

Substract neighbouring datasets:

\n" +"

Normalize on fit value M:

\n" +"

Logscale x:

\n" +"

Division by exponential decay:

\n" +"


")) + self.textEdit_4.setHtml(_translate("CalcDialog", "\n" +"\n" +"

y = y-s[i+1].y

\n" +"

y = y/fit[\'M_infty\']

\n" +"

x = np.log10(x)

\n" +"

y = y/np.exp(-x/10)

\n" +"

y = y/Exponential_Decay.func([0, 1, 10, 1], x)

")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("CalcDialog", "Example")) + self.treeWidget.headerItem().setText(0, _translate("CalcDialog", "Namespace")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("CalcDialog", "Namespace")) + self.label.setText(_translate("CalcDialog", "Expressions are evaluated line by line and change previous values")) + self.label_2.setText(_translate("CalcDialog", "

Select sets for evaluation
(no selection = all visible):

")) + self.overwrite_checkbox.setText(_translate("CalcDialog", "Overwrite values?")) diff --git a/nmreval/gui_qt/_py/expandablewidget.py b/nmreval/gui_qt/_py/expandablewidget.py new file mode 100644 index 0000000..3fc84db --- /dev/null +++ b/nmreval/gui_qt/_py/expandablewidget.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/expandablewidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ExpandableForm(object): + def setupUi(self, ExpandableForm): + ExpandableForm.setObjectName("ExpandableForm") + ExpandableForm.resize(400, 300) + self.verticalLayout = QtWidgets.QVBoxLayout(ExpandableForm) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.toolButton = QtWidgets.QToolButton(ExpandableForm) + self.toolButton.setStyleSheet("border: 0") + self.toolButton.setText("") + self.toolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.toolButton.setArrowType(QtCore.Qt.RightArrow) + self.toolButton.setObjectName("toolButton") + self.horizontalLayout.addWidget(self.toolButton) + self.label = QtWidgets.QLabel(ExpandableForm) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setText("") + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.line = QtWidgets.QFrame(ExpandableForm) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.horizontalLayout.addWidget(self.line) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(ExpandableForm) + QtCore.QMetaObject.connectSlotsByName(ExpandableForm) + + def retranslateUi(self, ExpandableForm): + _translate = QtCore.QCoreApplication.translate + ExpandableForm.setWindowTitle(_translate("ExpandableForm", "Form")) diff --git a/nmreval/gui_qt/_py/exportConfigTemplate.py b/nmreval/gui_qt/_py/exportConfigTemplate.py new file mode 100644 index 0000000..9f64f56 --- /dev/null +++ b/nmreval/gui_qt/_py/exportConfigTemplate.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/exportConfigTemplate.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(241, 367) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 3) + self.itemTree = QtWidgets.QTreeWidget(Form) + self.itemTree.setObjectName("itemTree") + self.itemTree.headerItem().setText(0, "1") + self.itemTree.header().setVisible(False) + self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) + self.label_2 = QtWidgets.QLabel(Form) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) + self.formatList = QtWidgets.QListWidget(Form) + self.formatList.setObjectName("formatList") + self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) + self.exportBtn = QtWidgets.QPushButton(Form) + self.exportBtn.setObjectName("exportBtn") + self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) + self.closeBtn = QtWidgets.QPushButton(Form) + self.closeBtn.setObjectName("closeBtn") + self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) + self.paramTree = ParameterTree(Form) + self.paramTree.setColumnCount(2) + self.paramTree.setObjectName("paramTree") + self.paramTree.headerItem().setText(0, "1") + self.paramTree.header().setVisible(False) + self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) + self.label_3 = QtWidgets.QLabel(Form) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) + self.copyBtn = QtWidgets.QPushButton(Form) + self.copyBtn.setObjectName("copyBtn") + self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Export")) + self.label.setText(_translate("Form", "Item to export:")) + self.label_2.setText(_translate("Form", "Export format")) + self.exportBtn.setText(_translate("Form", "Export")) + self.closeBtn.setText(_translate("Form", "Close")) + self.label_3.setText(_translate("Form", "Export options")) + self.copyBtn.setText(_translate("Form", "Copy")) +from ..parametertree import ParameterTree diff --git a/nmreval/gui_qt/_py/fcreader.py b/nmreval/gui_qt/_py/fcreader.py new file mode 100644 index 0000000..766c641 --- /dev/null +++ b/nmreval/gui_qt/_py/fcreader.py @@ -0,0 +1,196 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fcreader.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FCEval_dialog(object): + def setupUi(self, FCEval_dialog): + FCEval_dialog.setObjectName("FCEval_dialog") + FCEval_dialog.resize(457, 697) + self.verticalLayout = QtWidgets.QVBoxLayout(FCEval_dialog) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.input_box = QtWidgets.QGroupBox(FCEval_dialog) + self.input_box.setObjectName("input_box") + self.gridLayout_2 = QtWidgets.QGridLayout(self.input_box) + self.gridLayout_2.setContentsMargins(3, 3, 3, 3) + self.gridLayout_2.setSpacing(3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.file_pushbutton = QtWidgets.QPushButton(self.input_box) + self.file_pushbutton.setChecked(False) + self.file_pushbutton.setObjectName("file_pushbutton") + self.gridLayout_2.addWidget(self.file_pushbutton, 0, 0, 1, 1) + self.dir_pushbutton = QtWidgets.QPushButton(self.input_box) + self.dir_pushbutton.setObjectName("dir_pushbutton") + self.gridLayout_2.addWidget(self.dir_pushbutton, 0, 1, 1, 1) + self.listWidget = QtWidgets.QListWidget(self.input_box) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.listWidget.setTextElideMode(QtCore.Qt.ElideLeft) + self.listWidget.setObjectName("listWidget") + self.gridLayout_2.addWidget(self.listWidget, 1, 0, 1, 3) + self.overwrite_cb = QtWidgets.QCheckBox(self.input_box) + self.overwrite_cb.setObjectName("overwrite_cb") + self.gridLayout_2.addWidget(self.overwrite_cb, 0, 2, 1, 1) + self.verticalLayout.addWidget(self.input_box) + self.region_box = QtWidgets.QGroupBox(FCEval_dialog) + self.region_box.setCheckable(True) + self.region_box.setObjectName("region_box") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.region_box) + self.horizontalLayout.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.start_lineedit = QtWidgets.QLineEdit(self.region_box) + self.start_lineedit.setObjectName("start_lineedit") + self.horizontalLayout.addWidget(self.start_lineedit) + self.stop_lineedit = QtWidgets.QLineEdit(self.region_box) + self.stop_lineedit.setObjectName("stop_lineedit") + self.horizontalLayout.addWidget(self.stop_lineedit) + self.verticalLayout.addWidget(self.region_box) + self.fit_box = QtWidgets.QGroupBox(FCEval_dialog) + self.fit_box.setObjectName("fit_box") + self.gridLayout_3 = QtWidgets.QGridLayout(self.fit_box) + self.gridLayout_3.setObjectName("gridLayout_3") + self.label_12 = QtWidgets.QLabel(self.fit_box) + self.label_12.setObjectName("label_12") + self.gridLayout_3.addWidget(self.label_12, 0, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem, 0, 1, 1, 1) + self.kww_checkbox = QtWidgets.QCheckBox(self.fit_box) + self.kww_checkbox.setChecked(True) + self.kww_checkbox.setObjectName("kww_checkbox") + self.gridLayout_3.addWidget(self.kww_checkbox, 0, 2, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setSpacing(2) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_3 = QtWidgets.QLabel(self.fit_box) + self.label_3.setObjectName("label_3") + self.horizontalLayout_3.addWidget(self.label_3) + self.t1_cb = QtWidgets.QCheckBox(self.fit_box) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.t1_cb.sizePolicy().hasHeightForWidth()) + self.t1_cb.setSizePolicy(sizePolicy) + self.t1_cb.setText("") + self.t1_cb.setChecked(True) + self.t1_cb.setObjectName("t1_cb") + self.horizontalLayout_3.addWidget(self.t1_cb) + self.label_4 = QtWidgets.QLabel(self.fit_box) + self.label_4.setObjectName("label_4") + self.horizontalLayout_3.addWidget(self.label_4) + self.beta_cb = QtWidgets.QCheckBox(self.fit_box) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.beta_cb.sizePolicy().hasHeightForWidth()) + self.beta_cb.setSizePolicy(sizePolicy) + self.beta_cb.setChecked(True) + self.beta_cb.setObjectName("beta_cb") + self.horizontalLayout_3.addWidget(self.beta_cb) + self.label_5 = QtWidgets.QLabel(self.fit_box) + self.label_5.setObjectName("label_5") + self.horizontalLayout_3.addWidget(self.label_5) + self.m0_cb = QtWidgets.QCheckBox(self.fit_box) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.m0_cb.sizePolicy().hasHeightForWidth()) + self.m0_cb.setSizePolicy(sizePolicy) + self.m0_cb.setText("") + self.m0_cb.setObjectName("m0_cb") + self.horizontalLayout_3.addWidget(self.m0_cb) + self.label_6 = QtWidgets.QLabel(self.fit_box) + self.label_6.setObjectName("label_6") + self.horizontalLayout_3.addWidget(self.label_6) + self.off_cb = QtWidgets.QCheckBox(self.fit_box) + self.off_cb.setObjectName("off_cb") + self.horizontalLayout_3.addWidget(self.off_cb) + self.gridLayout_3.addLayout(self.horizontalLayout_3, 1, 0, 1, 3) + self.verticalLayout.addWidget(self.fit_box) + self.out_box = QtWidgets.QGroupBox(FCEval_dialog) + self.out_box.setObjectName("out_box") + self.gridLayout = QtWidgets.QGridLayout(self.out_box) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setObjectName("gridLayout") + self.savebutton = QtWidgets.QPushButton(self.out_box) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.savebutton.sizePolicy().hasHeightForWidth()) + self.savebutton.setSizePolicy(sizePolicy) + self.savebutton.setObjectName("savebutton") + self.gridLayout.addWidget(self.savebutton, 0, 1, 1, 1) + self.line = QtWidgets.QFrame(self.out_box) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 2, 0, 1, 2) + self.graph_comboBox = QtWidgets.QComboBox(self.out_box) + self.graph_comboBox.setObjectName("graph_comboBox") + self.gridLayout.addWidget(self.graph_comboBox, 3, 1, 1, 1) + self.graph_checkbox = QtWidgets.QCheckBox(self.out_box) + self.graph_checkbox.setChecked(True) + self.graph_checkbox.setObjectName("graph_checkbox") + self.gridLayout.addWidget(self.graph_checkbox, 3, 0, 1, 1) + self.label = QtWidgets.QLabel(self.out_box) + self.label.setMaximumSize(QtCore.QSize(16777215, 16777215)) + self.label.setText("") + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 1, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(self.out_box) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1) + self.verticalLayout.addWidget(self.out_box) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem1) + self.buttonBox = QtWidgets.QDialogButtonBox(FCEval_dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + self.label_12.setBuddy(self.kww_checkbox) + self.label_4.setBuddy(self.t1_cb) + self.label_5.setBuddy(self.beta_cb) + self.label_6.setBuddy(self.m0_cb) + + self.retranslateUi(FCEval_dialog) + self.buttonBox.accepted.connect(FCEval_dialog.accept) + self.buttonBox.rejected.connect(FCEval_dialog.reject) + QtCore.QMetaObject.connectSlotsByName(FCEval_dialog) + + def retranslateUi(self, FCEval_dialog): + _translate = QtCore.QCoreApplication.translate + FCEval_dialog.setWindowTitle(_translate("FCEval_dialog", "FC evaluation")) + self.input_box.setTitle(_translate("FCEval_dialog", "Input")) + self.file_pushbutton.setText(_translate("FCEval_dialog", "Add HDF files...")) + self.dir_pushbutton.setText(_translate("FCEval_dialog", "Add directory...")) + self.overwrite_cb.setText(_translate("FCEval_dialog", "Overwrite prev. data")) + self.region_box.setTitle(_translate("FCEval_dialog", "Evaluate region (empty values default to start/end)")) + self.start_lineedit.setPlaceholderText(_translate("FCEval_dialog", "start pos in µs")) + self.stop_lineedit.setPlaceholderText(_translate("FCEval_dialog", "end pos in µs")) + self.fit_box.setTitle(_translate("FCEval_dialog", "Fit equation")) + self.label_12.setText(_translate("FCEval_dialog", "y = M0 exp[-(x/T1)β] + Off")) + self.kww_checkbox.setToolTip(_translate("FCEval_dialog", "Check to fit a stretched exponential instead of exponential function.")) + self.kww_checkbox.setText(_translate("FCEval_dialog", "Stretched exponential")) + self.label_3.setText(_translate("FCEval_dialog", "Plot:")) + self.label_4.setText(_translate("FCEval_dialog", "T1")) + self.label_5.setText(_translate("FCEval_dialog", "β")) + self.label_6.setText(_translate("FCEval_dialog", "M0")) + self.off_cb.setText(_translate("FCEval_dialog", "Offset")) + self.out_box.setTitle(_translate("FCEval_dialog", "Output")) + self.savebutton.setText(_translate("FCEval_dialog", "Change directory...")) + self.graph_checkbox.setText(_translate("FCEval_dialog", "New graph")) + self.label_2.setText(_translate("FCEval_dialog", "Save location")) diff --git a/nmreval/gui_qt/_py/filedialog.py b/nmreval/gui_qt/_py/filedialog.py new file mode 100644 index 0000000..ade0cd7 --- /dev/null +++ b/nmreval/gui_qt/_py/filedialog.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/filedialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_QFileDialog(object): + def setupUi(self, QFileDialog): + QFileDialog.setObjectName("QFileDialog") + QFileDialog.resize(521, 316) + QFileDialog.setSizeGripEnabled(True) + self.gridlayout = QtWidgets.QGridLayout(QFileDialog) + self.gridlayout.setObjectName("gridlayout") + self.lookInLabel = QtWidgets.QLabel(QFileDialog) + self.lookInLabel.setObjectName("lookInLabel") + self.gridlayout.addWidget(self.lookInLabel, 0, 0, 1, 1) + self.hboxlayout = QtWidgets.QHBoxLayout() + self.hboxlayout.setObjectName("hboxlayout") + self.lookInCombo = QFileDialogComboBox(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lookInCombo.sizePolicy().hasHeightForWidth()) + self.lookInCombo.setSizePolicy(sizePolicy) + self.lookInCombo.setMinimumSize(QtCore.QSize(50, 0)) + self.lookInCombo.setObjectName("lookInCombo") + self.hboxlayout.addWidget(self.lookInCombo) + self.backButton = QtWidgets.QToolButton(QFileDialog) + self.backButton.setObjectName("backButton") + self.hboxlayout.addWidget(self.backButton) + self.forwardButton = QtWidgets.QToolButton(QFileDialog) + self.forwardButton.setObjectName("forwardButton") + self.hboxlayout.addWidget(self.forwardButton) + self.toParentButton = QtWidgets.QToolButton(QFileDialog) + self.toParentButton.setObjectName("toParentButton") + self.hboxlayout.addWidget(self.toParentButton) + self.newFolderButton = QtWidgets.QToolButton(QFileDialog) + self.newFolderButton.setObjectName("newFolderButton") + self.hboxlayout.addWidget(self.newFolderButton) + self.listModeButton = QtWidgets.QToolButton(QFileDialog) + self.listModeButton.setObjectName("listModeButton") + self.hboxlayout.addWidget(self.listModeButton) + self.detailModeButton = QtWidgets.QToolButton(QFileDialog) + self.detailModeButton.setObjectName("detailModeButton") + self.hboxlayout.addWidget(self.detailModeButton) + self.gridlayout.addLayout(self.hboxlayout, 0, 1, 1, 2) + self.splitter = QtWidgets.QSplitter(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth()) + self.splitter.setSizePolicy(sizePolicy) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setChildrenCollapsible(False) + self.splitter.setObjectName("splitter") + self.sidebar = QSidebar(self.splitter) + self.sidebar.setObjectName("sidebar") + self.frame = QtWidgets.QFrame(self.splitter) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame.setObjectName("frame") + self.vboxlayout = QtWidgets.QVBoxLayout(self.frame) + self.vboxlayout.setContentsMargins(0, 0, 0, 0) + self.vboxlayout.setSpacing(0) + self.vboxlayout.setObjectName("vboxlayout") + self.stackedWidget = QtWidgets.QStackedWidget(self.frame) + self.stackedWidget.setObjectName("stackedWidget") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.vboxlayout1 = QtWidgets.QVBoxLayout(self.page) + self.vboxlayout1.setContentsMargins(0, 0, 0, 0) + self.vboxlayout1.setSpacing(0) + self.vboxlayout1.setObjectName("vboxlayout1") + self.listView = QFileDialogListView(self.page) + self.listView.setObjectName("listView") + self.vboxlayout1.addWidget(self.listView) + self.stackedWidget.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.vboxlayout2 = QtWidgets.QVBoxLayout(self.page_2) + self.vboxlayout2.setContentsMargins(0, 0, 0, 0) + self.vboxlayout2.setSpacing(0) + self.vboxlayout2.setObjectName("vboxlayout2") + self.treeView = QFileDialogTreeView(self.page_2) + self.treeView.setObjectName("treeView") + self.vboxlayout2.addWidget(self.treeView) + self.stackedWidget.addWidget(self.page_2) + self.vboxlayout.addWidget(self.stackedWidget) + self.gridlayout.addWidget(self.splitter, 1, 0, 1, 3) + self.fileNameLabel = QtWidgets.QLabel(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileNameLabel.sizePolicy().hasHeightForWidth()) + self.fileNameLabel.setSizePolicy(sizePolicy) + self.fileNameLabel.setMinimumSize(QtCore.QSize(0, 0)) + self.fileNameLabel.setObjectName("fileNameLabel") + self.gridlayout.addWidget(self.fileNameLabel, 2, 0, 1, 1) + self.fileNameEdit = QFileDialogLineEdit(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileNameEdit.sizePolicy().hasHeightForWidth()) + self.fileNameEdit.setSizePolicy(sizePolicy) + self.fileNameEdit.setObjectName("fileNameEdit") + self.gridlayout.addWidget(self.fileNameEdit, 2, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(QFileDialog) + self.buttonBox.setOrientation(QtCore.Qt.Vertical) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridlayout.addWidget(self.buttonBox, 2, 2, 2, 1) + self.fileTypeLabel = QtWidgets.QLabel(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileTypeLabel.sizePolicy().hasHeightForWidth()) + self.fileTypeLabel.setSizePolicy(sizePolicy) + self.fileTypeLabel.setObjectName("fileTypeLabel") + self.gridlayout.addWidget(self.fileTypeLabel, 3, 0, 1, 1) + self.fileTypeCombo = QtWidgets.QComboBox(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileTypeCombo.sizePolicy().hasHeightForWidth()) + self.fileTypeCombo.setSizePolicy(sizePolicy) + self.fileTypeCombo.setObjectName("fileTypeCombo") + self.gridlayout.addWidget(self.fileTypeCombo, 3, 1, 1, 1) + + self.retranslateUi(QFileDialog) + self.stackedWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(QFileDialog) + QFileDialog.setTabOrder(self.lookInCombo, self.backButton) + QFileDialog.setTabOrder(self.backButton, self.forwardButton) + QFileDialog.setTabOrder(self.forwardButton, self.toParentButton) + QFileDialog.setTabOrder(self.toParentButton, self.newFolderButton) + QFileDialog.setTabOrder(self.newFolderButton, self.listModeButton) + QFileDialog.setTabOrder(self.listModeButton, self.detailModeButton) + QFileDialog.setTabOrder(self.detailModeButton, self.sidebar) + QFileDialog.setTabOrder(self.sidebar, self.treeView) + QFileDialog.setTabOrder(self.treeView, self.listView) + QFileDialog.setTabOrder(self.listView, self.fileNameEdit) + QFileDialog.setTabOrder(self.fileNameEdit, self.buttonBox) + QFileDialog.setTabOrder(self.buttonBox, self.fileTypeCombo) + + def retranslateUi(self, QFileDialog): + _translate = QtCore.QCoreApplication.translate + self.lookInLabel.setText(_translate("QFileDialog", "Look in:")) + self.backButton.setToolTip(_translate("QFileDialog", "Back")) + self.backButton.setAccessibleName(_translate("QFileDialog", "Back")) + self.backButton.setAccessibleDescription(_translate("QFileDialog", "Go back")) + self.backButton.setShortcut(_translate("QFileDialog", "Alt+Left")) + self.forwardButton.setToolTip(_translate("QFileDialog", "Forward")) + self.forwardButton.setAccessibleName(_translate("QFileDialog", "Forward")) + self.forwardButton.setAccessibleDescription(_translate("QFileDialog", "Go forward")) + self.forwardButton.setShortcut(_translate("QFileDialog", "Alt+Right")) + self.toParentButton.setToolTip(_translate("QFileDialog", "Parent Directory")) + self.toParentButton.setAccessibleName(_translate("QFileDialog", "Parent Directory")) + self.toParentButton.setAccessibleDescription(_translate("QFileDialog", "Go to the parent directory")) + self.toParentButton.setShortcut(_translate("QFileDialog", "Alt+Up")) + self.newFolderButton.setToolTip(_translate("QFileDialog", "Create New Folder")) + self.newFolderButton.setAccessibleName(_translate("QFileDialog", "Create New Folder")) + self.newFolderButton.setAccessibleDescription(_translate("QFileDialog", "Create a New Folder")) + self.listModeButton.setToolTip(_translate("QFileDialog", "List View")) + self.listModeButton.setAccessibleName(_translate("QFileDialog", "List View")) + self.listModeButton.setAccessibleDescription(_translate("QFileDialog", "Change to list view mode")) + self.detailModeButton.setToolTip(_translate("QFileDialog", "Detail View")) + self.detailModeButton.setAccessibleName(_translate("QFileDialog", "Detail View")) + self.detailModeButton.setAccessibleDescription(_translate("QFileDialog", "Change to detail view mode")) + self.sidebar.setAccessibleName(_translate("QFileDialog", "Sidebar")) + self.sidebar.setAccessibleDescription(_translate("QFileDialog", "List of places and bookmarks")) + self.listView.setAccessibleName(_translate("QFileDialog", "Files")) + self.treeView.setAccessibleName(_translate("QFileDialog", "Files")) + self.fileTypeLabel.setText(_translate("QFileDialog", "Files of type:")) +from private.qfiledialog_p import QFileDialogComboBox, QFileDialogLineEdit, QFileDialogListView, QFileDialogTreeView +from private.qsidebar_p import QSidebar diff --git a/nmreval/gui_qt/_py/fitcreationdialog.py b/nmreval/gui_qt/_py/fitcreationdialog.py new file mode 100644 index 0000000..f2abd98 --- /dev/null +++ b/nmreval/gui_qt/_py/fitcreationdialog.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitcreationdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(614, 776) + self.verticalLayout_5 = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout_5.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_5.setSpacing(3) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.groupBox = QtWidgets.QGroupBox(Dialog) + self.groupBox.setCheckable(True) + self.groupBox.setChecked(False) + self.groupBox.setObjectName("groupBox") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout_7.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_7.setSpacing(3) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.widget_2 = QtWidgets.QWidget(self.groupBox) + self.widget_2.setObjectName("widget_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setSpacing(3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.name_lineedit = QtWidgets.QLineEdit(self.widget_2) + self.name_lineedit.setObjectName("name_lineedit") + self.gridLayout_2.addWidget(self.name_lineedit, 0, 1, 1, 1) + self.group_lineedit = QtWidgets.QLineEdit(self.widget_2) + self.group_lineedit.setObjectName("group_lineedit") + self.gridLayout_2.addWidget(self.group_lineedit, 1, 1, 1, 1) + self.group_label = QtWidgets.QLabel(self.widget_2) + self.group_label.setObjectName("group_label") + self.gridLayout_2.addWidget(self.group_label, 1, 0, 1, 1) + self.name_label = QtWidgets.QLabel(self.widget_2) + self.name_label.setObjectName("name_label") + self.gridLayout_2.addWidget(self.name_label, 0, 0, 1, 1) + self.lineEdit = QtWidgets.QLineEdit(self.widget_2) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout_2.addWidget(self.lineEdit, 2, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget_2) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 2, 0, 1, 1) + self.verticalLayout_7.addWidget(self.widget_2) + self.verticalLayout_5.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(Dialog) + self.groupBox_2.setCheckable(True) + self.groupBox_2.setChecked(False) + self.groupBox_2.setObjectName("groupBox_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) + self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.widget_3 = QtWidgets.QWidget(self.groupBox_2) + self.widget_3.setObjectName("widget_3") + self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.widget_3) + self.verticalLayout_8.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_8.setSpacing(3) + self.verticalLayout_8.setObjectName("verticalLayout_8") + self.tableWidget = QtWidgets.QTableWidget(self.widget_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tableWidget.sizePolicy().hasHeightForWidth()) + self.tableWidget.setSizePolicy(sizePolicy) + self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableWidget.setColumnCount(4) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(3, item) + self.verticalLayout_8.addWidget(self.tableWidget) + self.parameter_button = QtWidgets.QToolButton(self.widget_3) + self.parameter_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.parameter_button.setAutoRaise(False) + self.parameter_button.setArrowType(QtCore.Qt.RightArrow) + self.parameter_button.setObjectName("parameter_button") + self.verticalLayout_8.addWidget(self.parameter_button) + self.verticalLayout_2.addWidget(self.widget_3) + self.verticalLayout_5.addWidget(self.groupBox_2) + self.groupBox_3 = QtWidgets.QGroupBox(Dialog) + self.groupBox_3.setCheckable(True) + self.groupBox_3.setChecked(False) + self.groupBox_3.setObjectName("groupBox_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_3) + self.verticalLayout_3.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_3.setSpacing(3) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.widget = QtWidgets.QWidget(self.groupBox_3) + self.widget.setObjectName("widget") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.widget) + self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_6.setSpacing(3) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.use_nuclei = QtWidgets.QCheckBox(self.widget) + self.use_nuclei.setObjectName("use_nuclei") + self.verticalLayout_6.addWidget(self.use_nuclei) + self.tabWidget = QtWidgets.QTabWidget(self.widget) + self.tabWidget.setTabPosition(QtWidgets.QTabWidget.West) + self.tabWidget.setTabsClosable(True) + self.tabWidget.setObjectName("tabWidget") + self.verticalLayout_6.addWidget(self.tabWidget) + self.selection_button = QtWidgets.QToolButton(self.widget) + self.selection_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.selection_button.setArrowType(QtCore.Qt.RightArrow) + self.selection_button.setObjectName("selection_button") + self.verticalLayout_6.addWidget(self.selection_button) + self.verticalLayout_3.addWidget(self.widget) + self.verticalLayout_5.addWidget(self.groupBox_3) + self.groupBox_4 = QtWidgets.QGroupBox(Dialog) + self.groupBox_4.setCheckable(True) + self.groupBox_4.setChecked(False) + self.groupBox_4.setObjectName("groupBox_4") + self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_4) + self.verticalLayout.setObjectName("verticalLayout") + self.namespace_widget = QNamespaceWidget(self.groupBox_4) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.namespace_widget.sizePolicy().hasHeightForWidth()) + self.namespace_widget.setSizePolicy(sizePolicy) + self.namespace_widget.setObjectName("namespace_widget") + self.verticalLayout.addWidget(self.namespace_widget) + self.verticalLayout_5.addWidget(self.groupBox_4) + self.frame_4 = QtWidgets.QFrame(Dialog) + self.frame_4.setObjectName("frame_4") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame_4) + self.verticalLayout_4.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_4.setSpacing(3) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.label = QtWidgets.QLabel(self.frame_4) + self.label.setObjectName("label") + self.verticalLayout_4.addWidget(self.label) + self.plainTextEdit = CodeEditor(self.frame_4) + self.plainTextEdit.setObjectName("plainTextEdit") + self.verticalLayout_4.addWidget(self.plainTextEdit) + self.verticalLayout_5.addWidget(self.frame_4) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout_5.addWidget(self.buttonBox) + self.name_label.setBuddy(self.name_lineedit) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.groupBox.setTitle(_translate("Dialog", "Description")) + self.group_label.setText(_translate("Dialog", "Group")) + self.name_label.setText(_translate("Dialog", "Name")) + self.label_2.setText(_translate("Dialog", "Equation")) + self.groupBox_2.setTitle(_translate("Dialog", "Variables")) + item = self.tableWidget.horizontalHeaderItem(0) + item.setText(_translate("Dialog", "Variable")) + item = self.tableWidget.horizontalHeaderItem(1) + item.setText(_translate("Dialog", "Name")) + item = self.tableWidget.horizontalHeaderItem(2) + item.setText(_translate("Dialog", "Lower bound")) + item = self.tableWidget.horizontalHeaderItem(3) + item.setText(_translate("Dialog", "Upper bound")) + self.parameter_button.setText(_translate("Dialog", "Add parameter")) + self.groupBox_3.setTitle(_translate("Dialog", "Multiple choice part")) + self.use_nuclei.setText(_translate("Dialog", "Add gyromagnetic ratios")) + self.selection_button.setText(_translate("Dialog", "Add selection")) + self.groupBox_4.setTitle(_translate("Dialog", "Available namespace")) + self.label.setText(_translate("Dialog", "Function y = func(x)")) +from ..lib.codeeditor import CodeEditor +from ..lib.namespace import QNamespaceWidget diff --git a/nmreval/gui_qt/_py/fitdialog.py b/nmreval/gui_qt/_py/fitdialog.py new file mode 100644 index 0000000..8ed9207 --- /dev/null +++ b/nmreval/gui_qt/_py/fitdialog.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FitDialog(object): + def setupUi(self, FitDialog): + FitDialog.setObjectName("FitDialog") + FitDialog.resize(347, 710) + self.verticalLayout = QtWidgets.QVBoxLayout(FitDialog) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.scrollArea = QtWidgets.QScrollArea(FitDialog) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents_2 = QtWidgets.QWidget() + self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 341, 665)) + self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_2) + self.gridLayout_2.setContentsMargins(0, 0, 0, -1) + self.gridLayout_2.setSpacing(3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.weight_combobox = QtWidgets.QComboBox(self.scrollAreaWidgetContents_2) + self.weight_combobox.setObjectName("weight_combobox") + 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.newmodel_button = QtWidgets.QPushButton(self.scrollAreaWidgetContents_2) + self.newmodel_button.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.newmodel_button.sizePolicy().hasHeightForWidth()) + self.newmodel_button.setSizePolicy(sizePolicy) + self.newmodel_button.setObjectName("newmodel_button") + self.gridLayout_2.addWidget(self.newmodel_button, 2, 0, 1, 1) + self.deletemodel_button = QtWidgets.QPushButton(self.scrollAreaWidgetContents_2) + self.deletemodel_button.setEnabled(False) + self.deletemodel_button.setObjectName("deletemodel_button") + self.gridLayout_2.addWidget(self.deletemodel_button, 2, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.scrollAreaWidgetContents_2) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 6, 0, 1, 1) + self.stackedWidget = QtWidgets.QStackedWidget(self.scrollAreaWidgetContents_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth()) + self.stackedWidget.setSizePolicy(sizePolicy) + self.stackedWidget.setObjectName("stackedWidget") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.stackedWidget.addWidget(self.page) + self.gridLayout_2.addWidget(self.stackedWidget, 1, 0, 1, 2) + self.functionwidget = QFunctionWidget(self.scrollAreaWidgetContents_2) + self.functionwidget.setObjectName("functionwidget") + self.gridLayout_2.addWidget(self.functionwidget, 0, 0, 1, 2) + self.model_frame = QtWidgets.QFrame(self.scrollAreaWidgetContents_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.model_frame.sizePolicy().hasHeightForWidth()) + self.model_frame.setSizePolicy(sizePolicy) + self.model_frame.setObjectName("model_frame") + self.gridLayout = QtWidgets.QGridLayout(self.model_frame) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setHorizontalSpacing(0) + self.gridLayout.setVerticalSpacing(1) + self.gridLayout.setObjectName("gridLayout") + self.show_combobox = QtWidgets.QComboBox(self.model_frame) + self.show_combobox.setObjectName("show_combobox") + self.show_combobox.addItem("") + self.gridLayout.addWidget(self.show_combobox, 1, 1, 1, 1) + self.default_combobox = QtWidgets.QComboBox(self.model_frame) + self.default_combobox.setObjectName("default_combobox") + self.default_combobox.addItem("") + self.gridLayout.addWidget(self.default_combobox, 0, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.model_frame) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.label = QtWidgets.QLabel(self.model_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, 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, 0, 1, 1) + self.gridLayout_2.addWidget(self.model_frame, 3, 0, 1, 2) + self.data_widget = ExpandableWidget(self.scrollAreaWidgetContents_2) + self.data_widget.setMinimumSize(QtCore.QSize(0, 24)) + self.data_widget.setObjectName("data_widget") + self.gridLayout_2.addWidget(self.data_widget, 5, 0, 1, 2) + self.scrollArea.setWidget(self.scrollAreaWidgetContents_2) + self.verticalLayout.addWidget(self.scrollArea) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.fit_button = QtWidgets.QPushButton(FitDialog) + self.fit_button.setStyleSheet("font-weight: bold") + self.fit_button.setObjectName("fit_button") + self.horizontalLayout.addWidget(self.fit_button) + self.abort_button = QtWidgets.QPushButton(FitDialog) + self.abort_button.setStyleSheet("font-weight: bold") + self.abort_button.setObjectName("abort_button") + self.horizontalLayout.addWidget(self.abort_button) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.preview_checkbox = QtWidgets.QCheckBox(FitDialog) + self.preview_checkbox.setObjectName("preview_checkbox") + self.horizontalLayout.addWidget(self.preview_checkbox) + self.preview_button = QtWidgets.QPushButton(FitDialog) + self.preview_button.setCheckable(False) + self.preview_button.setChecked(False) + self.preview_button.setFlat(False) + self.preview_button.setObjectName("preview_button") + self.horizontalLayout.addWidget(self.preview_button) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(FitDialog) + self.stackedWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(FitDialog) + + def retranslateUi(self, FitDialog): + _translate = QtCore.QCoreApplication.translate + FitDialog.setWindowTitle(_translate("FitDialog", "Form")) + self.weight_combobox.setItemText(0, _translate("FitDialog", "None")) + self.weight_combobox.setItemText(1, _translate("FitDialog", "y")) + self.weight_combobox.setItemText(2, _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.deletemodel_button.setText(_translate("FitDialog", "Delete model")) + self.label_3.setText(_translate("FitDialog", "Weight")) + self.show_combobox.setItemText(0, _translate("FitDialog", "Model a")) + self.default_combobox.setItemText(0, _translate("FitDialog", "Model a")) + self.label_2.setText(_translate("FitDialog", "Show model")) + self.label.setText(_translate("FitDialog", "Default")) + self.fit_button.setText(_translate("FitDialog", "Run fit!!!")) + self.abort_button.setText(_translate("FitDialog", "Abort")) + self.preview_checkbox.setText(_translate("FitDialog", "Preview")) + self.preview_button.setText(_translate("FitDialog", "Update")) +from ..fit.fitfunction import QFunctionWidget +from ..lib.expandablewidget import ExpandableWidget diff --git a/nmreval/gui_qt/_py/fitdialog_window.py b/nmreval/gui_qt/_py/fitdialog_window.py new file mode 100644 index 0000000..ad342de --- /dev/null +++ b/nmreval/gui_qt/_py/fitdialog_window.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitdialog_window.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FitDialog(object): + def setupUi(self, FitDialog): + FitDialog.setObjectName("FitDialog") + FitDialog.setWindowModality(QtCore.Qt.ApplicationModal) + FitDialog.resize(828, 827) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/logo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + FitDialog.setWindowIcon(icon) + self.centralwidget = QtWidgets.QWidget(FitDialog) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.splitter = QtWidgets.QSplitter(self.centralwidget) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.widget_2 = QtWidgets.QWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.gridLayout_3 = QtWidgets.QGridLayout(self.widget_2) + self.gridLayout_3.setContentsMargins(3, 3, 3, 3) + self.gridLayout_3.setSpacing(3) + self.gridLayout_3.setObjectName("gridLayout_3") + self.label_3 = QtWidgets.QLabel(self.widget_2) + self.label_3.setObjectName("label_3") + self.gridLayout_3.addWidget(self.label_3, 3, 0, 1, 1) + self.weight_combobox = QtWidgets.QComboBox(self.widget_2) + self.weight_combobox.setObjectName("weight_combobox") + self.weight_combobox.addItem("") + self.weight_combobox.addItem("") + self.weight_combobox.addItem("") + self.weight_combobox.addItem("") + self.weight_combobox.addItem("") + self.gridLayout_3.addWidget(self.weight_combobox, 3, 1, 1, 1) + self.line = QtWidgets.QFrame(self.widget_2) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout_3.addWidget(self.line, 2, 0, 1, 2) + self.tableWidget = QtWidgets.QTableWidget(self.widget_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tableWidget.sizePolicy().hasHeightForWidth()) + self.tableWidget.setSizePolicy(sizePolicy) + self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableWidget.setShowGrid(False) + self.tableWidget.setGridStyle(QtCore.Qt.NoPen) + self.tableWidget.setColumnCount(2) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setRowCount(0) + self.tableWidget.horizontalHeader().setVisible(False) + self.tableWidget.horizontalHeader().setStretchLastSection(True) + self.tableWidget.verticalHeader().setVisible(False) + self.gridLayout_3.addWidget(self.tableWidget, 0, 0, 1, 2) + self.horizontalFrame = QtWidgets.QFrame(self.widget_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.horizontalFrame.sizePolicy().hasHeightForWidth()) + self.horizontalFrame.setSizePolicy(sizePolicy) + self.horizontalFrame.setObjectName("horizontalFrame") + self.gridLayout = QtWidgets.QGridLayout(self.horizontalFrame) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setHorizontalSpacing(0) + self.gridLayout.setVerticalSpacing(1) + self.gridLayout.setObjectName("gridLayout") + self.show_combobox = QtWidgets.QComboBox(self.horizontalFrame) + self.show_combobox.setObjectName("show_combobox") + self.show_combobox.addItem("") + self.gridLayout.addWidget(self.show_combobox, 1, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.horizontalFrame) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.default_combobox = QtWidgets.QComboBox(self.horizontalFrame) + self.default_combobox.setObjectName("default_combobox") + self.default_combobox.addItem("") + self.gridLayout.addWidget(self.default_combobox, 0, 1, 1, 1) + self.label = QtWidgets.QLabel(self.horizontalFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, 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, 0, 1, 1) + self.gridLayout_3.addWidget(self.horizontalFrame, 1, 0, 1, 2) + self.middle_widget = QtWidgets.QWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.middle_widget.sizePolicy().hasHeightForWidth()) + self.middle_widget.setSizePolicy(sizePolicy) + self.middle_widget.setObjectName("middle_widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.middle_widget) + self.gridLayout_2.setContentsMargins(3, 3, 3, 3) + self.gridLayout_2.setSpacing(3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.newmodel_button = QtWidgets.QPushButton(self.middle_widget) + self.newmodel_button.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.newmodel_button.sizePolicy().hasHeightForWidth()) + self.newmodel_button.setSizePolicy(sizePolicy) + self.newmodel_button.setObjectName("newmodel_button") + self.gridLayout_2.addWidget(self.newmodel_button, 2, 0, 1, 1) + self.functionwidget = FunctionSelectionWidget(self.middle_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.functionwidget.sizePolicy().hasHeightForWidth()) + self.functionwidget.setSizePolicy(sizePolicy) + self.functionwidget.setObjectName("functionwidget") + self.gridLayout_2.addWidget(self.functionwidget, 0, 0, 1, 2) + self.deletemodel_button = QtWidgets.QPushButton(self.middle_widget) + self.deletemodel_button.setEnabled(False) + self.deletemodel_button.setObjectName("deletemodel_button") + self.gridLayout_2.addWidget(self.deletemodel_button, 2, 1, 1, 1) + self.stackedWidget = QtWidgets.QStackedWidget(self.middle_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth()) + self.stackedWidget.setSizePolicy(sizePolicy) + self.stackedWidget.setObjectName("stackedWidget") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.stackedWidget.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.stackedWidget.addWidget(self.page_2) + self.gridLayout_2.addWidget(self.stackedWidget, 1, 0, 1, 2) + self.verticalLayout_2.addWidget(self.splitter) + self.frame_4 = QtWidgets.QFrame(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame_4.sizePolicy().hasHeightForWidth()) + self.frame_4.setSizePolicy(sizePolicy) + self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_4.setLineWidth(2) + self.frame_4.setMidLineWidth(0) + self.frame_4.setObjectName("frame_4") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_4) + self.horizontalLayout_2.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.fit_button = QtWidgets.QPushButton(self.frame_4) + self.fit_button.setObjectName("fit_button") + self.horizontalLayout_2.addWidget(self.fit_button) + self.abort_button = QtWidgets.QPushButton(self.frame_4) + self.abort_button.setObjectName("abort_button") + self.horizontalLayout_2.addWidget(self.abort_button) + self.preview_button = QtWidgets.QPushButton(self.frame_4) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(":/fit_preview.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.preview_button.setIcon(icon1) + self.preview_button.setCheckable(True) + self.preview_button.setObjectName("preview_button") + self.horizontalLayout_2.addWidget(self.preview_button) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout_2.addWidget(self.frame_4) + FitDialog.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(FitDialog) + self.menubar.setGeometry(QtCore.QRect(0, 0, 828, 30)) + self.menubar.setObjectName("menubar") + self.menuOptions = QtWidgets.QMenu(self.menubar) + self.menuOptions.setObjectName("menuOptions") + self.menuMethod = QtWidgets.QMenu(self.menuOptions) + self.menuMethod.setObjectName("menuMethod") + self.menuLimits = QtWidgets.QMenu(self.menuOptions) + self.menuLimits.setObjectName("menuLimits") + self.menuHelp = QtWidgets.QMenu(self.menubar) + self.menuHelp.setObjectName("menuHelp") + self.menuUser = QtWidgets.QMenu(self.menubar) + self.menuUser.setObjectName("menuUser") + FitDialog.setMenuBar(self.menubar) + self.statusBar = QtWidgets.QStatusBar(FitDialog) + self.statusBar.setObjectName("statusBar") + FitDialog.setStatusBar(self.statusBar) + self.action_nm = QtWidgets.QAction(FitDialog) + self.action_nm.setCheckable(True) + self.action_nm.setObjectName("action_nm") + self.action_odr = QtWidgets.QAction(FitDialog) + self.action_odr.setCheckable(True) + self.action_odr.setObjectName("action_odr") + self.action_lm = QtWidgets.QAction(FitDialog) + self.action_lm.setCheckable(True) + self.action_lm.setChecked(True) + self.action_lm.setObjectName("action_lm") + self.actionHelp = QtWidgets.QAction(FitDialog) + self.actionHelp.setObjectName("actionHelp") + self.actionOpen_editor = QtWidgets.QAction(FitDialog) + self.actionOpen_editor.setObjectName("actionOpen_editor") + self.actionPreview_points = QtWidgets.QAction(FitDialog) + self.actionPreview_points.setObjectName("actionPreview_points") + self.actionSave_current_model = QtWidgets.QAction(FitDialog) + self.actionSave_current_model.setObjectName("actionSave_current_model") + self.action_no_range = QtWidgets.QAction(FitDialog) + self.action_no_range.setCheckable(True) + self.action_no_range.setObjectName("action_no_range") + self.action_x_range = QtWidgets.QAction(FitDialog) + self.action_x_range.setCheckable(True) + self.action_x_range.setChecked(True) + self.action_x_range.setObjectName("action_x_range") + self.action_custom_range = QtWidgets.QAction(FitDialog) + self.action_custom_range.setCheckable(True) + self.action_custom_range.setObjectName("action_custom_range") + self.menuMethod.addAction(self.action_lm) + self.menuMethod.addAction(self.action_nm) + self.menuMethod.addAction(self.action_odr) + self.menuLimits.addAction(self.action_no_range) + self.menuLimits.addAction(self.action_x_range) + self.menuLimits.addAction(self.action_custom_range) + self.menuOptions.addAction(self.menuMethod.menuAction()) + self.menuOptions.addAction(self.menuLimits.menuAction()) + self.menuOptions.addSeparator() + self.menuOptions.addAction(self.actionPreview_points) + self.menuHelp.addAction(self.actionHelp) + self.menuUser.addAction(self.actionOpen_editor) + self.menuUser.addAction(self.actionSave_current_model) + self.menubar.addAction(self.menuOptions.menuAction()) + self.menubar.addAction(self.menuUser.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + + self.retranslateUi(FitDialog) + QtCore.QMetaObject.connectSlotsByName(FitDialog) + + def retranslateUi(self, FitDialog): + _translate = QtCore.QCoreApplication.translate + FitDialog.setWindowTitle(_translate("FitDialog", "One must imagine Sisyphus happy.")) + self.label_3.setText(_translate("FitDialog", "Weight")) + self.weight_combobox.setItemText(0, _translate("FitDialog", "None")) + self.weight_combobox.setItemText(1, _translate("FitDialog", "y")) + self.weight_combobox.setItemText(2, _translate("FitDialog", "y²")) + self.weight_combobox.setItemText(3, _translate("FitDialog", "Δy")) + self.weight_combobox.setItemText(4, _translate("FitDialog", "log(y)")) + self.show_combobox.setItemText(0, _translate("FitDialog", "Model a")) + self.label_2.setText(_translate("FitDialog", "Show model")) + self.default_combobox.setItemText(0, _translate("FitDialog", "Model a")) + self.label.setText(_translate("FitDialog", "Default")) + self.newmodel_button.setText(_translate("FitDialog", "New model")) + self.deletemodel_button.setText(_translate("FitDialog", "Delete model")) + self.fit_button.setText(_translate("FitDialog", "Run fit!!!")) + self.abort_button.setText(_translate("FitDialog", "Abort")) + self.preview_button.setText(_translate("FitDialog", "Preview")) + self.menuOptions.setTitle(_translate("FitDialog", "Options")) + self.menuMethod.setTitle(_translate("FitDialog", "Method")) + self.menuLimits.setTitle(_translate("FitDialog", "Limits")) + self.menuHelp.setTitle(_translate("FitDialog", "Help")) + self.menuUser.setTitle(_translate("FitDialog", "User")) + self.action_nm.setText(_translate("FitDialog", "Nelder-Mead")) + self.action_odr.setText(_translate("FitDialog", "ODR")) + self.action_lm.setText(_translate("FitDialog", "Default stuff")) + self.actionHelp.setText(_translate("FitDialog", "Help!")) + self.actionOpen_editor.setText(_translate("FitDialog", "Open editor...")) + self.actionPreview_points.setText(_translate("FitDialog", "Preview points...")) + self.actionSave_current_model.setText(_translate("FitDialog", "Save current model...")) + self.action_no_range.setText(_translate("FitDialog", "None")) + self.action_x_range.setText(_translate("FitDialog", "Visible x range")) + self.action_custom_range.setText(_translate("FitDialog", "Custom...")) +from ..fit.function_selection import FunctionSelectionWidget +import images_rc diff --git a/nmreval/gui_qt/_py/fitfunctionwidget.py b/nmreval/gui_qt/_py/fitfunctionwidget.py new file mode 100644 index 0000000..3035f2f --- /dev/null +++ b/nmreval/gui_qt/_py/fitfunctionwidget.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitfunctionwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(314, 232) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.widget = ExpandableWidget(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) + self.widget.setSizePolicy(sizePolicy) + self.widget.setObjectName("widget") + self.gridLayout.addWidget(self.widget, 4, 0, 1, 2) + self.complex_widget = QtWidgets.QWidget(Form) + self.complex_widget.setObjectName("complex_widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.complex_widget) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_2 = QtWidgets.QLabel(self.complex_widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1) + self.complex_comboBox = QtWidgets.QComboBox(self.complex_widget) + self.complex_comboBox.setObjectName("complex_comboBox") + self.complex_comboBox.addItem("") + self.complex_comboBox.addItem("") + self.complex_comboBox.addItem("") + self.gridLayout_2.addWidget(self.complex_comboBox, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(self.complex_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 2) + self.gridLayout.addWidget(self.complex_widget, 5, 0, 1, 2) + self.use_function_button = QtWidgets.QToolButton(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.use_function_button.sizePolicy().hasHeightForWidth()) + self.use_function_button.setSizePolicy(sizePolicy) + self.use_function_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.use_function_button.setAutoRaise(False) + self.use_function_button.setArrowType(QtCore.Qt.RightArrow) + self.use_function_button.setObjectName("use_function_button") + self.gridLayout.addWidget(self.use_function_button, 3, 1, 1, 1) + self.fitcomboBox = QtWidgets.QComboBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fitcomboBox.sizePolicy().hasHeightForWidth()) + self.fitcomboBox.setSizePolicy(sizePolicy) + self.fitcomboBox.setObjectName("fitcomboBox") + self.gridLayout.addWidget(self.fitcomboBox, 1, 0, 1, 2) + self.typecomboBox = QtWidgets.QComboBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.typecomboBox.sizePolicy().hasHeightForWidth()) + self.typecomboBox.setSizePolicy(sizePolicy) + self.typecomboBox.setObjectName("typecomboBox") + self.gridLayout.addWidget(self.typecomboBox, 0, 0, 1, 2) + self.fitequation = QtWidgets.QLabel(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fitequation.sizePolicy().hasHeightForWidth()) + self.fitequation.setSizePolicy(sizePolicy) + self.fitequation.setWordWrap(True) + self.fitequation.setObjectName("fitequation") + self.gridLayout.addWidget(self.fitequation, 2, 0, 1, 2) + self.operator_combobox = QtWidgets.QComboBox(Form) + self.operator_combobox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + self.operator_combobox.setFrame(True) + self.operator_combobox.setObjectName("operator_combobox") + self.operator_combobox.addItem("") + self.operator_combobox.addItem("") + self.operator_combobox.addItem("") + self.operator_combobox.addItem("") + self.gridLayout.addWidget(self.operator_combobox, 3, 0, 1, 1) + self.use_combobox = QtWidgets.QComboBox(Form) + self.use_combobox.setObjectName("use_combobox") + self.gridLayout.addWidget(self.use_combobox, 6, 0, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.label_2.setText(_translate("Form", "Select part to fit")) + self.complex_comboBox.setItemText(0, _translate("Form", "Complex")) + self.complex_comboBox.setItemText(1, _translate("Form", "Real")) + self.complex_comboBox.setItemText(2, _translate("Form", "Imaginary")) + self.label.setText(_translate("Form", "Complex function found")) + self.use_function_button.setText(_translate("Form", "Use")) + self.fitequation.setText(_translate("Form", "Equation")) + self.operator_combobox.setItemText(0, _translate("Form", "Add")) + self.operator_combobox.setItemText(1, _translate("Form", "Multiply")) + self.operator_combobox.setItemText(2, _translate("Form", "Subtract")) + self.operator_combobox.setItemText(3, _translate("Form", "Divide by")) +from ..lib.expandablewidget import ExpandableWidget diff --git a/nmreval/gui_qt/_py/fitfuncwidget.py b/nmreval/gui_qt/_py/fitfuncwidget.py new file mode 100644 index 0000000..38fa341 --- /dev/null +++ b/nmreval/gui_qt/_py/fitfuncwidget.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitfuncwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FormFit(object): + def setupUi(self, FormFit): + FormFit.setObjectName("FormFit") + FormFit.resize(292, 477) + self.verticalLayout = QtWidgets.QVBoxLayout(FormFit) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(FormFit) + self.tabWidget.setObjectName("tabWidget") + self.general_tab = QtWidgets.QWidget() + self.general_tab.setObjectName("general_tab") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.general_tab) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.scrollArea = QtWidgets.QScrollArea(self.general_tab) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollwidget = QtWidgets.QWidget() + self.scrollwidget.setGeometry(QtCore.QRect(0, 0, 284, 442)) + self.scrollwidget.setObjectName("scrollwidget") + self.scrollArea.setWidget(self.scrollwidget) + self.verticalLayout_2.addWidget(self.scrollArea) + self.tabWidget.addTab(self.general_tab, "") + self.data_tab = QtWidgets.QWidget() + self.data_tab.setObjectName("data_tab") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.data_tab) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.comboBox = QtWidgets.QComboBox(self.data_tab) + self.comboBox.setObjectName("comboBox") + self.verticalLayout_3.addWidget(self.comboBox) + self.scrollArea2 = QtWidgets.QScrollArea(self.data_tab) + self.scrollArea2.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea2.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollArea2.setWidgetResizable(True) + self.scrollArea2.setObjectName("scrollArea2") + self.scrollwidget2 = QtWidgets.QWidget() + self.scrollwidget2.setGeometry(QtCore.QRect(0, 0, 272, 357)) + self.scrollwidget2.setObjectName("scrollwidget2") + self.scrollArea2.setWidget(self.scrollwidget2) + self.verticalLayout_3.addWidget(self.scrollArea2) + self.tabWidget.addTab(self.data_tab, "") + self.verticalLayout.addWidget(self.tabWidget) + + self.retranslateUi(FormFit) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(FormFit) + + def retranslateUi(self, FormFit): + _translate = QtCore.QCoreApplication.translate + FormFit.setWindowTitle(_translate("FormFit", "Form")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.general_tab), _translate("FormFit", "General settings")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.data_tab), _translate("FormFit", "Data parameter")) diff --git a/nmreval/gui_qt/_py/fitfuncwidget_old.py b/nmreval/gui_qt/_py/fitfuncwidget_old.py new file mode 100644 index 0000000..e949870 --- /dev/null +++ b/nmreval/gui_qt/_py/fitfuncwidget_old.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitfuncwidget_old.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FormFit(object): + def setupUi(self, FormFit): + FormFit.setObjectName("FormFit") + FormFit.resize(402, 523) + self.verticalLayout = QtWidgets.QVBoxLayout(FormFit) + self.verticalLayout.setContentsMargins(0, 6, 0, 0) + self.verticalLayout.setSpacing(6) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(FormFit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) + self.tabWidget.setSizePolicy(sizePolicy) + self.tabWidget.setObjectName("tabWidget") + self.general_tab = QtWidgets.QWidget() + self.general_tab.setObjectName("general_tab") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.general_tab) + self.verticalLayout_3.setContentsMargins(2, 2, 2, 2) + self.verticalLayout_3.setSpacing(2) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.scrollArea = QtWidgets.QScrollArea(self.general_tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) + self.scrollArea.setSizePolicy(sizePolicy) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollArea.setWidgetResizable(False) + self.scrollArea.setObjectName("scrollArea") + self.scrollwidget = QtWidgets.QWidget() + self.scrollwidget.setGeometry(QtCore.QRect(0, 0, 390, 478)) + self.scrollwidget.setObjectName("scrollwidget") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.scrollwidget) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.scrollArea.setWidget(self.scrollwidget) + self.verticalLayout_3.addWidget(self.scrollArea) + self.tabWidget.addTab(self.general_tab, "") + self.data_tab = QtWidgets.QWidget() + self.data_tab.setObjectName("data_tab") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.data_tab) + self.verticalLayout_2.setContentsMargins(2, 2, 2, 2) + self.verticalLayout_2.setSpacing(2) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.comboBox = QtWidgets.QComboBox(self.data_tab) + self.comboBox.setObjectName("comboBox") + self.verticalLayout_2.addWidget(self.comboBox) + self.scrollArea2 = QtWidgets.QScrollArea(self.data_tab) + self.scrollArea2.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea2.setWidgetResizable(True) + self.scrollArea2.setObjectName("scrollArea2") + self.scrollwidget2 = QtWidgets.QWidget() + self.scrollwidget2.setGeometry(QtCore.QRect(0, 0, 390, 444)) + self.scrollwidget2.setObjectName("scrollwidget2") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.scrollwidget2) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.scrollArea2.setWidget(self.scrollwidget2) + self.verticalLayout_2.addWidget(self.scrollArea2) + self.tabWidget.addTab(self.data_tab, "") + self.verticalLayout.addWidget(self.tabWidget) + + self.retranslateUi(FormFit) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(FormFit) + + def retranslateUi(self, FormFit): + _translate = QtCore.QCoreApplication.translate + FormFit.setWindowTitle(_translate("FormFit", "Form")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.general_tab), _translate("FormFit", "General settings")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.data_tab), _translate("FormFit", "Data parameter")) diff --git a/nmreval/gui_qt/_py/fitmodelfixwidget.py b/nmreval/gui_qt/_py/fitmodelfixwidget.py new file mode 100644 index 0000000..ef46181 --- /dev/null +++ b/nmreval/gui_qt/_py/fitmodelfixwidget.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitmodelfixwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FitFixParameter(object): + def setupUi(self, FitFixParameter): + FitFixParameter.setObjectName("FitFixParameter") + FitFixParameter.setWindowModality(QtCore.Qt.WindowModal) + FitFixParameter.resize(480, 267) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(FitFixParameter.sizePolicy().hasHeightForWidth()) + FitFixParameter.setSizePolicy(sizePolicy) + FitFixParameter.setAutoFillBackground(True) + self.gridLayout = QtWidgets.QGridLayout(FitFixParameter) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.frame = QtWidgets.QFrame(FitFixParameter) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame.setObjectName("frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout.setContentsMargins(0, 0, 3, 0) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.parametername = QtWidgets.QLabel(self.frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(10) + sizePolicy.setHeightForWidth(self.parametername.sizePolicy().hasHeightForWidth()) + self.parametername.setSizePolicy(sizePolicy) + self.parametername.setFrameShape(QtWidgets.QFrame.NoFrame) + self.parametername.setIndent(6) + self.parametername.setObjectName("parametername") + self.horizontalLayout.addWidget(self.parametername) + self.label = QtWidgets.QLabel(self.frame) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.gridLayout.addWidget(self.frame, 1, 0, 1, 1) + self.parameter_line = QtWidgets.QLineEdit(FitFixParameter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.parameter_line.sizePolicy().hasHeightForWidth()) + self.parameter_line.setSizePolicy(sizePolicy) + self.parameter_line.setObjectName("parameter_line") + self.gridLayout.addWidget(self.parameter_line, 1, 1, 1, 1) + + self.retranslateUi(FitFixParameter) + QtCore.QMetaObject.connectSlotsByName(FitFixParameter) + + def retranslateUi(self, FitFixParameter): + _translate = QtCore.QCoreApplication.translate + FitFixParameter.setWindowTitle(_translate("FitFixParameter", "Form")) + self.parametername.setText(_translate("FitFixParameter", "Parameter")) + self.label.setText(_translate("FitFixParameter", "Unit")) + self.parameter_line.setText(_translate("FitFixParameter", "1")) diff --git a/nmreval/gui_qt/_py/fitmodelwidget.py b/nmreval/gui_qt/_py/fitmodelwidget.py new file mode 100644 index 0000000..a41f14b --- /dev/null +++ b/nmreval/gui_qt/_py/fitmodelwidget.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitmodelwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FitParameter(object): + def setupUi(self, FitParameter): + FitParameter.setObjectName("FitParameter") + FitParameter.resize(365, 78) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(FitParameter.sizePolicy().hasHeightForWidth()) + FitParameter.setSizePolicy(sizePolicy) + self.verticalLayout = QtWidgets.QVBoxLayout(FitParameter) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.parametername = QtWidgets.QLabel(FitParameter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.parametername.sizePolicy().hasHeightForWidth()) + self.parametername.setSizePolicy(sizePolicy) + self.parametername.setMinimumSize(QtCore.QSize(28, 0)) + self.parametername.setObjectName("parametername") + self.horizontalLayout_2.addWidget(self.parametername) + self.parameter_line = LineEdit(FitParameter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.parameter_line.sizePolicy().hasHeightForWidth()) + self.parameter_line.setSizePolicy(sizePolicy) + self.parameter_line.setText("") + self.parameter_line.setObjectName("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.setObjectName("fixed_check") + self.horizontalLayout_2.addWidget(self.fixed_check) + self.global_checkbox = QtWidgets.QCheckBox(FitParameter) + self.global_checkbox.setObjectName("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.frame = QtWidgets.QFrame(FitParameter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame.setObjectName("frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.checkBox = QtWidgets.QCheckBox(self.frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox.sizePolicy().hasHeightForWidth()) + self.checkBox.setSizePolicy(sizePolicy) + self.checkBox.setLayoutDirection(QtCore.Qt.RightToLeft) + self.checkBox.setText("") + self.checkBox.setObjectName("checkBox") + self.horizontalLayout.addWidget(self.checkBox) + self.lineEdit = QtWidgets.QLineEdit(self.frame) + self.lineEdit.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth()) + self.lineEdit.setSizePolicy(sizePolicy) + self.lineEdit.setText("") + self.lineEdit.setFrame(True) + self.lineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout.addWidget(self.lineEdit) + self.label_3 = QtWidgets.QLabel(self.frame) + self.label_3.setEnabled(True) + self.label_3.setTextFormat(QtCore.Qt.RichText) + self.label_3.setAlignment(QtCore.Qt.AlignCenter) + self.label_3.setObjectName("label_3") + self.horizontalLayout.addWidget(self.label_3) + self.lineEdit_2 = QtWidgets.QLineEdit(self.frame) + self.lineEdit_2.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEdit_2.sizePolicy().hasHeightForWidth()) + self.lineEdit_2.setSizePolicy(sizePolicy) + self.lineEdit_2.setText("") + self.lineEdit_2.setFrame(True) + self.lineEdit_2.setObjectName("lineEdit_2") + self.horizontalLayout.addWidget(self.lineEdit_2) + self.verticalLayout.addWidget(self.frame) + self.line = QtWidgets.QFrame(FitParameter) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.verticalLayout.addWidget(self.line) + + self.retranslateUi(FitParameter) + QtCore.QMetaObject.connectSlotsByName(FitParameter) + + def retranslateUi(self, FitParameter): + _translate = QtCore.QCoreApplication.translate + FitParameter.setWindowTitle(_translate("FitParameter", "Form")) + self.parametername.setText(_translate("FitParameter", "A")) + self.parameter_line.setToolTip(_translate("FitParameter", "Initial values")) + self.parameter_line.setPlaceholderText(_translate("FitParameter", "0")) + self.fixed_check.setText(_translate("FitParameter", "Fix")) + self.global_checkbox.setText(_translate("FitParameter", "Global")) + self.lineEdit.setToolTip(_translate("FitParameter", "

Lower bound. Same bound is used for all data. Leave empty for no boundary condition.

")) + self.label_3.setText(_translate("FitParameter", "Textlabel")) + self.lineEdit_2.setToolTip(_translate("FitParameter", "

Upper bound. Same bound is used for all data. Leave empty for no boundary condition.

")) +from ..lib.forms import LineEdit diff --git a/nmreval/gui_qt/_py/fitparametertable.py b/nmreval/gui_qt/_py/fitparametertable.py new file mode 100644 index 0000000..5ad7241 --- /dev/null +++ b/nmreval/gui_qt/_py/fitparametertable.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitparametertable.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FitParameterDialog(object): + def setupUi(self, FitParameterDialog): + FitParameterDialog.setObjectName("FitParameterDialog") + FitParameterDialog.resize(898, 583) + self.verticalLayout = QtWidgets.QVBoxLayout(FitParameterDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout.addLayout(self.verticalLayout_2) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.pushButton = QtWidgets.QPushButton(FitParameterDialog) + self.pushButton.setObjectName("pushButton") + self.horizontalLayout.addWidget(self.pushButton) + self.buttonBox = QtWidgets.QDialogButtonBox(FitParameterDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close) + self.buttonBox.setCenterButtons(False) + self.buttonBox.setObjectName("buttonBox") + self.horizontalLayout.addWidget(self.buttonBox) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(FitParameterDialog) + self.buttonBox.accepted.connect(FitParameterDialog.accept) + self.buttonBox.rejected.connect(FitParameterDialog.reject) + QtCore.QMetaObject.connectSlotsByName(FitParameterDialog) + + def retranslateUi(self, FitParameterDialog): + _translate = QtCore.QCoreApplication.translate + FitParameterDialog.setWindowTitle(_translate("FitParameterDialog", "Fitparameter")) + self.pushButton.setText(_translate("FitParameterDialog", "Copy")) diff --git a/nmreval/gui_qt/_py/fitparameterwidget.py b/nmreval/gui_qt/_py/fitparameterwidget.py new file mode 100644 index 0000000..6d282bd --- /dev/null +++ b/nmreval/gui_qt/_py/fitparameterwidget.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitparameterwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_FormFit(object): + def setupUi(self, FormFit): + FormFit.setObjectName("FormFit") + FormFit.resize(292, 477) + self.verticalLayout = QtWidgets.QVBoxLayout(FormFit) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(FormFit) + self.tabWidget.setObjectName("tabWidget") + self.general_tab = QtWidgets.QWidget() + self.general_tab.setObjectName("general_tab") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.general_tab) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.scrollArea = QtWidgets.QScrollArea(self.general_tab) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollwidget = QtWidgets.QWidget() + self.scrollwidget.setGeometry(QtCore.QRect(0, 0, 284, 442)) + self.scrollwidget.setObjectName("scrollwidget") + self.scrollArea.setWidget(self.scrollwidget) + self.verticalLayout_2.addWidget(self.scrollArea) + self.tabWidget.addTab(self.general_tab, "") + self.data_tab = QtWidgets.QWidget() + self.data_tab.setObjectName("data_tab") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.data_tab) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.comboBox = QtWidgets.QComboBox(self.data_tab) + self.comboBox.setObjectName("comboBox") + self.verticalLayout_3.addWidget(self.comboBox) + self.scrollArea2 = QtWidgets.QScrollArea(self.data_tab) + self.scrollArea2.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea2.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.scrollArea2.setWidgetResizable(True) + self.scrollArea2.setObjectName("scrollArea2") + self.scrollwidget2 = QtWidgets.QWidget() + self.scrollwidget2.setGeometry(QtCore.QRect(0, 0, 272, 392)) + self.scrollwidget2.setObjectName("scrollwidget2") + self.scrollArea2.setWidget(self.scrollwidget2) + self.verticalLayout_3.addWidget(self.scrollArea2) + self.tabWidget.addTab(self.data_tab, "") + self.verticalLayout.addWidget(self.tabWidget) + + self.retranslateUi(FormFit) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(FormFit) + + def retranslateUi(self, FormFit): + _translate = QtCore.QCoreApplication.translate + FormFit.setWindowTitle(_translate("FormFit", "Form")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.general_tab), _translate("FormFit", "General settings")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.data_tab), _translate("FormFit", "Data parameter")) diff --git a/nmreval/gui_qt/_py/fitresult.py b/nmreval/gui_qt/_py/fitresult.py new file mode 100644 index 0000000..386e538 --- /dev/null +++ b/nmreval/gui_qt/_py/fitresult.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/fitresult.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(817, 584) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + 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.stack = QtWidgets.QToolBox(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth()) + self.stack.setSizePolicy(sizePolicy) + self.stack.setObjectName("stack") + self.page = QtWidgets.QWidget() + self.page.setGeometry(QtCore.QRect(0, 0, 399, 414)) + self.page.setObjectName("page") + self.verticalLayout = QtWidgets.QVBoxLayout(self.page) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.graphicsView = GraphicsLayoutWidget(self.page) + self.graphicsView.setObjectName("graphicsView") + self.verticalLayout.addWidget(self.graphicsView) + self.logy_box = QtWidgets.QCheckBox(self.page) + self.logy_box.setLayoutDirection(QtCore.Qt.RightToLeft) + self.logy_box.setObjectName("logy_box") + self.verticalLayout.addWidget(self.logy_box) + self.stack.addItem(self.page, "") + self.page_2 = QtWidgets.QWidget() + self.page_2.setGeometry(QtCore.QRect(0, 0, 399, 414)) + self.page_2.setObjectName("page_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.page_2) + self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.stats_tableWidget = QtWidgets.QTableWidget(self.page_2) + self.stats_tableWidget.setFrameShape(QtWidgets.QFrame.Box) + self.stats_tableWidget.setGridStyle(QtCore.Qt.NoPen) + self.stats_tableWidget.setColumnCount(1) + self.stats_tableWidget.setObjectName("stats_tableWidget") + self.stats_tableWidget.setRowCount(0) + self.stats_tableWidget.horizontalHeader().setVisible(False) + self.stats_tableWidget.horizontalHeader().setSortIndicatorShown(True) + self.verticalLayout_2.addWidget(self.stats_tableWidget) + self.stack.addItem(self.page_2, "") + self.page_3 = QtWidgets.QWidget() + self.page_3.setGeometry(QtCore.QRect(0, 0, 399, 414)) + self.page_3.setObjectName("page_3") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_3) + self.verticalLayout_3.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_3.setSpacing(3) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.corr_tableWidget = QtWidgets.QTableWidget(self.page_3) + self.corr_tableWidget.setFrameShape(QtWidgets.QFrame.Box) + self.corr_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.corr_tableWidget.setGridStyle(QtCore.Qt.NoPen) + self.corr_tableWidget.setObjectName("corr_tableWidget") + self.corr_tableWidget.setColumnCount(4) + self.corr_tableWidget.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.corr_tableWidget.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.corr_tableWidget.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.corr_tableWidget.setHorizontalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + self.corr_tableWidget.setHorizontalHeaderItem(3, item) + self.corr_tableWidget.horizontalHeader().setStretchLastSection(True) + self.corr_tableWidget.verticalHeader().setVisible(False) + self.verticalLayout_3.addWidget(self.corr_tableWidget) + self.stack.addItem(self.page_3, "") + self.gridLayout.addWidget(self.stack, 0, 1, 4, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.partial_checkBox = QtWidgets.QCheckBox(Dialog) + self.partial_checkBox.setObjectName("partial_checkBox") + self.horizontalLayout.addWidget(self.partial_checkBox) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.label_2 = QtWidgets.QLabel(Dialog) + self.label_2.setObjectName("label_2") + self.horizontalLayout.addWidget(self.label_2) + self.graph_checkBox = QtWidgets.QCheckBox(Dialog) + 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.horizontalLayout.addWidget(self.graph_checkBox) + self.graph_comboBox = QtWidgets.QComboBox(Dialog) + self.graph_comboBox.setEnabled(False) + self.graph_comboBox.setObjectName("graph_comboBox") + self.horizontalLayout.addWidget(self.graph_comboBox) + self.gridLayout.addLayout(self.horizontalLayout, 5, 0, 1, 2) + self.line_2 = QtWidgets.QFrame(Dialog) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout.addWidget(self.line_2, 3, 0, 1, 1) + 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.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.retranslateUi(Dialog) + self.stack.setCurrentIndex(0) + self.stack.layout().setSpacing(0) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Fit results")) + self.logy_box.setText(_translate("Dialog", "logarithmic y axis")) + self.stack.setItemText(self.stack.indexOf(self.page), _translate("Dialog", "Plot")) + self.stack.setItemText(self.stack.indexOf(self.page_2), _translate("Dialog", "Statistics")) + item = self.corr_tableWidget.horizontalHeaderItem(0) + item.setText(_translate("Dialog", "Parameter 1")) + item = self.corr_tableWidget.horizontalHeaderItem(1) + item.setText(_translate("Dialog", "Parameter 2")) + item = self.corr_tableWidget.horizontalHeaderItem(2) + item.setText(_translate("Dialog", "Corr.")) + item = self.corr_tableWidget.horizontalHeaderItem(3) + item.setText(_translate("Dialog", "Partial Corr.")) + self.stack.setItemText(self.stack.indexOf(self.page_3), _translate("Dialog", "Correlations")) + self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions")) + self.label_2.setText(_translate("Dialog", "Location of parameters:")) + self.graph_checkBox.setText(_translate("Dialog", "New graph")) + self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit")) + self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits")) +from ..lib.forms import ElideComboBox +from pyqtgraph import GraphicsLayoutWidget diff --git a/nmreval/gui_qt/_py/ftdialog.py b/nmreval/gui_qt/_py/ftdialog.py new file mode 100644 index 0000000..1fd694f --- /dev/null +++ b/nmreval/gui_qt/_py/ftdialog.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/ftdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 300) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.listWidget = QtWidgets.QListWidget(Dialog) + self.listWidget.setObjectName("listWidget") + self.verticalLayout.addWidget(self.listWidget) + self.mode_comboBox = QtWidgets.QComboBox(Dialog) + self.mode_comboBox.setObjectName("mode_comboBox") + self.mode_comboBox.addItem("") + self.mode_comboBox.addItem("") + self.mode_comboBox.addItem("") + self.verticalLayout.addWidget(self.mode_comboBox) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.graph_checkBox = QtWidgets.QCheckBox(Dialog) + self.graph_checkBox.setObjectName("graph_checkBox") + self.horizontalLayout.addWidget(self.graph_checkBox) + self.graph_comboBox = QtWidgets.QComboBox(Dialog) + self.graph_comboBox.setObjectName("graph_comboBox") + self.horizontalLayout.addWidget(self.graph_comboBox) + self.verticalLayout.addLayout(self.horizontalLayout) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Logarithmic Fourier")) + self.mode_comboBox.setItemText(0, _translate("Dialog", "Real")) + self.mode_comboBox.setItemText(1, _translate("Dialog", "Imag")) + self.mode_comboBox.setItemText(2, _translate("Dialog", "Complex")) + self.graph_checkBox.setText(_translate("Dialog", "New graph")) diff --git a/nmreval/gui_qt/_py/function_tree_widget.py b/nmreval/gui_qt/_py/function_tree_widget.py new file mode 100644 index 0000000..5235d8d --- /dev/null +++ b/nmreval/gui_qt/_py/function_tree_widget.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/function_tree_widget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(314, 232) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.widget_2 = ExpandableWidget(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) + self.widget_2.setSizePolicy(sizePolicy) + self.widget_2.setObjectName("widget_2") + self.gridLayout.addWidget(self.widget_2, 4, 0, 1, 2) + self.widget = QtWidgets.QWidget(Form) + self.widget.setObjectName("widget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.widget) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1) + self.complex_comboBox = QtWidgets.QComboBox(self.widget) + self.complex_comboBox.setObjectName("complex_comboBox") + self.complex_comboBox.addItem("") + self.complex_comboBox.addItem("") + self.complex_comboBox.addItem("") + self.gridLayout_2.addWidget(self.complex_comboBox, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 2) + self.gridLayout.addWidget(self.widget, 5, 0, 1, 2) + self.use_function_button = QtWidgets.QToolButton(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.use_function_button.sizePolicy().hasHeightForWidth()) + self.use_function_button.setSizePolicy(sizePolicy) + self.use_function_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.use_function_button.setAutoRaise(False) + self.use_function_button.setArrowType(QtCore.Qt.RightArrow) + self.use_function_button.setObjectName("use_function_button") + self.gridLayout.addWidget(self.use_function_button, 3, 1, 1, 1) + self.fitcomboBox = QtWidgets.QComboBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fitcomboBox.sizePolicy().hasHeightForWidth()) + self.fitcomboBox.setSizePolicy(sizePolicy) + self.fitcomboBox.setObjectName("fitcomboBox") + self.gridLayout.addWidget(self.fitcomboBox, 1, 0, 1, 2) + self.typecomboBox = QtWidgets.QComboBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.typecomboBox.sizePolicy().hasHeightForWidth()) + self.typecomboBox.setSizePolicy(sizePolicy) + self.typecomboBox.setObjectName("typecomboBox") + self.gridLayout.addWidget(self.typecomboBox, 0, 0, 1, 2) + self.fitequation = QtWidgets.QLabel(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fitequation.sizePolicy().hasHeightForWidth()) + self.fitequation.setSizePolicy(sizePolicy) + self.fitequation.setWordWrap(True) + self.fitequation.setObjectName("fitequation") + self.gridLayout.addWidget(self.fitequation, 2, 0, 1, 2) + self.operator_combobox = QtWidgets.QComboBox(Form) + self.operator_combobox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + self.operator_combobox.setFrame(True) + self.operator_combobox.setObjectName("operator_combobox") + self.operator_combobox.addItem("") + self.operator_combobox.addItem("") + self.operator_combobox.addItem("") + self.operator_combobox.addItem("") + self.gridLayout.addWidget(self.operator_combobox, 3, 0, 1, 1) + self.comboBox_2 = QtWidgets.QComboBox(Form) + self.comboBox_2.setObjectName("comboBox_2") + self.gridLayout.addWidget(self.comboBox_2, 6, 0, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.label_2.setText(_translate("Form", "Select part to fit")) + self.complex_comboBox.setItemText(0, _translate("Form", "Complex")) + self.complex_comboBox.setItemText(1, _translate("Form", "Real")) + self.complex_comboBox.setItemText(2, _translate("Form", "Imaginary")) + self.label.setText(_translate("Form", "Complex function found")) + self.use_function_button.setText(_translate("Form", "Use")) + self.fitequation.setText(_translate("Form", "Equation")) + self.operator_combobox.setItemText(0, _translate("Form", "Add")) + self.operator_combobox.setItemText(1, _translate("Form", "Multiply")) + self.operator_combobox.setItemText(2, _translate("Form", "Subtract")) + self.operator_combobox.setItemText(3, _translate("Form", "Divide by")) +from ..lib.expandablewidget import ExpandableWidget diff --git a/nmreval/gui_qt/_py/gol.py b/nmreval/gui_qt/_py/gol.py new file mode 100644 index 0000000..2d00e06 --- /dev/null +++ b/nmreval/gui_qt/_py/gol.py @@ -0,0 +1,276 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/gol.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(883, 732) + self.gridLayout_2 = QtWidgets.QGridLayout(Form) + self.gridLayout_2.setObjectName("gridLayout_2") + self.widget = QtWidgets.QWidget(Form) + self.widget.setObjectName("widget") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.vanish_shadow = QtWidgets.QRadioButton(self.widget) + self.vanish_shadow.setChecked(True) + self.vanish_shadow.setObjectName("vanish_shadow") + self.buttonGroup = QtWidgets.QButtonGroup(Form) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.vanish_shadow) + self.verticalLayout_3.addWidget(self.vanish_shadow) + self.full_shadow = QtWidgets.QRadioButton(self.widget) + self.full_shadow.setObjectName("full_shadow") + self.buttonGroup.addButton(self.full_shadow) + self.verticalLayout_3.addWidget(self.full_shadow) + self.radioButton = QtWidgets.QRadioButton(self.widget) + self.radioButton.setObjectName("radioButton") + self.buttonGroup.addButton(self.radioButton) + self.verticalLayout_3.addWidget(self.radioButton) + self.line = QtWidgets.QFrame(self.widget) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.verticalLayout_3.addWidget(self.line) + self.faster_button = QtWidgets.QToolButton(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.faster_button.sizePolicy().hasHeightForWidth()) + self.faster_button.setSizePolicy(sizePolicy) + self.faster_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.faster_button.setArrowType(QtCore.Qt.RightArrow) + self.faster_button.setObjectName("faster_button") + self.verticalLayout_3.addWidget(self.faster_button) + self.velocity_label = QtWidgets.QLabel(self.widget) + self.velocity_label.setObjectName("velocity_label") + self.verticalLayout_3.addWidget(self.velocity_label) + self.slower_button = QtWidgets.QToolButton(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.slower_button.sizePolicy().hasHeightForWidth()) + self.slower_button.setSizePolicy(sizePolicy) + self.slower_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.slower_button.setArrowType(QtCore.Qt.LeftArrow) + self.slower_button.setObjectName("slower_button") + self.verticalLayout_3.addWidget(self.slower_button) + self.current_step = QtWidgets.QLabel(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.current_step.sizePolicy().hasHeightForWidth()) + self.current_step.setSizePolicy(sizePolicy) + self.current_step.setObjectName("current_step") + self.verticalLayout_3.addWidget(self.current_step) + self.pause_button = QtWidgets.QPushButton(self.widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pause_button.sizePolicy().hasHeightForWidth()) + self.pause_button.setSizePolicy(sizePolicy) + self.pause_button.setCheckable(True) + self.pause_button.setObjectName("pause_button") + self.verticalLayout_3.addWidget(self.pause_button) + self.line_3 = QtWidgets.QFrame(self.widget) + self.line_3.setFrameShape(QtWidgets.QFrame.HLine) + self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_3.setObjectName("line_3") + self.verticalLayout_3.addWidget(self.line_3) + self.label_6 = QtWidgets.QLabel(self.widget) + self.label_6.setObjectName("label_6") + self.verticalLayout_3.addWidget(self.label_6) + self.cover_label = QtWidgets.QLabel(self.widget) + self.cover_label.setText("") + self.cover_label.setObjectName("cover_label") + self.verticalLayout_3.addWidget(self.cover_label) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_3.addItem(spacerItem) + self.gridLayout_2.addWidget(self.widget, 0, 0, 1, 1) + self.view = QtWidgets.QGraphicsView(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.view.sizePolicy().hasHeightForWidth()) + self.view.setSizePolicy(sizePolicy) + self.view.setStyleSheet("background-color: transparent") + self.view.setFrameShape(QtWidgets.QFrame.NoFrame) + self.view.setObjectName("view") + self.gridLayout_2.addWidget(self.view, 0, 1, 1, 1) + self.option_frame = QtWidgets.QFrame(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.option_frame.sizePolicy().hasHeightForWidth()) + self.option_frame.setSizePolicy(sizePolicy) + self.option_frame.setFrameShape(QtWidgets.QFrame.Box) + self.option_frame.setObjectName("option_frame") + self.gridLayout = QtWidgets.QGridLayout(self.option_frame) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setHorizontalSpacing(6) + self.gridLayout.setVerticalSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.rule_label = QtWidgets.QLabel(self.option_frame) + self.rule_label.setObjectName("rule_label") + self.horizontalLayout_2.addWidget(self.rule_label) + self.rule_cb = QtWidgets.QComboBox(self.option_frame) + self.rule_cb.setObjectName("rule_cb") + self.horizontalLayout_2.addWidget(self.rule_cb) + self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1) + self.line_2 = QtWidgets.QFrame(self.option_frame) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout.addWidget(self.line_2, 2, 0, 1, 1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setContentsMargins(-1, 0, -1, 0) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.object_widget = QtWidgets.QWidget(self.option_frame) + self.object_widget.setObjectName("object_widget") + self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.object_widget) + self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_6.setSpacing(3) + self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.label_5 = QtWidgets.QLabel(self.object_widget) + self.label_5.setObjectName("label_5") + self.horizontalLayout_6.addWidget(self.label_5) + self.object_size = QtWidgets.QSpinBox(self.object_widget) + self.object_size.setMinimum(1) + self.object_size.setMaximum(600) + self.object_size.setObjectName("object_size") + self.horizontalLayout_6.addWidget(self.object_size) + self.verticalLayout_2.addWidget(self.object_widget) + self.rand_button_wdgt = QtWidgets.QWidget(self.option_frame) + self.rand_button_wdgt.setObjectName("rand_button_wdgt") + self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.rand_button_wdgt) + self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_7.setSpacing(3) + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.add_random_button = QtWidgets.QPushButton(self.rand_button_wdgt) + self.add_random_button.setObjectName("add_random_button") + self.horizontalLayout_7.addWidget(self.add_random_button) + self.remove_random_button = QtWidgets.QPushButton(self.rand_button_wdgt) + self.remove_random_button.setObjectName("remove_random_button") + self.horizontalLayout_7.addWidget(self.remove_random_button) + self.verticalLayout_2.addWidget(self.rand_button_wdgt) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout_2.addLayout(self.verticalLayout) + self.gridLayout.addLayout(self.verticalLayout_2, 2, 1, 4, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_3 = QtWidgets.QLabel(self.option_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) + self.label_3.setSizePolicy(sizePolicy) + self.label_3.setObjectName("label_3") + self.horizontalLayout.addWidget(self.label_3) + self.survival_line = QtWidgets.QLineEdit(self.option_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.survival_line.sizePolicy().hasHeightForWidth()) + self.survival_line.setSizePolicy(sizePolicy) + self.survival_line.setObjectName("survival_line") + self.horizontalLayout.addWidget(self.survival_line) + self.label_4 = QtWidgets.QLabel(self.option_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy) + self.label_4.setObjectName("label_4") + self.horizontalLayout.addWidget(self.label_4) + self.birth_line = QtWidgets.QLineEdit(self.option_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.birth_line.sizePolicy().hasHeightForWidth()) + self.birth_line.setSizePolicy(sizePolicy) + self.birth_line.setObjectName("birth_line") + self.horizontalLayout.addWidget(self.birth_line) + self.gridLayout.addLayout(self.horizontalLayout, 4, 0, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label = QtWidgets.QLabel(self.option_frame) + self.label.setObjectName("label") + self.horizontalLayout_3.addWidget(self.label) + self.width_box = QtWidgets.QSpinBox(self.option_frame) + self.width_box.setMaximum(600) + self.width_box.setProperty("value", 100) + self.width_box.setObjectName("width_box") + self.horizontalLayout_3.addWidget(self.width_box) + self.label_2 = QtWidgets.QLabel(self.option_frame) + self.label_2.setObjectName("label_2") + self.horizontalLayout_3.addWidget(self.label_2) + self.height_box = QtWidgets.QSpinBox(self.option_frame) + self.height_box.setMaximum(600) + self.height_box.setProperty("value", 100) + self.height_box.setObjectName("height_box") + self.horizontalLayout_3.addWidget(self.height_box) + self.gridLayout.addLayout(self.horizontalLayout_3, 1, 0, 1, 1) + self.object_combobox = QtWidgets.QComboBox(self.option_frame) + self.object_combobox.setObjectName("object_combobox") + self.object_combobox.addItem("") + self.object_combobox.addItem("") + self.object_combobox.addItem("") + self.object_combobox.addItem("") + self.object_combobox.addItem("") + self.gridLayout.addWidget(self.object_combobox, 1, 1, 1, 1) + self.start_button = QtWidgets.QPushButton(self.option_frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.start_button.sizePolicy().hasHeightForWidth()) + self.start_button.setSizePolicy(sizePolicy) + self.start_button.setObjectName("start_button") + self.gridLayout.addWidget(self.start_button, 0, 0, 1, 2) + self.gridLayout_2.addWidget(self.option_frame, 1, 1, 1, 1) + self.hide_button = QtWidgets.QCheckBox(Form) + self.hide_button.setObjectName("hide_button") + self.gridLayout_2.addWidget(self.hide_button, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Game Of Life")) + self.vanish_shadow.setText(_translate("Form", "Motion blur")) + self.full_shadow.setText(_translate("Form", "Scorched earth")) + self.radioButton.setText(_translate("Form", "Nothing")) + self.faster_button.setText(_translate("Form", "Faster")) + self.velocity_label.setText(_translate("Form", "10 steps / s")) + self.slower_button.setText(_translate("Form", "Slower")) + self.current_step.setText(_translate("Form", "0 step")) + self.pause_button.setText(_translate("Form", "Pause")) + self.label_6.setText(_translate("Form", "Coverage:")) + self.rule_label.setText(_translate("Form", "Rule")) + self.label_5.setText(_translate("Form", "Size")) + self.add_random_button.setText(_translate("Form", "Add Random")) + self.remove_random_button.setText(_translate("Form", "Remove Random")) + self.label_3.setText(_translate("Form", "Survival")) + self.label_4.setText(_translate("Form", " Birth")) + self.label.setText(_translate("Form", "Width")) + self.label_2.setText(_translate("Form", "Height")) + self.object_combobox.setItemText(0, _translate("Form", "Random")) + self.object_combobox.setItemText(1, _translate("Form", "Circle")) + self.object_combobox.setItemText(2, _translate("Form", "Square")) + self.object_combobox.setItemText(3, _translate("Form", "Diamond")) + self.object_combobox.setItemText(4, _translate("Form", "Plus")) + self.start_button.setText(_translate("Form", "Start")) + self.hide_button.setText(_translate("Form", "Hide options")) diff --git a/nmreval/gui_qt/_py/gracemsgdialog.py b/nmreval/gui_qt/_py/gracemsgdialog.py new file mode 100644 index 0000000..9c3c25f --- /dev/null +++ b/nmreval/gui_qt/_py/gracemsgdialog.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/gracemsgdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_GraceMsgDialog(object): + def setupUi(self, GraceMsgDialog): + GraceMsgDialog.setObjectName("GraceMsgDialog") + GraceMsgDialog.resize(400, 300) + self.gridLayout = QtWidgets.QGridLayout(GraceMsgDialog) + self.gridLayout.setObjectName("gridLayout") + self.graph_combo = QtWidgets.QComboBox(GraceMsgDialog) + self.graph_combo.setObjectName("graph_combo") + self.gridLayout.addWidget(self.graph_combo, 1, 1, 1, 1) + self.graph_button = QtWidgets.QRadioButton(GraceMsgDialog) + self.graph_button.setObjectName("graph_button") + self.buttonGroup = QtWidgets.QButtonGroup(GraceMsgDialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.graph_button) + self.gridLayout.addWidget(self.graph_button, 1, 0, 1, 1) + self.overwrite_button = QtWidgets.QRadioButton(GraceMsgDialog) + self.overwrite_button.setChecked(True) + self.overwrite_button.setObjectName("overwrite_button") + self.buttonGroup.addButton(self.overwrite_button) + self.gridLayout.addWidget(self.overwrite_button, 0, 0, 1, 1) + self.radioButton = QtWidgets.QRadioButton(GraceMsgDialog) + self.radioButton.setObjectName("radioButton") + self.buttonGroup.addButton(self.radioButton) + self.gridLayout.addWidget(self.radioButton, 2, 0, 1, 1) + self.tableWidget = QtWidgets.QTableWidget(GraceMsgDialog) + self.tableWidget.setColumnCount(2) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setRowCount(0) + self.tableWidget.horizontalHeader().setVisible(False) + self.gridLayout.addWidget(self.tableWidget, 3, 0, 2, 2) + self.buttonBox = QtWidgets.QDialogButtonBox(GraceMsgDialog) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 5, 0, 1, 2) + + self.retranslateUi(GraceMsgDialog) + QtCore.QMetaObject.connectSlotsByName(GraceMsgDialog) + + def retranslateUi(self, GraceMsgDialog): + _translate = QtCore.QCoreApplication.translate + GraceMsgDialog.setWindowTitle(_translate("GraceMsgDialog", "Goodness gracious, file already exists.")) + self.graph_button.setText(_translate("GraceMsgDialog", "Add to graph")) + self.overwrite_button.setText(_translate("GraceMsgDialog", "Overwrite file")) + self.radioButton.setText(_translate("GraceMsgDialog", "Replace sets")) diff --git a/nmreval/gui_qt/_py/gracereader.py b/nmreval/gui_qt/_py/gracereader.py new file mode 100644 index 0000000..b769d0d --- /dev/null +++ b/nmreval/gui_qt/_py/gracereader.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/gracereader.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 613) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.label = QtWidgets.QLabel(Dialog) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.treeWidget = QtWidgets.QTreeWidget(Dialog) + self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.treeWidget.setObjectName("treeWidget") + self.treeWidget.headerItem().setText(0, "1") + self.treeWidget.header().setVisible(False) + self.verticalLayout.addWidget(self.treeWidget) + self.tableWidget = QtWidgets.QTableWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tableWidget.sizePolicy().hasHeightForWidth()) + self.tableWidget.setSizePolicy(sizePolicy) + self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableWidget.setGridStyle(QtCore.Qt.NoPen) + self.tableWidget.setRowCount(4) + self.tableWidget.setColumnCount(2) + self.tableWidget.setObjectName("tableWidget") + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(0, 0, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(0, 1, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(1, 0, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(1, 1, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(2, 0, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(2, 1, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(3, 0, item) + item = QtWidgets.QTableWidgetItem() + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.tableWidget.setItem(3, 1, item) + self.tableWidget.horizontalHeader().setVisible(False) + self.tableWidget.horizontalHeader().setStretchLastSection(True) + self.tableWidget.verticalHeader().setVisible(False) + self.verticalLayout.addWidget(self.tableWidget) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Load data from agr")) + self.label.setText(_translate("Dialog", "Only data will be loaded, no line and symbol properties!")) + __sortingEnabled = self.tableWidget.isSortingEnabled() + self.tableWidget.setSortingEnabled(False) + item = self.tableWidget.item(0, 0) + item.setText(_translate("Dialog", "Symbol")) + item = self.tableWidget.item(1, 0) + item.setText(_translate("Dialog", "Symbol color")) + item = self.tableWidget.item(2, 0) + item.setText(_translate("Dialog", "Linestyle")) + item = self.tableWidget.item(3, 0) + item.setText(_translate("Dialog", "Line color")) + self.tableWidget.setSortingEnabled(__sortingEnabled) diff --git a/nmreval/gui_qt/_py/graph.py b/nmreval/gui_qt/_py/graph.py new file mode 100644 index 0000000..32e7c0e --- /dev/null +++ b/nmreval/gui_qt/_py/graph.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/graph.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_GraphWindow(object): + def setupUi(self, GraphWindow): + GraphWindow.setObjectName("GraphWindow") + GraphWindow.resize(680, 520) + GraphWindow.setBaseSize(QtCore.QSize(300, 10)) + self.verticalLayout = QtWidgets.QVBoxLayout(GraphWindow) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.widget = QtWidgets.QWidget(GraphWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) + self.widget.setSizePolicy(sizePolicy) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(1) + self.horizontalLayout.setObjectName("horizontalLayout") + self.logx_button = QtWidgets.QToolButton(self.widget) + self.logx_button.setIconSize(QtCore.QSize(16, 16)) + self.logx_button.setCheckable(True) + self.logx_button.setAutoRaise(True) + self.logx_button.setObjectName("logx_button") + self.horizontalLayout.addWidget(self.logx_button) + self.logy_button = QtWidgets.QToolButton(self.widget) + self.logy_button.setIconSize(QtCore.QSize(16, 16)) + self.logy_button.setCheckable(True) + self.logy_button.setAutoRaise(True) + self.logy_button.setObjectName("logy_button") + self.horizontalLayout.addWidget(self.logy_button) + self.line = QtWidgets.QFrame(self.widget) + self.line.setFrameShape(QtWidgets.QFrame.VLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.horizontalLayout.addWidget(self.line) + self.gridbutton = QtWidgets.QToolButton(self.widget) + self.gridbutton.setCheckable(True) + self.gridbutton.setAutoRaise(True) + self.gridbutton.setObjectName("gridbutton") + self.horizontalLayout.addWidget(self.gridbutton) + self.bwbutton = QtWidgets.QToolButton(self.widget) + self.bwbutton.setCheckable(True) + self.bwbutton.setAutoRaise(True) + self.bwbutton.setObjectName("bwbutton") + self.horizontalLayout.addWidget(self.bwbutton) + self.line_2 = QtWidgets.QFrame(self.widget) + self.line_2.setFrameShape(QtWidgets.QFrame.VLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.horizontalLayout.addWidget(self.line_2) + self.legend_button = QtWidgets.QToolButton(self.widget) + self.legend_button.setIconSize(QtCore.QSize(16, 16)) + self.legend_button.setCheckable(True) + self.legend_button.setAutoRaise(True) + self.legend_button.setObjectName("legend_button") + self.horizontalLayout.addWidget(self.legend_button) + self.imag_button = QtWidgets.QToolButton(self.widget) + self.imag_button.setIconSize(QtCore.QSize(16, 16)) + self.imag_button.setCheckable(True) + self.imag_button.setChecked(True) + self.imag_button.setAutoRaise(True) + self.imag_button.setObjectName("imag_button") + self.horizontalLayout.addWidget(self.imag_button) + self.real_button = QtWidgets.QToolButton(self.widget) + self.real_button.setIconSize(QtCore.QSize(16, 16)) + self.real_button.setCheckable(True) + self.real_button.setChecked(True) + self.real_button.setAutoRaise(True) + self.real_button.setObjectName("real_button") + self.horizontalLayout.addWidget(self.real_button) + self.error_button = QtWidgets.QToolButton(self.widget) + self.error_button.setIconSize(QtCore.QSize(16, 16)) + self.error_button.setCheckable(True) + self.error_button.setAutoRaise(True) + self.error_button.setObjectName("error_button") + self.horizontalLayout.addWidget(self.error_button) + self.line_3 = QtWidgets.QFrame(self.widget) + self.line_3.setFrameShape(QtWidgets.QFrame.VLine) + self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_3.setObjectName("line_3") + self.horizontalLayout.addWidget(self.line_3) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.limit_button = QtWidgets.QToolButton(self.widget) + self.limit_button.setCheckable(True) + self.limit_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.limit_button.setAutoRaise(True) + self.limit_button.setArrowType(QtCore.Qt.RightArrow) + self.limit_button.setObjectName("limit_button") + self.horizontalLayout.addWidget(self.limit_button) + self.label_button = QtWidgets.QToolButton(self.widget) + self.label_button.setCheckable(True) + self.label_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.label_button.setAutoRaise(True) + self.label_button.setArrowType(QtCore.Qt.RightArrow) + self.label_button.setObjectName("label_button") + self.horizontalLayout.addWidget(self.label_button) + self.verticalLayout.addWidget(self.widget) + self.line_4 = QtWidgets.QFrame(GraphWindow) + self.line_4.setFrameShape(QtWidgets.QFrame.HLine) + self.line_4.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_4.setObjectName("line_4") + self.verticalLayout.addWidget(self.line_4) + self.limit_widget = QtWidgets.QWidget(GraphWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.limit_widget.sizePolicy().hasHeightForWidth()) + self.limit_widget.setSizePolicy(sizePolicy) + self.limit_widget.setObjectName("limit_widget") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.limit_widget) + self.horizontalLayout_2.setContentsMargins(1, 1, 1, 1) + self.horizontalLayout_2.setSpacing(2) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label = QtWidgets.QLabel(self.limit_widget) + self.label.setObjectName("label") + self.horizontalLayout_2.addWidget(self.label) + self.xmin_lineedit = QtWidgets.QLineEdit(self.limit_widget) + self.xmin_lineedit.setObjectName("xmin_lineedit") + self.horizontalLayout_2.addWidget(self.xmin_lineedit) + self.label_2 = QtWidgets.QLabel(self.limit_widget) + self.label_2.setObjectName("label_2") + self.horizontalLayout_2.addWidget(self.label_2) + self.xmax_lineedit = QtWidgets.QLineEdit(self.limit_widget) + self.xmax_lineedit.setObjectName("xmax_lineedit") + self.horizontalLayout_2.addWidget(self.xmax_lineedit) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.label_3 = QtWidgets.QLabel(self.limit_widget) + self.label_3.setObjectName("label_3") + self.horizontalLayout_2.addWidget(self.label_3) + self.ymin_lineedit = QtWidgets.QLineEdit(self.limit_widget) + self.ymin_lineedit.setObjectName("ymin_lineedit") + self.horizontalLayout_2.addWidget(self.ymin_lineedit) + self.label_4 = QtWidgets.QLabel(self.limit_widget) + self.label_4.setObjectName("label_4") + self.horizontalLayout_2.addWidget(self.label_4) + self.ymax_lineedit = QtWidgets.QLineEdit(self.limit_widget) + self.ymax_lineedit.setObjectName("ymax_lineedit") + self.horizontalLayout_2.addWidget(self.ymax_lineedit) + self.apply_button = QtWidgets.QPushButton(self.limit_widget) + icon = QtGui.QIcon.fromTheme("dialog-ok") + self.apply_button.setIcon(icon) + self.apply_button.setObjectName("apply_button") + self.horizontalLayout_2.addWidget(self.apply_button) + self.verticalLayout.addWidget(self.limit_widget) + self.label_widget = QtWidgets.QWidget(GraphWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_widget.sizePolicy().hasHeightForWidth()) + self.label_widget.setSizePolicy(sizePolicy) + self.label_widget.setObjectName("label_widget") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.label_widget) + self.horizontalLayout_3.setContentsMargins(1, 1, 1, 1) + self.horizontalLayout_3.setSpacing(2) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_5 = QtWidgets.QLabel(self.label_widget) + self.label_5.setObjectName("label_5") + self.horizontalLayout_3.addWidget(self.label_5) + self.title_lineedit = QtWidgets.QLineEdit(self.label_widget) + self.title_lineedit.setObjectName("title_lineedit") + self.horizontalLayout_3.addWidget(self.title_lineedit) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_3.addItem(spacerItem2) + self.label_6 = QtWidgets.QLabel(self.label_widget) + self.label_6.setObjectName("label_6") + self.horizontalLayout_3.addWidget(self.label_6) + self.xaxis_linedit = QtWidgets.QLineEdit(self.label_widget) + self.xaxis_linedit.setObjectName("xaxis_linedit") + self.horizontalLayout_3.addWidget(self.xaxis_linedit) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_3.addItem(spacerItem3) + self.label_7 = QtWidgets.QLabel(self.label_widget) + self.label_7.setObjectName("label_7") + self.horizontalLayout_3.addWidget(self.label_7) + self.yaxis_linedit = QtWidgets.QLineEdit(self.label_widget) + self.yaxis_linedit.setObjectName("yaxis_linedit") + self.horizontalLayout_3.addWidget(self.yaxis_linedit) + self.verticalLayout.addWidget(self.label_widget) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setHorizontalSpacing(3) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.listWidget = QtWidgets.QListWidget(GraphWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setObjectName("listWidget") + self.gridLayout.addWidget(self.listWidget, 1, 1, 1, 1) + self.checkBox = QtWidgets.QCheckBox(GraphWindow) + self.checkBox.setChecked(True) + self.checkBox.setObjectName("checkBox") + self.gridLayout.addWidget(self.checkBox, 0, 1, 1, 1) + self.graphic = PlotWidget(GraphWindow) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.graphic.sizePolicy().hasHeightForWidth()) + self.graphic.setSizePolicy(sizePolicy) + self.graphic.setObjectName("graphic") + self.gridLayout.addWidget(self.graphic, 0, 0, 2, 1) + self.verticalLayout.addLayout(self.gridLayout) + self.label.setBuddy(self.xmin_lineedit) + self.label_2.setBuddy(self.xmax_lineedit) + self.label_3.setBuddy(self.ymin_lineedit) + self.label_4.setBuddy(self.ymax_lineedit) + self.label_5.setBuddy(self.title_lineedit) + self.label_6.setBuddy(self.xaxis_linedit) + self.label_7.setBuddy(self.yaxis_linedit) + + self.retranslateUi(GraphWindow) + QtCore.QMetaObject.connectSlotsByName(GraphWindow) + GraphWindow.setTabOrder(self.logx_button, self.logy_button) + GraphWindow.setTabOrder(self.logy_button, self.gridbutton) + GraphWindow.setTabOrder(self.gridbutton, self.legend_button) + GraphWindow.setTabOrder(self.legend_button, self.imag_button) + GraphWindow.setTabOrder(self.imag_button, self.error_button) + GraphWindow.setTabOrder(self.error_button, self.limit_button) + GraphWindow.setTabOrder(self.limit_button, self.label_button) + GraphWindow.setTabOrder(self.label_button, self.xmin_lineedit) + GraphWindow.setTabOrder(self.xmin_lineedit, self.xmax_lineedit) + GraphWindow.setTabOrder(self.xmax_lineedit, self.ymin_lineedit) + GraphWindow.setTabOrder(self.ymin_lineedit, self.ymax_lineedit) + GraphWindow.setTabOrder(self.ymax_lineedit, self.title_lineedit) + GraphWindow.setTabOrder(self.title_lineedit, self.xaxis_linedit) + GraphWindow.setTabOrder(self.xaxis_linedit, self.yaxis_linedit) + + def retranslateUi(self, GraphWindow): + _translate = QtCore.QCoreApplication.translate + GraphWindow.setWindowTitle(_translate("GraphWindow", "Form")) + self.logx_button.setToolTip(_translate("GraphWindow", "Change x axis linear <-> logarithmic")) + self.logx_button.setText(_translate("GraphWindow", "Log X")) + self.logy_button.setToolTip(_translate("GraphWindow", "Change y axis linear <-> logarithmic")) + self.logy_button.setText(_translate("GraphWindow", "Log Y")) + self.gridbutton.setToolTip(_translate("GraphWindow", "Show/hide grid")) + self.gridbutton.setText(_translate("GraphWindow", "Grid")) + self.bwbutton.setToolTip(_translate("GraphWindow", "Change background")) + self.bwbutton.setText(_translate("GraphWindow", "Black/white")) + self.legend_button.setToolTip(_translate("GraphWindow", "Change legend")) + self.legend_button.setText(_translate("GraphWindow", "Legend")) + self.imag_button.setToolTip(_translate("GraphWindow", "Show/hide imaginary part")) + self.imag_button.setText(_translate("GraphWindow", "Imaginary")) + self.real_button.setToolTip(_translate("GraphWindow", "Show/hide real part")) + self.real_button.setText(_translate("GraphWindow", "Real")) + self.error_button.setToolTip(_translate("GraphWindow", "Show/hide errorbars")) + self.error_button.setText(_translate("GraphWindow", "Errorbars")) + self.limit_button.setText(_translate("GraphWindow", "Limits")) + self.label_button.setText(_translate("GraphWindow", "Labels")) + self.label.setText(_translate("GraphWindow", "X: ")) + self.label_2.setText(_translate("GraphWindow", "---")) + self.label_3.setText(_translate("GraphWindow", "Y: ")) + self.label_4.setText(_translate("GraphWindow", "---")) + self.apply_button.setText(_translate("GraphWindow", "Apply")) + self.label_5.setText(_translate("GraphWindow", "Title")) + self.label_6.setText(_translate("GraphWindow", "X Axis")) + self.label_7.setText(_translate("GraphWindow", "Y Axis")) + self.checkBox.setText(_translate("GraphWindow", "Show legend")) +from pyqtgraph import PlotWidget diff --git a/nmreval/gui_qt/_py/guidelinewidget.py b/nmreval/gui_qt/_py/guidelinewidget.py new file mode 100644 index 0000000..a69a325 --- /dev/null +++ b/nmreval/gui_qt/_py/guidelinewidget.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/guidelinewidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(431, 799) + self.gridLayout_2 = QtWidgets.QGridLayout(Form) + self.gridLayout_2.setContentsMargins(3, 3, 3, 3) + self.gridLayout_2.setSpacing(3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.mode_comboBox = QtWidgets.QComboBox(Form) + self.mode_comboBox.setObjectName("mode_comboBox") + self.mode_comboBox.addItem("") + self.mode_comboBox.addItem("") + self.gridLayout_2.addWidget(self.mode_comboBox, 2, 0, 1, 2) + self.graph_comboBox = QtWidgets.QComboBox(Form) + self.graph_comboBox.setObjectName("graph_comboBox") + self.gridLayout_2.addWidget(self.graph_comboBox, 0, 0, 1, 2) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.diagonal_widget = QtWidgets.QWidget(Form) + self.diagonal_widget.setObjectName("diagonal_widget") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.diagonal_widget) + self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.horizontalLayout_2.addWidget(self.diagonal_widget) + self.vh_widget = QtWidgets.QWidget(Form) + self.vh_widget.setObjectName("vh_widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.vh_widget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(self.vh_widget) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.vh_pos_lineEdit = QtWidgets.QLineEdit(self.vh_widget) + self.vh_pos_lineEdit.setObjectName("vh_pos_lineEdit") + self.horizontalLayout.addWidget(self.vh_pos_lineEdit) + self.horizontalLayout_2.addWidget(self.vh_widget) + self.drag_checkBox = QtWidgets.QCheckBox(Form) + self.drag_checkBox.setChecked(True) + self.drag_checkBox.setObjectName("drag_checkBox") + self.horizontalLayout_2.addWidget(self.drag_checkBox) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 3, 0, 1, 2) + self.color_comboBox = ColorListEditor(Form) + self.color_comboBox.setObjectName("color_comboBox") + self.gridLayout_2.addWidget(self.color_comboBox, 6, 1, 1, 1) + self.pushButton = QtWidgets.QPushButton(Form) + self.pushButton.setObjectName("pushButton") + self.gridLayout_2.addWidget(self.pushButton, 7, 0, 1, 2) + self.label_6 = QtWidgets.QLabel(Form) + self.label_6.setObjectName("label_6") + self.gridLayout_2.addWidget(self.label_6, 5, 0, 1, 1) + self.comment_lineEdit = QtWidgets.QLineEdit(Form) + self.comment_lineEdit.setObjectName("comment_lineEdit") + self.gridLayout_2.addWidget(self.comment_lineEdit, 5, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(Form) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 6, 0, 1, 1) + self.tableWidget = QtWidgets.QTableWidget(Form) + self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableWidget.setColumnCount(2) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(1, item) + self.tableWidget.horizontalHeader().setVisible(True) + self.tableWidget.horizontalHeader().setStretchLastSection(True) + self.gridLayout_2.addWidget(self.tableWidget, 8, 0, 1, 2) + self.line = QtWidgets.QFrame(Form) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout_2.addWidget(self.line, 1, 0, 1, 2) + self.line_2 = QtWidgets.QFrame(Form) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout_2.addWidget(self.line_2, 4, 0, 1, 2) + self.label.setBuddy(self.vh_pos_lineEdit) + self.label_6.setBuddy(self.comment_lineEdit) + self.label_2.setBuddy(self.color_comboBox) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + Form.setTabOrder(self.graph_comboBox, self.mode_comboBox) + Form.setTabOrder(self.mode_comboBox, self.vh_pos_lineEdit) + Form.setTabOrder(self.vh_pos_lineEdit, self.drag_checkBox) + Form.setTabOrder(self.drag_checkBox, self.comment_lineEdit) + Form.setTabOrder(self.comment_lineEdit, self.color_comboBox) + Form.setTabOrder(self.color_comboBox, self.pushButton) + Form.setTabOrder(self.pushButton, self.tableWidget) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.mode_comboBox.setItemText(0, _translate("Form", "Vertical")) + self.mode_comboBox.setItemText(1, _translate("Form", "Horizontal")) + self.label.setText(_translate("Form", "Position")) + self.vh_pos_lineEdit.setText(_translate("Form", "0")) + self.drag_checkBox.setText(_translate("Form", "Drag enabled")) + self.pushButton.setText(_translate("Form", "Create line")) + self.label_6.setText(_translate("Form", "Comment")) + self.label_2.setText(_translate("Form", "Color")) + item = self.tableWidget.horizontalHeaderItem(0) + item.setText(_translate("Form", "Pos.")) + item = self.tableWidget.horizontalHeaderItem(1) + item.setText(_translate("Form", "Comment")) +from ..lib.delegates import ColorListEditor diff --git a/nmreval/gui_qt/_py/hdftree.py b/nmreval/gui_qt/_py/hdftree.py new file mode 100644 index 0000000..b0e89f1 --- /dev/null +++ b/nmreval/gui_qt/_py/hdftree.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/hdftree.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Hdf_Dialog(object): + def setupUi(self, Hdf_Dialog): + Hdf_Dialog.setObjectName("Hdf_Dialog") + Hdf_Dialog.resize(460, 772) + self.verticalLayout = QtWidgets.QVBoxLayout(Hdf_Dialog) + self.verticalLayout.setContentsMargins(4, 4, 4, 4) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout_3 = QtWidgets.QVBoxLayout() + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout.addLayout(self.verticalLayout_3) + self.widget = QtWidgets.QWidget(Hdf_Dialog) + self.widget.setObjectName("widget") + self.gridLayout = QtWidgets.QGridLayout(self.widget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(2) + self.gridLayout.setObjectName("gridLayout") + self.comboBox_2 = QtWidgets.QComboBox(self.widget) + self.comboBox_2.setObjectName("comboBox_2") + self.gridLayout.addWidget(self.comboBox_2, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.comboBox = QtWidgets.QComboBox(self.widget) + self.comboBox.setObjectName("comboBox") + self.gridLayout.addWidget(self.comboBox, 0, 1, 1, 1) + self.verticalLayout.addWidget(self.widget) + self.widget_2 = ExpandableWidget(Hdf_Dialog) + self.widget_2.setObjectName("widget_2") + self.verticalLayout.addWidget(self.widget_2) + self.buttonBox = QtWidgets.QDialogButtonBox(Hdf_Dialog) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Hdf_Dialog) + self.buttonBox.rejected.connect(Hdf_Dialog.close) + self.buttonBox.accepted.connect(Hdf_Dialog.accept) + QtCore.QMetaObject.connectSlotsByName(Hdf_Dialog) + + def retranslateUi(self, Hdf_Dialog): + _translate = QtCore.QCoreApplication.translate + Hdf_Dialog.setWindowTitle(_translate("Hdf_Dialog", "View HDF file")) + self.label.setText(_translate("Hdf_Dialog", "Label")) + self.label_2.setText(_translate("Hdf_Dialog", "Group")) +from ..lib.expandablewidget import ExpandableWidget diff --git a/nmreval/gui_qt/_py/integral_widget.py b/nmreval/gui_qt/_py/integral_widget.py new file mode 100644 index 0000000..4f16d93 --- /dev/null +++ b/nmreval/gui_qt/_py/integral_widget.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/integral_widget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(397, 681) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.label_2 = QtWidgets.QLabel(Form) + self.label_2.setObjectName("label_2") + self.verticalLayout.addWidget(self.label_2) + self.set_combobox = QtWidgets.QComboBox(Form) + self.set_combobox.setObjectName("set_combobox") + self.verticalLayout.addWidget(self.set_combobox) + self.treeWidget = QtWidgets.QTreeWidget(Form) + self.treeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.treeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.treeWidget.setHeaderHidden(True) + self.treeWidget.setObjectName("treeWidget") + self.treeWidget.headerItem().setText(0, "1") + self.verticalLayout.addWidget(self.treeWidget) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.pushButton = QtWidgets.QPushButton(Form) + self.pushButton.setObjectName("pushButton") + self.horizontalLayout.addWidget(self.pushButton) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.label_2.setText(_translate("Form", "TextLabel")) + self.label.setText(_translate("Form", "Save integrals as dataset")) + self.pushButton.setText(_translate("Form", "Apply")) diff --git a/nmreval/gui_qt/_py/integratederive_dialog.py b/nmreval/gui_qt/_py/integratederive_dialog.py new file mode 100644 index 0000000..0ae75f4 --- /dev/null +++ b/nmreval/gui_qt/_py/integratederive_dialog.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/integratederive_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 308) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.listWidget = QtWidgets.QListWidget(Dialog) + self.listWidget.setObjectName("listWidget") + self.verticalLayout.addWidget(self.listWidget) + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setObjectName("widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.start_lineedit = QtWidgets.QLineEdit(self.widget) + self.start_lineedit.setEnabled(False) + self.start_lineedit.setObjectName("start_lineedit") + self.horizontalLayout.addWidget(self.start_lineedit) + self.stop_lineedit = QtWidgets.QLineEdit(self.widget) + self.stop_lineedit.setEnabled(False) + self.stop_lineedit.setObjectName("stop_lineedit") + self.horizontalLayout.addWidget(self.stop_lineedit) + self.range_checkbox = QtWidgets.QCheckBox(self.widget) + self.range_checkbox.setChecked(True) + self.range_checkbox.setObjectName("range_checkbox") + self.horizontalLayout.addWidget(self.range_checkbox) + self.verticalLayout.addWidget(self.widget) + self.ft_comboBox = QtWidgets.QComboBox(Dialog) + self.ft_comboBox.setObjectName("ft_comboBox") + self.ft_comboBox.addItem("") + self.ft_comboBox.addItem("") + self.ft_comboBox.addItem("") + self.verticalLayout.addWidget(self.ft_comboBox) + self.log_checkbox = QtWidgets.QCheckBox(Dialog) + self.log_checkbox.setObjectName("log_checkbox") + self.verticalLayout.addWidget(self.log_checkbox) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.newgraph_checkbox = QtWidgets.QCheckBox(Dialog) + self.newgraph_checkbox.setObjectName("newgraph_checkbox") + self.horizontalLayout_2.addWidget(self.newgraph_checkbox) + self.graph_combobox = QtWidgets.QComboBox(Dialog) + self.graph_combobox.setObjectName("graph_combobox") + self.horizontalLayout_2.addWidget(self.graph_combobox) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label.setText(_translate("Dialog", "Limits")) + self.range_checkbox.setText(_translate("Dialog", "Full")) + self.ft_comboBox.setItemText(0, _translate("Dialog", "Real")) + self.ft_comboBox.setItemText(1, _translate("Dialog", "Imag")) + self.ft_comboBox.setItemText(2, _translate("Dialog", "Complex")) + self.log_checkbox.setText(_translate("Dialog", "use logarithmic x axis")) + self.newgraph_checkbox.setText(_translate("Dialog", "New graph")) diff --git a/nmreval/gui_qt/_py/interpol_dialog.py b/nmreval/gui_qt/_py/interpol_dialog.py new file mode 100644 index 0000000..c819070 --- /dev/null +++ b/nmreval/gui_qt/_py/interpol_dialog.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/interpol_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(416, 494) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.src_widget = QtWidgets.QWidget(Dialog) + self.src_widget.setObjectName("src_widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.src_widget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.graph_combobox = QtWidgets.QComboBox(self.src_widget) + self.graph_combobox.setObjectName("graph_combobox") + self.horizontalLayout.addWidget(self.graph_combobox) + self.set_combobox = QtWidgets.QComboBox(self.src_widget) + self.set_combobox.setObjectName("set_combobox") + self.horizontalLayout.addWidget(self.set_combobox) + self.gridLayout.addWidget(self.src_widget, 8, 0, 1, 2) + self.label_3 = QtWidgets.QLabel(Dialog) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1) + self.label = QtWidgets.QLabel(Dialog) + self.label.setToolTip("") + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 4, 0, 1, 1) + self.line_2 = QtWidgets.QFrame(Dialog) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout.addWidget(self.line_2, 5, 0, 1, 2) + self.ylog_checkBox = QtWidgets.QCheckBox(Dialog) + self.ylog_checkBox.setObjectName("ylog_checkBox") + self.gridLayout.addWidget(self.ylog_checkBox, 2, 1, 1, 1) + self.interp_comboBox = QtWidgets.QComboBox(Dialog) + self.interp_comboBox.setToolTip("") + self.interp_comboBox.setObjectName("interp_comboBox") + self.interp_comboBox.addItem("") + self.interp_comboBox.addItem("") + self.gridLayout.addWidget(self.interp_comboBox, 4, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 12, 0, 1, 2) + 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.label_2 = QtWidgets.QLabel(Dialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1) + self.listWidget = QtWidgets.QListWidget(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth()) + self.listWidget.setSizePolicy(sizePolicy) + self.listWidget.setObjectName("listWidget") + self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 2) + self.sampling_widget = QtWidgets.QWidget(Dialog) + self.sampling_widget.setObjectName("sampling_widget") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.sampling_widget) + self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_4 = QtWidgets.QLabel(self.sampling_widget) + self.label_4.setObjectName("label_4") + self.horizontalLayout_2.addWidget(self.label_4) + self.start_lineEdit = QtWidgets.QLineEdit(self.sampling_widget) + self.start_lineEdit.setObjectName("start_lineEdit") + self.horizontalLayout_2.addWidget(self.start_lineEdit) + self.label_5 = QtWidgets.QLabel(self.sampling_widget) + self.label_5.setObjectName("label_5") + self.horizontalLayout_2.addWidget(self.label_5) + self.stop_lineEdit = QtWidgets.QLineEdit(self.sampling_widget) + self.stop_lineEdit.setObjectName("stop_lineEdit") + self.horizontalLayout_2.addWidget(self.stop_lineEdit) + self.label_6 = QtWidgets.QLabel(self.sampling_widget) + self.label_6.setObjectName("label_6") + self.horizontalLayout_2.addWidget(self.label_6) + self.step_lineEdit = QtWidgets.QLineEdit(self.sampling_widget) + self.step_lineEdit.setObjectName("step_lineEdit") + self.horizontalLayout_2.addWidget(self.step_lineEdit) + self.logspace_checkBox = QtWidgets.QCheckBox(self.sampling_widget) + self.logspace_checkBox.setObjectName("logspace_checkBox") + self.horizontalLayout_2.addWidget(self.logspace_checkBox) + self.gridLayout.addWidget(self.sampling_widget, 7, 0, 1, 2) + self.xaxis_comboBox = QtWidgets.QComboBox(Dialog) + self.xaxis_comboBox.setObjectName("xaxis_comboBox") + self.xaxis_comboBox.addItem("") + self.xaxis_comboBox.addItem("") + self.gridLayout.addWidget(self.xaxis_comboBox, 6, 1, 1, 1) + self.line_3 = QtWidgets.QFrame(Dialog) + self.line_3.setFrameShape(QtWidgets.QFrame.HLine) + self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_3.setObjectName("line_3") + self.gridLayout.addWidget(self.line_3, 9, 0, 1, 2) + self.dest_combobox = QtWidgets.QComboBox(Dialog) + self.dest_combobox.setObjectName("dest_combobox") + self.gridLayout.addWidget(self.dest_combobox, 10, 1, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 11, 0, 1, 1) + self.label_8 = QtWidgets.QLabel(Dialog) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 10, 0, 1, 1) + self.xlog_checkBox = QtWidgets.QCheckBox(Dialog) + self.xlog_checkBox.setObjectName("xlog_checkBox") + self.gridLayout.addWidget(self.xlog_checkBox, 2, 0, 1, 1) + self.label.setBuddy(self.interp_comboBox) + self.label_2.setBuddy(self.xaxis_comboBox) + self.label_4.setBuddy(self.start_lineEdit) + self.label_5.setBuddy(self.stop_lineEdit) + self.label_6.setBuddy(self.step_lineEdit) + self.label_8.setBuddy(self.dest_combobox) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + Dialog.setTabOrder(self.listWidget, self.ylog_checkBox) + Dialog.setTabOrder(self.ylog_checkBox, self.interp_comboBox) + Dialog.setTabOrder(self.interp_comboBox, self.xaxis_comboBox) + Dialog.setTabOrder(self.xaxis_comboBox, self.start_lineEdit) + Dialog.setTabOrder(self.start_lineEdit, self.stop_lineEdit) + Dialog.setTabOrder(self.stop_lineEdit, self.step_lineEdit) + Dialog.setTabOrder(self.step_lineEdit, self.logspace_checkBox) + Dialog.setTabOrder(self.logspace_checkBox, self.graph_combobox) + Dialog.setTabOrder(self.graph_combobox, self.set_combobox) + Dialog.setTabOrder(self.set_combobox, self.dest_combobox) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Data interpolation")) + self.label_3.setText(_translate("Dialog", "Source data")) + self.label.setText(_translate("Dialog", "Spline")) + self.ylog_checkBox.setToolTip(_translate("Dialog", "If your data is on a logarithmic scale in y, check this box")) + self.ylog_checkBox.setText(_translate("Dialog", "use log(y)")) + self.interp_comboBox.setItemText(0, _translate("Dialog", "Cubic")) + self.interp_comboBox.setItemText(1, _translate("Dialog", "Linear")) + self.buttonBox.setToolTip(_translate("Dialog", "Accept to create new data sets.")) + self.label_2.setText(_translate("Dialog", "New x axis")) + self.listWidget.setToolTip(_translate("Dialog", "Select sets that shall be interpolated. No selection will create interpolations of all visible sets.")) + self.label_4.setText(_translate("Dialog", "Start")) + self.label_5.setText(_translate("Dialog", "Stop")) + self.label_6.setText(_translate("Dialog", "Steps")) + self.logspace_checkBox.setText(_translate("Dialog", "log-spaced?")) + self.xaxis_comboBox.setItemText(0, _translate("Dialog", "new values")) + self.xaxis_comboBox.setItemText(1, _translate("Dialog", "from data")) + self.label_8.setText(_translate("Dialog", "Add interpolated data to")) + self.xlog_checkBox.setText(_translate("Dialog", "use log(x)")) diff --git a/nmreval/gui_qt/_py/lineedit_dialog.py b/nmreval/gui_qt/_py/lineedit_dialog.py new file mode 100644 index 0000000..5a1bf2c --- /dev/null +++ b/nmreval/gui_qt/_py/lineedit_dialog.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/lineedit_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_LineEdit_Dialog(object): + def setupUi(self, LineEdit_Dialog): + LineEdit_Dialog.setObjectName("LineEdit_Dialog") + LineEdit_Dialog.resize(400, 84) + self.formLayout = QtWidgets.QFormLayout(LineEdit_Dialog) + self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.ExpandingFieldsGrow) + self.formLayout.setObjectName("formLayout") + self.label = QtWidgets.QLabel(LineEdit_Dialog) + self.label.setObjectName("label") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label) + self.new_string = QtWidgets.QLineEdit(LineEdit_Dialog) + self.new_string.setObjectName("new_string") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.new_string) + self.buttonBox = QtWidgets.QDialogButtonBox(LineEdit_Dialog) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.buttonBox) + + self.retranslateUi(LineEdit_Dialog) + self.buttonBox.accepted.connect(LineEdit_Dialog.accept) + self.buttonBox.rejected.connect(LineEdit_Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(LineEdit_Dialog) + + def retranslateUi(self, LineEdit_Dialog): + _translate = QtCore.QCoreApplication.translate + LineEdit_Dialog.setWindowTitle(_translate("LineEdit_Dialog", "Dialog")) + self.label.setText(_translate("LineEdit_Dialog", "Label")) diff --git a/nmreval/gui_qt/_py/mean_form.py b/nmreval/gui_qt/_py/mean_form.py new file mode 100644 index 0000000..f120794 --- /dev/null +++ b/nmreval/gui_qt/_py/mean_form.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/mean_form.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_mean_form(object): + def setupUi(self, mean_form): + mean_form.setObjectName("mean_form") + mean_form.resize(712, 34) + self.horizontalLayout = QtWidgets.QHBoxLayout(mean_form) + self.horizontalLayout.setContentsMargins(1, 1, 1, 1) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(mean_form) + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.digit_checkbox = QtWidgets.QCheckBox(mean_form) + self.digit_checkbox.setChecked(True) + self.digit_checkbox.setObjectName("digit_checkbox") + self.horizontalLayout.addWidget(self.digit_checkbox) + self.lineEdit = QtWidgets.QLineEdit(mean_form) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout.addWidget(self.lineEdit) + self.data_checkbox = QtWidgets.QCheckBox(mean_form) + self.data_checkbox.setObjectName("data_checkbox") + self.horizontalLayout.addWidget(self.data_checkbox) + self.frame = QtWidgets.QFrame(mean_form) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame.setObjectName("frame") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2.setSpacing(2) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.graph_combobox = QtWidgets.QComboBox(self.frame) + self.graph_combobox.setObjectName("graph_combobox") + self.horizontalLayout_2.addWidget(self.graph_combobox) + self.set_combobox = QtWidgets.QComboBox(self.frame) + self.set_combobox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) + self.set_combobox.setObjectName("set_combobox") + self.horizontalLayout_2.addWidget(self.set_combobox) + self.horizontalLayout.addWidget(self.frame) + + self.retranslateUi(mean_form) + QtCore.QMetaObject.connectSlotsByName(mean_form) + + def retranslateUi(self, mean_form): + _translate = QtCore.QCoreApplication.translate + mean_form.setWindowTitle(_translate("mean_form", "Form")) + self.label.setText(_translate("mean_form", "TextLabel")) + self.digit_checkbox.setText(_translate("mean_form", "Digit")) + self.lineEdit.setText(_translate("mean_form", "1")) + self.data_checkbox.setText(_translate("mean_form", "Data")) diff --git a/nmreval/gui_qt/_py/meandialog.py b/nmreval/gui_qt/_py/meandialog.py new file mode 100644 index 0000000..9a59fcf --- /dev/null +++ b/nmreval/gui_qt/_py/meandialog.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/meandialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_calc_means_dialog(object): + def setupUi(self, calc_means_dialog): + calc_means_dialog.setObjectName("calc_means_dialog") + calc_means_dialog.resize(481, 322) + self.verticalLayout_2 = QtWidgets.QVBoxLayout(calc_means_dialog) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.dist_combobox = QtWidgets.QComboBox(calc_means_dialog) + self.dist_combobox.setObjectName("dist_combobox") + self.verticalLayout_2.addWidget(self.dist_combobox) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.verticalLayout_2.addLayout(self.verticalLayout) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.from_combobox = QtWidgets.QComboBox(calc_means_dialog) + self.from_combobox.setObjectName("from_combobox") + self.from_combobox.addItem("") + self.from_combobox.addItem("") + self.from_combobox.addItem("") + self.from_combobox.addItem("") + self.horizontalLayout.addWidget(self.from_combobox) + self.label_4 = QtWidgets.QLabel(calc_means_dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setPointSize(20) + self.label_4.setFont(font) + self.label_4.setObjectName("label_4") + self.horizontalLayout.addWidget(self.label_4) + self.to_combobox = QtWidgets.QComboBox(calc_means_dialog) + self.to_combobox.setObjectName("to_combobox") + self.to_combobox.addItem("") + self.to_combobox.addItem("") + self.to_combobox.addItem("") + self.to_combobox.addItem("") + self.horizontalLayout.addWidget(self.to_combobox) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.line = QtWidgets.QFrame(calc_means_dialog) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.verticalLayout_2.addWidget(self.line) + self.label = QtWidgets.QLabel(calc_means_dialog) + self.label.setObjectName("label") + self.verticalLayout_2.addWidget(self.label) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_2.addItem(spacerItem) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.checkBox = QtWidgets.QCheckBox(calc_means_dialog) + self.checkBox.setObjectName("checkBox") + self.horizontalLayout_2.addWidget(self.checkBox) + self.graph_combobox = QtWidgets.QComboBox(calc_means_dialog) + self.graph_combobox.setObjectName("graph_combobox") + self.horizontalLayout_2.addWidget(self.graph_combobox) + self.verticalLayout_2.addLayout(self.horizontalLayout_2) + self.buttonBox = QtWidgets.QDialogButtonBox(calc_means_dialog) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout_2.addWidget(self.buttonBox) + + self.retranslateUi(calc_means_dialog) + self.to_combobox.setCurrentIndex(1) + QtCore.QMetaObject.connectSlotsByName(calc_means_dialog) + + def retranslateUi(self, calc_means_dialog): + _translate = QtCore.QCoreApplication.translate + calc_means_dialog.setWindowTitle(_translate("calc_means_dialog", "Mean times")) + self.from_combobox.setItemText(0, _translate("calc_means_dialog", "Function value: τ")) + self.from_combobox.setItemText(1, _translate("calc_means_dialog", "Peak time: τₚ")) + self.from_combobox.setItemText(2, _translate("calc_means_dialog", "Arithmetic mean: ⟨τ⟩")) + self.from_combobox.setItemText(3, _translate("calc_means_dialog", "Geometric mean: exp( ⟨ln(τ)⟩ )")) + self.label_4.setText(_translate("calc_means_dialog", " ➝ ")) + self.to_combobox.setItemText(0, _translate("calc_means_dialog", "Function value: τ")) + self.to_combobox.setItemText(1, _translate("calc_means_dialog", "Peak time: τₚ")) + self.to_combobox.setItemText(2, _translate("calc_means_dialog", "Arithmetic mean: ⟨τ⟩")) + self.to_combobox.setItemText(3, _translate("calc_means_dialog", "Geometric mean: exp( ⟨ln(τ)⟩ )")) + self.label.setText(_translate("calc_means_dialog", "TextLabel")) + self.checkBox.setText(_translate("calc_means_dialog", "New graph")) diff --git a/nmreval/gui_qt/_py/modelwidget.py b/nmreval/gui_qt/_py/modelwidget.py new file mode 100644 index 0000000..df2c5ac --- /dev/null +++ b/nmreval/gui_qt/_py/modelwidget.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/modelwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(188, 44) + self.horizontalLayout = QtWidgets.QHBoxLayout(Form) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.lineEdit = QtWidgets.QLineEdit(Form) + self.lineEdit.setObjectName("lineEdit") + self.horizontalLayout.addWidget(self.lineEdit) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.label.setText(_translate("Form", "TextLabel")) diff --git a/nmreval/gui_qt/_py/move_dialog.py b/nmreval/gui_qt/_py/move_dialog.py new file mode 100644 index 0000000..4234ddb --- /dev/null +++ b/nmreval/gui_qt/_py/move_dialog.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/move_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MoveDialog(object): + def setupUi(self, MoveDialog): + MoveDialog.setObjectName("MoveDialog") + MoveDialog.resize(395, 345) + self.gridLayout = QtWidgets.QGridLayout(MoveDialog) + self.gridLayout.setVerticalSpacing(1) + self.gridLayout.setObjectName("gridLayout") + self.tocomboBox = QtWidgets.QComboBox(MoveDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tocomboBox.sizePolicy().hasHeightForWidth()) + self.tocomboBox.setSizePolicy(sizePolicy) + self.tocomboBox.setObjectName("tocomboBox") + self.gridLayout.addWidget(self.tocomboBox, 3, 1, 1, 1) + self.fromcomboBox = QtWidgets.QComboBox(MoveDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fromcomboBox.sizePolicy().hasHeightForWidth()) + self.fromcomboBox.setSizePolicy(sizePolicy) + self.fromcomboBox.setObjectName("fromcomboBox") + self.gridLayout.addWidget(self.fromcomboBox, 0, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(MoveDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(MoveDialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 3, 0, 1, 1) + self.line = QtWidgets.QFrame(MoveDialog) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 4, 0, 1, 2) + self.label = QtWidgets.QLabel(MoveDialog) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout.setObjectName("horizontalLayout") + self.copy_button = QtWidgets.QRadioButton(MoveDialog) + self.copy_button.setObjectName("copy_button") + self.buttonGroup = QtWidgets.QButtonGroup(MoveDialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.copy_button) + self.horizontalLayout.addWidget(self.copy_button) + self.move_button = QtWidgets.QRadioButton(MoveDialog) + self.move_button.setChecked(True) + self.move_button.setObjectName("move_button") + self.buttonGroup.addButton(self.move_button) + self.horizontalLayout.addWidget(self.move_button) + self.gridLayout.addLayout(self.horizontalLayout, 5, 0, 1, 2) + self.listWidget = QtWidgets.QListWidget(MoveDialog) + self.listWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.listWidget.setObjectName("listWidget") + self.gridLayout.addWidget(self.listWidget, 1, 1, 1, 1) + self.label_2.setBuddy(self.tocomboBox) + self.label.setBuddy(self.fromcomboBox) + + self.retranslateUi(MoveDialog) + QtCore.QMetaObject.connectSlotsByName(MoveDialog) + MoveDialog.setTabOrder(self.fromcomboBox, self.listWidget) + MoveDialog.setTabOrder(self.listWidget, self.tocomboBox) + MoveDialog.setTabOrder(self.tocomboBox, self.buttonBox) + + def retranslateUi(self, MoveDialog): + _translate = QtCore.QCoreApplication.translate + MoveDialog.setWindowTitle(_translate("MoveDialog", "Insert Reel 2 Real song.")) + self.label_2.setText(_translate("MoveDialog", "To")) + self.label.setText(_translate("MoveDialog", "From")) + self.copy_button.setText(_translate("MoveDialog", "Copy")) + self.move_button.setText(_translate("MoveDialog", "Move")) diff --git a/nmreval/gui_qt/_py/namespace_widget.py b/nmreval/gui_qt/_py/namespace_widget.py new file mode 100644 index 0000000..e0f508b --- /dev/null +++ b/nmreval/gui_qt/_py/namespace_widget.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/namespace_widget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 300) + self.gridLayout_2 = QtWidgets.QGridLayout(Form) + self.gridLayout_2.setContentsMargins(1, 1, 1, 1) + self.gridLayout_2.setSpacing(3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.groups_comboBox = QtWidgets.QComboBox(Form) + self.groups_comboBox.setObjectName("groups_comboBox") + self.gridLayout_2.addWidget(self.groups_comboBox, 0, 0, 1, 1) + self.subgroups_comboBox = QtWidgets.QComboBox(Form) + self.subgroups_comboBox.setObjectName("subgroups_comboBox") + self.gridLayout_2.addWidget(self.subgroups_comboBox, 0, 1, 1, 1) + self.namespace_table = QtWidgets.QTableWidget(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.namespace_table.sizePolicy().hasHeightForWidth()) + self.namespace_table.setSizePolicy(sizePolicy) + self.namespace_table.setMinimumSize(QtCore.QSize(0, 0)) + self.namespace_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.namespace_table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.namespace_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.namespace_table.setTextElideMode(QtCore.Qt.ElideNone) + self.namespace_table.setColumnCount(2) + self.namespace_table.setObjectName("namespace_table") + self.namespace_table.setRowCount(5) + item = QtWidgets.QTableWidgetItem() + self.namespace_table.setVerticalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.namespace_table.setVerticalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.namespace_table.setVerticalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + self.namespace_table.setVerticalHeaderItem(3, item) + item = QtWidgets.QTableWidgetItem() + self.namespace_table.setVerticalHeaderItem(4, item) + self.namespace_table.horizontalHeader().setVisible(False) + self.namespace_table.horizontalHeader().setStretchLastSection(True) + self.namespace_table.verticalHeader().setVisible(False) + self.gridLayout_2.addWidget(self.namespace_table, 1, 0, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + item = self.namespace_table.verticalHeaderItem(0) + item.setText(_translate("Form", "Neue Zeile")) + item = self.namespace_table.verticalHeaderItem(1) + item.setText(_translate("Form", "Neue Zeile")) + item = self.namespace_table.verticalHeaderItem(2) + item.setText(_translate("Form", "Neue Zeile")) + item = self.namespace_table.verticalHeaderItem(3) + item.setText(_translate("Form", "Neue Zeile")) + item = self.namespace_table.verticalHeaderItem(4) + item.setText(_translate("Form", "Neue Zeile")) diff --git a/nmreval/gui_qt/_py/option_selection.py b/nmreval/gui_qt/_py/option_selection.py new file mode 100644 index 0000000..456ec65 --- /dev/null +++ b/nmreval/gui_qt/_py/option_selection.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/option_selection.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 182) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setObjectName("gridLayout") + self.tableWidget = QtWidgets.QTableWidget(Form) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setColumnCount(2) + self.tableWidget.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.tableWidget.setHorizontalHeaderItem(1, item) + self.gridLayout.addWidget(self.tableWidget, 1, 1, 1, 1) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.label_2 = QtWidgets.QLabel(Form) + 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.setObjectName("label_2") + self.verticalLayout.addWidget(self.label_2) + self.lineEdit = QtWidgets.QLineEdit(Form) + self.lineEdit.setObjectName("lineEdit") + self.verticalLayout.addWidget(self.lineEdit) + self.label = QtWidgets.QLabel(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.comboBox = QtWidgets.QComboBox(Form) + self.comboBox.setObjectName("comboBox") + self.verticalLayout.addWidget(self.comboBox) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.gridLayout.addLayout(self.verticalLayout, 1, 0, 1, 1) + self.pushButton = QtWidgets.QPushButton(Form) + self.pushButton.setObjectName("pushButton") + self.gridLayout.addWidget(self.pushButton, 2, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + item = self.tableWidget.horizontalHeaderItem(0) + item.setText(_translate("Form", "Name")) + item = self.tableWidget.horizontalHeaderItem(1) + item.setText(_translate("Form", "Value")) + self.label_2.setText(_translate("Form", "Nice name")) + self.label.setText(_translate("Form", "TextLabel")) + self.pushButton.setText(_translate("Form", "Add option")) diff --git a/nmreval/gui_qt/_py/parameterform.py b/nmreval/gui_qt/_py/parameterform.py new file mode 100644 index 0000000..5bf7ba2 --- /dev/null +++ b/nmreval/gui_qt/_py/parameterform.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/parameterform.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_parameterform(object): + def setupUi(self, parameterform): + parameterform.setObjectName("parameterform") + parameterform.setWindowModality(QtCore.Qt.WindowModal) + parameterform.resize(290, 37) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(parameterform.sizePolicy().hasHeightForWidth()) + parameterform.setSizePolicy(sizePolicy) + parameterform.setAutoFillBackground(True) + self.horizontalLayout = QtWidgets.QHBoxLayout(parameterform) + self.horizontalLayout.setContentsMargins(1, 1, 1, 1) + self.horizontalLayout.setSpacing(2) + self.horizontalLayout.setObjectName("horizontalLayout") + self.line_2 = QtWidgets.QFrame(parameterform) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.horizontalLayout.addWidget(self.line_2) + self.label = QtWidgets.QLabel(parameterform) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(10) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setFrameShape(QtWidgets.QFrame.NoFrame) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + spacerItem = QtWidgets.QSpacerItem(65, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.vals = QtWidgets.QLineEdit(parameterform) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.vals.sizePolicy().hasHeightForWidth()) + self.vals.setSizePolicy(sizePolicy) + self.vals.setObjectName("vals") + self.horizontalLayout.addWidget(self.vals) + self.checkBox = QtWidgets.QCheckBox(parameterform) + self.checkBox.setObjectName("checkBox") + self.horizontalLayout.addWidget(self.checkBox) + + self.retranslateUi(parameterform) + QtCore.QMetaObject.connectSlotsByName(parameterform) + + def retranslateUi(self, parameterform): + _translate = QtCore.QCoreApplication.translate + parameterform.setWindowTitle(_translate("parameterform", "Form")) + self.label.setText(_translate("parameterform", "Name")) + self.vals.setText(_translate("parameterform", "1")) + self.checkBox.setText(_translate("parameterform", "Fix?")) diff --git a/nmreval/gui_qt/_py/phase_corr_dialog.py b/nmreval/gui_qt/_py/phase_corr_dialog.py new file mode 100644 index 0000000..02c5898 --- /dev/null +++ b/nmreval/gui_qt/_py/phase_corr_dialog.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/phase_corr_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SignalEdit(object): + def setupUi(self, SignalEdit): + SignalEdit.setObjectName("SignalEdit") + SignalEdit.resize(919, 595) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(SignalEdit.sizePolicy().hasHeightForWidth()) + SignalEdit.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(SignalEdit) + self.gridLayout.setContentsMargins(6, 6, 6, 6) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.graphicsView = PlotWidget(SignalEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) + self.graphicsView.setSizePolicy(sizePolicy) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout.addWidget(self.graphicsView, 0, 0, 1, 8) + self.pivot_lineedit = QtWidgets.QLineEdit(SignalEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pivot_lineedit.sizePolicy().hasHeightForWidth()) + self.pivot_lineedit.setSizePolicy(sizePolicy) + self.pivot_lineedit.setInputMethodHints(QtCore.Qt.ImhDigitsOnly) + self.pivot_lineedit.setObjectName("pivot_lineedit") + self.gridLayout.addWidget(self.pivot_lineedit, 1, 7, 1, 1) + self.ph0slider = QtWidgets.QDoubleSpinBox(SignalEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ph0slider.sizePolicy().hasHeightForWidth()) + self.ph0slider.setSizePolicy(sizePolicy) + self.ph0slider.setMinimum(-180.0) + self.ph0slider.setMaximum(180.0) + self.ph0slider.setObjectName("ph0slider") + self.gridLayout.addWidget(self.ph0slider, 1, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(SignalEdit) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 2, 1, 1, 7) + self.ph1slider = QtWidgets.QDoubleSpinBox(SignalEdit) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ph1slider.sizePolicy().hasHeightForWidth()) + self.ph1slider.setSizePolicy(sizePolicy) + self.ph1slider.setMinimum(-360.0) + self.ph1slider.setMaximum(360.0) + self.ph1slider.setObjectName("ph1slider") + self.gridLayout.addWidget(self.ph1slider, 1, 4, 1, 1) + self.label_8 = QtWidgets.QLabel(SignalEdit) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 1, 6, 1, 1) + self.label_6 = QtWidgets.QLabel(SignalEdit) + self.label_6.setObjectName("label_6") + self.gridLayout.addWidget(self.label_6, 1, 3, 1, 1) + self.label = QtWidgets.QLabel(SignalEdit) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 1, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 1, 2, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 1, 5, 1, 1) + + self.retranslateUi(SignalEdit) + self.buttonBox.accepted.connect(SignalEdit.accept) + self.buttonBox.rejected.connect(SignalEdit.close) + QtCore.QMetaObject.connectSlotsByName(SignalEdit) + + def retranslateUi(self, SignalEdit): + _translate = QtCore.QCoreApplication.translate + SignalEdit.setWindowTitle(_translate("SignalEdit", "Phase correction")) + self.pivot_lineedit.setText(_translate("SignalEdit", "0")) + self.label_8.setText(_translate("SignalEdit", "Pivot")) + self.label_6.setText(_translate("SignalEdit", "Phase 1")) + self.label.setText(_translate("SignalEdit", "Phase 0")) +from pyqtgraph import PlotWidget diff --git a/nmreval/gui_qt/_py/plotConfigTemplate.py b/nmreval/gui_qt/_py/plotConfigTemplate.py new file mode 100644 index 0000000..a88d8db --- /dev/null +++ b/nmreval/gui_qt/_py/plotConfigTemplate.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/plotConfigTemplate.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(481, 840) + self.averageGroup = QtWidgets.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName("averageGroup") + self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) + self.gridLayout_5.setContentsMargins(0, 0, 0, 0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName("gridLayout_5") + self.avgParamList = QtWidgets.QListWidget(self.averageGroup) + self.avgParamList.setObjectName("avgParamList") + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtWidgets.QFrame(Form) + self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) + self.decimateGroup.setObjectName("decimateGroup") + self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) + self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName("gridLayout_4") + self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.clipToViewCheck.setObjectName("clipToViewCheck") + self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) + self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName("maxTracesCheck") + self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) + self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.downsampleCheck.setObjectName("downsampleCheck") + self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) + self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) + self.peakRadio.setChecked(True) + self.peakRadio.setObjectName("peakRadio") + self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) + self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName("maxTracesSpin") + self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) + self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName("forgetTracesCheck") + self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) + self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) + self.meanRadio.setObjectName("meanRadio") + self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) + self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) + self.subsampleRadio.setObjectName("subsampleRadio") + self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) + self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.autoDownsampleCheck.setChecked(True) + self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") + self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) + spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty("value", 1) + self.downsampleSpin.setObjectName("downsampleSpin") + self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) + self.transformGroup = QtWidgets.QFrame(Form) + self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) + self.transformGroup.setObjectName("transformGroup") + self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) + self.logYCheck.setObjectName("logYCheck") + self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) + self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) + self.logXCheck.setObjectName("logXCheck") + self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) + self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) + self.fftCheck.setObjectName("fftCheck") + self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) + self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) + self.derivativeCheck.setObjectName("derivativeCheck") + self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) + self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) + self.phasemapCheck.setObjectName("phasemapCheck") + self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) + self.pointsGroup = QtWidgets.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName("pointsGroup") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName("autoPointsCheck") + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtWidgets.QFrame(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) + self.gridGroup.setObjectName("gridGroup") + self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) + self.gridLayout_2.setObjectName("gridLayout_2") + self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) + self.xGridCheck.setObjectName("xGridCheck") + self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) + self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) + self.yGridCheck.setObjectName("yGridCheck") + self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) + self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty("value", 128) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.gridAlphaSlider.setObjectName("gridAlphaSlider") + self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) + self.label = QtWidgets.QLabel(self.gridGroup) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.alphaGroup = QtWidgets.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName("alphaGroup") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName("horizontalLayout") + self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName("autoAlphaCheck") + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setObjectName("alphaSlider") + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) + self.averageGroup.setTitle(_translate("Form", "Average")) + self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) + self.clipToViewCheck.setText(_translate("Form", "Clip to View")) + self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.")) + self.maxTracesCheck.setText(_translate("Form", "Max Traces:")) + self.downsampleCheck.setText(_translate("Form", "Downsample")) + self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.")) + self.peakRadio.setText(_translate("Form", "Peak")) + self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.")) + self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).")) + self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces")) + self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.")) + self.meanRadio.setText(_translate("Form", "Mean")) + self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.")) + self.subsampleRadio.setText(_translate("Form", "Subsample")) + self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.")) + self.autoDownsampleCheck.setText(_translate("Form", "Auto")) + self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)")) + self.downsampleSpin.setSuffix(_translate("Form", "x")) + self.logYCheck.setText(_translate("Form", "Log Y")) + self.logXCheck.setText(_translate("Form", "Log X")) + self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)")) + self.derivativeCheck.setText(_translate("Form", "dy/dx")) + self.phasemapCheck.setText(_translate("Form", "Y vs. Y\'")) + self.pointsGroup.setTitle(_translate("Form", "Points")) + self.autoPointsCheck.setText(_translate("Form", "Auto")) + self.xGridCheck.setText(_translate("Form", "Show X Grid")) + self.yGridCheck.setText(_translate("Form", "Show Y Grid")) + self.label.setText(_translate("Form", "Opacity")) + self.alphaGroup.setTitle(_translate("Form", "Alpha")) + self.autoAlphaCheck.setText(_translate("Form", "Auto")) diff --git a/nmreval/gui_qt/_py/pokemon.py b/nmreval/gui_qt/_py/pokemon.py new file mode 100644 index 0000000..e601c7e --- /dev/null +++ b/nmreval/gui_qt/_py/pokemon.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/pokemon.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(400, 359) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) + Dialog.setSizePolicy(sizePolicy) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(Dialog) + self.tabWidget.setObjectName("tabWidget") + self.verticalLayout.addWidget(self.tabWidget) + self.formLayout = QtWidgets.QFormLayout() + self.formLayout.setObjectName("formLayout") + self.label = QtWidgets.QLabel(Dialog) + self.label.setObjectName("label") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label) + self.pokedex_nr = QtWidgets.QLabel(Dialog) + self.pokedex_nr.setObjectName("pokedex_nr") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.pokedex_nr) + self.label_2 = QtWidgets.QLabel(Dialog) + self.label_2.setObjectName("label_2") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2) + self.name = QtWidgets.QComboBox(Dialog) + self.name.setObjectName("name") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.name) + self.label_3 = QtWidgets.QLabel(Dialog) + self.label_3.setObjectName("label_3") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3) + self.category = QtWidgets.QLabel(Dialog) + self.category.setObjectName("category") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.category) + self.label_4 = QtWidgets.QLabel(Dialog) + self.label_4.setObjectName("label_4") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_4) + self.poketype = QtWidgets.QLabel(Dialog) + self.poketype.setObjectName("poketype") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.poketype) + self.label_5 = QtWidgets.QLabel(Dialog) + self.label_5.setObjectName("label_5") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_5) + self.height = QtWidgets.QLabel(Dialog) + self.height.setObjectName("height") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.height) + self.label_6 = QtWidgets.QLabel(Dialog) + self.label_6.setObjectName("label_6") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_6) + self.weight = QtWidgets.QLabel(Dialog) + self.weight.setObjectName("weight") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.weight) + self.label_7 = QtWidgets.QLabel(Dialog) + self.label_7.setObjectName("label_7") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_7) + self.color = QtWidgets.QLabel(Dialog) + self.color.setObjectName("color") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.color) + self.label_8 = QtWidgets.QLabel(Dialog) + self.label_8.setObjectName("label_8") + self.formLayout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.label_8) + self.info = QtWidgets.QLabel(Dialog) + self.info.setObjectName("info") + self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.info) + self.verticalLayout.addLayout(self.formLayout) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.prev_button = QtWidgets.QToolButton(Dialog) + self.prev_button.setObjectName("prev_button") + self.horizontalLayout_2.addWidget(self.prev_button) + self.next_button = QtWidgets.QToolButton(Dialog) + self.next_button.setObjectName("next_button") + self.horizontalLayout_2.addWidget(self.next_button) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close|QtWidgets.QDialogButtonBox.Retry) + self.buttonBox.setCenterButtons(False) + self.buttonBox.setObjectName("buttonBox") + self.horizontalLayout_2.addWidget(self.buttonBox) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.label_2.setBuddy(self.name) + + self.retranslateUi(Dialog) + self.tabWidget.setCurrentIndex(-1) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Random Pokémon")) + self.label.setText(_translate("Dialog", "National-Dex")) + self.pokedex_nr.setText(_translate("Dialog", "TextLabel")) + self.label_2.setText(_translate("Dialog", "Name")) + self.label_3.setText(_translate("Dialog", "Kategorie")) + self.category.setText(_translate("Dialog", "TextLabel")) + self.label_4.setText(_translate("Dialog", "Typ")) + self.poketype.setText(_translate("Dialog", "TextLabel")) + self.label_5.setText(_translate("Dialog", "Größe")) + self.height.setText(_translate("Dialog", "TextLabel")) + self.label_6.setText(_translate("Dialog", "Gewicht")) + self.weight.setText(_translate("Dialog", "TextLabel")) + self.label_7.setText(_translate("Dialog", "Farbe")) + self.color.setText(_translate("Dialog", "TextLabel")) + self.label_8.setText(_translate("Dialog", "Mehr...")) + self.info.setText(_translate("Dialog", "TextLabel")) + self.prev_button.setText(_translate("Dialog", "Prev.")) + self.next_button.setText(_translate("Dialog", "Next")) diff --git a/nmreval/gui_qt/_py/propwidget.py b/nmreval/gui_qt/_py/propwidget.py new file mode 100644 index 0000000..a3c4c3b --- /dev/null +++ b/nmreval/gui_qt/_py/propwidget.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/propwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 300) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(2, 2, 2, 2) + self.verticalLayout.setSpacing(2) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtWidgets.QTabWidget(Form) + self.tabWidget.setObjectName("tabWidget") + self.tabWidgetPage2 = QtWidgets.QWidget() + self.tabWidgetPage2.setObjectName("tabWidgetPage2") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.tabWidgetPage2) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.tableWidget_2 = QtWidgets.QTableWidget(self.tabWidgetPage2) + self.tableWidget_2.setObjectName("tableWidget_2") + self.tableWidget_2.setColumnCount(0) + self.tableWidget_2.setRowCount(0) + self.verticalLayout_3.addWidget(self.tableWidget_2) + self.tabWidget.addTab(self.tabWidgetPage2, "") + self.tabWidgetPage1 = QtWidgets.QWidget() + self.tabWidgetPage1.setObjectName("tabWidgetPage1") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.tabWidgetPage1) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.tableWidget = QtWidgets.QTableWidget(self.tabWidgetPage1) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setColumnCount(0) + self.tableWidget.setRowCount(0) + self.verticalLayout_2.addWidget(self.tableWidget) + self.tabWidget.addTab(self.tabWidgetPage1, "") + self.verticalLayout.addWidget(self.tabWidget) + + self.retranslateUi(Form) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage2), _translate("Form", "General")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("Form", "Symbol")) diff --git a/nmreval/gui_qt/_py/ptstab.py b/nmreval/gui_qt/_py/ptstab.py new file mode 100644 index 0000000..677b38b --- /dev/null +++ b/nmreval/gui_qt/_py/ptstab.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/ptstab.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(316, 747) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setObjectName("verticalLayout") + self.peaktable = QtWidgets.QListWidget(Form) + self.peaktable.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed) + self.peaktable.setObjectName("peaktable") + self.verticalLayout.addWidget(self.peaktable) + self.groupBox = QtWidgets.QGroupBox(Form) + self.groupBox.setObjectName("groupBox") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox) + self.horizontalLayout.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout.setSpacing(3) + self.horizontalLayout.setObjectName("horizontalLayout") + self.left_pt = QtWidgets.QSpinBox(self.groupBox) + self.left_pt.setMaximum(999) + self.left_pt.setObjectName("left_pt") + self.horizontalLayout.addWidget(self.left_pt) + self.right_pt = QtWidgets.QSpinBox(self.groupBox) + self.right_pt.setMaximum(999) + self.right_pt.setObjectName("right_pt") + self.horizontalLayout.addWidget(self.right_pt) + self.average_combobox = QtWidgets.QComboBox(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.average_combobox.sizePolicy().hasHeightForWidth()) + self.average_combobox.setSizePolicy(sizePolicy) + self.average_combobox.setObjectName("average_combobox") + self.average_combobox.addItem("") + self.average_combobox.addItem("") + self.average_combobox.addItem("") + self.horizontalLayout.addWidget(self.average_combobox) + self.verticalLayout.addWidget(self.groupBox) + self.groupBox_2 = QtWidgets.QGroupBox(Form) + self.groupBox_2.setCheckable(True) + self.groupBox_2.setChecked(False) + self.groupBox_2.setObjectName("groupBox_2") + self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.groupBox_2) + self.horizontalLayout_5.setContentsMargins(3, 3, 3, 3) + self.horizontalLayout_5.setSpacing(2) + self.horizontalLayout_5.setObjectName("horizontalLayout_5") + self.special_comboBox = QtWidgets.QComboBox(self.groupBox_2) + self.special_comboBox.setObjectName("special_comboBox") + self.special_comboBox.addItem("") + self.special_comboBox.addItem("") + self.special_comboBox.addItem("") + self.special_comboBox.addItem("") + self.horizontalLayout_5.addWidget(self.special_comboBox) + self.verticalLayout.addWidget(self.groupBox_2) + self.groupBox_3 = QtWidgets.QGroupBox(Form) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.xbutton = QtWidgets.QCheckBox(self.groupBox_3) + self.xbutton.setObjectName("xbutton") + self.gridLayout.addWidget(self.xbutton, 0, 0, 1, 1) + self.ybutton = QtWidgets.QCheckBox(self.groupBox_3) + self.ybutton.setChecked(True) + self.ybutton.setObjectName("ybutton") + self.gridLayout.addWidget(self.ybutton, 0, 1, 1, 1) + self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3) + self.graph_checkbox.setChecked(True) + self.graph_checkbox.setObjectName("graph_checkbox") + self.gridLayout.addWidget(self.graph_checkbox, 1, 0, 1, 1) + self.graph_combobox = QtWidgets.QComboBox(self.groupBox_3) + self.graph_combobox.setEnabled(False) + self.graph_combobox.setObjectName("graph_combobox") + self.gridLayout.addWidget(self.graph_combobox, 1, 1, 1, 1) + self.verticalLayout.addWidget(self.groupBox_3) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(2) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.okButton = QtWidgets.QPushButton(Form) + icon = QtGui.QIcon.fromTheme("dialog-ok") + self.okButton.setIcon(icon) + self.okButton.setObjectName("okButton") + self.horizontalLayout_2.addWidget(self.okButton) + self.deleteButton = QtWidgets.QPushButton(Form) + icon = QtGui.QIcon.fromTheme("dialog-cancel") + self.deleteButton.setIcon(icon) + self.deleteButton.setObjectName("deleteButton") + self.horizontalLayout_2.addWidget(self.deleteButton) + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.peaktable.setToolTip(_translate("Form", "Edit by entering new value: \n" +"Single number for points (e.g. 1e-6); \n" +"two numbers separated by space for regions (e.g. 1e-6 5e-6). \n" +"Changing between regions and points is NOT possible")) + self.groupBox.setTitle(_translate("Form", "Average")) + self.left_pt.setSuffix(_translate("Form", " pts")) + self.left_pt.setPrefix(_translate("Form", "- ")) + self.right_pt.setSuffix(_translate("Form", " pts")) + self.right_pt.setPrefix(_translate("Form", "+ ")) + self.average_combobox.setItemText(0, _translate("Form", "Mean")) + self.average_combobox.setItemText(1, _translate("Form", "Sum")) + self.average_combobox.setItemText(2, _translate("Form", "Integral")) + self.groupBox_2.setTitle(_translate("Form", "Special value")) + self.special_comboBox.setToolTip(_translate("Form", "Automatic selection of respective points")) + self.special_comboBox.setItemText(0, _translate("Form", "max(y)")) + self.special_comboBox.setItemText(1, _translate("Form", "max(abs(y))")) + self.special_comboBox.setItemText(2, _translate("Form", "min(y)")) + self.special_comboBox.setItemText(3, _translate("Form", "min(abs(y))")) + self.groupBox_3.setTitle(_translate("Form", "Result")) + self.xbutton.setText(_translate("Form", "x")) + self.ybutton.setText(_translate("Form", "y")) + self.graph_checkbox.setText(_translate("Form", "New graph?")) + self.okButton.setText(_translate("Form", "Apply")) + self.deleteButton.setText(_translate("Form", "Delete selected")) diff --git a/nmreval/gui_qt/_py/qfiledialog.py b/nmreval/gui_qt/_py/qfiledialog.py new file mode 100644 index 0000000..52829e2 --- /dev/null +++ b/nmreval/gui_qt/_py/qfiledialog.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/qfiledialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_QFileDialog(object): + def setupUi(self, QFileDialog): + QFileDialog.setObjectName("QFileDialog") + QFileDialog.resize(521, 316) + QFileDialog.setSizeGripEnabled(True) + self.gridlayout = QtWidgets.QGridLayout(QFileDialog) + self.gridlayout.setObjectName("gridlayout") + self.lookInLabel = QtWidgets.QLabel(QFileDialog) + self.lookInLabel.setObjectName("lookInLabel") + self.gridlayout.addWidget(self.lookInLabel, 0, 0, 1, 1) + self.hboxlayout = QtWidgets.QHBoxLayout() + self.hboxlayout.setObjectName("hboxlayout") + self.lookInCombo = QtWidgets.QComboBox(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lookInCombo.sizePolicy().hasHeightForWidth()) + self.lookInCombo.setSizePolicy(sizePolicy) + self.lookInCombo.setMinimumSize(QtCore.QSize(50, 0)) + self.lookInCombo.setObjectName("lookInCombo") + self.hboxlayout.addWidget(self.lookInCombo) + self.backButton = QtWidgets.QToolButton(QFileDialog) + self.backButton.setObjectName("backButton") + self.hboxlayout.addWidget(self.backButton) + self.forwardButton = QtWidgets.QToolButton(QFileDialog) + self.forwardButton.setObjectName("forwardButton") + self.hboxlayout.addWidget(self.forwardButton) + self.toParentButton = QtWidgets.QToolButton(QFileDialog) + self.toParentButton.setObjectName("toParentButton") + self.hboxlayout.addWidget(self.toParentButton) + self.newFolderButton = QtWidgets.QToolButton(QFileDialog) + self.newFolderButton.setObjectName("newFolderButton") + self.hboxlayout.addWidget(self.newFolderButton) + self.listModeButton = QtWidgets.QToolButton(QFileDialog) + self.listModeButton.setObjectName("listModeButton") + self.hboxlayout.addWidget(self.listModeButton) + self.detailModeButton = QtWidgets.QToolButton(QFileDialog) + self.detailModeButton.setObjectName("detailModeButton") + self.hboxlayout.addWidget(self.detailModeButton) + self.gridlayout.addLayout(self.hboxlayout, 0, 1, 1, 2) + self.splitter = QtWidgets.QSplitter(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth()) + self.splitter.setSizePolicy(sizePolicy) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setChildrenCollapsible(False) + self.splitter.setObjectName("splitter") + self.sidebar = QtWidgets.QListWidget(self.splitter) + self.sidebar.setObjectName("sidebar") + self.frame = QtWidgets.QFrame(self.splitter) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame.setObjectName("frame") + self.vboxlayout = QtWidgets.QVBoxLayout(self.frame) + self.vboxlayout.setContentsMargins(0, 0, 0, 0) + self.vboxlayout.setSpacing(0) + self.vboxlayout.setObjectName("vboxlayout") + self.stackedWidget = QtWidgets.QStackedWidget(self.frame) + self.stackedWidget.setObjectName("stackedWidget") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.vboxlayout1 = QtWidgets.QVBoxLayout(self.page) + self.vboxlayout1.setContentsMargins(0, 0, 0, 0) + self.vboxlayout1.setSpacing(0) + self.vboxlayout1.setObjectName("vboxlayout1") + self.listView = QtWidgets.QListView(self.page) + self.listView.setObjectName("listView") + self.vboxlayout1.addWidget(self.listView) + self.stackedWidget.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.vboxlayout2 = QtWidgets.QVBoxLayout(self.page_2) + self.vboxlayout2.setContentsMargins(0, 0, 0, 0) + self.vboxlayout2.setSpacing(0) + self.vboxlayout2.setObjectName("vboxlayout2") + self.treeView = QtWidgets.QTreeView(self.page_2) + self.treeView.setObjectName("treeView") + self.vboxlayout2.addWidget(self.treeView) + self.stackedWidget.addWidget(self.page_2) + self.vboxlayout.addWidget(self.stackedWidget) + self.gridlayout.addWidget(self.splitter, 1, 0, 1, 3) + self.fileNameLabel = QtWidgets.QLabel(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileNameLabel.sizePolicy().hasHeightForWidth()) + self.fileNameLabel.setSizePolicy(sizePolicy) + self.fileNameLabel.setMinimumSize(QtCore.QSize(0, 0)) + self.fileNameLabel.setObjectName("fileNameLabel") + self.gridlayout.addWidget(self.fileNameLabel, 2, 0, 1, 1) + self.fileNameEdit = QtWidgets.QLineEdit(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileNameEdit.sizePolicy().hasHeightForWidth()) + self.fileNameEdit.setSizePolicy(sizePolicy) + self.fileNameEdit.setObjectName("fileNameEdit") + self.gridlayout.addWidget(self.fileNameEdit, 2, 1, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(QFileDialog) + self.buttonBox.setOrientation(QtCore.Qt.Vertical) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridlayout.addWidget(self.buttonBox, 2, 2, 2, 1) + self.fileTypeLabel = QtWidgets.QLabel(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileTypeLabel.sizePolicy().hasHeightForWidth()) + self.fileTypeLabel.setSizePolicy(sizePolicy) + self.fileTypeLabel.setObjectName("fileTypeLabel") + self.gridlayout.addWidget(self.fileTypeLabel, 3, 0, 1, 1) + self.fileTypeCombo = QtWidgets.QComboBox(QFileDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fileTypeCombo.sizePolicy().hasHeightForWidth()) + self.fileTypeCombo.setSizePolicy(sizePolicy) + self.fileTypeCombo.setObjectName("fileTypeCombo") + self.gridlayout.addWidget(self.fileTypeCombo, 3, 1, 1, 1) + + self.retranslateUi(QFileDialog) + self.stackedWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(QFileDialog) + QFileDialog.setTabOrder(self.lookInCombo, self.backButton) + QFileDialog.setTabOrder(self.backButton, self.forwardButton) + QFileDialog.setTabOrder(self.forwardButton, self.toParentButton) + QFileDialog.setTabOrder(self.toParentButton, self.newFolderButton) + QFileDialog.setTabOrder(self.newFolderButton, self.listModeButton) + QFileDialog.setTabOrder(self.listModeButton, self.detailModeButton) + QFileDialog.setTabOrder(self.detailModeButton, self.sidebar) + QFileDialog.setTabOrder(self.sidebar, self.treeView) + QFileDialog.setTabOrder(self.treeView, self.listView) + QFileDialog.setTabOrder(self.listView, self.fileNameEdit) + QFileDialog.setTabOrder(self.fileNameEdit, self.buttonBox) + QFileDialog.setTabOrder(self.buttonBox, self.fileTypeCombo) + + def retranslateUi(self, QFileDialog): + _translate = QtCore.QCoreApplication.translate + self.lookInLabel.setText(_translate("QFileDialog", "Look in:")) + self.backButton.setToolTip(_translate("QFileDialog", "Back")) + self.backButton.setAccessibleName(_translate("QFileDialog", "Back")) + self.backButton.setAccessibleDescription(_translate("QFileDialog", "Go back")) + self.backButton.setShortcut(_translate("QFileDialog", "Alt+Left")) + self.forwardButton.setToolTip(_translate("QFileDialog", "Forward")) + self.forwardButton.setAccessibleName(_translate("QFileDialog", "Forward")) + self.forwardButton.setAccessibleDescription(_translate("QFileDialog", "Go forward")) + self.forwardButton.setShortcut(_translate("QFileDialog", "Alt+Right")) + self.toParentButton.setToolTip(_translate("QFileDialog", "Parent Directory")) + self.toParentButton.setAccessibleName(_translate("QFileDialog", "Parent Directory")) + self.toParentButton.setAccessibleDescription(_translate("QFileDialog", "Go to the parent directory")) + self.toParentButton.setShortcut(_translate("QFileDialog", "Alt+Up")) + self.newFolderButton.setToolTip(_translate("QFileDialog", "Create New Folder")) + self.newFolderButton.setAccessibleName(_translate("QFileDialog", "Create New Folder")) + self.newFolderButton.setAccessibleDescription(_translate("QFileDialog", "Create a New Folder")) + self.listModeButton.setToolTip(_translate("QFileDialog", "List View")) + self.listModeButton.setAccessibleName(_translate("QFileDialog", "List View")) + self.listModeButton.setAccessibleDescription(_translate("QFileDialog", "Change to list view mode")) + self.detailModeButton.setToolTip(_translate("QFileDialog", "Detail View")) + self.detailModeButton.setAccessibleName(_translate("QFileDialog", "Detail View")) + self.detailModeButton.setAccessibleDescription(_translate("QFileDialog", "Change to detail view mode")) + self.sidebar.setAccessibleName(_translate("QFileDialog", "Sidebar")) + self.sidebar.setAccessibleDescription(_translate("QFileDialog", "List of places and bookmarks")) + self.listView.setAccessibleName(_translate("QFileDialog", "Files")) + self.treeView.setAccessibleName(_translate("QFileDialog", "Files")) + self.fileTypeLabel.setText(_translate("QFileDialog", "Files of type:")) diff --git a/nmreval/gui_qt/_py/save_fit_parameter.py b/nmreval/gui_qt/_py/save_fit_parameter.py new file mode 100644 index 0000000..4db6c00 --- /dev/null +++ b/nmreval/gui_qt/_py/save_fit_parameter.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/save_fit_parameter.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_fitparameter_save_dialog(object): + def setupUi(self, fitparameter_save_dialog): + fitparameter_save_dialog.setObjectName("fitparameter_save_dialog") + fitparameter_save_dialog.resize(578, 537) + self.gridLayout = QtWidgets.QGridLayout(fitparameter_save_dialog) + self.gridLayout.setObjectName("gridLayout") + self.save_path_line = QtWidgets.QLineEdit(fitparameter_save_dialog) + self.save_path_line.setObjectName("save_path_line") + self.gridLayout.addWidget(self.save_path_line, 0, 1, 1, 6) + self.save_path_button = QtWidgets.QToolButton(fitparameter_save_dialog) + self.save_path_button.setObjectName("save_path_button") + self.gridLayout.addWidget(self.save_path_button, 0, 0, 1, 1) + self.comment_line = QtWidgets.QLineEdit(fitparameter_save_dialog) + self.comment_line.setObjectName("comment_line") + self.gridLayout.addWidget(self.comment_line, 6, 6, 1, 1) + self.prec_spinbox = QtWidgets.QSpinBox(fitparameter_save_dialog) + self.prec_spinbox.setProperty("value", 8) + self.prec_spinbox.setObjectName("prec_spinbox") + self.gridLayout.addWidget(self.prec_spinbox, 6, 3, 1, 1) + self.line_2 = QtWidgets.QFrame(fitparameter_save_dialog) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout.addWidget(self.line_2, 4, 0, 1, 7) + self.label_5 = QtWidgets.QLabel(fitparameter_save_dialog) + self.label_5.setObjectName("label_5") + self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.col_line = QtWidgets.QLineEdit(fitparameter_save_dialog) + self.col_line.setObjectName("col_line") + self.horizontalLayout.addWidget(self.col_line) + self.usage_button = QtWidgets.QToolButton(fitparameter_save_dialog) + self.usage_button.setObjectName("usage_button") + self.horizontalLayout.addWidget(self.usage_button) + self.gridLayout.addLayout(self.horizontalLayout, 3, 1, 1, 6) + self.header_edit = QtWidgets.QTextEdit(fitparameter_save_dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.header_edit.sizePolicy().hasHeightForWidth()) + self.header_edit.setSizePolicy(sizePolicy) + self.header_edit.setObjectName("header_edit") + self.gridLayout.addWidget(self.header_edit, 5, 1, 1, 6) + self.label_4 = QtWidgets.QLabel(fitparameter_save_dialog) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 6, 2, 1, 1) + self.line = QtWidgets.QFrame(fitparameter_save_dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth()) + self.line.setSizePolicy(sizePolicy) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 1, 0, 1, 7) + self.missing_value_line = QtWidgets.QLineEdit(fitparameter_save_dialog) + self.missing_value_line.setObjectName("missing_value_line") + self.gridLayout.addWidget(self.missing_value_line, 6, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(fitparameter_save_dialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 6, 4, 1, 1) + self.tableWidget = QtWidgets.QTableWidget(fitparameter_save_dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tableWidget.sizePolicy().hasHeightForWidth()) + self.tableWidget.setSizePolicy(sizePolicy) + self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.tableWidget.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly) + self.tableWidget.setAlternatingRowColors(True) + self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableWidget.setColumnCount(2) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setRowCount(0) + self.tableWidget.horizontalHeader().setVisible(False) + self.tableWidget.horizontalHeader().setStretchLastSection(True) + self.gridLayout.addWidget(self.tableWidget, 2, 0, 1, 7) + self.buttonBox = QtWidgets.QDialogButtonBox(fitparameter_save_dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 7) + self.label = QtWidgets.QLabel(fitparameter_save_dialog) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 6, 0, 1, 1) + self.header_checkBox = QtWidgets.QCheckBox(fitparameter_save_dialog) + self.header_checkBox.setObjectName("header_checkBox") + self.gridLayout.addWidget(self.header_checkBox, 5, 0, 1, 1) + self.label_5.setBuddy(self.col_line) + self.label_2.setBuddy(self.comment_line) + self.label.setBuddy(self.missing_value_line) + + self.retranslateUi(fitparameter_save_dialog) + self.buttonBox.accepted.connect(fitparameter_save_dialog.accept) + self.buttonBox.rejected.connect(fitparameter_save_dialog.reject) + QtCore.QMetaObject.connectSlotsByName(fitparameter_save_dialog) + fitparameter_save_dialog.setTabOrder(self.save_path_button, self.save_path_line) + fitparameter_save_dialog.setTabOrder(self.save_path_line, self.tableWidget) + fitparameter_save_dialog.setTabOrder(self.tableWidget, self.header_edit) + fitparameter_save_dialog.setTabOrder(self.header_edit, self.missing_value_line) + fitparameter_save_dialog.setTabOrder(self.missing_value_line, self.comment_line) + + def retranslateUi(self, fitparameter_save_dialog): + _translate = QtCore.QCoreApplication.translate + fitparameter_save_dialog.setWindowTitle(_translate("fitparameter_save_dialog", "Save parameter")) + self.save_path_button.setText(_translate("fitparameter_save_dialog", "Save path...")) + self.comment_line.setText(_translate("fitparameter_save_dialog", "#")) + self.label_5.setText(_translate("fitparameter_save_dialog", "Columns")) + self.col_line.setPlaceholderText(_translate("fitparameter_save_dialog", "e.g. x; 1000/x; mean(T_1,beta); {beta/beta_1}; M_0*x")) + self.usage_button.setText(_translate("fitparameter_save_dialog", "Usage?")) + self.header_edit.setPlaceholderText(_translate("fitparameter_save_dialog", "Header ends always with column description. Additional header lines are commented automatically")) + self.label_4.setText(_translate("fitparameter_save_dialog", "Precision")) + self.missing_value_line.setText(_translate("fitparameter_save_dialog", "1e308")) + self.label_2.setText(_translate("fitparameter_save_dialog", "Comment")) + self.buttonBox.setToolTip(_translate("fitparameter_save_dialog", "Number of significant digits.")) + self.label.setText(_translate("fitparameter_save_dialog", "Missing values")) + self.header_checkBox.setText(_translate("fitparameter_save_dialog", "Header")) diff --git a/nmreval/gui_qt/_py/save_fitmodel_dialog.py b/nmreval/gui_qt/_py/save_fitmodel_dialog.py new file mode 100644 index 0000000..25f058a --- /dev/null +++ b/nmreval/gui_qt/_py/save_fitmodel_dialog.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/save_fitmodel_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SaveDialog(object): + def setupUi(self, SaveDialog): + SaveDialog.setObjectName("SaveDialog") + SaveDialog.resize(400, 166) + self.gridLayout = QtWidgets.QGridLayout(SaveDialog) + self.gridLayout.setObjectName("gridLayout") + self.label_2 = QtWidgets.QLabel(SaveDialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(SaveDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1) + self.comboBox = QtWidgets.QComboBox(SaveDialog) + self.comboBox.setObjectName("comboBox") + self.gridLayout.addWidget(self.comboBox, 1, 1, 1, 1) + self.label = QtWidgets.QLabel(SaveDialog) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.lineEdit = QtWidgets.QLineEdit(SaveDialog) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 4, 1, 1, 1) + self.frame = QtWidgets.QFrame(SaveDialog) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame.setObjectName("frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.lineEdit_2 = QtWidgets.QLineEdit(self.frame) + self.lineEdit_2.setObjectName("lineEdit_2") + self.horizontalLayout.addWidget(self.lineEdit_2) + self.toolButton = QtWidgets.QToolButton(self.frame) + self.toolButton.setObjectName("toolButton") + self.horizontalLayout.addWidget(self.toolButton) + self.gridLayout.addWidget(self.frame, 2, 1, 1, 1) + + self.retranslateUi(SaveDialog) + self.buttonBox.accepted.connect(SaveDialog.accept) + self.buttonBox.rejected.connect(SaveDialog.reject) + QtCore.QMetaObject.connectSlotsByName(SaveDialog) + + def retranslateUi(self, SaveDialog): + _translate = QtCore.QCoreApplication.translate + SaveDialog.setWindowTitle(_translate("SaveDialog", "Dialog")) + self.label_2.setText(_translate("SaveDialog", "Group")) + self.label.setText(_translate("SaveDialog", "Name")) + self.toolButton.setText(_translate("SaveDialog", "OK")) diff --git a/nmreval/gui_qt/_py/save_options.py b/nmreval/gui_qt/_py/save_options.py new file mode 100644 index 0000000..602a2c8 --- /dev/null +++ b/nmreval/gui_qt/_py/save_options.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/save_options.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 58) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setObjectName("verticalLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.checkBox = QtWidgets.QCheckBox(Form) + self.checkBox.setObjectName("checkBox") + self.verticalLayout.addWidget(self.checkBox) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.label.setText(_translate("Form", "

Use <label> as placeholder in filename for data label.

")) + self.checkBox.setText(_translate("Form", "Replace spaces with underscore")) diff --git a/nmreval/gui_qt/_py/saveoptions.py b/nmreval/gui_qt/_py/saveoptions.py new file mode 100644 index 0000000..c210a7e --- /dev/null +++ b/nmreval/gui_qt/_py/saveoptions.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/saveoptions.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Frame(object): + def setupUi(self, Frame): + Frame.setObjectName("Frame") + Frame.resize(464, 62) + Frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + Frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.verticalLayout = QtWidgets.QVBoxLayout(Frame) + self.verticalLayout.setObjectName("verticalLayout") + self.label = QtWidgets.QLabel(Frame) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.line = QtWidgets.QFrame(Frame) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.verticalLayout.addWidget(self.line) + self.checkBox = QtWidgets.QCheckBox(Frame) + self.checkBox.setChecked(True) + self.checkBox.setObjectName("checkBox") + self.verticalLayout.addWidget(self.checkBox) + + self.retranslateUi(Frame) + QtCore.QMetaObject.connectSlotsByName(Frame) + + def retranslateUi(self, Frame): + _translate = QtCore.QCoreApplication.translate + Frame.setWindowTitle(_translate("Frame", "Frame")) + self.label.setText(_translate("Frame", "

Use <label> as placeholder in filename. (e.g. t1_<label>.dat)

")) + self.checkBox.setText(_translate("Frame", "Replace spaces with underscore")) diff --git a/nmreval/gui_qt/_py/sdmodelwidget.py b/nmreval/gui_qt/_py/sdmodelwidget.py new file mode 100644 index 0000000..4f3a9a0 --- /dev/null +++ b/nmreval/gui_qt/_py/sdmodelwidget.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/sdmodelwidget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SDParameter(object): + def setupUi(self, SDParameter): + SDParameter.setObjectName("SDParameter") + SDParameter.setWindowModality(QtCore.Qt.WindowModal) + SDParameter.resize(290, 37) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(SDParameter.sizePolicy().hasHeightForWidth()) + SDParameter.setSizePolicy(sizePolicy) + SDParameter.setAutoFillBackground(True) + self.verticalLayout = QtWidgets.QVBoxLayout(SDParameter) + self.verticalLayout.setContentsMargins(1, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setContentsMargins(6, -1, 0, -1) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.parameter_line = LineEdit(SDParameter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.parameter_line.sizePolicy().hasHeightForWidth()) + self.parameter_line.setSizePolicy(sizePolicy) + self.parameter_line.setObjectName("parameter_line") + self.gridLayout.addWidget(self.parameter_line, 0, 2, 1, 1) + self.parametername = QtWidgets.QLabel(SDParameter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(10) + sizePolicy.setHeightForWidth(self.parametername.sizePolicy().hasHeightForWidth()) + self.parametername.setSizePolicy(sizePolicy) + self.parametername.setFrameShape(QtWidgets.QFrame.NoFrame) + self.parametername.setObjectName("parametername") + self.gridLayout.addWidget(self.parametername, 0, 0, 1, 1) + self.checkBox = QtWidgets.QCheckBox(SDParameter) + self.checkBox.setObjectName("checkBox") + self.gridLayout.addWidget(self.checkBox, 0, 3, 1, 1) + self.line_2 = QtWidgets.QFrame(SDParameter) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout.addWidget(self.line_2, 1, 0, 1, 4) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 0, 1, 1, 1) + self.verticalLayout.addLayout(self.gridLayout) + + self.retranslateUi(SDParameter) + QtCore.QMetaObject.connectSlotsByName(SDParameter) + + def retranslateUi(self, SDParameter): + _translate = QtCore.QCoreApplication.translate + SDParameter.setWindowTitle(_translate("SDParameter", "Form")) + self.parameter_line.setText(_translate("SDParameter", "1")) + self.parametername.setText(_translate("SDParameter", "Fitparameter")) + self.checkBox.setText(_translate("SDParameter", "Fix?")) +from nmrevalgui.lib.forms import LineEdit diff --git a/nmreval/gui_qt/_py/selection_widget.py b/nmreval/gui_qt/_py/selection_widget.py new file mode 100644 index 0000000..45614d4 --- /dev/null +++ b/nmreval/gui_qt/_py/selection_widget.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/selection_widget.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SelectionWidget(object): + def setupUi(self, SelectionWidget): + SelectionWidget.setObjectName("SelectionWidget") + SelectionWidget.resize(367, 43) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(SelectionWidget.sizePolicy().hasHeightForWidth()) + SelectionWidget.setSizePolicy(sizePolicy) + self.horizontalLayout = QtWidgets.QHBoxLayout(SelectionWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(SelectionWidget) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.comboBox = QtWidgets.QComboBox(SelectionWidget) + self.comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength) + self.comboBox.setFrame(True) + self.comboBox.setObjectName("comboBox") + self.horizontalLayout.addWidget(self.comboBox) + + self.retranslateUi(SelectionWidget) + QtCore.QMetaObject.connectSlotsByName(SelectionWidget) + + def retranslateUi(self, SelectionWidget): + _translate = QtCore.QCoreApplication.translate + SelectionWidget.setWindowTitle(_translate("SelectionWidget", "Form")) + self.label.setText(_translate("SelectionWidget", "TextLabel")) diff --git a/nmreval/gui_qt/_py/setbyfunction_dialog.py b/nmreval/gui_qt/_py/setbyfunction_dialog.py new file mode 100644 index 0000000..ecd065d --- /dev/null +++ b/nmreval/gui_qt/_py/setbyfunction_dialog.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/setbyfunction_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_NewCurveDialog(object): + def setupUi(self, NewCurveDialog): + NewCurveDialog.setObjectName("NewCurveDialog") + NewCurveDialog.resize(648, 578) + self.gridLayout_4 = QtWidgets.QGridLayout(NewCurveDialog) + self.gridLayout_4.setObjectName("gridLayout_4") + self.groupBox_2 = QtWidgets.QGroupBox(NewCurveDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + 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.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_2.setContentsMargins(2, 2, 2, 2) + self.gridLayout_2.setSpacing(2) + self.gridLayout_2.setObjectName("gridLayout_2") + self.lineEdit_3 = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_3.setPlaceholderText("") + self.lineEdit_3.setObjectName("lineEdit_3") + self.gridLayout_2.addWidget(self.lineEdit_3, 0, 1, 1, 1) + self.lineEdit_5 = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_5.setObjectName("lineEdit_5") + self.gridLayout_2.addWidget(self.lineEdit_5, 0, 6, 1, 1) + self.label_5 = QtWidgets.QLabel(self.groupBox_2) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 0, 3, 1, 1) + self.lineEdit_4 = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_4.setObjectName("lineEdit_4") + self.gridLayout_2.addWidget(self.lineEdit_4, 0, 4, 1, 1) + self.label_4 = QtWidgets.QLabel(self.groupBox_2) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 0, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(self.groupBox_2) + self.label_6.setObjectName("label_6") + self.gridLayout_2.addWidget(self.label_6, 0, 5, 1, 1) + self.checkBox = QtWidgets.QCheckBox(self.groupBox_2) + self.checkBox.setLayoutDirection(QtCore.Qt.RightToLeft) + self.checkBox.setObjectName("checkBox") + self.gridLayout_2.addWidget(self.checkBox, 0, 7, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_2, 0, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(NewCurveDialog) + self.groupBox.setObjectName("groupBox") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_3.setContentsMargins(3, 3, 3, 3) + self.gridLayout_3.setSpacing(2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_2.setObjectName("lineEdit_2") + self.gridLayout_3.addWidget(self.lineEdit_2, 1, 2, 1, 1) + self.comboBox_6 = QtWidgets.QComboBox(self.groupBox) + self.comboBox_6.setObjectName("comboBox_6") + self.gridLayout_3.addWidget(self.comboBox_6, 2, 0, 1, 1) + self.pushButton = QtWidgets.QPushButton(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.pushButton.sizePolicy().hasHeightForWidth()) + self.pushButton.setSizePolicy(sizePolicy) + self.pushButton.setObjectName("pushButton") + self.gridLayout_3.addWidget(self.pushButton, 4, 0, 1, 1) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.gridLayout_3.addLayout(self.verticalLayout, 3, 0, 1, 1) + self.comboBox_7 = QtWidgets.QComboBox(self.groupBox) + self.comboBox_7.setObjectName("comboBox_7") + self.gridLayout_3.addWidget(self.comboBox_7, 2, 2, 1, 1) + self.lineEdit = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout_3.addWidget(self.lineEdit, 1, 0, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setObjectName("label") + self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.gridLayout_3.addLayout(self.verticalLayout_2, 3, 2, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy) + self.label_2.setObjectName("label_2") + self.gridLayout_3.addWidget(self.label_2, 0, 2, 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_3.addWidget(self.line_2, 0, 1, 4, 1) + self.gridLayout_4.addWidget(self.groupBox, 1, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(NewCurveDialog) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setSpacing(2) + self.gridLayout.setObjectName("gridLayout") + self.doubleSpinBox = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.doubleSpinBox.setDecimals(1) + self.doubleSpinBox.setMaximum(20.0) + self.doubleSpinBox.setSingleStep(0.5) + self.doubleSpinBox.setProperty("value", 1.0) + self.doubleSpinBox.setObjectName("doubleSpinBox") + self.gridLayout.addWidget(self.doubleSpinBox, 3, 4, 1, 1) + self.line = QtWidgets.QFrame(self.groupBox_3) + self.line.setFrameShape(QtWidgets.QFrame.VLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 0, 2, 4, 1) + self.spinBox = QtWidgets.QSpinBox(self.groupBox_3) + self.spinBox.setMaximum(100) + self.spinBox.setProperty("value", 10) + self.spinBox.setObjectName("spinBox") + self.gridLayout.addWidget(self.spinBox, 3, 1, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox_3) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 0, 4, 1, 1) + self.comboBox = SymbolStyleEditor(self.groupBox_3) + self.comboBox.setObjectName("comboBox") + self.gridLayout.addWidget(self.comboBox, 1, 1, 1, 1) + self.comboBox_2 = LineStyleEditor(self.groupBox_3) + self.comboBox_2.setObjectName("comboBox_2") + self.gridLayout.addWidget(self.comboBox_2, 1, 4, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_3) + self.label_7.setObjectName("label_7") + self.gridLayout.addWidget(self.label_7, 0, 1, 1, 1) + self.comboBox_4 = ColorListEditor(self.groupBox_3) + self.comboBox_4.setObjectName("comboBox_4") + self.gridLayout.addWidget(self.comboBox_4, 2, 4, 1, 1) + self.comboBox_5 = ColorListEditor(self.groupBox_3) + self.comboBox_5.setObjectName("comboBox_5") + self.gridLayout.addWidget(self.comboBox_5, 2, 1, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_3, 2, 0, 1, 1) + self.groupBox_21 = QtWidgets.QGroupBox(NewCurveDialog) + self.groupBox_21.setObjectName("groupBox_21") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_21) + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_3 = QtWidgets.QLabel(self.groupBox_21) + self.label_3.setObjectName("label_3") + self.gridLayout_5.addWidget(self.label_3, 0, 0, 1, 1) + self.lineEdit_6 = QtWidgets.QLineEdit(self.groupBox_21) + self.lineEdit_6.setObjectName("lineEdit_6") + self.gridLayout_5.addWidget(self.lineEdit_6, 0, 1, 1, 1) + self.label_10 = QtWidgets.QLabel(self.groupBox_21) + self.label_10.setObjectName("label_10") + self.gridLayout_5.addWidget(self.label_10, 0, 2, 1, 1) + self.comboBox_3 = QtWidgets.QComboBox(self.groupBox_21) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.comboBox_3.sizePolicy().hasHeightForWidth()) + self.comboBox_3.setSizePolicy(sizePolicy) + self.comboBox_3.setObjectName("comboBox_3") + self.gridLayout_5.addWidget(self.comboBox_3, 0, 3, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_21, 3, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(NewCurveDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout_4.addWidget(self.buttonBox, 5, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem, 4, 0, 1, 1) + self.label_5.setBuddy(self.lineEdit_4) + self.label_4.setBuddy(self.lineEdit_3) + self.label_6.setBuddy(self.lineEdit_5) + self.label.setBuddy(self.lineEdit) + self.label_2.setBuddy(self.lineEdit_2) + self.label_8.setBuddy(self.comboBox_2) + self.label_7.setBuddy(self.comboBox) + self.label_3.setBuddy(self.lineEdit_6) + self.label_10.setBuddy(self.comboBox_3) + + self.retranslateUi(NewCurveDialog) + self.comboBox.setCurrentIndex(-1) + self.comboBox_2.setCurrentIndex(-1) + self.buttonBox.accepted.connect(NewCurveDialog.accept) + self.buttonBox.rejected.connect(NewCurveDialog.reject) + QtCore.QMetaObject.connectSlotsByName(NewCurveDialog) + NewCurveDialog.setTabOrder(self.lineEdit_3, self.lineEdit_4) + NewCurveDialog.setTabOrder(self.lineEdit_4, self.lineEdit_5) + NewCurveDialog.setTabOrder(self.lineEdit_5, self.checkBox) + NewCurveDialog.setTabOrder(self.checkBox, self.lineEdit) + NewCurveDialog.setTabOrder(self.lineEdit, self.comboBox) + NewCurveDialog.setTabOrder(self.comboBox, self.spinBox) + NewCurveDialog.setTabOrder(self.spinBox, self.comboBox_2) + NewCurveDialog.setTabOrder(self.comboBox_2, self.doubleSpinBox) + NewCurveDialog.setTabOrder(self.doubleSpinBox, self.lineEdit_6) + NewCurveDialog.setTabOrder(self.lineEdit_6, self.comboBox_3) + NewCurveDialog.setTabOrder(self.comboBox_3, self.buttonBox) + + def retranslateUi(self, NewCurveDialog): + _translate = QtCore.QCoreApplication.translate + NewCurveDialog.setWindowTitle(_translate("NewCurveDialog", "Create new data by function")) + self.groupBox_2.setTitle(_translate("NewCurveDialog", "Control variable i")) + self.lineEdit_3.setText(_translate("NewCurveDialog", "0")) + self.lineEdit_5.setText(_translate("NewCurveDialog", "10")) + self.label_5.setText(_translate("NewCurveDialog", "Stop at")) + self.lineEdit_4.setText(_translate("NewCurveDialog", "1")) + self.label_4.setText(_translate("NewCurveDialog", "Start at")) + self.label_6.setText(_translate("NewCurveDialog", "# points")) + self.checkBox.setText(_translate("NewCurveDialog", "Logarithmic?")) + self.groupBox.setTitle(_translate("NewCurveDialog", "Expressions")) + self.lineEdit_2.setText(_translate("NewCurveDialog", "x**2")) + self.pushButton.setText(_translate("NewCurveDialog", "Check")) + self.lineEdit.setText(_translate("NewCurveDialog", "i")) + self.label.setText(_translate("NewCurveDialog", " x = ")) + self.label_2.setText(_translate("NewCurveDialog", " y = ")) + self.groupBox_3.setTitle(_translate("NewCurveDialog", "Look")) + self.label_8.setText(_translate("NewCurveDialog", "Line")) + self.label_7.setText(_translate("NewCurveDialog", "Symbol")) + self.groupBox_21.setTitle(_translate("NewCurveDialog", "Designation")) + self.label_3.setText(_translate("NewCurveDialog", "Name")) + self.label_10.setText(_translate("NewCurveDialog", "Graph")) +from ..lib.delegates import ColorListEditor, LineStyleEditor, SymbolStyleEditor diff --git a/nmreval/gui_qt/_py/shift_scale_dialog.py b/nmreval/gui_qt/_py/shift_scale_dialog.py new file mode 100644 index 0000000..b8605e5 --- /dev/null +++ b/nmreval/gui_qt/_py/shift_scale_dialog.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/shift_scale_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_shift_dialog(object): + def setupUi(self, shift_dialog): + shift_dialog.setObjectName("shift_dialog") + shift_dialog.resize(959, 639) + self.verticalLayout = QtWidgets.QVBoxLayout(shift_dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.splitter = QtWidgets.QSplitter(shift_dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth()) + self.splitter.setSizePolicy(sizePolicy) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.tabWidget = QtWidgets.QTabWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) + self.tabWidget.setSizePolicy(sizePolicy) + self.tabWidget.setObjectName("tabWidget") + self.shift_page = QtWidgets.QWidget() + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.shift_page.sizePolicy().hasHeightForWidth()) + self.shift_page.setSizePolicy(sizePolicy) + self.shift_page.setObjectName("shift_page") + self.gridLayout_2 = QtWidgets.QGridLayout(self.shift_page) + self.gridLayout_2.setContentsMargins(3, 3, 3, 3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_7 = QtWidgets.QLabel(self.shift_page) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 2, 0, 1, 1) + self.label = QtWidgets.QLabel(self.shift_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 2, 1, 1, 1) + self.x_shift_spinbox = SciSpinBox(self.shift_page) + self.x_shift_spinbox.setMinimumSize(QtCore.QSize(150, 0)) + self.x_shift_spinbox.setDecimals(3) + self.x_shift_spinbox.setProperty("value", 0.0) + self.x_shift_spinbox.setObjectName("x_shift_spinbox") + self.gridLayout_2.addWidget(self.x_shift_spinbox, 2, 2, 1, 1) + self.label_5 = QtWidgets.QLabel(self.shift_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) + self.label_5.setSizePolicy(sizePolicy) + self.label_5.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 3, 1, 1, 1) + self.y_shift_spinbox = SciSpinBox(self.shift_page) + self.y_shift_spinbox.setMinimumSize(QtCore.QSize(150, 0)) + self.y_shift_spinbox.setDecimals(3) + self.y_shift_spinbox.setProperty("value", 0.0) + self.y_shift_spinbox.setObjectName("y_shift_spinbox") + self.gridLayout_2.addWidget(self.y_shift_spinbox, 3, 2, 1, 1) + self.shift_table = QtWidgets.QTableWidget(self.shift_page) + self.shift_table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.shift_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.shift_table.setColumnCount(3) + self.shift_table.setObjectName("shift_table") + self.shift_table.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.shift_table.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.shift_table.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.shift_table.setHorizontalHeaderItem(2, item) + self.shift_table.verticalHeader().setVisible(False) + self.gridLayout_2.addWidget(self.shift_table, 0, 0, 1, 3) + self.tabWidget.addTab(self.shift_page, "") + self.scale_page = QtWidgets.QWidget() + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scale_page.sizePolicy().hasHeightForWidth()) + self.scale_page.setSizePolicy(sizePolicy) + self.scale_page.setObjectName("scale_page") + self.gridLayout = QtWidgets.QGridLayout(self.scale_page) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setObjectName("gridLayout") + self.label_3 = QtWidgets.QLabel(self.scale_page) + 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.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 1, 1, 1, 1) + self.x_scale_spinbox = SciSpinBox(self.scale_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.x_scale_spinbox.sizePolicy().hasHeightForWidth()) + self.x_scale_spinbox.setSizePolicy(sizePolicy) + self.x_scale_spinbox.setMinimumSize(QtCore.QSize(150, 0)) + self.x_scale_spinbox.setDecimals(3) + self.x_scale_spinbox.setProperty("value", 1.0) + self.x_scale_spinbox.setObjectName("x_scale_spinbox") + self.gridLayout.addWidget(self.x_scale_spinbox, 1, 2, 1, 1) + self.label_6 = QtWidgets.QLabel(self.scale_page) + self.label_6.setObjectName("label_6") + self.gridLayout.addWidget(self.label_6, 1, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.scale_page) + 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.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 1, 1, 1) + self.y_scale_spinbox = SciSpinBox(self.scale_page) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.y_scale_spinbox.sizePolicy().hasHeightForWidth()) + self.y_scale_spinbox.setSizePolicy(sizePolicy) + self.y_scale_spinbox.setMinimumSize(QtCore.QSize(150, 0)) + self.y_scale_spinbox.setDecimals(3) + self.y_scale_spinbox.setProperty("value", 1.0) + self.y_scale_spinbox.setObjectName("y_scale_spinbox") + self.gridLayout.addWidget(self.y_scale_spinbox, 2, 2, 1, 1) + self.scale_table = QtWidgets.QTableWidget(self.scale_page) + self.scale_table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.scale_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.scale_table.setObjectName("scale_table") + self.scale_table.setColumnCount(3) + self.scale_table.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.scale_table.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.scale_table.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.scale_table.setHorizontalHeaderItem(2, item) + self.scale_table.verticalHeader().setVisible(False) + self.gridLayout.addWidget(self.scale_table, 0, 0, 1, 3) + self.tabWidget.addTab(self.scale_page, "") + self.verticalFrame_2 = QtWidgets.QFrame(self.splitter) + self.verticalFrame_2.setObjectName("verticalFrame_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalFrame_2) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.graphicsView = PlotWidget(self.verticalFrame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth()) + self.graphicsView.setSizePolicy(sizePolicy) + self.graphicsView.setObjectName("graphicsView") + self.verticalLayout_2.addWidget(self.graphicsView) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.label_4 = QtWidgets.QLabel(self.verticalFrame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy) + self.label_4.setObjectName("label_4") + self.horizontalLayout.addWidget(self.label_4) + self.xlog_checkbox = QtWidgets.QCheckBox(self.verticalFrame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.xlog_checkbox.sizePolicy().hasHeightForWidth()) + self.xlog_checkbox.setSizePolicy(sizePolicy) + self.xlog_checkbox.setObjectName("xlog_checkbox") + self.horizontalLayout.addWidget(self.xlog_checkbox) + self.ylog_checkbox = QtWidgets.QCheckBox(self.verticalFrame_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ylog_checkbox.sizePolicy().hasHeightForWidth()) + self.ylog_checkbox.setSizePolicy(sizePolicy) + self.ylog_checkbox.setObjectName("ylog_checkbox") + self.horizontalLayout.addWidget(self.ylog_checkbox) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.verticalLayout.addWidget(self.splitter) + self.line = QtWidgets.QFrame(shift_dialog) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.verticalLayout.addWidget(self.line) + self.frame = QtWidgets.QFrame(shift_dialog) + self.frame.setObjectName("frame") + self.gridLayout_4 = QtWidgets.QGridLayout(self.frame) + self.gridLayout_4.setContentsMargins(3, 3, 3, 3) + self.gridLayout_4.setSpacing(3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.value_checkbox = QtWidgets.QCheckBox(self.frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.value_checkbox.sizePolicy().hasHeightForWidth()) + self.value_checkbox.setSizePolicy(sizePolicy) + self.value_checkbox.setChecked(True) + self.value_checkbox.setObjectName("value_checkbox") + self.gridLayout_4.addWidget(self.value_checkbox, 0, 1, 1, 1) + self.overwrite_checkbox = QtWidgets.QCheckBox(self.frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.overwrite_checkbox.sizePolicy().hasHeightForWidth()) + self.overwrite_checkbox.setSizePolicy(sizePolicy) + self.overwrite_checkbox.setObjectName("overwrite_checkbox") + self.gridLayout_4.addWidget(self.overwrite_checkbox, 0, 0, 1, 1) + self.data_newgraph = QtWidgets.QCheckBox(self.frame) + self.data_newgraph.setChecked(True) + self.data_newgraph.setObjectName("data_newgraph") + self.gridLayout_4.addWidget(self.data_newgraph, 1, 0, 1, 1) + self.data_combobox = QtWidgets.QComboBox(self.frame) + self.data_combobox.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.data_combobox.sizePolicy().hasHeightForWidth()) + self.data_combobox.setSizePolicy(sizePolicy) + self.data_combobox.setObjectName("data_combobox") + self.gridLayout_4.addWidget(self.data_combobox, 2, 0, 1, 1) + self.values_newgraph = QtWidgets.QCheckBox(self.frame) + self.values_newgraph.setChecked(True) + self.values_newgraph.setObjectName("values_newgraph") + self.gridLayout_4.addWidget(self.values_newgraph, 1, 1, 1, 1) + self.values_combobox = QtWidgets.QComboBox(self.frame) + self.values_combobox.setEnabled(False) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.values_combobox.sizePolicy().hasHeightForWidth()) + self.values_combobox.setSizePolicy(sizePolicy) + self.values_combobox.setObjectName("values_combobox") + self.gridLayout_4.addWidget(self.values_combobox, 2, 1, 1, 1) + self.verticalLayout.addWidget(self.frame) + self.buttonBox = QtWidgets.QDialogButtonBox(shift_dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + self.label.setBuddy(self.x_shift_spinbox) + self.label_5.setBuddy(self.y_shift_spinbox) + self.label_3.setBuddy(self.x_scale_spinbox) + self.label_2.setBuddy(self.y_scale_spinbox) + + self.retranslateUi(shift_dialog) + self.tabWidget.setCurrentIndex(0) + self.buttonBox.accepted.connect(shift_dialog.accept) + self.buttonBox.rejected.connect(shift_dialog.reject) + QtCore.QMetaObject.connectSlotsByName(shift_dialog) + shift_dialog.setTabOrder(self.tabWidget, self.shift_table) + shift_dialog.setTabOrder(self.shift_table, self.x_shift_spinbox) + shift_dialog.setTabOrder(self.x_shift_spinbox, self.y_shift_spinbox) + shift_dialog.setTabOrder(self.y_shift_spinbox, self.scale_table) + shift_dialog.setTabOrder(self.scale_table, self.x_scale_spinbox) + shift_dialog.setTabOrder(self.x_scale_spinbox, self.y_scale_spinbox) + shift_dialog.setTabOrder(self.y_scale_spinbox, self.xlog_checkbox) + shift_dialog.setTabOrder(self.xlog_checkbox, self.ylog_checkbox) + shift_dialog.setTabOrder(self.ylog_checkbox, self.graphicsView) + + def retranslateUi(self, shift_dialog): + _translate = QtCore.QCoreApplication.translate + shift_dialog.setWindowTitle(_translate("shift_dialog", "Shift + Scale")) + self.label_7.setText(_translate("shift_dialog", "Global")) + self.label.setText(_translate("shift_dialog", "

x

")) + self.label_5.setText(_translate("shift_dialog", "

y

")) + item = self.shift_table.horizontalHeaderItem(0) + item.setText(_translate("shift_dialog", "Data")) + item = self.shift_table.horizontalHeaderItem(1) + item.setText(_translate("shift_dialog", "x")) + item = self.shift_table.horizontalHeaderItem(2) + item.setText(_translate("shift_dialog", "y")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.shift_page), _translate("shift_dialog", "Shift")) + self.label_3.setText(_translate("shift_dialog", "

x

")) + self.label_6.setText(_translate("shift_dialog", "Global:")) + self.label_2.setText(_translate("shift_dialog", "

y

")) + item = self.scale_table.horizontalHeaderItem(0) + item.setText(_translate("shift_dialog", "Data")) + item = self.scale_table.horizontalHeaderItem(1) + item.setText(_translate("shift_dialog", "x")) + item = self.scale_table.horizontalHeaderItem(2) + item.setText(_translate("shift_dialog", "y")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.scale_page), _translate("shift_dialog", "Scale")) + self.label_4.setText(_translate("shift_dialog", "Axes:")) + self.xlog_checkbox.setText(_translate("shift_dialog", "log x")) + self.ylog_checkbox.setText(_translate("shift_dialog", "log y")) + self.value_checkbox.setText(_translate("shift_dialog", "Export shift/scale values")) + self.overwrite_checkbox.setText(_translate("shift_dialog", "Overwrite data")) + self.data_newgraph.setText(_translate("shift_dialog", "New graph")) + self.values_newgraph.setText(_translate("shift_dialog", "New graph")) +from ..lib.utils import SciSpinBox +from pyqtgraph import PlotWidget diff --git a/nmreval/gui_qt/_py/skipdialog.py b/nmreval/gui_qt/_py/skipdialog.py new file mode 100644 index 0000000..f541dc2 --- /dev/null +++ b/nmreval/gui_qt/_py/skipdialog.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/skipdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SkipDialog(object): + def setupUi(self, SkipDialog): + SkipDialog.setObjectName("SkipDialog") + SkipDialog.resize(448, 208) + SkipDialog.setWindowTitle("") + self.gridLayout = QtWidgets.QGridLayout(SkipDialog) + self.gridLayout.setObjectName("gridLayout") + self.label_2 = QtWidgets.QLabel(SkipDialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 3, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(SkipDialog) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 0, 0, 1, 4) + self.offset_spinbox = QtWidgets.QSpinBox(SkipDialog) + self.offset_spinbox.setMinimum(0) + self.offset_spinbox.setMaximum(1) + self.offset_spinbox.setProperty("value", 0) + self.offset_spinbox.setObjectName("offset_spinbox") + self.gridLayout.addWidget(self.offset_spinbox, 1, 3, 1, 1) + self.label = QtWidgets.QLabel(SkipDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, 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, 1, 2, 1, 1) + self.step_spinbox = QtWidgets.QSpinBox(SkipDialog) + self.step_spinbox.setMinimum(2) + self.step_spinbox.setObjectName("step_spinbox") + self.gridLayout.addWidget(self.step_spinbox, 1, 1, 1, 1) + self.invert_check = QtWidgets.QCheckBox(SkipDialog) + self.invert_check.setLayoutDirection(QtCore.Qt.LeftToRight) + self.invert_check.setObjectName("invert_check") + self.gridLayout.addWidget(self.invert_check, 1, 4, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(SkipDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 5) + self.hide_button = QtWidgets.QRadioButton(SkipDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.hide_button.sizePolicy().hasHeightForWidth()) + self.hide_button.setSizePolicy(sizePolicy) + self.hide_button.setChecked(True) + self.hide_button.setObjectName("hide_button") + self.buttonGroup = QtWidgets.QButtonGroup(SkipDialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.hide_button) + self.gridLayout.addWidget(self.hide_button, 2, 0, 1, 2) + self.delete_button = QtWidgets.QRadioButton(SkipDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.delete_button.sizePolicy().hasHeightForWidth()) + self.delete_button.setSizePolicy(sizePolicy) + self.delete_button.setObjectName("delete_button") + self.buttonGroup.addButton(self.delete_button) + self.gridLayout.addWidget(self.delete_button, 2, 2, 1, 3) + self.label_2.setBuddy(self.step_spinbox) + self.label.setBuddy(self.offset_spinbox) + + self.retranslateUi(SkipDialog) + self.buttonBox.accepted.connect(SkipDialog.accept) + self.buttonBox.rejected.connect(SkipDialog.reject) + QtCore.QMetaObject.connectSlotsByName(SkipDialog) + SkipDialog.setTabOrder(self.step_spinbox, self.offset_spinbox) + SkipDialog.setTabOrder(self.offset_spinbox, self.invert_check) + SkipDialog.setTabOrder(self.invert_check, self.hide_button) + SkipDialog.setTabOrder(self.hide_button, self.delete_button) + + def retranslateUi(self, SkipDialog): + _translate = QtCore.QCoreApplication.translate + self.label_2.setText(_translate("SkipDialog", "Step")) + self.label_4.setText(_translate("SkipDialog", "

Show every step point, beginning with offset (<step).
Use Invert to hide every step point.

")) + self.label.setText(_translate("SkipDialog", "Offset")) + self.invert_check.setText(_translate("SkipDialog", "Invert")) + self.hide_button.setText(_translate("SkipDialog", "Hide skipped pts.")) + self.delete_button.setText(_translate("SkipDialog", "Copy without any hidden pts.")) diff --git a/nmreval/gui_qt/_py/smoothdialog.py b/nmreval/gui_qt/_py/smoothdialog.py new file mode 100644 index 0000000..634fde5 --- /dev/null +++ b/nmreval/gui_qt/_py/smoothdialog.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/smoothdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SmoothDialog(object): + def setupUi(self, SmoothDialog): + SmoothDialog.setObjectName("SmoothDialog") + SmoothDialog.resize(451, 220) + self.gridLayout = QtWidgets.QGridLayout(SmoothDialog) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.frac_label = QtWidgets.QLabel(SmoothDialog) + self.frac_label.setObjectName("frac_label") + self.gridLayout.addWidget(self.frac_label, 1, 0, 1, 1) + self.widget_2 = QtWidgets.QWidget(SmoothDialog) + self.widget_2.setObjectName("widget_2") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget_2) + self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_3.setSpacing(3) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_3 = QtWidgets.QLabel(self.widget_2) + self.label_3.setObjectName("label_3") + self.horizontalLayout_3.addWidget(self.label_3) + self.iter_spinBox = QtWidgets.QSpinBox(self.widget_2) + self.iter_spinBox.setMaximum(3) + self.iter_spinBox.setSingleStep(1) + self.iter_spinBox.setProperty("value", 1) + self.iter_spinBox.setObjectName("iter_spinBox") + self.horizontalLayout_3.addWidget(self.iter_spinBox) + self.gridLayout.addWidget(self.widget_2, 3, 0, 1, 2) + self.line = QtWidgets.QFrame(SmoothDialog) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout.addWidget(self.line, 4, 0, 1, 2) + self.buttonBox = QtWidgets.QDialogButtonBox(SmoothDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 2) + self.widget = QtWidgets.QWidget(SmoothDialog) + self.widget.setObjectName("widget") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2.setSpacing(3) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.horizontalLayout_2.addWidget(self.label) + self.polynom_spinBox = QtWidgets.QSpinBox(self.widget) + self.polynom_spinBox.setMinimum(1) + self.polynom_spinBox.setMaximum(3) + self.polynom_spinBox.setObjectName("polynom_spinBox") + self.horizontalLayout_2.addWidget(self.polynom_spinBox) + self.gridLayout.addWidget(self.widget, 2, 0, 1, 2) + self.frac_spinBox = QtWidgets.QSpinBox(SmoothDialog) + self.frac_spinBox.setMinimum(1) + self.frac_spinBox.setMaximum(999) + self.frac_spinBox.setObjectName("frac_spinBox") + self.gridLayout.addWidget(self.frac_spinBox, 1, 1, 1, 1) + self.comboBox = QtWidgets.QComboBox(SmoothDialog) + self.comboBox.setObjectName("comboBox") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.gridLayout.addWidget(self.comboBox, 0, 0, 1, 2) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 6, 0, 1, 1) + self.y_checkBox = QtWidgets.QCheckBox(SmoothDialog) + self.y_checkBox.setObjectName("y_checkBox") + self.gridLayout.addWidget(self.y_checkBox, 5, 1, 1, 1) + self.x_checkBox = QtWidgets.QCheckBox(SmoothDialog) + self.x_checkBox.setObjectName("x_checkBox") + self.gridLayout.addWidget(self.x_checkBox, 5, 0, 1, 1) + self.frac_label.setBuddy(self.frac_spinBox) + self.label_3.setBuddy(self.iter_spinBox) + self.label.setBuddy(self.polynom_spinBox) + + self.retranslateUi(SmoothDialog) + self.buttonBox.accepted.connect(SmoothDialog.accept) + self.buttonBox.rejected.connect(SmoothDialog.reject) + QtCore.QMetaObject.connectSlotsByName(SmoothDialog) + SmoothDialog.setTabOrder(self.comboBox, self.frac_spinBox) + SmoothDialog.setTabOrder(self.frac_spinBox, self.polynom_spinBox) + SmoothDialog.setTabOrder(self.polynom_spinBox, self.iter_spinBox) + SmoothDialog.setTabOrder(self.iter_spinBox, self.x_checkBox) + SmoothDialog.setTabOrder(self.x_checkBox, self.y_checkBox) + + def retranslateUi(self, SmoothDialog): + _translate = QtCore.QCoreApplication.translate + SmoothDialog.setWindowTitle(_translate("SmoothDialog", "1D smoothing filter")) + self.frac_label.setText(_translate("SmoothDialog", "Window length")) + self.label_3.setText(_translate("SmoothDialog", "Iterations")) + self.label.setText(_translate("SmoothDialog", "Polynomial degree")) + self.polynom_spinBox.setToolTip(_translate("SmoothDialog", "Deg")) + self.frac_spinBox.setToolTip(_translate("SmoothDialog", "

Number of data points used as smoothing window.

")) + self.comboBox.setItemText(0, _translate("SmoothDialog", "Moving mean")) + self.comboBox.setItemText(1, _translate("SmoothDialog", "Savitzky-Golay")) + self.comboBox.setItemText(2, _translate("SmoothDialog", "Loess (slow, use for < 10000 pts)")) + self.comboBox.setItemText(3, _translate("SmoothDialog", "Moving median")) + self.comboBox.setItemText(4, _translate("SmoothDialog", "Moving standard deviation")) + self.comboBox.setItemText(5, _translate("SmoothDialog", "Moving variance")) + self.comboBox.setItemText(6, _translate("SmoothDialog", "Moving maximum")) + self.comboBox.setItemText(7, _translate("SmoothDialog", "Moving minimum")) + self.comboBox.setItemText(8, _translate("SmoothDialog", "Moving sum")) + self.y_checkBox.setText(_translate("SmoothDialog", "y log-spaced?")) + self.x_checkBox.setText(_translate("SmoothDialog", "x log-spaced?")) diff --git a/nmreval/gui_qt/_py/t1_calc_dialog.py b/nmreval/gui_qt/_py/t1_calc_dialog.py new file mode 100644 index 0000000..b560eba --- /dev/null +++ b/nmreval/gui_qt/_py/t1_calc_dialog.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/t1_calc_dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(582, 698) + self.gridLayout_2 = QtWidgets.QGridLayout(Dialog) + self.gridLayout_2.setObjectName("gridLayout_2") + self.groupBox_2 = QtWidgets.QGroupBox(Dialog) + self.groupBox_2.setObjectName("groupBox_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2) + self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.specdens_combobox = QtWidgets.QComboBox(self.groupBox_2) + self.specdens_combobox.setObjectName("specdens_combobox") + self.verticalLayout_2.addWidget(self.specdens_combobox) + self.specdens_frame = QtWidgets.QFrame(self.groupBox_2) + self.specdens_frame.setObjectName("specdens_frame") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.specdens_frame) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_2.addWidget(self.specdens_frame) + self.coupling_combobox = QtWidgets.QComboBox(self.groupBox_2) + self.coupling_combobox.setObjectName("coupling_combobox") + self.verticalLayout_2.addWidget(self.coupling_combobox) + self.coupling_frame = QtWidgets.QFrame(self.groupBox_2) + self.coupling_frame.setObjectName("coupling_frame") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.coupling_frame) + self.verticalLayout_4.setContentsMargins(-1, -1, 1, 1) + self.verticalLayout_4.setSpacing(0) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.verticalLayout_2.addWidget(self.coupling_frame) + self.gridLayout_2.addWidget(self.groupBox_2, 1, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(Dialog) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_4.setContentsMargins(3, 3, 3, 3) + self.gridLayout_4.setSpacing(3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.graph_combobox = QtWidgets.QComboBox(self.groupBox_3) + self.graph_combobox.setEnabled(False) + self.graph_combobox.setObjectName("graph_combobox") + self.gridLayout_4.addWidget(self.graph_combobox, 1, 1, 1, 1) + self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3) + self.graph_checkbox.setChecked(True) + self.graph_checkbox.setObjectName("graph_checkbox") + self.gridLayout_4.addWidget(self.graph_checkbox, 1, 0, 1, 1) + self.relax_combox = QtWidgets.QComboBox(self.groupBox_3) + self.relax_combox.setObjectName("relax_combox") + self.relax_combox.addItem("") + self.relax_combox.addItem("") + self.gridLayout_4.addWidget(self.relax_combox, 0, 0, 1, 2) + self.gridLayout_2.addWidget(self.groupBox_3, 2, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem, 3, 0, 1, 1) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout_2.addWidget(self.buttonBox, 4, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(Dialog) + self.groupBox.setObjectName("groupBox") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_3.setContentsMargins(3, 3, 3, 3) + self.gridLayout_3.setSpacing(2) + self.gridLayout_3.setObjectName("gridLayout_3") + self.second_x_lineEdit = QtWidgets.QLineEdit(self.groupBox) + self.second_x_lineEdit.setObjectName("second_x_lineEdit") + self.gridLayout_3.addWidget(self.second_x_lineEdit, 10, 1, 1, 1) + self.x_input_combobox = QtWidgets.QComboBox(self.groupBox) + self.x_input_combobox.setObjectName("x_input_combobox") + self.x_input_combobox.addItem("") + self.x_input_combobox.addItem("") + self.gridLayout_3.addWidget(self.x_input_combobox, 1, 1, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setSpacing(2) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.radioButton = QtWidgets.QRadioButton(self.groupBox) + self.radioButton.setChecked(True) + self.radioButton.setObjectName("radioButton") + self.buttonGroup = QtWidgets.QButtonGroup(Dialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.radioButton) + self.horizontalLayout_4.addWidget(self.radioButton) + self.radioButton_2 = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_2.setObjectName("radioButton_2") + self.buttonGroup.addButton(self.radioButton_2) + self.horizontalLayout_4.addWidget(self.radioButton_2) + self.radioButton_4 = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_4.setObjectName("radioButton_4") + self.buttonGroup.addButton(self.radioButton_4) + self.horizontalLayout_4.addWidget(self.radioButton_4) + self.radioButton_3 = QtWidgets.QRadioButton(self.groupBox) + self.radioButton_3.setObjectName("radioButton_3") + self.buttonGroup.addButton(self.radioButton_3) + self.horizontalLayout_4.addWidget(self.radioButton_3) + self.gridLayout_3.addLayout(self.horizontalLayout_4, 0, 0, 1, 2) + self.label_7 = QtWidgets.QLabel(self.groupBox) + self.label_7.setObjectName("label_7") + self.gridLayout_3.addWidget(self.label_7, 10, 0, 1, 1) + self.line = QtWidgets.QFrame(self.groupBox) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.gridLayout_3.addWidget(self.line, 9, 0, 1, 2) + self.line_2 = QtWidgets.QFrame(self.groupBox) + self.line_2.setFrameShape(QtWidgets.QFrame.HLine) + self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_2.setObjectName("line_2") + self.gridLayout_3.addWidget(self.line_2, 7, 0, 1, 2) + self.range_widget = QtWidgets.QWidget(self.groupBox) + self.range_widget.setObjectName("range_widget") + self.gridLayout = QtWidgets.QGridLayout(self.range_widget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 0, 0, 1, 1) + self.stop_lineEdit = QtWidgets.QLineEdit(self.range_widget) + self.stop_lineEdit.setObjectName("stop_lineEdit") + self.gridLayout.addWidget(self.stop_lineEdit, 0, 3, 1, 1) + self.label_4 = QtWidgets.QLabel(self.range_widget) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 0, 2, 1, 1) + self.spinBox = QtWidgets.QSpinBox(self.range_widget) + self.spinBox.setProperty("value", 50) + self.spinBox.setObjectName("spinBox") + self.gridLayout.addWidget(self.spinBox, 0, 5, 1, 1) + self.start_lineEdit = QtWidgets.QLineEdit(self.range_widget) + self.start_lineEdit.setObjectName("start_lineEdit") + self.gridLayout.addWidget(self.start_lineEdit, 0, 1, 1, 1) + self.checkBox = QtWidgets.QCheckBox(self.range_widget) + self.checkBox.setObjectName("checkBox") + self.gridLayout.addWidget(self.checkBox, 0, 6, 1, 1) + self.gridLayout_3.addWidget(self.range_widget, 2, 0, 1, 2) + self.data_widget = QtWidgets.QWidget(self.groupBox) + self.data_widget.setObjectName("data_widget") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.data_widget) + self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2.setSpacing(2) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.tau_graph_combobox = QtWidgets.QComboBox(self.data_widget) + self.tau_graph_combobox.setObjectName("tau_graph_combobox") + self.horizontalLayout_2.addWidget(self.tau_graph_combobox) + self.tau_set_combobox = QtWidgets.QComboBox(self.data_widget) + self.tau_set_combobox.setObjectName("tau_set_combobox") + self.horizontalLayout_2.addWidget(self.tau_set_combobox) + self.label_10 = QtWidgets.QLabel(self.data_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth()) + self.label_10.setSizePolicy(sizePolicy) + self.label_10.setObjectName("label_10") + self.horizontalLayout_2.addWidget(self.label_10) + self.x_radioButton = QtWidgets.QRadioButton(self.data_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.x_radioButton.sizePolicy().hasHeightForWidth()) + self.x_radioButton.setSizePolicy(sizePolicy) + self.x_radioButton.setObjectName("x_radioButton") + self.buttonGroup_2 = QtWidgets.QButtonGroup(Dialog) + self.buttonGroup_2.setObjectName("buttonGroup_2") + self.buttonGroup_2.addButton(self.x_radioButton) + self.horizontalLayout_2.addWidget(self.x_radioButton) + self.y_radioButton = QtWidgets.QRadioButton(self.data_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.y_radioButton.sizePolicy().hasHeightForWidth()) + self.y_radioButton.setSizePolicy(sizePolicy) + self.y_radioButton.setChecked(True) + self.y_radioButton.setObjectName("y_radioButton") + self.buttonGroup_2.addButton(self.y_radioButton) + self.horizontalLayout_2.addWidget(self.y_radioButton) + self.gridLayout_3.addWidget(self.data_widget, 3, 0, 1, 2) + self.temp_widget = QtWidgets.QWidget(self.groupBox) + self.temp_widget.setObjectName("temp_widget") + self.gridLayout_5 = QtWidgets.QGridLayout(self.temp_widget) + self.gridLayout_5.setContentsMargins(0, 0, 0, 0) + self.gridLayout_5.setHorizontalSpacing(6) + self.gridLayout_5.setVerticalSpacing(0) + self.gridLayout_5.setObjectName("gridLayout_5") + self.arr_widget = QtWidgets.QWidget(self.temp_widget) + self.arr_widget.setObjectName("arr_widget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.arr_widget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(2) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem2) + self.label = QtWidgets.QLabel(self.arr_widget) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.tau0_lineEdit = QtWidgets.QLineEdit(self.arr_widget) + self.tau0_lineEdit.setObjectName("tau0_lineEdit") + self.horizontalLayout.addWidget(self.tau0_lineEdit) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem3) + self.label_2 = QtWidgets.QLabel(self.arr_widget) + self.label_2.setObjectName("label_2") + self.horizontalLayout.addWidget(self.label_2) + self.ea_lineEdit = QtWidgets.QLineEdit(self.arr_widget) + self.ea_lineEdit.setObjectName("ea_lineEdit") + self.horizontalLayout.addWidget(self.ea_lineEdit) + self.gridLayout_5.addWidget(self.arr_widget, 0, 1, 1, 1) + self.vft_widget = QtWidgets.QWidget(self.temp_widget) + self.vft_widget.setObjectName("vft_widget") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.vft_widget) + self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_3.setSpacing(2) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_3 = QtWidgets.QLabel(self.vft_widget) + self.label_3.setObjectName("label_3") + self.horizontalLayout_3.addWidget(self.label_3) + self.tau0_vft_lineEdit = QtWidgets.QLineEdit(self.vft_widget) + self.tau0_vft_lineEdit.setObjectName("tau0_vft_lineEdit") + self.horizontalLayout_3.addWidget(self.tau0_vft_lineEdit) + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_3.addItem(spacerItem4) + self.label_5 = QtWidgets.QLabel(self.vft_widget) + self.label_5.setObjectName("label_5") + self.horizontalLayout_3.addWidget(self.label_5) + self.b_vft_lineEdit = QtWidgets.QLineEdit(self.vft_widget) + self.b_vft_lineEdit.setObjectName("b_vft_lineEdit") + self.horizontalLayout_3.addWidget(self.b_vft_lineEdit) + spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_3.addItem(spacerItem5) + self.label_6 = QtWidgets.QLabel(self.vft_widget) + self.label_6.setObjectName("label_6") + self.horizontalLayout_3.addWidget(self.label_6) + self.t0_vft_lineEdit = QtWidgets.QLineEdit(self.vft_widget) + self.t0_vft_lineEdit.setObjectName("t0_vft_lineEdit") + self.horizontalLayout_3.addWidget(self.t0_vft_lineEdit) + self.gridLayout_5.addWidget(self.vft_widget, 1, 1, 1, 1) + self.temp_combobox = QtWidgets.QComboBox(self.temp_widget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.temp_combobox.sizePolicy().hasHeightForWidth()) + self.temp_combobox.setSizePolicy(sizePolicy) + self.temp_combobox.setObjectName("temp_combobox") + self.temp_combobox.addItem("") + self.temp_combobox.addItem("") + self.gridLayout_5.addWidget(self.temp_combobox, 0, 0, 2, 1) + self.gridLayout_3.addWidget(self.temp_widget, 4, 0, 1, 2) + self.xtype_combobox = QtWidgets.QComboBox(self.groupBox) + self.xtype_combobox.setObjectName("xtype_combobox") + self.xtype_combobox.addItem("") + self.xtype_combobox.addItem("") + self.xtype_combobox.addItem("") + self.xtype_combobox.addItem("") + self.gridLayout_3.addWidget(self.xtype_combobox, 8, 1, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox) + self.label_8.setObjectName("label_8") + self.gridLayout_3.addWidget(self.label_8, 8, 0, 1, 1) + self.label_9 = QtWidgets.QLabel(self.groupBox) + self.label_9.setObjectName("label_9") + self.gridLayout_3.addWidget(self.label_9, 1, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox, 0, 0, 1, 1) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Calculate relaxation")) + self.groupBox_2.setTitle(_translate("Dialog", "Model")) + self.groupBox_3.setTitle(_translate("Dialog", "Result")) + self.graph_checkbox.setText(_translate("Dialog", "New graph?")) + self.relax_combox.setStatusTip(_translate("Dialog", "NOTE: Mean values are not available for all spectral densities. For more information ask someone.")) + self.relax_combox.setItemText(0, _translate("Dialog", "Spin-Lattice Relaxation T1")) + self.relax_combox.setItemText(1, _translate("Dialog", "Spin-Spin Relaxation T2")) + self.groupBox.setTitle(_translate("Dialog", "Axis")) + self.x_input_combobox.setItemText(0, _translate("Dialog", "Range")) + self.x_input_combobox.setItemText(1, _translate("Dialog", "Data")) + self.radioButton.setText(_translate("Dialog", "τ / s")) + self.radioButton_2.setText(_translate("Dialog", "ω / Hz")) + self.radioButton_4.setText(_translate("Dialog", "1000 K / T")) + self.radioButton_3.setText(_translate("Dialog", "T / K")) + self.label_7.setText(_translate("Dialog", "2nd axis")) + self.label_4.setText(_translate("Dialog", "–")) + self.spinBox.setSuffix(_translate("Dialog", " pts.")) + self.checkBox.setText(_translate("Dialog", "Log?")) + self.label_10.setText(_translate("Dialog", " Use")) + self.x_radioButton.setText(_translate("Dialog", "x")) + self.y_radioButton.setText(_translate("Dialog", "y")) + self.label.setText(_translate("Dialog", "τ0 / s ")) + self.tau0_lineEdit.setText(_translate("Dialog", "1")) + self.label_2.setText(_translate("Dialog", "E_A / eV ")) + self.ea_lineEdit.setText(_translate("Dialog", "1")) + self.label_3.setText(_translate("Dialog", "τ0 / s ")) + self.tau0_vft_lineEdit.setText(_translate("Dialog", "1")) + self.label_5.setText(_translate("Dialog", "B / K ")) + self.b_vft_lineEdit.setText(_translate("Dialog", "1")) + self.label_6.setText(_translate("Dialog", "T_0 / K ")) + self.t0_vft_lineEdit.setText(_translate("Dialog", "1")) + self.temp_combobox.setItemText(0, _translate("Dialog", "Arrhenius")) + self.temp_combobox.setItemText(1, _translate("Dialog", "VFT")) + self.xtype_combobox.setItemText(0, _translate("Dialog", "Function parameter: τ")) + self.xtype_combobox.setItemText(1, _translate("Dialog", "Peak time: τₚ")) + self.xtype_combobox.setItemText(2, _translate("Dialog", "Arithmetic mean: ⟨τ⟩")) + self.xtype_combobox.setItemText(3, _translate("Dialog", "Geometric mean: exp(⟨ln τ⟩)")) + self.label_8.setText(_translate("Dialog", "Interpretation as:")) + self.label_9.setText(_translate("Dialog", "Input from: ")) diff --git a/nmreval/gui_qt/_py/t1_dock.py b/nmreval/gui_qt/_py/t1_dock.py new file mode 100644 index 0000000..cc1bdf0 --- /dev/null +++ b/nmreval/gui_qt/_py/t1_dock.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/t1_dock.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_t1dialog(object): + def setupUi(self, t1dialog): + t1dialog.setObjectName("t1dialog") + t1dialog.resize(295, 771) + self.dockWidgetContents = QtWidgets.QWidget() + self.dockWidgetContents.setObjectName("dockWidgetContents") + self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents) + self.verticalLayout.setObjectName("verticalLayout") + self.scrollArea = QtWidgets.QScrollArea(self.dockWidgetContents) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents = QtWidgets.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 279, 727)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents) + self.verticalLayout_7.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_7.setSpacing(0) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.groupBox_1 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_1.setObjectName("groupBox_1") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_1) + self.gridLayout_3.setVerticalSpacing(0) + self.gridLayout_3.setObjectName("gridLayout_3") + self.comboBox_3 = QtWidgets.QComboBox(self.groupBox_1) + self.comboBox_3.setObjectName("comboBox_3") + self.comboBox_3.addItem("") + self.comboBox_3.addItem("") + self.comboBox_3.addItem("") + self.comboBox_3.addItem("") + self.gridLayout_3.addWidget(self.comboBox_3, 0, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.groupBox_1) + self.label_6.setObjectName("label_6") + self.gridLayout_3.addWidget(self.label_6, 0, 0, 1, 1) + self.comboBox_2 = QtWidgets.QComboBox(self.groupBox_1) + self.comboBox_2.setObjectName("comboBox_2") + self.comboBox_2.addItem("") + self.comboBox_2.addItem("") + self.comboBox_2.addItem("") + self.gridLayout_3.addWidget(self.comboBox_2, 1, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.groupBox_1) + self.label_5.setObjectName("label_5") + self.gridLayout_3.addWidget(self.label_5, 1, 0, 1, 1) + self.verticalLayout_7.addWidget(self.groupBox_1) + self.groupBox_5 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_5.setObjectName("groupBox_5") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_5) + self.verticalLayout_6.setSpacing(0) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.comboBox_6 = QtWidgets.QComboBox(self.groupBox_5) + self.comboBox_6.setObjectName("comboBox_6") + self.comboBox_6.addItem("") + self.comboBox_6.addItem("") + self.comboBox_6.addItem("") + self.comboBox_6.addItem("") + self.verticalLayout_6.addWidget(self.comboBox_6) + self.frame = QtWidgets.QFrame(self.groupBox_5) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame.setObjectName("frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout.setContentsMargins(0, 6, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_9 = QtWidgets.QLabel(self.frame) + self.label_9.setObjectName("label_9") + self.horizontalLayout.addWidget(self.label_9) + self.lineEdit_2 = QtWidgets.QLineEdit(self.frame) + self.lineEdit_2.setObjectName("lineEdit_2") + self.horizontalLayout.addWidget(self.lineEdit_2) + self.label_10 = QtWidgets.QLabel(self.frame) + self.label_10.setObjectName("label_10") + self.horizontalLayout.addWidget(self.label_10) + self.lineEdit_3 = QtWidgets.QLineEdit(self.frame) + self.lineEdit_3.setObjectName("lineEdit_3") + self.horizontalLayout.addWidget(self.lineEdit_3) + self.checkBox = QtWidgets.QCheckBox(self.frame) + self.checkBox.setObjectName("checkBox") + self.horizontalLayout.addWidget(self.checkBox) + self.verticalLayout_6.addWidget(self.frame) + self.frame_2 = QtWidgets.QFrame(self.groupBox_5) + self.frame_2.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame_2.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame_2.setObjectName("frame_2") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_2) + self.horizontalLayout_2.setContentsMargins(0, 6, 0, 0) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_11 = QtWidgets.QLabel(self.frame_2) + self.label_11.setObjectName("label_11") + self.horizontalLayout_2.addWidget(self.label_11) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.label_13 = QtWidgets.QLabel(self.frame_2) + self.label_13.setObjectName("label_13") + self.horizontalLayout_2.addWidget(self.label_13) + self.label_12 = QtWidgets.QLabel(self.frame_2) + self.label_12.setObjectName("label_12") + self.horizontalLayout_2.addWidget(self.label_12) + self.pushButton_2 = QtWidgets.QPushButton(self.frame_2) + self.pushButton_2.setObjectName("pushButton_2") + self.horizontalLayout_2.addWidget(self.pushButton_2) + self.verticalLayout_6.addWidget(self.frame_2) + self.verticalLayout_7.addWidget(self.groupBox_5) + self.groupBox_2 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label_3 = QtWidgets.QLabel(self.groupBox_2) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 0, 3, 1, 1) + self.t1_min_edit = QtWidgets.QLineEdit(self.groupBox_2) + self.t1_min_edit.setObjectName("t1_min_edit") + self.gridLayout.addWidget(self.t1_min_edit, 0, 4, 1, 1) + self.label_7 = QtWidgets.QLabel(self.groupBox_2) + self.label_7.setObjectName("label_7") + self.gridLayout.addWidget(self.label_7, 0, 5, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 0, 1, 1, 1) + self.t1_pos_edit = QtWidgets.QLineEdit(self.groupBox_2) + self.t1_pos_edit.setObjectName("t1_pos_edit") + self.gridLayout.addWidget(self.t1_pos_edit, 0, 2, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 1, 0, 1, 1) + self.label_8 = QtWidgets.QLabel(self.groupBox_2) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 1, 5, 1, 1) + self.lineEdit = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit.setInputMethodHints(QtCore.Qt.ImhDigitsOnly|QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout.addWidget(self.lineEdit, 1, 4, 1, 1) + self.verticalLayout_7.addWidget(self.groupBox_2) + self.groupBox = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox.setObjectName("groupBox") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.comboBox_4 = QtWidgets.QComboBox(self.groupBox) + self.comboBox_4.setObjectName("comboBox_4") + self.verticalLayout_2.addWidget(self.comboBox_4) + self.verticalLayout_3 = QtWidgets.QVBoxLayout() + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_2.addLayout(self.verticalLayout_3) + self.label_14 = QtWidgets.QLabel(self.groupBox) + self.label_14.setObjectName("label_14") + self.verticalLayout_2.addWidget(self.label_14) + self.verticalLayout_7.addWidget(self.groupBox) + self.groupBox_4 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_4.setObjectName("groupBox_4") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.groupBox_4) + self.verticalLayout_5.setSpacing(0) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.comboBox_5 = QtWidgets.QComboBox(self.groupBox_4) + self.comboBox_5.setObjectName("comboBox_5") + self.verticalLayout_5.addWidget(self.comboBox_5) + self.verticalLayout_4 = QtWidgets.QVBoxLayout() + self.verticalLayout_4.setContentsMargins(-1, -1, 0, 0) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.verticalLayout_5.addLayout(self.verticalLayout_4) + self.verticalLayout_7.addWidget(self.groupBox_4) + self.groupBox_3 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_2.setContentsMargins(-1, 1, -1, -1) + self.gridLayout_2.setObjectName("gridLayout_2") + self.comboBox = QtWidgets.QComboBox(self.groupBox_3) + self.comboBox.setObjectName("comboBox") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.gridLayout_2.addWidget(self.comboBox, 0, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.groupBox_3) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 0, 0, 1, 1) + self.checkBox_interpol = QtWidgets.QCheckBox(self.groupBox_3) + self.checkBox_interpol.setObjectName("checkBox_interpol") + self.gridLayout_2.addWidget(self.checkBox_interpol, 1, 1, 1, 1) + self.verticalLayout_7.addWidget(self.groupBox_3) + self.pushButton = QtWidgets.QPushButton(self.scrollAreaWidgetContents) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.pushButton.setFont(font) + self.pushButton.setObjectName("pushButton") + self.verticalLayout_7.addWidget(self.pushButton) + spacerItem2 = QtWidgets.QSpacerItem(17, 423, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_7.addItem(spacerItem2) + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + self.verticalLayout.addWidget(self.scrollArea) + t1dialog.setWidget(self.dockWidgetContents) + + self.retranslateUi(t1dialog) + QtCore.QMetaObject.connectSlotsByName(t1dialog) + + def retranslateUi(self, t1dialog): + _translate = QtCore.QCoreApplication.translate + t1dialog.setWindowTitle(_translate("t1dialog", "Evaluate T1 (temperature)")) + self.groupBox_1.setTitle(_translate("t1dialog", "Axes")) + self.comboBox_3.setItemText(0, _translate("t1dialog", "T1")) + self.comboBox_3.setItemText(1, _translate("t1dialog", "1/T1")) + self.comboBox_3.setItemText(2, _translate("t1dialog", "log10(T1)")) + self.comboBox_3.setItemText(3, _translate("t1dialog", "log10(1/T1)")) + self.label_6.setText(_translate("t1dialog", "Relaxation in")) + self.comboBox_2.setItemText(0, _translate("t1dialog", "T")) + self.comboBox_2.setItemText(1, _translate("t1dialog", "1000/T")) + self.comboBox_2.setItemText(2, _translate("t1dialog", "1/T")) + self.label_5.setText(_translate("t1dialog", "Temperature in")) + self.groupBox_5.setTitle(_translate("t1dialog", "T1 minimon")) + self.comboBox_6.setItemText(0, _translate("t1dialog", "Data minimum")) + self.comboBox_6.setItemText(1, _translate("t1dialog", "Parabola")) + self.comboBox_6.setItemText(2, _translate("t1dialog", "Cubic spline")) + self.comboBox_6.setItemText(3, _translate("t1dialog", "Pchip")) + self.label_9.setText(_translate("t1dialog", "start (K)")) + self.lineEdit_2.setPlaceholderText(_translate("t1dialog", "1")) + self.label_10.setText(_translate("t1dialog", " end (K)")) + self.lineEdit_3.setPlaceholderText(_translate("t1dialog", "2")) + self.checkBox.setText(_translate("t1dialog", "Show?")) + self.label_11.setText(_translate("t1dialog", "Minimon")) + self.label_13.setText(_translate("t1dialog", "x value")) + self.label_12.setText(_translate("t1dialog", "y value")) + self.pushButton_2.setText(_translate("t1dialog", "Use")) + self.groupBox_2.setTitle(_translate("t1dialog", "Parameter")) + self.label_3.setText(_translate("t1dialog", "

T1 minimum

")) + self.label_2.setText(_translate("t1dialog", "K")) + self.t1_min_edit.setPlaceholderText(_translate("t1dialog", "1e-3")) + self.label_7.setText(_translate("t1dialog", "s")) + self.t1_pos_edit.setPlaceholderText(_translate("t1dialog", "100")) + self.label.setText(_translate("t1dialog", "frequency")) + self.label_8.setText(_translate("t1dialog", "Hz")) + self.lineEdit.setPlaceholderText(_translate("t1dialog", "100e6")) + self.groupBox.setTitle(_translate("t1dialog", "Spectral density")) + self.label_14.setText(_translate("t1dialog", "Calculated minimum:")) + self.groupBox_4.setTitle(_translate("t1dialog", "Coupling")) + self.groupBox_3.setTitle(_translate("t1dialog", "Result")) + self.comboBox.setStatusTip(_translate("t1dialog", "NOTE: Mean values are not available for all spectral densities. For more information ask someone.")) + self.comboBox.setItemText(0, _translate("t1dialog", "Fit parameter: τ")) + self.comboBox.setItemText(1, _translate("t1dialog", "Peak time: τₚ")) + self.comboBox.setItemText(2, _translate("t1dialog", "Arithmetic mean: ⟨τ⟩")) + self.comboBox.setItemText(3, _translate("t1dialog", "Geometric mean: exp(⟨ln τ⟩)")) + self.label_4.setText(_translate("t1dialog", "Result as")) + self.checkBox_interpol.setText(_translate("t1dialog", "Use interpolation")) + self.pushButton.setText(_translate("t1dialog", "Calculate")) diff --git a/nmreval/gui_qt/_py/t1_tau_calculation.py b/nmreval/gui_qt/_py/t1_tau_calculation.py new file mode 100644 index 0000000..d61945a --- /dev/null +++ b/nmreval/gui_qt/_py/t1_tau_calculation.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/t1_tau_calculation.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 799) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setObjectName("verticalLayout") + self.groupBox_2 = QtWidgets.QGroupBox(Form) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_2.setContentsMargins(3, 3, 3, 3) + self.gridLayout_2.setHorizontalSpacing(3) + self.gridLayout_2.setVerticalSpacing(1) + self.gridLayout_2.setObjectName("gridLayout_2") + self.freq_spinbox = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.freq_spinbox.setMaximum(999.99) + self.freq_spinbox.setProperty("value", 100.0) + self.freq_spinbox.setObjectName("freq_spinbox") + self.gridLayout_2.addWidget(self.freq_spinbox, 0, 0, 1, 1) + self.freq_combox = QtWidgets.QComboBox(self.groupBox_2) + self.freq_combox.setObjectName("freq_combox") + self.freq_combox.addItem("") + self.freq_combox.addItem("") + self.freq_combox.addItem("") + self.gridLayout_2.addWidget(self.freq_combox, 0, 1, 1, 1) + self.verticalLayout.addWidget(self.groupBox_2) + self.scrollArea = QtWidgets.QScrollArea(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) + self.scrollArea.setSizePolicy(sizePolicy) + self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) + self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain) + self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents = QtWidgets.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 388, 713)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_5.setSpacing(3) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.groupBox_6 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_6.setObjectName("groupBox_6") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_6) + self.verticalLayout_7.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_7.setSpacing(1) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.comboBox_7 = QtWidgets.QComboBox(self.groupBox_6) + self.comboBox_7.setObjectName("comboBox_7") + self.comboBox_7.addItem("") + self.comboBox_7.addItem("") + self.comboBox_7.addItem("") + self.verticalLayout_7.addWidget(self.comboBox_7) + self.arr_frame = QtWidgets.QFrame(self.groupBox_6) + self.arr_frame.setObjectName("arr_frame") + self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.arr_frame) + self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_6.setSpacing(1) + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.verticalLayout_7.addWidget(self.arr_frame) + self.temp_frame = QtWidgets.QFrame(self.groupBox_6) + self.temp_frame.setObjectName("temp_frame") + self.gridLayout = QtWidgets.QGridLayout(self.temp_frame) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.lineEdit_3 = QtWidgets.QLineEdit(self.temp_frame) + self.lineEdit_3.setObjectName("lineEdit_3") + self.gridLayout.addWidget(self.lineEdit_3, 0, 1, 1, 1) + self.checkBox = QtWidgets.QCheckBox(self.temp_frame) + self.checkBox.setObjectName("checkBox") + self.gridLayout.addWidget(self.checkBox, 0, 7, 1, 1) + self.label_4 = QtWidgets.QLabel(self.temp_frame) + self.label_4.setObjectName("label_4") + self.gridLayout.addWidget(self.label_4, 0, 2, 1, 1) + self.lineEdit_4 = QtWidgets.QLineEdit(self.temp_frame) + self.lineEdit_4.setObjectName("lineEdit_4") + self.gridLayout.addWidget(self.lineEdit_4, 0, 3, 1, 1) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 0, 4, 1, 1) + self.spinBox = QtWidgets.QSpinBox(self.temp_frame) + self.spinBox.setProperty("value", 50) + self.spinBox.setObjectName("spinBox") + self.gridLayout.addWidget(self.spinBox, 0, 5, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem1, 0, 6, 1, 1) + self.verticalLayout_7.addWidget(self.temp_frame) + self.data_frame = QtWidgets.QFrame(self.groupBox_6) + self.data_frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.data_frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.data_frame.setObjectName("data_frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.data_frame) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setSpacing(1) + self.horizontalLayout.setObjectName("horizontalLayout") + self.tau_graph_combobox = QtWidgets.QComboBox(self.data_frame) + self.tau_graph_combobox.setObjectName("tau_graph_combobox") + self.horizontalLayout.addWidget(self.tau_graph_combobox) + self.tau_set_combobox = QtWidgets.QComboBox(self.data_frame) + self.tau_set_combobox.setObjectName("tau_set_combobox") + self.horizontalLayout.addWidget(self.tau_set_combobox) + self.verticalLayout_7.addWidget(self.data_frame) + self.comboBox = QtWidgets.QComboBox(self.groupBox_6) + self.comboBox.setObjectName("comboBox") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.comboBox.addItem("") + self.verticalLayout_7.addWidget(self.comboBox) + self.verticalLayout_5.addWidget(self.groupBox_6) + self.groupBox = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox.setObjectName("groupBox") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.specdens_combobox = QtWidgets.QComboBox(self.groupBox) + self.specdens_combobox.setObjectName("specdens_combobox") + self.verticalLayout_2.addWidget(self.specdens_combobox) + self.specdens_frame = QtWidgets.QFrame(self.groupBox) + self.specdens_frame.setObjectName("specdens_frame") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.specdens_frame) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_2.addWidget(self.specdens_frame) + self.coupling_combobox = QtWidgets.QComboBox(self.groupBox) + self.coupling_combobox.setObjectName("coupling_combobox") + self.verticalLayout_2.addWidget(self.coupling_combobox) + self.coupling_frame = QtWidgets.QFrame(self.groupBox) + self.coupling_frame.setObjectName("coupling_frame") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.coupling_frame) + self.verticalLayout_4.setContentsMargins(-1, -1, 1, 1) + self.verticalLayout_4.setSpacing(0) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.verticalLayout_2.addWidget(self.coupling_frame) + self.verticalLayout_5.addWidget(self.groupBox) + self.groupBox_3 = QtWidgets.QGroupBox(self.scrollAreaWidgetContents) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_4.setContentsMargins(3, 3, 3, 3) + self.gridLayout_4.setSpacing(3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.tau_combox = QtWidgets.QComboBox(self.groupBox_3) + self.tau_combox.setObjectName("tau_combox") + self.tau_combox.addItem("") + self.tau_combox.addItem("") + self.tau_combox.addItem("") + self.tau_combox.addItem("") + self.gridLayout_4.addWidget(self.tau_combox, 0, 0, 1, 2) + self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3) + self.graph_checkbox.setChecked(True) + self.graph_checkbox.setObjectName("graph_checkbox") + self.gridLayout_4.addWidget(self.graph_checkbox, 1, 0, 1, 1) + self.graph_combobox = QtWidgets.QComboBox(self.groupBox_3) + self.graph_combobox.setEnabled(True) + self.graph_combobox.setObjectName("graph_combobox") + self.gridLayout_4.addWidget(self.graph_combobox, 1, 1, 1, 1) + self.verticalLayout_5.addWidget(self.groupBox_3) + self.pushButton = QtWidgets.QPushButton(self.scrollAreaWidgetContents) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.pushButton.setFont(font) + self.pushButton.setObjectName("pushButton") + self.verticalLayout_5.addWidget(self.pushButton) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_5.addItem(spacerItem2) + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + self.verticalLayout.addWidget(self.scrollArea) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) + self.groupBox_2.setTitle(_translate("Form", "Frequency")) + self.freq_combox.setItemText(0, _translate("Form", "MHz")) + self.freq_combox.setItemText(1, _translate("Form", "kHz")) + self.freq_combox.setItemText(2, _translate("Form", "Hz")) + self.groupBox_6.setTitle(_translate("Form", "Correlation times")) + self.comboBox_7.setItemText(0, _translate("Form", "Arrhenius")) + self.comboBox_7.setItemText(1, _translate("Form", "VFT")) + self.comboBox_7.setItemText(2, _translate("Form", "Data")) + self.lineEdit_3.setPlaceholderText(_translate("Form", "1 K")) + self.checkBox.setText(_translate("Form", "1000/T")) + self.label_4.setText(_translate("Form", "–")) + self.lineEdit_4.setPlaceholderText(_translate("Form", "100 K")) + self.spinBox.setSuffix(_translate("Form", " pts.")) + self.comboBox.setItemText(0, _translate("Form", "Fit parameter: τ")) + self.comboBox.setItemText(1, _translate("Form", "Peak time: τₚ")) + self.comboBox.setItemText(2, _translate("Form", "Arithmetic mean: ⟨τ⟩")) + self.comboBox.setItemText(3, _translate("Form", "Geometric mean: exp(⟨ln τ⟩)")) + self.groupBox.setTitle(_translate("Form", "Model")) + self.groupBox_3.setTitle(_translate("Form", "Result")) + self.tau_combox.setStatusTip(_translate("Form", "NOTE: Mean values are not available for all spectral densities. For more information ask someone.")) + self.tau_combox.setItemText(0, _translate("Form", "Fit parameter: τ")) + self.tau_combox.setItemText(1, _translate("Form", "Peak time: τₚ")) + self.tau_combox.setItemText(2, _translate("Form", "Arithmetic mean: ⟨τ⟩")) + self.tau_combox.setItemText(3, _translate("Form", "Geometric mean: exp(⟨ln τ⟩)")) + self.graph_checkbox.setText(_translate("Form", "New graph?")) + self.pushButton.setText(_translate("Form", "Calculate")) diff --git a/nmreval/gui_qt/_py/t1dialog.py b/nmreval/gui_qt/_py/t1dialog.py new file mode 100644 index 0000000..7f6c421 --- /dev/null +++ b/nmreval/gui_qt/_py/t1dialog.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/t1dialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_t1dialog(object): + def setupUi(self, t1dialog): + t1dialog.setObjectName("t1dialog") + t1dialog.resize(316, 741) + self.gridLayout_2 = QtWidgets.QGridLayout(t1dialog) + self.gridLayout_2.setContentsMargins(3, 3, 3, 3) + self.gridLayout_2.setObjectName("gridLayout_2") + self.groupBox_1 = QtWidgets.QGroupBox(t1dialog) + self.groupBox_1.setObjectName("groupBox_1") + self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_1) + self.gridLayout_3.setContentsMargins(3, 3, 3, 3) + self.gridLayout_3.setHorizontalSpacing(3) + self.gridLayout_3.setVerticalSpacing(0) + self.gridLayout_3.setObjectName("gridLayout_3") + self.t1_combobox = QtWidgets.QComboBox(self.groupBox_1) + self.t1_combobox.setObjectName("t1_combobox") + self.t1_combobox.addItem("") + self.t1_combobox.addItem("") + self.gridLayout_3.addWidget(self.t1_combobox, 1, 1, 1, 1) + self.temp_combobox = QtWidgets.QComboBox(self.groupBox_1) + self.temp_combobox.setObjectName("temp_combobox") + self.temp_combobox.addItem("") + self.temp_combobox.addItem("") + self.temp_combobox.addItem("") + self.gridLayout_3.addWidget(self.temp_combobox, 1, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_1, 0, 0, 1, 1) + self.groupBox_2 = QtWidgets.QGroupBox(t1dialog) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout.setContentsMargins(3, 3, 3, 3) + self.gridLayout.setHorizontalSpacing(3) + self.gridLayout.setVerticalSpacing(1) + self.gridLayout.setObjectName("gridLayout") + self.freq_combox = QtWidgets.QComboBox(self.groupBox_2) + self.freq_combox.setObjectName("freq_combox") + self.freq_combox.addItem("") + self.freq_combox.addItem("") + self.freq_combox.addItem("") + self.gridLayout.addWidget(self.freq_combox, 0, 1, 1, 1) + self.freq_spinbox = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.freq_spinbox.setMaximum(999.99) + self.freq_spinbox.setProperty("value", 100.0) + self.freq_spinbox.setObjectName("freq_spinbox") + self.gridLayout.addWidget(self.freq_spinbox, 0, 0, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_2, 1, 0, 1, 1) + self.groupBox = QtWidgets.QGroupBox(t1dialog) + self.groupBox.setObjectName("groupBox") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox) + self.verticalLayout_2.setContentsMargins(3, 3, 3, -1) + self.verticalLayout_2.setSpacing(2) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.specdens_combobox = QtWidgets.QComboBox(self.groupBox) + self.specdens_combobox.setObjectName("specdens_combobox") + self.verticalLayout_2.addWidget(self.specdens_combobox) + self.sd_frame = QtWidgets.QFrame(self.groupBox) + self.sd_frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.sd_frame.setFrameShadow(QtWidgets.QFrame.Sunken) + self.sd_frame.setObjectName("sd_frame") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.sd_frame) + self.verticalLayout_3.setContentsMargins(1, 1, 1, 1) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout_2.addWidget(self.sd_frame) + self.coupling_combobox = QtWidgets.QComboBox(self.groupBox) + self.coupling_combobox.setObjectName("coupling_combobox") + self.verticalLayout_2.addWidget(self.coupling_combobox) + self.cp_frame = QtWidgets.QFrame(self.groupBox) + self.cp_frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.cp_frame.setFrameShadow(QtWidgets.QFrame.Sunken) + self.cp_frame.setObjectName("cp_frame") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.cp_frame) + self.verticalLayout_4.setContentsMargins(1, 1, 1, 1) + self.verticalLayout_4.setSpacing(0) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.verticalLayout_2.addWidget(self.cp_frame) + self.gridLayout_2.addWidget(self.groupBox, 3, 0, 1, 1) + self.groupBox_5 = QtWidgets.QGroupBox(t1dialog) + self.groupBox_5.setObjectName("groupBox_5") + self.gridLayout_5 = QtWidgets.QGridLayout(self.groupBox_5) + self.gridLayout_5.setContentsMargins(3, 3, 3, 3) + self.gridLayout_5.setHorizontalSpacing(2) + self.gridLayout_5.setObjectName("gridLayout_5") + self.interpol_combobox = QtWidgets.QComboBox(self.groupBox_5) + self.interpol_combobox.setObjectName("interpol_combobox") + self.interpol_combobox.addItem("") + self.interpol_combobox.addItem("") + self.interpol_combobox.addItem("") + self.interpol_combobox.addItem("") + self.gridLayout_5.addWidget(self.interpol_combobox, 0, 0, 1, 1) + self.frame = QtWidgets.QFrame(self.groupBox_5) + self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame.setObjectName("frame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label_9 = QtWidgets.QLabel(self.frame) + self.label_9.setObjectName("label_9") + self.horizontalLayout.addWidget(self.label_9) + self.lineEdit_2 = QtWidgets.QLineEdit(self.frame) + self.lineEdit_2.setObjectName("lineEdit_2") + self.horizontalLayout.addWidget(self.lineEdit_2) + self.label_10 = QtWidgets.QLabel(self.frame) + self.label_10.setObjectName("label_10") + self.horizontalLayout.addWidget(self.label_10) + self.lineEdit_3 = QtWidgets.QLineEdit(self.frame) + self.lineEdit_3.setObjectName("lineEdit_3") + self.horizontalLayout.addWidget(self.lineEdit_3) + self.gridLayout_5.addWidget(self.frame, 1, 0, 1, 2) + self.frame_2 = QtWidgets.QFrame(self.groupBox_5) + self.frame_2.setFrameShape(QtWidgets.QFrame.NoFrame) + self.frame_2.setFrameShadow(QtWidgets.QFrame.Plain) + self.frame_2.setObjectName("frame_2") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_2) + self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_11 = QtWidgets.QLabel(self.frame_2) + self.label_11.setObjectName("label_11") + self.horizontalLayout_2.addWidget(self.label_11) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.label_13 = QtWidgets.QLabel(self.frame_2) + self.label_13.setObjectName("label_13") + self.horizontalLayout_2.addWidget(self.label_13) + self.label_12 = QtWidgets.QLabel(self.frame_2) + self.label_12.setObjectName("label_12") + self.horizontalLayout_2.addWidget(self.label_12) + self.gridLayout_5.addWidget(self.frame_2, 2, 0, 1, 2) + self.t1min_toolButton = QtWidgets.QToolButton(self.groupBox_5) + self.t1min_toolButton.setObjectName("t1min_toolButton") + self.gridLayout_5.addWidget(self.t1min_toolButton, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_5, 2, 0, 1, 1) + self.groupBox_3 = QtWidgets.QGroupBox(t1dialog) + self.groupBox_3.setObjectName("groupBox_3") + self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_3) + self.gridLayout_4.setContentsMargins(3, 3, 3, 3) + self.gridLayout_4.setSpacing(3) + self.gridLayout_4.setObjectName("gridLayout_4") + self.graph_combobox = QtWidgets.QComboBox(self.groupBox_3) + self.graph_combobox.setEnabled(False) + self.graph_combobox.setObjectName("graph_combobox") + self.gridLayout_4.addWidget(self.graph_combobox, 3, 1, 1, 1) + self.tau_combox = QtWidgets.QComboBox(self.groupBox_3) + self.tau_combox.setObjectName("tau_combox") + self.tau_combox.addItem("") + self.tau_combox.addItem("") + self.tau_combox.addItem("") + self.tau_combox.addItem("") + self.gridLayout_4.addWidget(self.tau_combox, 1, 0, 1, 2) + self.checkBox_interpol = QtWidgets.QCheckBox(self.groupBox_3) + self.checkBox_interpol.setObjectName("checkBox_interpol") + self.gridLayout_4.addWidget(self.checkBox_interpol, 2, 0, 1, 2) + self.graph_checkbox = QtWidgets.QCheckBox(self.groupBox_3) + self.graph_checkbox.setChecked(True) + self.graph_checkbox.setObjectName("graph_checkbox") + self.gridLayout_4.addWidget(self.graph_checkbox, 3, 0, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox_3) + self.label.setObjectName("label") + self.gridLayout_4.addWidget(self.label, 0, 0, 1, 1) + self.label_t1min = QtWidgets.QLabel(self.groupBox_3) + self.label_t1min.setText("") + self.label_t1min.setObjectName("label_t1min") + self.gridLayout_4.addWidget(self.label_t1min, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.groupBox_3, 4, 0, 1, 1) + spacerItem1 = QtWidgets.QSpacerItem(17, 19, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem1, 6, 0, 1, 1) + self.calc_pushButton = QtWidgets.QPushButton(t1dialog) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.calc_pushButton.setFont(font) + self.calc_pushButton.setObjectName("calc_pushButton") + self.gridLayout_2.addWidget(self.calc_pushButton, 5, 0, 1, 1) + self.label_9.setBuddy(self.lineEdit_2) + self.label_10.setBuddy(self.lineEdit_3) + + self.retranslateUi(t1dialog) + QtCore.QMetaObject.connectSlotsByName(t1dialog) + + def retranslateUi(self, t1dialog): + _translate = QtCore.QCoreApplication.translate + t1dialog.setWindowTitle(_translate("t1dialog", "Form")) + self.groupBox_1.setTitle(_translate("t1dialog", "Axes")) + self.t1_combobox.setItemText(0, _translate("t1dialog", "T1")) + self.t1_combobox.setItemText(1, _translate("t1dialog", "1/T1")) + self.temp_combobox.setItemText(0, _translate("t1dialog", "1000/T")) + self.temp_combobox.setItemText(1, _translate("t1dialog", "T")) + self.temp_combobox.setItemText(2, _translate("t1dialog", "1/T")) + self.groupBox_2.setTitle(_translate("t1dialog", "Frequency")) + self.freq_combox.setItemText(0, _translate("t1dialog", "MHz")) + self.freq_combox.setItemText(1, _translate("t1dialog", "kHz")) + self.freq_combox.setItemText(2, _translate("t1dialog", "Hz")) + self.groupBox.setTitle(_translate("t1dialog", "Model")) + self.groupBox_5.setTitle(_translate("t1dialog", "Pick T1 minimon")) + self.interpol_combobox.setItemText(0, _translate("t1dialog", "Data minimum")) + self.interpol_combobox.setItemText(1, _translate("t1dialog", "Parabola")) + self.interpol_combobox.setItemText(2, _translate("t1dialog", "Cubic spline")) + self.interpol_combobox.setItemText(3, _translate("t1dialog", "Akima spline")) + self.label_9.setText(_translate("t1dialog", "Interpolate")) + self.lineEdit_2.setPlaceholderText(_translate("t1dialog", "1 K")) + self.label_10.setText(_translate("t1dialog", "--")) + self.lineEdit_3.setPlaceholderText(_translate("t1dialog", "2000 K")) + self.label_11.setText(_translate("t1dialog", "Minimon")) + self.label_13.setText(_translate("t1dialog", "x value")) + self.label_12.setText(_translate("t1dialog", "y value")) + self.t1min_toolButton.setText(_translate("t1dialog", "Set")) + self.groupBox_3.setTitle(_translate("t1dialog", "Result")) + self.tau_combox.setStatusTip(_translate("t1dialog", "NOTE: Mean values are not available for all spectral densities. For more information ask someone.")) + self.tau_combox.setItemText(0, _translate("t1dialog", "Fit parameter: τ")) + self.tau_combox.setItemText(1, _translate("t1dialog", "Peak time: τₚ")) + self.tau_combox.setItemText(2, _translate("t1dialog", "Arithmetic mean: ⟨τ⟩")) + self.tau_combox.setItemText(3, _translate("t1dialog", "Geometric mean: exp(⟨ln τ⟩)")) + self.checkBox_interpol.setText(_translate("t1dialog", "Use minimon interpolation")) + self.graph_checkbox.setText(_translate("t1dialog", "New graph?")) + self.label.setText(_translate("t1dialog", "Calculated minimon")) + self.calc_pushButton.setText(_translate("t1dialog", "Calculate")) diff --git a/nmreval/gui_qt/_py/tntdialog.py b/nmreval/gui_qt/_py/tntdialog.py new file mode 100644 index 0000000..d3754ec --- /dev/null +++ b/nmreval/gui_qt/_py/tntdialog.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/tntdialog.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_tntdialog(object): + def setupUi(self, tntdialog): + tntdialog.setObjectName("tntdialog") + tntdialog.resize(373, 482) + self.gridLayout = QtWidgets.QGridLayout(tntdialog) + self.gridLayout.setObjectName("gridLayout") + self.widget_3 = QDelayWidget(tntdialog) + self.widget_3.setObjectName("widget_3") + self.gridLayout.addWidget(self.widget_3, 3, 1, 1, 2) + self.label_3 = QtWidgets.QLabel(tntdialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) + self.label_3.setSizePolicy(sizePolicy) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 0, 2, 1, 1) + self.widget = QDelayWidget(tntdialog) + self.widget.setObjectName("widget") + self.gridLayout.addWidget(self.widget, 1, 1, 1, 2) + self.label_2 = QtWidgets.QLabel(tntdialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout.addItem(spacerItem, 6, 1, 1, 1) + self.widget_2 = QDelayWidget(tntdialog) + self.widget_2.setObjectName("widget_2") + self.gridLayout.addWidget(self.widget_2, 2, 1, 1, 2) + self.buttonBox = QtWidgets.QDialogButtonBox(tntdialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.gridLayout.addWidget(self.buttonBox, 9, 1, 1, 2) + self.frame = QtWidgets.QFrame(tntdialog) + self.frame.setObjectName("frame") + self.gridLayout_2 = QtWidgets.QGridLayout(self.frame) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.checkBox_2 = QtWidgets.QCheckBox(self.frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_2.sizePolicy().hasHeightForWidth()) + self.checkBox_2.setSizePolicy(sizePolicy) + self.checkBox_2.setObjectName("checkBox_2") + self.gridLayout_2.addWidget(self.checkBox_2, 3, 0, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_5 = QtWidgets.QLabel(self.frame) + self.label_5.setObjectName("label_5") + self.horizontalLayout_2.addWidget(self.label_5) + self.start_lineedit = QtWidgets.QLineEdit(self.frame) + self.start_lineedit.setObjectName("start_lineedit") + self.horizontalLayout_2.addWidget(self.start_lineedit) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.label_6 = QtWidgets.QLabel(self.frame) + self.label_6.setObjectName("label_6") + self.horizontalLayout_2.addWidget(self.label_6) + self.end_lineedit = QtWidgets.QLineEdit(self.frame) + self.end_lineedit.setFrame(True) + self.end_lineedit.setObjectName("end_lineedit") + self.horizontalLayout_2.addWidget(self.end_lineedit) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 1, 0, 1, 2) + self.label = QtWidgets.QLabel(self.frame) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.lineEdit = QtWidgets.QLineEdit(self.frame) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout_2.addWidget(self.lineEdit, 0, 1, 1, 1) + self.spinBox = QtWidgets.QSpinBox(self.frame) + self.spinBox.setObjectName("spinBox") + self.gridLayout_2.addWidget(self.spinBox, 3, 1, 1, 1) + self.checkBox = QtWidgets.QCheckBox(self.frame) + self.checkBox.setLayoutDirection(QtCore.Qt.LeftToRight) + self.checkBox.setObjectName("checkBox") + self.gridLayout_2.addWidget(self.checkBox, 4, 0, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setContentsMargins(25, -1, -1, -1) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.pushButton = QtWidgets.QPushButton(self.frame) + self.pushButton.setObjectName("pushButton") + self.horizontalLayout.addWidget(self.pushButton) + self.pushButton_2 = QtWidgets.QPushButton(self.frame) + self.pushButton_2.setObjectName("pushButton_2") + self.horizontalLayout.addWidget(self.pushButton_2) + self.gridLayout_2.addLayout(self.horizontalLayout, 4, 1, 1, 1) + self.gridLayout.addWidget(self.frame, 8, 1, 1, 2) + self.frame_2 = QtWidgets.QFrame(tntdialog) + self.frame_2.setObjectName("frame_2") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.frame_2) + self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_4 = QtWidgets.QLabel(self.frame_2) + self.label_4.setObjectName("label_4") + self.horizontalLayout_3.addWidget(self.label_4) + self.unknown_delay_combobox = QtWidgets.QComboBox(self.frame_2) + self.unknown_delay_combobox.setObjectName("unknown_delay_combobox") + self.horizontalLayout_3.addWidget(self.unknown_delay_combobox) + self.gridLayout.addWidget(self.frame_2, 4, 1, 1, 2) + + self.retranslateUi(tntdialog) + self.buttonBox.accepted.connect(tntdialog.accept) + self.buttonBox.rejected.connect(tntdialog.reject) + QtCore.QMetaObject.connectSlotsByName(tntdialog) + + def retranslateUi(self, tntdialog): + _translate = QtCore.QCoreApplication.translate + tntdialog.setWindowTitle(_translate("tntdialog", "Read tnt file")) + self.label_3.setText(_translate("tntdialog", "TextLabel")) + self.label_2.setText(_translate("tntdialog", "Dimensions")) + self.checkBox_2.setText(_translate("tntdialog", "Staggered range")) + self.label_5.setText(_translate("tntdialog", "Start")) + self.start_lineedit.setPlaceholderText(_translate("tntdialog", "0")) + self.label_6.setText(_translate("tntdialog", "End")) + self.end_lineedit.setPlaceholderText(_translate("tntdialog", "1")) + self.label.setText(_translate("tntdialog", "Name")) + self.checkBox.setToolTip(_translate("tntdialog", "NOTE: There is no inspection if start and end are valid values.")) + self.checkBox.setText(_translate("tntdialog", "Logarithmic scale")) + self.pushButton.setText(_translate("tntdialog", "Apply")) + self.pushButton_2.setText(_translate("tntdialog", "Cancel")) + self.label_4.setText(_translate("tntdialog", "Unassigned lists")) +from widgets.subwidgets import QDelayWidget diff --git a/nmreval/gui_qt/_py/typeconversion.py b/nmreval/gui_qt/_py/typeconversion.py new file mode 100644 index 0000000..fcfa751 --- /dev/null +++ b/nmreval/gui_qt/_py/typeconversion.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/typeconversion.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(839, 502) + self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout_2.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_2.setSpacing(3) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.label_4 = QtWidgets.QLabel(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy) + self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_4.setObjectName("label_4") + self.verticalLayout_2.addWidget(self.label_4) + self.splitter = QtWidgets.QSplitter(Dialog) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.set_list = QtWidgets.QListWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.set_list.sizePolicy().hasHeightForWidth()) + self.set_list.setSizePolicy(sizePolicy) + self.set_list.setDragEnabled(True) + self.set_list.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly) + self.set_list.setObjectName("set_list") + self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.gridLayout = QtWidgets.QGridLayout(self.verticalLayoutWidget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setHorizontalSpacing(0) + self.gridLayout.setVerticalSpacing(2) + self.gridLayout.setObjectName("gridLayout") + self.simple_button = QtWidgets.QRadioButton(self.verticalLayoutWidget) + self.simple_button.setChecked(True) + self.simple_button.setObjectName("simple_button") + self.buttonGroup = QtWidgets.QButtonGroup(Dialog) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.simple_button) + self.gridLayout.addWidget(self.simple_button, 0, 0, 1, 1) + self.merge_button = QtWidgets.QRadioButton(self.verticalLayoutWidget) + self.merge_button.setObjectName("merge_button") + self.buttonGroup.addButton(self.merge_button) + self.gridLayout.addWidget(self.merge_button, 0, 1, 1, 1) + self.stackedWidget = QtWidgets.QStackedWidget(self.verticalLayoutWidget) + self.stackedWidget.setObjectName("stackedWidget") + self.page_1 = QtWidgets.QWidget() + self.page_1.setObjectName("page_1") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.page_1) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.simple_table = QtWidgets.QTableWidget(self.page_1) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.simple_table.sizePolicy().hasHeightForWidth()) + self.simple_table.setSizePolicy(sizePolicy) + self.simple_table.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly) + self.simple_table.setObjectName("simple_table") + self.simple_table.setColumnCount(2) + self.simple_table.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.simple_table.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.simple_table.setHorizontalHeaderItem(1, item) + self.simple_table.horizontalHeader().setStretchLastSection(True) + self.verticalLayout_3.addWidget(self.simple_table) + self.stackedWidget.addWidget(self.page_1) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.page_2) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_5.setSpacing(0) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.merge_table = QtWidgets.QTableWidget(self.page_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.merge_table.sizePolicy().hasHeightForWidth()) + self.merge_table.setSizePolicy(sizePolicy) + self.merge_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.merge_table.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly) + self.merge_table.setDefaultDropAction(QtCore.Qt.CopyAction) + self.merge_table.setObjectName("merge_table") + self.merge_table.setColumnCount(3) + self.merge_table.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.merge_table.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.merge_table.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.merge_table.setHorizontalHeaderItem(2, item) + self.merge_table.horizontalHeader().setStretchLastSection(True) + self.verticalLayout_5.addWidget(self.merge_table) + self.stackedWidget.addWidget(self.page_2) + self.gridLayout.addWidget(self.stackedWidget, 1, 0, 1, 2) + self.verticalLayout_2.addWidget(self.splitter) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout_2.addWidget(self.buttonBox) + + self.retranslateUi(Dialog) + self.stackedWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Change type")) + self.label_4.setText(_translate("Dialog", "Drag & drop datasets and select new type.")) + self.simple_button.setText(_translate("Dialog", "Simple conversion")) + self.merge_button.setText(_translate("Dialog", "Merge to complex set")) + item = self.simple_table.horizontalHeaderItem(0) + item.setText(_translate("Dialog", "Set")) + item = self.simple_table.horizontalHeaderItem(1) + item.setText(_translate("Dialog", "Type")) + item = self.merge_table.horizontalHeaderItem(0) + item.setText(_translate("Dialog", "Real")) + item = self.merge_table.horizontalHeaderItem(1) + item.setText(_translate("Dialog", "Imag")) + item = self.merge_table.horizontalHeaderItem(2) + item.setText(_translate("Dialog", "Type")) diff --git a/nmreval/gui_qt/_py/untitled.py b/nmreval/gui_qt/_py/untitled.py new file mode 100644 index 0000000..4b991d4 --- /dev/null +++ b/nmreval/gui_qt/_py/untitled.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/untitled.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(800, 600) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.toolButton = QtWidgets.QToolButton(self.centralwidget) + self.toolButton.setGeometry(QtCore.QRect(0, 0, 31, 34)) + self.toolButton.setObjectName("toolButton") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30)) + self.menubar.setObjectName("menubar") + self.menuDfgdfg = QtWidgets.QMenu(self.menubar) + self.menuDfgdfg.setObjectName("menuDfgdfg") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.toolBar = QtWidgets.QToolBar(MainWindow) + self.toolBar.setObjectName("toolBar") + MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) + self.actionAsdasd = QtWidgets.QAction(MainWindow) + self.actionAsdasd.setObjectName("actionAsdasd") + self.menuDfgdfg.addAction(self.actionAsdasd) + self.menubar.addAction(self.menuDfgdfg.menuAction()) + self.toolBar.addSeparator() + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.toolButton.setText(_translate("MainWindow", "...")) + self.menuDfgdfg.setTitle(_translate("MainWindow", "dfgdfg")) + self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar")) + self.actionAsdasd.setText(_translate("MainWindow", "asdasd")) diff --git a/nmreval/gui_qt/_py/userfitassist.py b/nmreval/gui_qt/_py/userfitassist.py new file mode 100644 index 0000000..c587927 --- /dev/null +++ b/nmreval/gui_qt/_py/userfitassist.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/userfitassist.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(675, 682) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.label_3 = QtWidgets.QLabel(Dialog) + self.label_3.setIndent(4) + self.label_3.setObjectName("label_3") + self.verticalLayout.addWidget(self.label_3) + self.lineEdit_2 = QtWidgets.QLineEdit(Dialog) + self.lineEdit_2.setObjectName("lineEdit_2") + self.verticalLayout.addWidget(self.lineEdit_2) + self.label_4 = QtWidgets.QLabel(Dialog) + self.label_4.setIndent(4) + self.label_4.setObjectName("label_4") + self.verticalLayout.addWidget(self.label_4) + self.lineEdit_3 = QtWidgets.QLineEdit(Dialog) + self.lineEdit_3.setObjectName("lineEdit_3") + self.verticalLayout.addWidget(self.lineEdit_3) + self.label_2 = QtWidgets.QLabel(Dialog) + self.label_2.setIndent(4) + self.label_2.setObjectName("label_2") + self.verticalLayout.addWidget(self.label_2) + self.lineEdit = QtWidgets.QLineEdit(Dialog) + self.lineEdit.setText("") + self.lineEdit.setObjectName("lineEdit") + self.verticalLayout.addWidget(self.lineEdit) + self.parameterLabel = QtWidgets.QLabel(Dialog) + self.parameterLabel.setIndent(4) + self.parameterLabel.setObjectName("parameterLabel") + self.verticalLayout.addWidget(self.parameterLabel) + self.parameterLineEdit = QtWidgets.QLineEdit(Dialog) + self.parameterLineEdit.setObjectName("parameterLineEdit") + self.verticalLayout.addWidget(self.parameterLineEdit) + self.checkBox = QtWidgets.QCheckBox(Dialog) + self.checkBox.setObjectName("checkBox") + self.verticalLayout.addWidget(self.checkBox) + self.externalParametersLineEdit = QtWidgets.QLineEdit(Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.externalParametersLineEdit.sizePolicy().hasHeightForWidth()) + self.externalParametersLineEdit.setSizePolicy(sizePolicy) + self.externalParametersLineEdit.setObjectName("externalParametersLineEdit") + self.verticalLayout.addWidget(self.externalParametersLineEdit) + self.checkBox_2 = QtWidgets.QCheckBox(Dialog) + self.checkBox_2.setObjectName("checkBox_2") + self.verticalLayout.addWidget(self.checkBox_2) + self.tableWidget = QtWidgets.QTableWidget(Dialog) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setColumnCount(0) + self.tableWidget.setRowCount(0) + self.verticalLayout.addWidget(self.tableWidget) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Save) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + self.label_3.setBuddy(self.lineEdit_2) + self.label_4.setBuddy(self.lineEdit_3) + self.label_2.setBuddy(self.lineEdit) + self.parameterLabel.setBuddy(self.parameterLineEdit) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label_3.setText(_translate("Dialog", "Name")) + self.lineEdit_2.setPlaceholderText(_translate("Dialog", "Name of function, e.g. Hopperbagger")) + self.label_4.setText(_translate("Dialog", "Group")) + self.lineEdit_3.setPlaceholderText(_translate("Dialog", "Type of function, e.g., Relaxation, Diffusion, Dredge,...")) + self.label_2.setText(_translate("Dialog", "Equation")) + self.lineEdit.setPlaceholderText(_translate("Dialog", "\\alpha + B*exp(x*C_{33}) + D")) + self.parameterLabel.setText(_translate("Dialog", "Parameters")) + self.parameterLineEdit.setPlaceholderText(_translate("Dialog", "\\alpha B C_{33}")) + self.checkBox.setText(_translate("Dialog", "Fixed parameter")) + self.externalParametersLineEdit.setPlaceholderText(_translate("Dialog", "D")) + self.checkBox_2.setText(_translate("Dialog", "Selection")) + self.buttonBox.setToolTip(_translate("Dialog", "Fit model is saved in myfitmodels.py and ready to use without restart.")) diff --git a/nmreval/gui_qt/_py/usermodeleditor.py b/nmreval/gui_qt/_py/usermodeleditor.py new file mode 100644 index 0000000..5858c0b --- /dev/null +++ b/nmreval/gui_qt/_py/usermodeleditor.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/usermodeleditor.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(800, 600) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.edit_field = CodeEditor(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(10) + self.edit_field.setFont(font) + self.edit_field.setObjectName("edit_field") + self.verticalLayout.addWidget(self.edit_field) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.actionOpen = QtWidgets.QAction(MainWindow) + self.actionOpen.setObjectName("actionOpen") + self.actionSave = QtWidgets.QAction(MainWindow) + self.actionSave.setObjectName("actionSave") + self.actionSave_as = QtWidgets.QAction(MainWindow) + self.actionSave_as.setObjectName("actionSave_as") + self.actionClose = QtWidgets.QAction(MainWindow) + self.actionClose.setObjectName("actionClose") + self.menuFile.addAction(self.actionOpen) + self.menuFile.addAction(self.actionSave) + self.menuFile.addAction(self.actionSave_as) + self.menuFile.addAction(self.actionClose) + self.menubar.addAction(self.menuFile.menuAction()) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Editor")) + self.menuFile.setTitle(_translate("MainWindow", "File")) + self.actionOpen.setText(_translate("MainWindow", "Open...")) + self.actionSave.setText(_translate("MainWindow", "Save")) + self.actionSave_as.setText(_translate("MainWindow", "Save as...")) + self.actionClose.setText(_translate("MainWindow", "Close")) +from ..lib.codeeditor import CodeEditor diff --git a/nmreval/gui_qt/_py/valueeditor.py b/nmreval/gui_qt/_py/valueeditor.py new file mode 100644 index 0000000..bfab40a --- /dev/null +++ b/nmreval/gui_qt/_py/valueeditor.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'resources/_ui/valueeditor.ui' +# +# Created by: PyQt5 UI code generator 5.12.3 +# +# WARNING! All changes made in this file will be lost! + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MaskDialog(object): + def setupUi(self, MaskDialog): + MaskDialog.setObjectName("MaskDialog") + MaskDialog.resize(383, 645) + self.verticalLayout = QtWidgets.QVBoxLayout(MaskDialog) + self.verticalLayout.setContentsMargins(3, 3, 3, 3) + self.verticalLayout.setSpacing(3) + self.verticalLayout.setObjectName("verticalLayout") + self.comboBox = QtWidgets.QComboBox(MaskDialog) + self.comboBox.setObjectName("comboBox") + self.verticalLayout.addWidget(self.comboBox) + self.comboBox_2 = QtWidgets.QComboBox(MaskDialog) + self.comboBox_2.setObjectName("comboBox_2") + self.verticalLayout.addWidget(self.comboBox_2) + self.tableView = QtWidgets.QTableView(MaskDialog) + self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableView.setObjectName("tableView") + self.verticalLayout.addWidget(self.tableView) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(MaskDialog) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.spinBox = QtWidgets.QSpinBox(MaskDialog) + self.spinBox.setMinimum(1) + self.spinBox.setObjectName("spinBox") + self.horizontalLayout.addWidget(self.spinBox) + self.toolButton = QtWidgets.QToolButton(MaskDialog) + self.toolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.toolButton.setAutoRaise(True) + self.toolButton.setArrowType(QtCore.Qt.RightArrow) + self.toolButton.setObjectName("toolButton") + self.horizontalLayout.addWidget(self.toolButton) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.verticalLayout.addLayout(self.horizontalLayout) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setContentsMargins(-1, 0, -1, -1) + self.gridLayout.setSpacing(3) + self.gridLayout.setObjectName("gridLayout") + self.unmaskbutton = QtWidgets.QPushButton(MaskDialog) + self.unmaskbutton.setObjectName("unmaskbutton") + self.gridLayout.addWidget(self.unmaskbutton, 1, 0, 1, 1) + self.delete_button = QtWidgets.QPushButton(MaskDialog) + icon = QtGui.QIcon.fromTheme("list-remove") + self.delete_button.setIcon(icon) + self.delete_button.setObjectName("delete_button") + self.gridLayout.addWidget(self.delete_button, 0, 1, 1, 1) + self.add_button = QtWidgets.QPushButton(MaskDialog) + icon = QtGui.QIcon.fromTheme("list-add") + self.add_button.setIcon(icon) + self.add_button.setObjectName("add_button") + self.gridLayout.addWidget(self.add_button, 0, 0, 1, 1) + self.mask_button = QtWidgets.QPushButton(MaskDialog) + self.mask_button.setObjectName("mask_button") + self.gridLayout.addWidget(self.mask_button, 1, 1, 1, 1) + self.verticalLayout.addLayout(self.gridLayout) + self.label.setBuddy(self.spinBox) + + self.retranslateUi(MaskDialog) + QtCore.QMetaObject.connectSlotsByName(MaskDialog) + + def retranslateUi(self, MaskDialog): + _translate = QtCore.QCoreApplication.translate + MaskDialog.setWindowTitle(_translate("MaskDialog", "Form")) + self.label.setText(_translate("MaskDialog", "Go to line:")) + self.toolButton.setText(_translate("MaskDialog", "Go Go Power Rangers!")) + self.unmaskbutton.setText(_translate("MaskDialog", "Show all")) + self.delete_button.setText(_translate("MaskDialog", "Delete rows")) + self.add_button.setText(_translate("MaskDialog", "Add row")) + self.mask_button.setToolTip(_translate("MaskDialog", "

Masked rows are shown in green

")) + self.mask_button.setText(_translate("MaskDialog", "Hide/Show selected")) diff --git a/nmreval/gui_qt/data/__init__.py b/nmreval/gui_qt/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmreval/gui_qt/data/container.py b/nmreval/gui_qt/data/container.py new file mode 100644 index 0000000..7027698 --- /dev/null +++ b/nmreval/gui_qt/data/container.py @@ -0,0 +1,651 @@ +from collections import OrderedDict +from itertools import cycle +from typing import Any + +import numpy as np +from pyqtgraph import mkPen + +from ...data.points import Points +from ...data.signals import Signal +from ...utils.text import convert +from ...data.bds import BDS +from ...lib.colors import Colors +from ...lib.lines import LineStyle +from ...lib.symbols import SymbolStyle, symbolcycle +from ...data.nmr import Spectrum, FID + +from ..Qt import QtCore +from ..io.exporters import pgitem_to_dic +from ..lib.decorators import plot_update +from ..lib.pg_objects import ErrorBars, PlotItem + + +class ExperimentContainer(QtCore.QObject): + dataChanged = QtCore.pyqtSignal(str) + labelChanged = QtCore.pyqtSignal(str, str) + groupChanged = QtCore.pyqtSignal(str, str) + colors = cycle(Colors) + + def __init__(self, identifier, data, **kwargs): + super().__init__() + self.id = str(identifier) + + self._fits = [] + self._data = data + self._manager = kwargs.get('manager') + + self.mode = 'point' + self.plot_real = None + self.plot_imag = None + self.plot_error = None + + self.actions = {} + self._update_actions() + + def _init_plot(self): + raise NotImplementedError + + def __getitem__(self, item): + try: + return self._data[item] + except KeyError: + raise KeyError('Unknown key %s' % str(item)) + + def __del__(self): + del self._data + del self.plot_real + del self.plot_imag + del self.plot_error + + def __repr__(self): + return 'name:' + self.name + + def __len__(self): + return len(self._data) + + def copy(self, full: bool = False): + if full: + new_data = type(self)(str(self.id), self._data.copy(), manager=self._manager) + new_data.mode = self.mode + + for src, dest in [(self.plot_real, new_data.plot_real), (self.plot_imag, new_data.plot_imag)]: + if src is not None: + dest.set_symbol(symbol=src.symbol, size=src.symbolsize, color=src.symbolcolor) + dest.set_line(style=src.linestyle, width=src.linewidth, color=src.linecolor) + + return new_data + + else: + return self._data.copy() + + def change_type(self, data): + if isinstance(data, (FID, Spectrum, BDS)): + new_type = SignalContainer + elif isinstance(data, Points): + new_type = PointContainer + else: + raise TypeError('Unknown data type') + + new_data = new_type(str(self.id), data, manager=self._manager) + + for src, dest in [(self.plot_real, new_data.plot_real), (self.plot_imag, new_data.plot_imag)]: + if dest is not None: + if src is not None: + dest.set_symbol(symbol=src.symbol, size=src.symbolsize, color=src.symbolcolor) + dest.set_line(style=src.linestyle, width=src.linewidth, color=src.linecolor) + else: + dest.set_symbol(symbol=self.plot_real.symbol, size=self.plot_real.symbolsize, color=self.plot_real.symbolcolor) + dest.set_line(style=self.plot_real.linestyle, width=self.plot_real.linewidth, color=self.plot_real.linecolor) + + return new_data + + @property + def x(self): + return self._data.x[self._data.mask] + + @x.setter + @plot_update + def x(self, value): + if len(self._data.x) == len(value): + self._data.x = value + elif len(self._data.x[self._data.mask]) == len(value): + self._data.x = value + self._data.y = self._data.y[self._data.mask] + self._data.mask = np.ma.array(np.ones_like(self._data.x, dtype=bool)) + else: + raise ValueError('x and y have different dimensions!') + + @property + def y(self): + return self._data.y[self._data.mask] + + @y.setter + @plot_update + def y(self, value): + if len(self._data.y) == len(value): + self._data.y = value + elif len(self._data.y[self._data.mask]) == len(value): + self._data.y = value + self._data.x = self._data.x[self._data.mask] + self._data.mask = np.ma.array(np.ones_like(self._data.y, dtype=bool)) + else: + raise ValueError('x and y have different dimensions!') + + @property + def y_err(self): + return self._data.y_err[self._data.mask] + + @y_err.setter + @plot_update + def y_err(self, value): + if len(self._data.y_err) == len(value): + self._data.y_err = value + elif len(self._data.y[self._data.mask]) == len(value): + self._data.y_err[self._data.mask] = value + else: + raise ValueError('y_err has not correct length') + + @property + def name(self): + return self._data.name + + @name.setter + @plot_update + def name(self, value: str): + self._data.name = value + self.plot_real.opts['name'] = value + try: + self.plot_imag.opts['name'] = value + except AttributeError: + pass + try: + num_val = float(value) + self._data.value = num_val + except ValueError: + pass + + @property + def value(self): + return self._data.value + + @value.setter + def value(self, val): + self._data.value = float(val) + + @property + def group(self): + return str(self._data['group']) + + @group.setter + def group(self, valium): + self._data['group'] = str(valium) + self.groupChanged.emit(self.id, str(valium)) + + @property + def data(self): + return self._data + + @data.setter + @plot_update + def data(self, new_data): + self._data = new_data + self._update_actions() + + @property + def opts(self): + return self._data.meta + + @property + def plots(self): + return self.plot_real, self.plot_imag, self.plot_error + + def get_state(self): + ret_dic = { + 'id': self.id, + 'data': self._data.get_state(), + 'mode': self.mode, + 'fits': self._fits, + 'real': ({'symbol': self.plot_real.symbol.value, + 'size': self.plot_real.symbolsize, + 'color': self.plot_real.symbolcolor.value}, + {'style': self.plot_real.linestyle.value, + 'width': self.plot_real.linewidth, + 'color': self.plot_real.linecolor.value}) + } + + if self.plot_imag is not None: + ret_dic['imag'] = ({'symbol': self.plot_imag.symbol.value, + 'size': self.plot_imag.symbolsize, + 'color': self.plot_imag.symbolcolor.value}, + {'style': self.plot_imag.linestyle.value, + 'width': self.plot_imag.linewidth, + 'color': self.plot_imag.linecolor.value}) + + return ret_dic + + def get_fits(self): + return [self._manager[idx] for idx in self._fits] + + def has_fits(self): + return len(self._fits) != 0 + + def set_fits(self, value: str or list, replace: bool = False): + if isinstance(value, str): + value = [value] + + if replace: + if isinstance(value, list): + self._fits = value + else: + raise TypeError() + else: + self._fits.extend(value) + + def _update_actions(self): + self.actions.update({'sort': self._data.sort, + 'cut': self._data.cut, + 'norm': self._data.normalize, + 'center': self.center}) + + @plot_update + def update(self, opts: dict): + self._data.update(opts) + + def get_properties(self) -> dict: + props = OrderedDict() + props['General'] = OrderedDict([('Name', self.name), ('Value', str(self.value)), ('Group', str(self.group))]) + props['Symbol'] = OrderedDict() + props['Line'] = OrderedDict() + + props['Symbol']['Symbol'] = self.plot_real.symbol + props['Symbol']['Size'] = self.plot_real.symbolsize + props['Symbol']['Color'] = self.plot_real.symbolcolor + + props['Line']['Style'] = self.plot_real.linestyle + props['Line']['Width'] = self.plot_real.linewidth + props['Line']['Color'] = self.plot_real.linecolor + + if self.plot_imag is not None: + props['Symbol']['Symbol (imag)'] = self.plot_imag.symbol + props['Symbol']['Size (imag)'] = self.plot_imag.symbolsize + props['Symbol']['Color (imag)'] = self.plot_imag.symbolcolor + + props['Line']['Style (imag)'] = self.plot_imag.linestyle + props['Line']['Width (imag)'] = self.plot_imag.linewidth + props['Line']['Color (imag)'] = self.plot_imag.linecolor + + return props + + def setColor(self, color, symbol=False, line=False, mode='real'): + if mode == 'real': + self.plot_real.set_color(color, symbol=symbol, line=line) + if self.plot_real.symbol != SymbolStyle.No and symbol: + err_pen = self.plot_error.pen + err_pen.setColor(self.plot_real.symbolcolor.rbg()) + self.plot_error.setData(pen=err_pen) + elif line: + err_pen = self.plot_error.pen + err_pen.setColor(self.plot_real.linecolor.rbg()) + self.plot_error.setData(pen=err_pen) + + elif mode == 'imag' and self.plot_imag is not None: + 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'): + if mode == 'real': + self.plot_real.set_symbol(symbol=symbol, size=size, color=color) + elif mode == 'imag' and self.plot_imag is not None: + self.plot_imag.set_symbol(symbol=symbol, size=size, color=color) + else: + print('Updating symbol failed for ' + str(self.id)) + + def setLine(self, width=None, style=None, color=None, mode='real'): + if mode == 'real': + self.plot_real.set_line(width=width, style=style, color=color) + elif mode == 'imag' and self.plot_imag is not None: + self.plot_imag.set_line(width=width, style=style, color=color) + else: + print('Updating line failed for ' + str(self.id)) + + def update_property(self, key1: str, key2: str, value: Any): + keykey = key2.split() + if len(keykey) == 1: + if key1 == 'Symbol': + self.setSymbol(mode='real', **{key2.lower(): value}) + + elif key1 == 'Line': + self.setLine(mode='real', **{key2.lower(): value}) + + elif key1 == 'General': + setattr(self, key2.lower(), value) + + else: + if key1 == 'Symbol': + self.setSymbol(mode='imag', **{keykey[0].lower(): value}) + + elif key1 == 'Line': + self.setLine(mode='imag', **{keykey[0].lower(): value}) + + def points(self, params: dict): + return self._data.points(**params) + + @plot_update + def apply(self, func: str, args: tuple): + if func in self.actions: + f = self.actions[func] + f(*args) + + return self + + @plot_update + def unsort(self, order: np.ndarray): + # this exists only to update plots after an undo action + self._data.x = self._data.x[order] + self._data.y = self._data.y[order] + self._data.y_err = self._data.y_err[order] + self._data.mask = self._data.mask[order] + + def save(self, fname): + ext = fname.suffix + if ext == '.agr': + real_dic = pgitem_to_dic(self.plot_real) + from ..io.exporters import GraceExporter + + GraceExporter(real_dic).export(fname) + + elif ext in ['.dat', '.txt']: + self._data.savetxt(fname, err=True) + + else: + raise ValueError('Unknown extension ' + ext) + + @plot_update + def setvalues(self, pos, valium): + xy, position = pos + if xy == 0: + self._data.x[position] = valium + elif xy == 1: + self._data.y[position] = valium + else: + self._data.y_err[position] = valium + + @property + def mask(self): + return self._data.mask + + @mask.setter + @plot_update + def mask(self, m): + self._data.mask = np.asarray(m, dtype=bool) + + @plot_update + def add(self, m): + if isinstance(m, (np.ndarray, list, tuple)): + self._data.append(m[0], m[1], y_err=m[2]) + elif isinstance(m, (Points, ExperimentContainer)): + self._data.append(m.x, m.y, y_err=m.y_err) + else: + raise TypeError('Unknown type ' + type(m)) + + @plot_update + def remove(self, m): + self._data.remove(m) + + @plot_update + def center(self) -> float: + offset = self.x[np.argmax(self.y.real)] + self._data._x -= offset + + return offset + + def get_namespace(self, i: int = None, j: int = None) -> dict: + if (i is None) and (j is None): + prefix = '' + else: + prefix = 'g[%i].s[%i].' % (i, j) + + namespace = {prefix + 'x': (self.x, 'x values'), + prefix + 'y': [self.y, 'y values'], + prefix + 'y_err': (self.y_err, 'y error values'), + prefix + 'value': (self.value, str(self.value))} + + if len(self._fits) == 1: + namespace.update({ + "%sfit['%s']" % (prefix, convert(pname, old='tex', new='str')): (pvalue.value, str(pvalue.value)) + for (pname, pvalue) in self._manager[self._fits[0]].parameter.items() + }) + else: + for k, f in enumerate(self._fits): + namespace.update({ + "%sfit['%s_%d']" % (prefix, convert(pname, old='tex', new='str'), k): (pvalue.value, str(pvalue.value)) + for (pname, pvalue) in self._manager[f].parameter.items() + }) + + return namespace + + def eval_expression(self, cmds, namespace): + namespace.update({'x': self.x, 'y': self.y, 'y_err': self.y_err, 'value': self.value}) + + if len(self._fits) == 1: + namespace.update({"fit['%s']" % (convert(pname, old='tex', new='str')): pvalue.value + for (pname, pvalue) in self._manager[self._fits[0]].parameter.items()}) + else: + for k, f in enumerate(self._fits): + namespace.update({"fit['%s_%i']" % (convert(pname, old='tex', new='str'), k): pvalue.value + for (pname, pvalue) in self._manager[f].parameter.items()}) + + new_data = self.copy() + for c in cmds: + if c: + exec(c, globals(), namespace) + + new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err']) + new_data.value = namespace['value'] + + return new_data + + +class PointContainer(ExperimentContainer): + symbols = symbolcycle() + + def __init__(self, identifier, data, **kwargs): + super().__init__(identifier, data, **kwargs) + + self.mode = 'pts' + self._init_plot(**kwargs) + + def _init_plot(self, **kwargs): + self.plot_imag = None + + color = kwargs.get('color', None) + symcolor = kwargs.get('symbolcolor', color) + linecolor = kwargs.get('linecolor', color) + + if symcolor is None and linecolor is None: + color = next(self.colors) + symcolor = color + linecolor = color + elif symcolor is None: + symcolor = linecolor + elif linecolor is None: + linecolor = symcolor + + sym_kwargs = { + 'symbol': kwargs.get('symbol', None), + 'size': kwargs.get('symbolsize', 10), + 'color': symcolor + } + + line_kwargs = { + 'style': kwargs.get('linestyle', None), + 'width': kwargs.get('linewidth', 1), + 'color': linecolor + } + + if sym_kwargs['symbol'] is None and line_kwargs['style'] is None: + if len(self._data) > 1000: + line_kwargs['style'] = LineStyle.Solid + sym_kwargs['symbol'] = SymbolStyle.No + else: + line_kwargs['style'] = LineStyle.No + sym_kwargs['symbol'] = next(PointContainer.symbols) + + self.plot_real = PlotItem(x=self._data.x, y=self._data.y, name=self.name, + symbol=None, pen=None, connect='finite') + + self.setSymbol(mode='real', **sym_kwargs) + self.setLine(mode='real', **line_kwargs) + + 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, + pen=mkPen({'color': self.plot_real.symbolcolor.rgb()})) + else: + self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err, + pen=mkPen({'color': self.plot_real.linecolor.rgb()})) + + def update_property(self, key1: str, key2: str, value: Any): + # update default color + if key1 == 'Symbol' and key2 == 'Color': + self.plot_real.set_color(value, symbol=True, line=False) + super().update_property(key1, key2, value) + + +class FitContainer(ExperimentContainer): + def __init__(self, identifier, data, **kwargs): + super().__init__(identifier, data, **kwargs) + self.fitted_key = kwargs.get('src', '') + self.mode = 'fit' + self.parent_set = kwargs.get('src', '') + + self._init_plot(**kwargs) + + for n in ['statistics', 'nobs', 'nvar', 'parameter', 'model_name']: + setattr(self, n, getattr(data, n)) + + def _init_plot(self, **kwargs): + color = kwargs.get('color', (0, 0, 0)) + if isinstance(color, Colors): + color = color.rgb() + + self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name, + pen=mkPen({'color': color}), + connect='finite', symbol=None) + + if np.iscomplexobj(self._data.y): + self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name, + pen=mkPen({'color': color}), + connect='finite', symbol=None) + + @property + def fitted_key(self): + return self._data.idx + + @fitted_key.setter + def fitted_key(self, val): + self._data.idx = val + + def get_namespace(self, i: int = None, j: int = None): + namespace = super().get_namespace(i, j) + + namespace.update({ + "g[%i].s[%i].fit['%s']" % (i, j, convert(pname, old='latex', new='plain')): (pvalue.value, str(pvalue.value)) + for (pname, pvalue) in self._data.parameter.items() + }) + + return namespace + + +class SignalContainer(ExperimentContainer): + symbols = symbolcycle() + + def __init__(self, identifier, data, symbol=None, **kwargs): + super().__init__(identifier, data, **kwargs) + + self.mode = 'signal' + self._init_plot(symbol=symbol, **kwargs) + + def _init_plot(self, **kwargs): + self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name, + symbol=None, pen=None, connect='finite') + self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name, + symbol=None, pen=None, connect='finite') + + color = kwargs.get('color', None) + symcolor = kwargs.get('symbolcolor', color) + linecolor = kwargs.get('linecolor', color) + + if symcolor is None and linecolor is None: + color = next(self.colors) + symcolor = color + linecolor = color + elif symcolor is None: + symcolor = linecolor + elif linecolor is None: + linecolor = symcolor + + sym_kwargs = { + 'symbol': kwargs.get('symbol', None), + 'size': kwargs.get('symbolsize', 10), + 'color': symcolor + } + + line_kwargs = { + 'style': kwargs.get('linestyle', None), + 'width': kwargs.get('linewidth', 1), + 'color': linecolor + } + + if isinstance(self._data, BDS): + self.mode = 'bds' + + if sym_kwargs['symbol'] is None and line_kwargs['style'] is None: + sym_kwargs['symbol'] = next(PointContainer.symbols) + line_kwargs['style'] = LineStyle.No + + elif isinstance(self._data, Signal): + if line_kwargs['style'] is None and sym_kwargs['symbol'] is None: + line_kwargs['style'] = LineStyle.Solid + sym_kwargs['symbol'] = SymbolStyle.No + + if isinstance(self._data, FID): + self.mode = 'fid' + else: + self.mode = 'spectrum' + else: + raise TypeError('Unknown class %s, should be FID, Spectrum, or BDS.' % type(self._data)) + + for mode in ['real', 'imag']: + if mode == 'imag': + line_kwargs['style'] = LineStyle.Dashed + self.setSymbol(mode=mode, **sym_kwargs) + self.setLine(mode=mode, **line_kwargs) + + def _update_actions(self): + super()._update_actions() + + self.actions.update({'ph': self._data.manual_phase, 'bls': self._data.baseline_spline}) + if isinstance(self._data, Spectrum): + self.actions.update({'bl': self._data.baseline, 'ls': self._data.shift, + 'divide': self._data.divide, 'ft': self.fourier}) + self.mode = 'spectrum' + + elif isinstance(self._data, FID): + self.actions.update({'bl': self._data.baseline, 'ls': self._data.shift, + 'zf': self._data.zerofill, 'divide': self._data.divide, + 'ap': self._data.apod, 'ft': self.fourier}) + self.mode = 'fid' + + @plot_update + def fourier(self, mode='normal'): + if mode == 'normal': + self._data = self._data.fourier() + elif mode == 'depake': + try: + self._data = self._data.fft_depake() + except AttributeError: + return + self._update_actions() + + return self diff --git a/nmreval/gui_qt/data/conversion.py b/nmreval/gui_qt/data/conversion.py new file mode 100644 index 0000000..dbcb0b8 --- /dev/null +++ b/nmreval/gui_qt/data/conversion.py @@ -0,0 +1,171 @@ +from nmreval.gui_qt.Qt import QtWidgets, QtCore, QtGui +from nmreval.gui_qt._py.typeconversion import Ui_Dialog + + +class ConversionDialog(QtWidgets.QDialog, Ui_Dialog): + convertSets = QtCore.pyqtSignal(list) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.simple_table.installEventFilter(self) + self.merge_table.installEventFilter(self) + + self._simple_table_dropevent = self.simple_table.dropEvent + self.simple_table.dropEvent = self._drop_on_simple_table + + self._merge_table_dropevent = self.merge_table.dropEvent + self.merge_table.dropEvent = self._drop_on_simple_table + + self.buttonGroup.buttonClicked.connect(self.change_table) + + def set_graphs(self, graphs: dict): + self.set_list.clear() + self.simple_table.clear() + self.simple_table.setHorizontalHeaderLabels(['Simple', 'Type']) + self.merge_table.clear() + self.merge_table.setHorizontalHeaderLabels(['Real', 'Imag', 'Type']) + + for graph, datasets in graphs.items(): + for set_id, set_name in datasets: + item_name = set_name + ' (' + graph[1] + ')' + + item1 = QtWidgets.QListWidgetItem(item_name) + item1.setData(QtCore.Qt.UserRole, set_id) + item1.setData(QtCore.Qt.UserRole+1, graph[0]) + self.set_list.addItem(item1) + + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent): + if evt.type() in [QtCore.QEvent.DragEnter, QtCore.QEvent.DragMove]: + evt.accept() + return True + + if evt.type() == QtCore.QEvent.KeyPress and evt.key() == QtCore.Qt.Key_Delete: + if src == self.simple_table: + type_idx = 1 + else: + type_idx = 2 + + if len(src.selectedIndexes()) > 0: + idx = src.selectedIndexes()[0] + row, col = idx.row(), idx.column() + + if col != type_idx: + src.takeItem(row, col) + is_empty = all(src.item(row, i) is None for i in range(type_idx)) + if is_empty: + src.removeRow(row) + + return True + + return super().eventFilter(src, evt) + + def _drop_on_simple_table(self, evt: QtGui.QDropEvent): + """ + event filter does not receive dropevents? + """ + if self.stackedWidget.currentIndex() == 0: + table = self.simple_table + type_column = 1 + default_drop = self._simple_table_dropevent + type_name = ['Points', 'FID', 'Spectrum', 'BDS'] + else: + table = self.merge_table + type_column = 2 + default_drop = self._merge_table_dropevent + type_name = ['FID', 'Spectrum', 'BDS'] + + pos = evt.pos() + drop_col = table.columnAt(pos.x()) + if drop_col == type_column: + evt.ignore() + else: + drop_row = table.rowAt(pos.y()) + default_drop(evt) + + if drop_row == -1: + w = QtWidgets.QComboBox() + w.addItems(type_name) + item = QtWidgets.QTableWidgetItem('') + drop_row = table.rowAt(pos.y()) + table.setItem(drop_row, type_column, item) + table.setCellWidget(drop_row, type_column, w) + + item = table.item(drop_row, drop_col) + idx = table.indexFromItem(item) + + if idx.row() == -1 and idx.column() == -1: + item = table.takeItem(drop_row, 0) + table.setItem(drop_row, drop_col, item) + + if item is not None: + item.setToolTip(item.text()) + + table.resizeColumnsToContents() + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton) + def change_table(self, button: QtWidgets.QAbstractButton): + idx = [self.simple_button, self.merge_button].index(button) + self.stackedWidget.setCurrentIndex(idx) + + def collect_args(self) -> list: + src_sets = [] + for row in range(self.simple_table.rowCount()): + item = self.simple_table.item(row, 0) + set_id = item.data(QtCore.Qt.UserRole) + graph_id = item.data(QtCore.Qt.UserRole+1) + type_idx = self.simple_table.cellWidget(row, 1).currentIndex() + + src_sets.append((set_id, graph_id, type_idx)) + + for row in range(self.merge_table.rowCount()): + item = self.merge_table.item(row, 0) + graph_id = '' + if item is not None: + set_id_real = item.data(QtCore.Qt.UserRole) + graph_id = item.data(QtCore.Qt.UserRole+1) + else: + set_id_real = '' + + item = self.merge_table.item(row, 1) + if item is not None: + set_id_imag = item.data(QtCore.Qt.UserRole) + graph_id = item.data(QtCore.Qt.UserRole+1) if graph_id == '' else graph_id + else: + set_id_imag = '' + type_idx = self.merge_table.cellWidget(row, 2).currentIndex() + 1 + + src_sets.append((set_id_real, set_id_imag, graph_id, type_idx)) + + print(src_sets) + + self.convertSets.emit(src_sets) + + return src_sets + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonBox_clicked') + def button_clicked(self, button): + role = self.buttonBox.buttonRole(button) + if role == self.buttonBox.RejectRole: + self.close() + else: + self.collect_args() + self.accept() + + +if __name__ == '__main__': + import sys + from collections import OrderedDict + + app = QtWidgets.QApplication(sys.argv) + d = ConversionDialog() + + data = OrderedDict([ + (('1', 'Graph 0'), [('a', 'Das Sein und das Nichts'), ('b', 'b'), ('c', 'c'), ('d', 'd')]), + (('2', 'Graph 2'), [('e', 'e'), ('f', 'f'), ('g', 'g'), ('h', 'h')])]) + + d.set_graphs(data) + d.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/data/datawidget/__init__.py b/nmreval/gui_qt/data/datawidget/__init__.py new file mode 100644 index 0000000..009a5fb --- /dev/null +++ b/nmreval/gui_qt/data/datawidget/__init__.py @@ -0,0 +1 @@ +from .datawidget import * diff --git a/nmreval/gui_qt/data/datawidget/datawidget.py b/nmreval/gui_qt/data/datawidget/datawidget.py new file mode 100644 index 0000000..61311bf --- /dev/null +++ b/nmreval/gui_qt/data/datawidget/datawidget.py @@ -0,0 +1,483 @@ +from typing import List, Union + +from .properties import PropWidget +from ...Qt import QtWidgets, QtGui, QtCore +from ..._py.datawidget import Ui_DataWidget +from ...lib import make_action_icons +from ...lib.delegates import HeaderDelegate + + +class DataTree(QtWidgets.QTreeWidget): + stateChanged = QtCore.pyqtSignal(list, list) # selected, deselected + keyChanged = QtCore.pyqtSignal(str, str) # id, text + positionChanged = QtCore.pyqtSignal(QtWidgets.QTreeWidgetItem) + deleteItem = QtCore.pyqtSignal(list) + moveItem = QtCore.pyqtSignal(list, str, str, int) # items, from, to, new row + copyItem = QtCore.pyqtSignal(list, str) + saveFits = QtCore.pyqtSignal(list) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setColumnCount(1) + self.invisibleRootItem().setFlags(self.invisibleRootItem().flags() ^ QtCore.Qt.ItemIsDropEnabled) + + self.itemChanged.connect(self.data_change) + + self.setColumnCount(2) + + self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + self.setDragDropMode(QtWidgets.QTreeView.InternalMove) + self.setDefaultDropAction(QtCore.Qt.IgnoreAction) + self.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection) + self.setSelectionBehavior(QtWidgets.QTreeView.SelectRows) + + self._checked_graphs = set() + self._checked_sets = set() + self.management = None + + header = QtWidgets.QHeaderView(QtCore.Qt.Horizontal, self) + self.setHeader(header) + header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) + header.setVisible(False) + header.moveSection(1, 0) + self.setColumnWidth(1, 16) + self.setItemDelegateForColumn(1, HeaderDelegate()) + + def add_graph(self, idd: str, name: str): + item = QtWidgets.QTreeWidgetItem() + item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable | + QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) + item.setText(0, name) + item.setData(0, QtCore.Qt.UserRole, idd) + item.setCheckState(0, QtCore.Qt.Checked) + + self.addTopLevelItem(item) + self._checked_graphs.add(idd) + item.setExpanded(True) + + def add_item(self, items: Union[tuple, List[tuple]], gid: str): + if isinstance(items, tuple): + items = [items] + + for row in range(self.invisibleRootItem().childCount()): + graph = self.invisibleRootItem().child(row) + if graph.data(0, QtCore.Qt.UserRole) == gid: + for (idd, name) in items: + item = QtWidgets.QTreeWidgetItem([name]) + item.setData(0, QtCore.Qt.UserRole, idd) + item.setCheckState(0, QtCore.Qt.Checked) + item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable | + QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable) + graph.addChild(item) + self._checked_sets.add(idd) + + self.resizeColumnToContents(0) + break + + @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem) + def data_change(self, item: QtWidgets.QTreeWidgetItem) -> (list, list): + idd = item.data(0, QtCore.Qt.UserRole) + is_selected = item.checkState(0) == QtCore.Qt.Checked + to_be_hidden = set() + to_be_shown = set() + + # item is top-level item / graph + if item.parent() is None: + was_selected = idd in self._checked_graphs + + # check state changed to selected + if is_selected != was_selected: + # check state changed to checked + if is_selected: + self._checked_graphs.add(idd) + iterator = QtWidgets.QTreeWidgetItemIterator(item) + iterator += 1 + self.blockSignals(True) + for i in range(item.childCount()): + child = item.child(i) + child.setCheckState(0, QtCore.Qt.Checked) + to_be_shown.add(child.data(0, QtCore.Qt.UserRole)) + self.blockSignals(False) + + # check state change to unchecked + else: + self._checked_graphs.remove(idd) + self.blockSignals(True) + for i in range(item.childCount()): + child = item.child(i) + child.setCheckState(0, QtCore.Qt.Unchecked) + to_be_hidden.add(child.data(0, QtCore.Qt.UserRole)) + try: + self._checked_sets.remove(child.data(0, QtCore.Qt.UserRole)) + except KeyError: + pass + self.blockSignals(False) + + else: + self.keyChanged.emit(idd, item.text(0)) + + # item is a set + else: + was_selected = idd in self._checked_sets + if is_selected != was_selected: + if is_selected: + to_be_shown.add(idd) + self._checked_sets.add(idd) + + else: + to_be_hidden.add(idd) + try: + self._checked_sets.remove(idd) + except KeyError: + pass + + else: + self.keyChanged.emit(idd, item.text(0)) + + if to_be_shown or to_be_hidden: + self.stateChanged.emit(list(to_be_shown), list(to_be_hidden)) + + return to_be_shown, to_be_hidden + + def dropEvent(self, evt: QtGui.QDropEvent): + dropped_index = self.indexAt(evt.pos()) + if not dropped_index.isValid(): + return + + to_parent = self.itemFromIndex(dropped_index).parent() + append = False + if not to_parent: + # dropped on graph item -> append items + to_parent = self.itemFromIndex(dropped_index) + append = True + + # index may change + persistent_drop = QtCore.QPersistentModelIndex(dropped_index) + + tobemoved = [] + take_from = [] + for it in self.selectedItems(): + from_parent = it.parent() + if from_parent is None: + continue + + from_parent.removeChild(it) + tobemoved.append(it) + take_from.append(from_parent.data(0, QtCore.Qt.UserRole)) + + pos = QtCore.QModelIndex(persistent_drop) + if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.BelowItem: + pos = pos.sibling(pos.row()+1, 0) + + row = pos.row() + if (row == -1) or append: + to_parent.addChildren(tobemoved) + else: + to_parent.insertChildren(row, tobemoved) + + self.management.move_sets([it.data(0, QtCore.Qt.UserRole) for it in tobemoved], + to_parent.data(0, QtCore.Qt.UserRole), take_from, + pos=-1 if append else row) + + def move_sets(self, sid: str, gid_in: str, gid_out: str): + self.blockSignals(True) + to_parent = None + from_parent = None + it = None + + iterator = QtWidgets.QTreeWidgetItemIterator(self) + while iterator.value(): + item = iterator.value() + if item is not None: + data = item.data(0, QtCore.Qt.UserRole) + if data == gid_out: + from_parent = item + + elif data == gid_in: + to_parent = item + + elif data == sid: + it = item + + iterator += 1 + + if (from_parent is None) or (to_parent is None) or (it is None): + print('Komisch') + return + + from_parent.removeChild(it) + to_parent.addChild(it) + + self.blockSignals(False) + + def set_name(self, sid, name): + iterator = QtWidgets.QTreeWidgetItemIterator(self) + while iterator.value(): + item = iterator.value() + if item is not None: + data = item.data(0, QtCore.Qt.UserRole) + if data == sid: + if name != item.text(0): + item.setText(0, name) + break + + iterator += 1 + + def keyPressEvent(self, evt: QtGui.QKeyEvent): + if evt.key() == QtCore.Qt.Key_Delete: + rm_sets = [] + rm_graphs = [] + for idx in self.selectedIndexes(): + if idx.column() == 1: + continue + item = self.itemFromIndex(idx) + if item.parent() is None: + for c_i in range(item.childCount()): + rm_sets.append(item.child(c_i).data(0, QtCore.Qt.UserRole)) + rm_graphs.append(item.data(0, QtCore.Qt.UserRole)) + else: + rm_sets.append(item.data(0, QtCore.Qt.UserRole)) + + # self.deleteItem.emit(rm_sets+rm_graphs) + self.management.delete_sets(rm_sets+rm_graphs) + + elif evt.key() == QtCore.Qt.Key_Space: + sets = [] + from_parent = [] + + for idx in self.selectedIndexes(): + if idx.column() != 0: + continue + item = self.itemFromIndex(idx) + + if item.parent() is None: + is_selected = item.checkState(0) + self.blockSignals(True) + for i in range(item.childCount()): + child = item.child(i) + from_parent.append(child) + self.blockSignals(False) + if is_selected == QtCore.Qt.Checked: + item.setCheckState(0, QtCore.Qt.Unchecked) + else: + item.setCheckState(0, QtCore.Qt.Checked) + + else: + sets.append(item) + + for it in sets: + if it in from_parent: + continue + it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked) + else: + super().keyPressEvent(evt) + + def mousePressEvent(self, evt: QtGui.QMouseEvent): + # disable drag-and-drop when column 1 ('header') is clicked + idx = self.indexAt(evt.pos()) + self.setDragEnabled(idx.column() == 0) + super().mousePressEvent(evt) + + def remove_item(self, ids: list): + iterator = QtWidgets.QTreeWidgetItemIterator(self) + while iterator.value(): + item = iterator.value() + _id = item.data(0, QtCore.Qt.UserRole) + if _id in ids: + try: + idx = item.parent().indexOfChild(item) + item.parent().takeChild(idx) + if _id in self._checked_sets: + self._checked_sets.remove(_id) + except AttributeError: + idx = self.invisibleRootItem().indexOfChild(item) + self.invisibleRootItem().takeChild(idx) + self._checked_graphs.remove(_id) + + iterator += 1 + + def contextMenuEvent(self, evt): + menu = QtWidgets.QMenu() + d = menu.addAction('Hello') + d.setEnabled(False) + menu.addSeparator() + + idx = self.selectedIndexes() + + if self.invisibleRootItem().childCount() == 0 and len(idx) == 0: + rdn_action = menu.addAction('Randomness') + + action = menu.exec(evt.globalPos()) + + if action == rdn_action: + import webbrowser + webbrowser.open('https://en.wikipedia.org/wiki/Special:Random') + + else: + del_action = menu.addAction('Exterminate') + cp_action = menu.addAction('Replicate') + cat_action = menu.addAction('Join us!') + plt_action = None + save_action = None + + idx = {} + has_fits = False + for i in self.selectedIndexes(): + item = self.itemFromIndex(i) + parent = item.parent() + if parent is None: + continue + + else: + graph_id = parent.data(0, QtCore.Qt.UserRole) + if graph_id not in idx: + idx[graph_id] = [] + # collect sets in their graph + idx[graph_id].append(item.data(0, QtCore.Qt.UserRole)) + data = self.management[item.data(0, QtCore.Qt.UserRole)] + if data.mode == 'fit': + has_fits = True + + if has_fits: + menu.addSeparator() + plt_action = menu.addAction('Plot fit parameter') + save_action = menu.addAction('Save fit parameter') + + action = menu.exec(evt.globalPos()) + + if action == del_action: + s = [] + for gid, sets in idx.items(): + s.extend(sets) + self.management.delete_sets(s) + + elif action == cp_action: + for gid, sets in idx.items(): + self.management.copy_sets(sets, gid) + + elif action == cat_action: + s = [] + for gid, sets in idx.items(): + s.extend(sets) + self.management.cat(s) + + elif action == plt_action: + s = [] + for gid, sets in idx.items(): + s.extend(sets) + self.management.make_fit_parameter(s) + + elif action == save_action: + s = [] + for gid, sets in idx.items(): + s.extend(sets) + self.saveFits.emit(s) + + evt.accept() + + def highlight(self, gid: str): + iterator = QtWidgets.QTreeWidgetItemIterator(self) + while iterator.value(): + item = iterator.value() + if item is not None: + if item.data(0, QtCore.Qt.UserRole) == gid: + item.setBackground(0, QtGui.QBrush(QtGui.QColor('gray'))) + else: + item.setBackground(0, QtGui.QBrush()) + iterator += 1 + + def uncheck_sets(self, sets: List[str]): + self.blockSignals(True) + iterator = QtWidgets.QTreeWidgetItemIterator(self) + while iterator.value(): + item = iterator.value() + if item is not None: + if item.data(0, QtCore.Qt.UserRole) in sets: + item.setCheckState(0, QtCore.Qt.Unchecked) + iterator += 1 + self.blockSignals(False) + + +class DataWidget(QtWidgets.QWidget, Ui_DataWidget): + keyChanged = QtCore.pyqtSignal(str, str) + deleteItem = QtCore.pyqtSignal(list) + startShowProperty = QtCore.pyqtSignal(list) + propertyChanged = QtCore.pyqtSignal(list, str, str, object) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + self.tree = DataTree(self) + self.verticalLayout.addWidget(self.tree) + 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.proptable = PropWidget(self) + self.propwidget.addWidget(self.proptable) + self.propwidget.setText('Properties') + self.propwidget.expansionChanged.connect(self.show_property) + self.proptable.propertyChanged.connect(self.change_property) + + make_action_icons(self) + + def add_graph(self, idd: str, name: str): + self.tree.blockSignals(True) + self.tree.add_graph(idd, name) + self.tree.blockSignals(False) + + def add_item(self, idd: str, name: str, gid: str): + self.tree.blockSignals(True) + self.tree.add_item((idd, name), gid) + self.tree.blockSignals(False) + + def add_item_list(self, loi: list, gid: str): + self.tree.blockSignals(True) + self.tree.add_item(loi, gid) + self.tree.blockSignals(False) + + def remove_item(self, key): + self.tree.remove_item(key) + + def show_property(self, _: QtCore.QModelIndex = None): + if not self.propwidget.isExpanded(): + return + + sid = [] + for i in self.tree.selectedIndexes(): + if i.column() == 0: + sid.append(i.data(role=QtCore.Qt.UserRole)) + + self.startShowProperty.emit(sid) + + @QtCore.pyqtSlot(dict) + def set_properties(self, props: dict): + self.proptable.populate(props) + + def change_property(self, key1, key2, value): + ids = [item.data(0, QtCore.Qt.UserRole) for item in self.tree.selectedItems()] + if key2 == 'Value': + try: + value = float(value) + except ValueError: + QtWidgets.QMessageBox.warning(self, 'Invalid entry', + 'Value %r is not a valid number for `value`.' % value) + return + + self.propertyChanged.emit(ids, key1, key2, value) + + def uncheck_sets(self, sets: List[str]): + self.tree.uncheck_sets(sets) + + def set_name(self, sid, value): + self.tree.set_name(sid, value) + + @property + def management(self): + return self.tree.management + + @management.setter + def management(self, value): + self.tree.management = value diff --git a/nmreval/gui_qt/data/datawidget/properties.py b/nmreval/gui_qt/data/datawidget/properties.py new file mode 100644 index 0000000..dd07087 --- /dev/null +++ b/nmreval/gui_qt/data/datawidget/properties.py @@ -0,0 +1,78 @@ +from ...Qt import QtWidgets, QtCore, QtGui +from ...lib.delegates import PropertyDelegate + + +class PropWidget(QtWidgets.QWidget): + propertyChanged = QtCore.pyqtSignal(str, str, object) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.layout = QtWidgets.QVBoxLayout(self) + self.layout.setContentsMargins(2, 2, 2, 2) + self.tab = QtWidgets.QTabWidget(self) + self.layout.addWidget(self.tab) + self.pages = [] + + self.tab.currentChanged.connect(self.tab_change) + + self._tab_idx = 0 + + def populate(self, props: dict): + self.pages = [] + self.tab.blockSignals(True) + while self.tab.count(): + self.tab.removeTab(0) + + for k, v in props.items(): + table = PropTable(self) + table.populate(v) + table.itemChanged.connect(self.property_change) + self.tab.addTab(table, k) + self.pages.append(table) + self.tab.blockSignals(False) + + self.tab.setCurrentIndex(self._tab_idx) + + @QtCore.pyqtSlot(QtWidgets.QTableWidgetItem) + def property_change(self, item: QtWidgets.QTableWidgetItem): + tab_idx = self.tab.currentIndex() + table = self.pages[tab_idx] + idx = table.indexFromItem(item) + self.propertyChanged.emit(self.tab.tabText(tab_idx), + table.item(idx.row(), idx.column()-1).text(), + item.data(QtCore.Qt.DisplayRole)) + + @QtCore.pyqtSlot(int) + def tab_change(self, idx: int): + self._tab_idx = idx + + +class PropTable(QtWidgets.QTableWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setColumnCount(2) + self.setItemDelegateForColumn(1, PropertyDelegate()) + self.horizontalHeader().setStretchLastSection(True) + self.verticalHeader().setVisible(False) + self.horizontalHeader().setVisible(False) + self.setFrameShape(QtWidgets.QFrame.NoFrame) + self.setFrameShadow(QtWidgets.QFrame.Plain) + + def populate(self, prop: dict): + self.clear() + self.setRowCount(0) + self.blockSignals(True) + for k, v in prop.items(): + value_item = QtWidgets.QTableWidgetItem('') + value_item.setData(QtCore.Qt.DisplayRole, v) + + key_item = QtWidgets.QTableWidgetItem(k) + key_item.setFlags(QtCore.Qt.NoItemFlags) + key_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0))) + + self.setRowCount(self.rowCount()+1) + self.setItem(self.rowCount()-1, 0, key_item) + self.setItem(self.rowCount()-1, 1, value_item) + self.blockSignals(False) diff --git a/nmreval/gui_qt/data/integral_widget.py b/nmreval/gui_qt/data/integral_widget.py new file mode 100644 index 0000000..19272bb --- /dev/null +++ b/nmreval/gui_qt/data/integral_widget.py @@ -0,0 +1,213 @@ +from itertools import cycle + +import pyqtgraph as pg +from numpy import nanmax, nanmin, inf, argsort, where +try: + # numpy > 1.19 renamed some integration functions + from scipy.integrate import cumulative_trapezoid +except ImportError: + from scipy.integrate import cumtrapz as cumulative_trapezoid + +from ..Qt import QtWidgets, QtCore, QtGui +from .._py.integral_widget import Ui_Form + + +class IntegralWidget(QtWidgets.QWidget, Ui_Form): + colors = cycle(['red', 'green', 'blue', 'cyan', 'magenta', + 'darkRed', 'darkGreen', 'darkBlue', 'darkCyan', 'darkMagenta']) + + requestData = QtCore.pyqtSignal(str) + item_deleted = QtCore.pyqtSignal(pg.GraphicsObject) + newData = QtCore.pyqtSignal(str, list) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.connected_figure = '' + self.graph_shown = None + self.shown_set = None + + self._data = None + self.ranges = [] + self.lines = [] + + self.max_area = 0 + self.max_y = inf + self.min_y = -inf + + def __call__(self, graph_name, items): + self.label_2.setText(f'Connected to {graph_name}') + + self.clear() + + self.set_combobox.blockSignals(True) + self.set_combobox.clear() + + for sid, name in items: + self.set_combobox.addItem(name, userData=sid) + + self.set_combobox.blockSignals(False) + + self.set_combobox.setCurrentIndex(0) + self.set_combobox.currentIndexChanged.emit(0) + + return self + + def keyPressEvent(self, e): + if e.key() == QtCore.Qt.Key_Delete: + self.remove_integral() + else: + super().keyPressEvent(e) + + @QtCore.pyqtSlot(int, name='on_set_combobox_currentIndexChanged') + def change_set(self, idx: int): + key = self.set_combobox.itemData(idx) + self.requestData.emit(key) + + def set_data(self, ptr): + self._data = ptr + self.max_y = nanmax(self._data.y.real) + self.min_y = nanmin(self._data.y.real) + for idx, rnge in enumerate(self.ranges): + self._update_values(idx, rnge) + + def add(self, pos): + x = pos[0] + self.ranges.append((x, x * 1.1)) + + c = next(IntegralWidget.colors) + qc = QtGui.QColor(c) + qc.setAlpha(40) + region = pg.LinearRegionItem(values=[x, x*1.1], brush=QtGui.QBrush(qc), pen=pg.mkPen(QtGui.QColor(c))) + integral_plot = pg.PlotDataItem(x=[], y=[]) + region.sigRegionChanged.connect(self._update_integral) + + self.lines.append((region, integral_plot)) + self.areas.append(0) + self._make_entry(c) + + return region, integral_plot + + def _make_entry(self, c): + item = QtWidgets.QTreeWidgetItem() + item.setText(0, f'Integral {len(self.ranges)}') + + item.setForeground(0, QtGui.QBrush(QtGui.QColor(c))) + + pts_i = self.ranges[-1] + item_list = [] + for text, val in [('Start', pts_i[0]), ('Stop', pts_i[1]), ('Areas', 0), ('Ratio', 1.)]: + child = QtWidgets.QTreeWidgetItem() + child.setFlags(QtCore.Qt.NoItemFlags) + child.setText(0, f'{text}: {val:.5g}') + child.setForeground(0, QtGui.QBrush(QtGui.QColor('black'))) + + item_list.append(child) + + item.addChildren(item_list) + + self.treeWidget.addTopLevelItem(item) + self.treeWidget.expandToDepth(1) + + self._update_values(len(self.ranges) - 1, pts_i) + + def _update_integral(self): + idx = None + sender = self.sender() + for i, (reg, _) in enumerate(self.lines): + if sender == reg: + idx = i + break + + if idx is None: + return + + self._update_values(idx, sender.getRegion()) + + def _update_values(self, idx, new_range): + self.ranges[idx] = new_range + area = self.calc_integral(idx, *new_range) + + item = self.treeWidget.topLevelItem(idx) + item.child(0).setText(0, f'Start: {new_range[0]:.5g}') + item.child(1).setText(0, f'Stop: {new_range[1]:.5g}') + + if area is not None: + self.areas[idx] = area + item.child(2).setText(0, f'Area: {area:.5g}') + if self.max_area > 0: + self._set_ratios(idx, self.max_area) + + curr_max = max(self.areas) + if curr_max != self.max_area: + if curr_max > 0: + root = self.treeWidget.invisibleRootItem() + for i in range(root.childCount()): + self._set_ratios(i, curr_max) + self.max_area = curr_max + + def _set_ratios(self, idx, max_value): + item = self.treeWidget.invisibleRootItem().child(idx) + area_i = self.areas[idx] + item.child(3).setText(0, f'Ratio: {area_i / max_value:.3g}') + + integral_line = self.lines[idx][1] + x_i, y_i = integral_line.getData() + scale = (self.max_y - self.min_y) / y_i[-1] * (area_i / max_value) + integral_line.setData(x=x_i, y=y_i * scale) + + def calc_integral(self, idx, x_min, x_max): + int_range = where((self._data.x >= x_min) & (self._data.x <= x_max))[0] + if len(int_range) > 1: + x_int = self._data.x[int_range] + y_int = self._data.y[int_range].real + order = argsort(x_int) + + integral = cumulative_trapezoid(y=y_int[order], x=x_int[order], initial=0) + scale = (self.max_y-self.min_y) / integral[-1] + self.lines[idx][1].setData(x=x_int[order], y=integral*scale + self.min_y) + + return integral[-1] + + else: + self.lines[idx][1].setData(x=[], y=[]) + return None + + def remove_integral(self): + root = self.treeWidget.invisibleRootItem() + for item in self.treeWidget.selectedItems(): + idx = root.indexOfChild(item) + + self.ranges.pop(idx) + self.item_deleted.emit(self.lines[idx][0]) + self.item_deleted.emit(self.lines[idx][1]) + self.lines.pop(idx) + self.areas.pop(idx) + self.treeWidget.takeTopLevelItem(idx) + + @QtCore.pyqtSlot(name='on_pushButton_clicked') + def convert_to_datasets(self): + set_id = self.set_combobox.currentData() + values = [] + for i in range(len(self.ranges)): + x_i, y_i = self.lines[i][1].getData() + start_i, stop_i = self.ranges[i] + area_i = self.areas[i] + values.append((x_i, y_i, start_i, stop_i, area_i)) + + self.newData.emit(set_id, values) + + def clear(self): + self.connected_figure = '' + self.graph_shown = None + self.shown_set = None + + self._data = None + self.ranges = [] + self.lines = [] + + self.max_area = 0 + self.max_y = inf + self.min_y = -inf + diff --git a/nmreval/gui_qt/data/interpolate_dialog.py b/nmreval/gui_qt/data/interpolate_dialog.py new file mode 100644 index 0000000..e70fc4b --- /dev/null +++ b/nmreval/gui_qt/data/interpolate_dialog.py @@ -0,0 +1,41 @@ +from numpy import linspace, logspace, log10 + +from ..Qt import QtWidgets, QtCore +from .._py.interpol_dialog import Ui_Dialog + + +class QInterpol(QtWidgets.QDialog, Ui_Dialog): + ready = QtCore.pyqtSignal(dict) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + + self.on_comboBox_2_currentIndexChanged(0) + + @QtCore.pyqtSlot(int) + def on_comboBox_2_currentIndexChanged(self, idx: int): + if idx == 0: + self.frame.hide() + self.frame_2.show() + else: + self.frame.show() + self.frame_2.hide() + + def accept(self): + ret_dic = {'kind': self.comboBox.currentIndex(), 'ylog': self.checkBox_2.isChecked()} + if self.comboBox_2.currentIndex() == 0: + ret_dic['xaxis'] = int(self.lineEdit.text()) + else: + if self.checkBox.isChecked(): + ret_dic['xaxis'] = logspace(log10(float(self.lineEdit_2.text())), + log10(float(self.lineEdit_3.text())), + num=int(self.lineEdit_4.text())) + else: + ret_dic['xaxis'] = linspace(float(self.lineEdit_2.text()), + float(self.lineEdit_3.text()), + num=int(self.lineEdit_4.text())) + ret_dic['idx'] = [i.row() for i in self.listWidget.selectedIndexes()] + self.ready.emit(ret_dic) + self.close() diff --git a/nmreval/gui_qt/data/plot_dialog.py b/nmreval/gui_qt/data/plot_dialog.py new file mode 100644 index 0000000..a49102a --- /dev/null +++ b/nmreval/gui_qt/data/plot_dialog.py @@ -0,0 +1,123 @@ +from random import randint + +import numpy as np + +from ... import models +from ...lib.importer import find_models +from ...lib.utils import valid_function + +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.setbyfunction_dialog import Ui_NewCurveDialog + + +class QPlotDialog(QtWidgets.QDialog, Ui_NewCurveDialog): + line_created = QtCore.pyqtSignal(object) + + def __init__(self): + super().__init__() + self.setupUi(self) + + self._function = find_models(models) + + self.lineEdit_3.setValidator(QtGui.QDoubleValidator()) + self.lineEdit_4.setValidator(QtGui.QDoubleValidator()) + self.lineEdit_5.setValidator(QtGui.QIntValidator().setBottom(0)) + + self.buttonBox.accepted.connect(self.make_line) + + for cb in [self.comboBox, self.comboBox_2, self.comboBox_4, self.comboBox_5]: + cb.setCurrentIndex(randint(0, cb.count())) + + for cb in [self.comboBox_6, self.comboBox_7]: + self.load_models(cb) + + def load_models(self, cb: QtWidgets.QComboBox): + for f in self._function: + cb.addItem(f'{f.name} ({f.type})', userData=f) + + @QtCore.pyqtSlot(name='on_pushButton_clicked') + def check_input(self): + err = [] + try: + start = float(self.lineEdit_3.text()) + except ValueError: + err.append(0) + start = 1 + try: + stop = float(self.lineEdit_4.text()) + except ValueError: + err.append(1) + stop = 10 + + try: + nums = int(self.lineEdit_5.text()) + except ValueError: + err.append(2) + nums = 10 + + if self.checkBox.isChecked(): + if start <= 0 or stop <= 0: + err.append(3) + start, stop = abs(start)+1e-12, abs(stop) + grid = np.geomspace(start, stop, num=nums) + else: + grid = np.linspace(start, stop, num=nums) + + x_func = self.lineEdit.text() + x, isok = valid_function(x_func, extra_namespace={'i': grid}) + if not isok: + err.append(4) + x = grid + + y_func = self.lineEdit_2.text() + y, isok = valid_function(y_func, extra_namespace={'i': grid, 'x':x}) + if not isok: + err.append(5) + + msg_err = {0: 'Invalid value for grid start', + 1: 'Invalid value for grid end', + 2: 'Invalid number of grid steps', + 3: 'Negative numbers in logarithmic grid', + 4: 'Invalid expression for x', + 5: 'Invalid expression for y' + } + + if err: + m = '\n'.join([msg_err[e] for e in err]) + QtWidgets.QMessageBox().information(self, 'Error detected', m) + return False + + return True + + def make_line(self): + if not self.check_input(): + return + + start = float(self.lineEdit_3.text()) + stop = float(self.lineEdit_4.text()) + nums = int(self.lineEdit_5.text()) + if self.checkBox.isChecked(): + x = np.geomspace(start, stop, num=nums) + else: + x = np.linspace(start, stop, num=nums) + x_func = self.lineEdit.text() + y_func = self.lineEdit_2.text() + + sym = self.comboBox.currentText() + lin = self.comboBox_2.currentText() + + name = self.lineEdit_6.text() + if not name: + name = 'self done' + + lw = self.doubleSpinBox.value() + sw = self.spinBox.value() + + +if __name__ == '__main__': + import sys + app = QtWidgets.QApplication(sys.argv) + win = QPlotDialog() + win.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/data/point_select.py b/nmreval/gui_qt/data/point_select.py new file mode 100644 index 0000000..c400249 --- /dev/null +++ b/nmreval/gui_qt/data/point_select.py @@ -0,0 +1,196 @@ +import re + +from ..Qt import QtCore, QtWidgets +from .._py.ptstab import Ui_Form + +__all__ = ['PointSelectWidget'] + +from ..lib.pg_objects import LogInfiniteLine, RegionItem + +REGION_RE = re.compile(r'(?P[+-]*\d+(?:\.\d*)*(?:[eE][+-]*\d+)*)' + r'(?: ?- ?(?P[+-]*\d+(?:\.\d*)*(?:[eE][+-]*\d+)*))*') + + +class PointSelectWidget(QtWidgets.QWidget, Ui_Form): + widget_closed = QtCore.pyqtSignal() + points_selected = QtCore.pyqtSignal(dict, str) + point_removed = QtCore.pyqtSignal(LogInfiniteLine) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.pts = [] + self.pts_lines = [] + self.nop = 0 + self._prev_pos = '' + self._last_item = None + self.connected_figure = '' + + self.okButton.clicked.connect(self.apply) + self.deleteButton.clicked.connect(self.remove_points) + self.peaktable.itemChanged.connect(self.editing_finished) + self.peaktable.itemDoubleClicked.connect(self.editing_started) + + def keyPressEvent(self, e): + if e.key() == QtCore.Qt.Key_Delete: + self.remove_points() + elif e.key() == QtCore.Qt.Key_F2: + self.editing_started() + else: + super().keyPressEvent(e) + + def clear(self): + self.pts = [] + self.nop = 0 + self.peaktable.clear() + self.pts_lines = [] + + @QtCore.pyqtSlot(tuple, bool) + def add(self, pos: tuple, double: bool): + x = pos[0] + if double: + self.removepoint(-1) + + self.pts.append((x, x*1.1)) + item = RegionItem(values=[x, x*1.1], mode='mid') + item.sigRegionChanged.connect(self._update_region) + else: + self.pts.append(x) + item = LogInfiniteLine(pos=x, movable=True) + item.sigPositionChanged.connect(self._update_line) + + self.pts_lines.append(item) + self.nop += 1 + self._makerow() + + return item + + def remove_points(self): + for i in sorted(self.peaktable.selectedIndexes(), key=lambda x: x.row(), reverse=True): + self.removepoint(pos=i.row()) + + def removepoint(self, pos=0): + if pos == -1: + pos = len(self.pts) - 1 + + try: + self.pts.pop(pos) + self.nop -= 1 + item = self.peaktable.takeItem(pos) + del item + + del_line = self.pts_lines.pop(pos) + self.point_removed.emit(del_line) + del del_line + except IndexError: + pass + + def _makerow(self): + if isinstance(self.pts[-1], tuple): + item = QtWidgets.QListWidgetItem(f'{self.pts[-1][0]:.5g} - {self.pts[-1][1]:.5g}') + else: + item = QtWidgets.QListWidgetItem(f'{self.pts[-1]:.5g}') + item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable) + self.peaktable.blockSignals(True) + self.peaktable.addItem(item) + self.peaktable.blockSignals(False) + + def closeEvent(self, evt): + self.widget_closed.emit() + super().closeEvent(evt) + + @QtCore.pyqtSlot() + def apply(self) -> dict: + ret_dic = {'avg_range': [self.left_pt.value(), self.right_pt.value()], + 'avg_mode': {0: 'mean', 1: 'sum', 2: 'integral'}[self.average_combobox.currentIndex()], + 'special': None, 'idx': None, + 'xy': (self.xbutton.isChecked(), self.ybutton.isChecked())} + + if self.groupBox_2.isChecked(): + ret_dic['special'] = {0: 'max', 1: 'absmax', 2: 'min', 3: 'absmin'}[self.special_comboBox.currentIndex()] + + if len(self.pts) != 0: + ret_dic['idx'] = self.pts + + if self.graph_checkbox.isChecked(): + gid = '' + else: + gid = self.graph_combobox.currentData() + + self.points_selected.emit(ret_dic, gid) + + return ret_dic + + def _update_region(self): + try: + idx = self.pts_lines.index(self.sender()) + except ValueError: + return + + self.pts[idx] = self.sender().getRegion() + self.peaktable.blockSignals(True) + self.peaktable.item(idx).setText('{:.5g} - {:.5g}'.format(*self.pts[idx])) + self.peaktable.blockSignals(False) + + def _update_line(self): + try: + idx = self.pts_lines.index(self.sender()) + except ValueError: + return + + self.pts[idx] = self.sender().value() + self.peaktable.blockSignals(True) + self.peaktable.item(idx).setText(f'{self.pts[idx]:.5g}') + self.peaktable.blockSignals(False) + + @QtCore.pyqtSlot(QtWidgets.QListWidgetItem) + def editing_started(self, item=None): + if item is None: + item = self.peaktable.selectedItems()[0] + self._prev_pos = item.text() + self.peaktable.editItem(item) + + @QtCore.pyqtSlot(QtWidgets.QListWidgetItem) + def editing_finished(self, it: QtWidgets.QListWidgetItem): + m = re.match(REGION_RE, it.text()).groupdict() + undo = True + if m: + start, stop = m['first'], m['second'] + row = self.peaktable.row(it) + it_pts = self.pts_lines[row] + if ((stop is None) and isinstance(it_pts, RegionItem)) or \ + ((stop is not None) and isinstance(it_pts, LogInfiniteLine)): + QtWidgets.QMessageBox().information(self, 'Invalid type', + 'Conversion between point and region is not possible.') + else: + if stop is None: + it_pts.blockSignals(True) + it_pts.setValue(float(start)) + it_pts.blockSignals(False) + self.pts[row] = float(start) + else: + start, stop = float(start), float(stop) + pos = (min(start, stop), max(start, stop)) + self.pts[row] = pos + self.peaktable.blockSignals(True) + it.setText(f'{pos[0]:.5g} - {pos[1]:.5g}') + self.peaktable.blockSignals(False) + it_pts.blockSignals(True) + it_pts.setRegion(pos) + it_pts.blockSignals(False) + undo = False + + if undo: + self.peaktable.blockSignals(True) + it.setText(self._prev_pos) + self.peaktable.blockSignals(False) + + def set_graphs(self, graphs: list): + self.graph_combobox.clear() + for g in graphs: + self.graph_combobox.addItem(g[1], userData=g[0]) + + @QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged') + def changed_state(self, checked): + self.graph_combobox.setEnabled(checked!=QtCore.Qt.Checked) diff --git a/nmreval/gui_qt/data/shift_graphs.py b/nmreval/gui_qt/data/shift_graphs.py new file mode 100644 index 0000000..0fa7f32 --- /dev/null +++ b/nmreval/gui_qt/data/shift_graphs.py @@ -0,0 +1,190 @@ +import numpy as np +from itertools import cycle + +from pyqtgraph import mkColor, mkPen + +from ...lib.colors import Tab10 +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.shift_scale_dialog import Ui_shift_dialog +from ..lib.pg_objects import PlotItem +from ..lib.utils import SciSpinBox + + +class QShift(QtWidgets.QDialog, Ui_shift_dialog): + valuesChanged = QtCore.pyqtSignal(dict, tuple) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.graphicsView.setMenuEnabled(False) + self.splitter.setSizes([int(self.width()/4), int(self.width()*2/3)]) + + self.movements = {} + self.data = {} + self._colors = cycle(Tab10) + + delegate = SpinBoxDelegate() + delegate.valueChanged.connect(self.shift) + self.shift_table.setItemDelegate(delegate) + self.x_shift_spinbox.valueChanged.connect(lambda: self.glob_shift('h')) + self.y_shift_spinbox.valueChanged.connect(lambda: self.glob_shift('v')) + + delegate = SpinBoxDelegate() + delegate.valueChanged.connect(self.scale) + self.scale_table.setItemDelegate(delegate) + self.x_scale_spinbox.valueChanged.connect(lambda: self.glob_scale('h')) + self.y_scale_spinbox.valueChanged.connect(lambda: self.glob_scale('v')) + + def add_item(self, idx, name, x, y): + color = mkColor(next(self._colors).rgb()) + if np.iscomplexobj(y): + pl = [PlotItem(x, y.real, name=name, pen=mkPen(color=color)), + PlotItem(x, y.imag, name=name, pen=mkPen(color=color))] + else: + pl = [PlotItem(x, y, name=name, pen=mkPen(color=color))] + + self.data[idx] = (pl, x, y) + + # [[horizontal shift, vertical shift], [horizontal scale, vertical scale]] + self.movements[idx] = [[0, 0], [1, 1]] + + for i, tw in enumerate([self.shift_table, self.scale_table]): + tw.blockSignals(True) + row = tw.rowCount() + tw.insertRow(row) + + item = QtWidgets.QTableWidgetItem(name) + item.setForeground(QtGui.QBrush(color)) + item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) + item.setCheckState(QtCore.Qt.Checked) + item.setData(QtCore.Qt.UserRole, idx) + tw.setItem(row, 0, item) + + tw.setItem(row, 1, QtWidgets.QTableWidgetItem(str(i))) + tw.setItem(row, 2, QtWidgets.QTableWidgetItem(str(i))) + + tw.blockSignals(False) + for i in pl: + self.graphicsView.addItem(i) + + def set_graphs(self, graphs: list): + for key, name in graphs: + self.data_combobox.addItem(name, userData=key) + self.values_combobox.addItem(name, userData=key) + + def glob_shift_scale(self, widget: QtWidgets.QTableWidget, mode: int, col: int, value: float): + for row in range(widget.rowCount()): + if widget.item(row, 0).checkState() == QtCore.Qt.Checked: + item = widget.item(row, col) + item.setText(str(value)) + self.shift_scale(widget, mode, row, col-1, value) + + def glob_shift(self, direction: str): + if direction == 'h': + val = self.x_shift_spinbox.value() + self.glob_shift_scale(self.shift_table, 0, 1, val) + else: + val = self.y_shift_spinbox.value() + self.glob_shift_scale(self.shift_table, 0, 2, val) + + def glob_scale(self, direction: str): + if direction == 'h': + val = self.x_scale_spinbox.value() + self.glob_shift_scale(self.scale_table, 1, 1, val) + else: + val = self.y_scale_spinbox.value() + self.glob_shift_scale(self.scale_table, 1, 2, val) + + def shift_scale(self, widget: QtWidgets.QTableWidget, mode: int, + row: int, col: int, value: float): + item = widget.item(row, 0) + key = item.data(QtCore.Qt.UserRole) + self.movements[key][mode][col] = value + + (x_off, y_off), (x_scale, y_scale) = self.movements[key] + + pl, x, y = self.data[key] + y_part = [np.real, np.imag] + for i, item in enumerate(pl): + item.setData(x=x*x_scale+x_off, y=y_part[i](y) * y_scale + y_off) + + @QtCore.pyqtSlot(int, int, float) + def shift(self, row: int, column: int, value: float): + self.shift_scale(self.shift_table, 0, row, column-1, value) + + @QtCore.pyqtSlot(int, int, float) + def scale(self, row: int, column: int, value: float): + self.shift_scale(self.scale_table, 1, row, column-1, value) + + @QtCore.pyqtSlot(int, name='on_xlog_checkbox_stateChanged') + @QtCore.pyqtSlot(int, name='on_ylog_checkbox_stateChanged') + def set_log(self, state: int): + if self.sender() == self.xlog_checkbox: + log_state = self.graphicsView.plotItem.ctrl.logXCheck + else: + log_state = self.graphicsView.plotItem.ctrl.logYCheck + log_state.setCheckState(state) + self.graphicsView.plotItem.updateLogMode() + + def on_overwrite_checkbox_stateChanged(self, state: int): + self.data_newgraph.setVisible(state != QtCore.Qt.Checked) + self.data_combobox.setVisible(state != QtCore.Qt.Checked) + + def on_value_checkbox_stateChanged(self, state: int): + self.values_newgraph.setVisible(state == QtCore.Qt.Checked) + self.values_combobox.setVisible(state == QtCore.Qt.Checked) + + def on_data_newgraph_stateChanged(self, state: int): + self.data_combobox.setEnabled(state != QtCore.Qt.Checked) + + def on_values_newgraph_stateChanged(self, state: int): + self.values_combobox.setEnabled(state != QtCore.Qt.Checked) + + def accept(self): + data_saving = None + if not self.overwrite_checkbox.isChecked(): + if self.data_newgraph.isChecked(): + data_saving = '' + else: + data_saving = self.data_combobox.currentData() + + value_saving = None + if self.value_checkbox.isChecked(): + if self.values_newgraph.isChecked(): + value_saving = '' + else: + value_saving = self.values_combobox.currentData() + + self.valuesChanged.emit(self.movements, (data_saving, value_saving)) + self.close() + + +class SpinBoxDelegate(QtWidgets.QStyledItemDelegate): + valueChanged = QtCore.pyqtSignal(int, int, float) + + def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem, + idx: QtCore.QModelIndex) -> QtWidgets.QWidget: + editor = SciSpinBox(parent) + editor.valueChanged.connect(self.new_value) + + return editor + + def new_value(self, val): + # geht bestimmt besser... + table = self.sender().parent().parent() + self.valueChanged.emit(table.currentRow(), table.currentColumn(), val) + + +if __name__ == '__main__': + import sys + + app = QtWidgets.QApplication(sys.argv) + mplQt = QShift() + xx = np.geomspace(1, 100) + mplQt.add_item('aa', 'a', xx, xx/(1+xx**2)*10) + mplQt.add_item('bb', 'b', xx, xx/(1+(0.1*xx)**2)) + mplQt.add_item('cc', 'c', xx, xx/(1+(0.01*xx)**2)/10) + mplQt.set_graphs([('123', 'zyx'), ('456', 'wvu')]) + mplQt.show() + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/data/signaledit/__init__.py b/nmreval/gui_qt/data/signaledit/__init__.py new file mode 100644 index 0000000..b92851f --- /dev/null +++ b/nmreval/gui_qt/data/signaledit/__init__.py @@ -0,0 +1,2 @@ +from .phase_dialog import QApodDialog, QPhasedialog +from .baseline_dialog import QBaselineDialog diff --git a/nmreval/gui_qt/data/signaledit/baseline_dialog.py b/nmreval/gui_qt/data/signaledit/baseline_dialog.py new file mode 100644 index 0000000..c231cd7 --- /dev/null +++ b/nmreval/gui_qt/data/signaledit/baseline_dialog.py @@ -0,0 +1,92 @@ +import numpy as np +import pyqtgraph as pg + +from scipy.interpolate import splrep, splev + +from ...Qt import QtCore, QtWidgets +from ..._py.baseline_dialog import Ui_SignalEdit + + +class QBaselineDialog(QtWidgets.QDialog, Ui_SignalEdit): + finished = QtCore.pyqtSignal(str, tuple) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.data = None + + self.graph = pg.PlotDataItem(x=[], y=[], pen=pg.mkPen({'color': 'b'})) + self.graph_corr = pg.PlotDataItem(x=[], y=[], pen=pg.mkPen({'color': 'r'})) + self.baseline = pg.PlotDataItem(x=[], y=[]) + + self.anchors = [] + self.anchor_lines = [] + self.spline = None + + self.graphicsView.scene().sigMouseClicked.connect(self.add_node) + self.graphicsView.addItem(self.graph_corr) + self.graphicsView.addItem(self.graph) + self.graphicsView.addItem(self.baseline) + + def add_data(self, x, y): + if self.data is not None: + QtWidgets.QMessageBox().information(self, 'Invalid number of datasets', + 'Baseline correction is only working on one set at a time.') + self.close() + self.anchors.extend([np.min(x), np.max(x)]) + self.data = (x, y) + self.graph.setData(x=x, y=y.real) + self.graph_corr.setData(x=x, y=y.real) + + def accept(self): + self.finished.emit('bls', (splev(self.data[0], self.spline),)) + self.close() + + def add_node(self, evt): + vb = self.graphicsView.plotItem.vb + + if self.graphicsView.plotItem.sceneBoundingRect().contains(evt.scenePos()) and evt.button() == 1: + pos = vb.mapSceneToView(evt.scenePos()) + x = pos.x() + + self.anchors.append(x) + self.anchors.sort() + row = self.anchors.index(x) + self.listWidget.insertItem(row-1, QtWidgets.QListWidgetItem(str(x))) + + inf_line = pg.InfiniteLine(pos=x) + self.anchor_lines.insert(row-1, inf_line) + self.graphicsView.addItem(inf_line) + + self.change_baseline() + + def change_baseline(self): + if self.data: + x, y = self.data + + def mean(xx): + return np.mean(y[max(0, np.argmin(abs(x-xx))-5):min(len(x), np.argmin(abs(x-xx))+6)].real) + + y_node = [mean(x_node) for x_node in self.anchors] + try: + self.spline = splrep(self.anchors, y_node, per=False) + except TypeError: + self.spline = splrep(self.anchors, y_node, per=False, k=1) + + bl = splev(x, self.spline) + + self.baseline.setData(x=x, y=bl) + self.graph_corr.setData(x=x, y=y.real-bl) + + def keyPressEvent(self, evt): + if self.listWidget.hasFocus() and evt.key() == QtCore.Qt.Key_Delete: + r = self.listWidget.currentRow() + self.anchors.pop(r+1) + listitem = self.listWidget.takeItem(r) + del listitem + self.graphicsView.removeItem(self.anchor_lines.pop(r)) + self.change_baseline() + + else: + super().keyPressEvent(evt) diff --git a/nmreval/gui_qt/data/signaledit/editsignalwidget.py b/nmreval/gui_qt/data/signaledit/editsignalwidget.py new file mode 100644 index 0000000..23bb9cd --- /dev/null +++ b/nmreval/gui_qt/data/signaledit/editsignalwidget.py @@ -0,0 +1,92 @@ +from ....math import apodization +from ....lib.importer import find_models +from ....utils.text import convert + +from ...Qt import QtCore, QtWidgets, QtGui +from ...lib.forms import FormWidget +from ..._py.editsignalwidget import Ui_Form + + +class EditSignalWidget(QtWidgets.QWidget, Ui_Form): + do_something = QtCore.pyqtSignal(str, tuple) + get_values = QtCore.pyqtSignal() + preview_triggered = QtCore.pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.apodlist = find_models(apodization) + + self.lineEdit.hide() + self.lineEdit.setValidator(QtGui.QDoubleValidator()) + + for ap in self.apodlist: + self.apodcombobox.addItem(str(ap().name)) + self.change_apodization(0) + + self.baselinebutton.clicked.connect(lambda: self.apply_changes('bl')) + self.zfbutton.clicked.connect(lambda: self.apply_changes('zf')) + self.phasebutton.clicked.connect(lambda: self.apply_changes('ph')) + self.apodbutton.clicked.connect(lambda: self.apply_changes('ap')) + self.leftshiftbutton.clicked.connect(lambda: self.apply_changes('ls')) + self.fourierutton.clicked.connect(lambda: self.apply_changes('ft')) + + self.pushButton.clicked.connect(lambda: self.preview_triggered.emit('ap')) + self.pushButton_2.clicked.connect(lambda: self.preview_triggered.emit('ph')) + + @QtCore.pyqtSlot(str) + def apply_changes(self, sender): + if sender in ['bl', 'zf', 'ft']: + self.do_something.emit(sender, tuple()) + + elif sender == 'ls': + if self.comboBox.currentIndex() == 0: + _nop = int(self.lsspinBox.text()) + stype = 'pts' + else: + try: + _nop = float(self.lineEdit.text()) + except ValueError: + _nop = 0.0 + stype = 'time' + self.do_something.emit(sender, (_nop, stype)) + + elif sender == 'ap': + apodmodel = self.apodlist[self.apodcombobox.currentIndex()] + p = [float(x.text()) for x in self.groupBox_3.findChildren(QtWidgets.QLineEdit)] + self.do_something.emit(sender, (p, apodmodel)) + + elif sender == 'ph': + ph0 = float(self.ph0slider.value()) + ph1 = float(self.ph1slider.value()) + pvt = float(self.pivot_lineedit.text()) + self.do_something.emit(sender, (ph0, ph1, pvt)) + + else: + print('You should never reach this by accident.') + + @QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged') + def change_apodization(self, index): + apod_func = self.apodlist[index] + self.label_2.setText(convert(apod_func.equation)) + + while self.verticalLayout_8.count(): + item = self.verticalLayout_8.takeAt(0) + try: + item.widget().deleteLater() + except AttributeError: + pass + + for k, v in enumerate(apod_func.params): + widgt = FormWidget(name=v) + self.verticalLayout_8.addWidget(widgt) + + @QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged') + def change_ls(self, idx): + if idx: + self.lineEdit.show() + self.lsspinBox.hide() + else: + self.lineEdit.hide() + self.lsspinBox.show() diff --git a/nmreval/gui_qt/data/signaledit/phase_dialog.py b/nmreval/gui_qt/data/signaledit/phase_dialog.py new file mode 100644 index 0000000..0f89208 --- /dev/null +++ b/nmreval/gui_qt/data/signaledit/phase_dialog.py @@ -0,0 +1,196 @@ +import numpy as np +import pyqtgraph as pg +from numpy import inf, linspace +from numpy.fft import fft, fftfreq, fftshift + +from ....lib.importer import find_models +from ....math import apodization as apodization +from ....utils.text import convert + +from ...Qt import QtCore, QtWidgets +from ..._py.apod_dialog import Ui_ApodEdit +from ..._py.phase_corr_dialog import Ui_SignalEdit +from ...lib.forms import FormWidget + + +class QPreviewDialogs(QtWidgets.QDialog): + finished = QtCore.pyqtSignal(str, tuple) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.data = [] + self.graphs = [] + + self.mode = '' + + def add_data(self, x, y): + self.data.append((x, y)) + real_plt = pg.PlotDataItem(x=x, y=y.real, pen=pg.mkColor('b')) + imag_plt = pg.PlotDataItem(x=x, y=y.imag, pen=pg.mkColor('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.mode = 'ph' + + self.pvt_line = pg.InfiniteLine(pos=0, movable=True) + self.graphicsView.addItem(self.pvt_line) + self.pvt_line.sigPositionChanged.connect(self.move_line) + + @QtCore.pyqtSlot(float, name='on_ph1slider_valueChanged') + @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(str(int(evt.value()))) + + +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.apodcombobox.blockSignals(True) + for ap in self.apods: + self.apodcombobox.addItem(ap().name) + self.apodcombobox.blockSignals(False) + + self.apod_graph = pg.PlotDataItem(x=[], y=[]) + self.graphicsView.addItem(self.apod_graph) + + self.mode = 'ap' + + self.change_apodization(0) + + def add_data(self, x, y): + real_plt = pg.PlotDataItem(x=x, y=y.real, pen=pg.mkPen('b')) + # imag_plt = pg.PlotDataItem(x=x, y=y.imag, pen=pg.mkPen('r')) + self.graphicsView.addItem(real_plt) + # self.graphicsView.addItem(imag_plt) + + y_fft = fftshift(fft(y)) + x_fft = fftshift(fftfreq(len(x), d=x[1]-x[0])) + real_plt_fft = pg.PlotDataItem(x=x_fft, y=y_fft.real, pen=pg.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)) + self.data.append((x, y, x_fft)) + + xlimits = (max(x.min(), self._limits[0][0]), min(x.max(), self._limits[0][1])) + ylimit = max(self._limits[1], y.real.max()) + self._limits = xlimits, ylimit + + @QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged') + def change_apodization(self, index): + # delete old widgets + self.eqn_label.setText(convert(self.apods[index].equation)) + while self.widget_layout.count(): + item = self.widget_layout.takeAt(0) + try: + item.widget().deleteLater() + except AttributeError: + pass + + # set up parameter widgets for new model + for k, v in enumerate(self.apods[index]().params): + widgt = FormWidget(name=v) + widgt.valueChanged.connect(self._temp_apod) + self.widget_layout.addWidget(widgt) + + self.widget_layout.addStretch() + self._temp_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): + p = [] + for i in range(self.widget_layout.count()): + item = self.widget_layout.itemAt(i) + w = item.widget() + try: + p.append(w.value) + except AttributeError: + continue + + return p + + def get_value(self): + apodmodel = self.apods[self.apodcombobox.currentIndex()] + p = self._get_parameter() + + return p, apodmodel diff --git a/nmreval/gui_qt/data/valueeditwidget.py b/nmreval/gui_qt/data/valueeditwidget.py new file mode 100644 index 0000000..f79e32c --- /dev/null +++ b/nmreval/gui_qt/data/valueeditwidget.py @@ -0,0 +1,336 @@ +from typing import Union, List + +import numpy as np + +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.valueeditor import Ui_MaskDialog + + +class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): + requestData = QtCore.pyqtSignal(str) + maskSignal = QtCore.pyqtSignal(str, list) + itemChanged = QtCore.pyqtSignal(str, tuple, object) + itemDeleted = QtCore.pyqtSignal(str, list) + itemAdded = QtCore.pyqtSignal(str) + values_selected = QtCore.pyqtSignal(str, list, list) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + + self.graph_shown = None + self.shown_set = None + self.items = {} + + self.model = ValueModel() + self.model.itemChanged.connect(self.update_items) + + self.selection_model = QtCore.QItemSelectionModel(self.model) + self.selection_model.selectionChanged.connect(self.show_position) + + self.tableView.setModel(self.model) + self.tableView.setSelectionModel(self.selection_model) + self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.tableView.customContextMenuRequested.connect(self.ctx) + + self.comboBox.currentIndexChanged.connect(self._populate_sets) + self.comboBox_2.currentIndexChanged.connect(self._populate_table) + + def __call__(self, items: dict): + self.items = items + + self.comboBox.blockSignals(True) + self.comboBox.clear() + for k, v in items.items(): + self.comboBox.addItem(k[1], userData=k[0]) + self.comboBox.blockSignals(False) + + idx = self.comboBox.findData(self.graph_shown) + if idx == -1: + idx = 0 + + self.comboBox.setCurrentIndex(idx) + self.comboBox.currentIndexChanged.emit(idx) + + return self + + @QtCore.pyqtSlot(int) + def _populate_sets(self, idx: int): + if idx == -1: + self.comboBox.setCurrentIndex(0) + return + + self.comboBox_2.blockSignals(True) + self.comboBox_2.clear() + self.graph_shown = self.comboBox.currentData() + + if self.items: + for sid, name in self.items[(self.comboBox.currentData(), self.comboBox.currentText())]: + self.comboBox_2.addItem(name, userData=sid) + self.comboBox_2.blockSignals(False) + + sidx = self.comboBox_2.findData(self.shown_set) + if sidx == -1: + sidx = 0 + + self.comboBox_2.setCurrentIndex(sidx) + self.comboBox_2.currentIndexChanged.emit(sidx) + + @QtCore.pyqtSlot(int) + def _populate_table(self, idx): + self.selection_model.clearSelection() + self.shown_set = self.comboBox_2.itemData(idx) + self.requestData.emit(self.comboBox_2.itemData(idx)) + + def set_data(self, data: list, mask: np.ndarray): + self.selection_model.clearSelection() + self.model.loadData(data, mask) + self.spinBox.setMaximum(self.model.rowCount()) + + def ctx(self, pos: QtCore.QPoint): + idx = self.tableView.indexAt(pos) + if not idx.isValid(): + return + + menu = QtWidgets.QMenu() + menu.addSeparator() + + hide_action = menu.addAction('Hide/Show') + menu.addSeparator() + del_action = menu.addAction('Delete') + menu.addSeparator() + cpc_action = menu.addAction('Copy to clipboard') + action = menu.exec(self.tableView.viewport().mapToGlobal(pos)) + + if action == hide_action: + self.mask_row() + elif action == del_action: + self.delete_item() + elif action == cpc_action: + self.copy_selection() + + def keyPressEvent(self, evt): + if evt.matches(QtGui.QKeySequence.Copy): + self.copy_selection() + elif evt.key() == QtCore.Qt.Key_Delete: + self.delete_item() + else: + super().keyPressEvent(evt) + + def copy_selection(self) -> str: + table = '' + for r in self.selection_model.selectedRows(): + table += self.model.data(r) + '\t' + table += '\t'.join([self.model.data(r.sibling(r.row(), i)) for i in [1, 2]]) + '\n' + + QtWidgets.QApplication.clipboard().setText(table) + + return table + + @QtCore.pyqtSlot(name='on_mask_button_clicked') + def mask_row(self): + for r in self.selection_model.selectedRows(): + self.model.setData(r, not self.model.data(r, ValueModel.maskRole), ValueModel.maskRole) + self.maskSignal.emit(self.comboBox_2.currentData(), self.model.mask) + + @QtCore.pyqtSlot(name='on_unmaskbutton_clicked') + def unmask(self): + self.model.unmask() + self.maskSignal.emit(self.comboBox_2.currentData(), self.model.mask) + + @QtCore.pyqtSlot(name='on_delete_button_clicked') + def delete_item(self): + idx = [r.row() for r in self.selection_model.selectedRows()] + success = False + for i in sorted(idx, reverse=True): + success = self.model.removeRow(i) + + if success: + self.itemDeleted.emit(self.comboBox_2.currentData(), idx) + self.spinBox.setMaximum(self.spinBox.maximum()-len(idx)) + + @QtCore.pyqtSlot(name='on_add_button_clicked') + def new_value(self): + success = self.model.addRows() + if success: + self.spinBox.setMaximum(self.spinBox.maximum()+1) + self.itemAdded.emit(self.comboBox_2.currentData()) + + @QtCore.pyqtSlot(int, int, str) + def update_items(self, col, row, val): + sid = self.comboBox_2.currentData() + new_value = complex(val) + new_value = new_value.real if new_value.imag == 0 else new_value + + self.itemChanged.emit(sid, (col, row), new_value) + + @QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection) + def show_position(self, items_selected, items_deselected): + xvals = [] + yvals = [] + for idx in self.selection_model.selectedRows(): + xvals.append(float(self.model.data(idx))) + try: + yvals.append(float(self.model.data(idx.sibling(idx.row(), 1)))) + except ValueError: + yvals.append(complex(self.model.data(idx.sibling(idx.row(), 1))).real) + + self.values_selected.emit(self.graph_shown, xvals, yvals) + + @QtCore.pyqtSlot(name='on_toolButton_clicked') + def goto(self): + self.tableView.scrollTo(self.model.index(self.spinBox.value()-1, 0)) + + +class ValueModel(QtCore.QAbstractTableModel): + """ + TableModel with lazy loading + """ + itemChanged = QtCore.pyqtSignal(int, int, str) + load_number = 20 + maskRole = QtCore.Qt.UserRole+321 + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self._data = None + self.total_rows = 0 + self.rows_loaded = 0 + self.mask = None + self.headers = ['x', 'y', '\u0394y'] + for i, hd in enumerate(self.headers): + self.setHeaderData(i, QtCore.Qt.Horizontal, hd) + + def rowCount(self, *args, **kwargs) -> int: + return self.total_rows + + def columnCount(self, *args, **kwargs) -> int: + return len(self.headers) + + def loadData(self, data: List[np.ndarray], mask: np.ndarray): + self.beginResetModel() + self._data = [] + for x, y, y_err in zip(*data): + self._data.append([x, y, y_err]) + self.total_rows = len(self._data) + + self.mask = mask.tolist() + + self.endResetModel() + self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [QtCore.Qt.DisplayRole]) + + def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> object: + if not idx.isValid(): + return + + row = idx.row() + if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]: + val = self._data[row][idx.column()] + if isinstance(val, complex): + return f'{val.real:.8g}{val.imag:+.8g}j' + else: + return f'{val:.8g}' + + elif role == QtCore.Qt.BackgroundRole: + pal = QtGui.QGuiApplication.palette() + if not self.mask[row]: + return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Base) + else: + return pal.color(QtGui.QPalette.Base) + + elif role == QtCore.Qt.ForegroundRole: + pal = QtGui.QGuiApplication.palette() + if not self.mask[row]: + return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text) + else: + return pal.color(QtGui.QPalette.Text) + + elif role == ValueModel.maskRole: + return self.mask[row] + + else: + return + + def setData(self, idx: QtCore.QModelIndex, value: Union[str, bool], role=QtCore.Qt.DisplayRole) -> object: + col, row = idx.column(), idx.row() + + if role == ValueModel.maskRole: + self.mask[row] = bool(value) + self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role]) + + return True + + if value: + if role == QtCore.Qt.EditRole: + try: + value = complex(value) + except ValueError: + # not a number + return False + self._data[row][col] = value.real if value.imag == 0 else value + self.itemChanged.emit(col, row, str(value)) + self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role]) + + else: + return super().setData(idx, value, role=role) + + return True + + else: + return False + + def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> object: + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + return self.headers[section] + else: + return str(section+1) + + return + + def canFetchMore(self, idx: QtCore.QModelIndex) -> bool: + if not idx.isValid(): + return False + + return self.total_rows > self.rows_loaded + + def fetchMore(self, idx: QtCore.QModelIndex): + remaining = self.total_rows - self.rows_loaded + to_be_loaded = min(remaining, ValueModel.load_number) + + self.beginInsertRows(QtCore.QModelIndex(), self.rows_loaded, self.rows_loaded + to_be_loaded - 1) + self.rows_loaded += to_be_loaded + self.endInsertRows() + + def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags: + return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemIsEditable + + def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool: + self.beginRemoveRows(parent, pos, pos+rows-1) + + for _ in range(rows): + self._data.pop(pos) + self.mask.pop(pos) + + self.endRemoveRows() + return True + + def addRows(self, num=1): + return self.insertRows(self.rowCount(), num) + + def insertRows(self, pos: int, rows: int, parent=QtCore.QModelIndex(), *args, **kwargs): + self.beginInsertRows(parent, pos, pos+rows-1) + + for _ in range(rows): + self._data.insert(pos, [0.0] * self.columnCount()) + self.mask.insert(pos, True) + self.total_rows += rows + + self.endInsertRows() + + return True + + def unmask(self): + self.mask = [True] * self.total_rows + self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole]) diff --git a/nmreval/gui_qt/fit/__init__.py b/nmreval/gui_qt/fit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmreval/gui_qt/fit/fit_forms.py b/nmreval/gui_qt/fit/fit_forms.py new file mode 100644 index 0000000..958e883 --- /dev/null +++ b/nmreval/gui_qt/fit/fit_forms.py @@ -0,0 +1,453 @@ +from typing import Tuple, Union + +from ...utils.text import convert +from ..Qt import QtCore, QtWidgets, QtGui +from .._py.fitmodelwidget import Ui_FitParameter +from .._py.save_fitmodel_dialog import Ui_SaveDialog +from ..lib import get_icon + + +class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter): + value_requested = QtCore.pyqtSignal(object) + value_changed = QtCore.pyqtSignal(str) + state_changed = QtCore.pyqtSignal() + + def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False): + super().__init__(parent) + self.setupUi(self) + + self.parametername.setText(label + ' ') + + validator = QtGui.QDoubleValidator() + validator.setDecimals(9) + self.parameter_line.setValidator(validator) + self.parameter_line.setText('1') + self.parameter_line.setMaximumWidth(60) + self.lineEdit.setMaximumWidth(60) + self.lineEdit_2.setMaximumWidth(60) + + self.label_3.setText(f'< {label} <') + + self.checkBox.stateChanged.connect(self.enableBounds) + self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit()) + self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self)) + self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text())) + self.fixed_check.toggled.connect(self.set_fixed) + + if fixed: + self.fixed_check.hide() + + self.menu = QtWidgets.QMenu(self) + self.add_links() + + self.is_linked = None + self.parameter_pos = None + self.func_idx = None + + self._linetext = '1' + + @property + def name(self): + return convert(self.parametername.text().strip(), old='html', new='str') + + def set_parameter_string(self, p: str): + self.parameter_line.setText(str(p)) + self.parameter_line.setToolTip(str(p)) + + def set_bounds(self, lb: float, ub: float, cbox: bool = True): + self.checkBox.setCheckState(QtCore.Qt.Checked if cbox else QtCore.Qt.Unchecked) + for val, bds_line in [(lb, self.lineEdit), (ub, self.lineEdit_2)]: + if val is not None: + bds_line.setText(str(val)) + else: + bds_line.setText('') + + def enableBounds(self, value: int): + self.lineEdit.setEnabled(value == 2) + self.lineEdit_2.setEnabled(value == 2) + + def set_parameter(self, p: list, bds: Tuple[float, float, bool] = (None, None, False), + fixed: bool = False, glob: bool = False): + if p is None: + # 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 = ' '.join([f'{pp:.4g}' for pp in p]) + + self.set_parameter_string(ptext) + + self.set_bounds(*bds) + + self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked) + self.global_checkbox.setCheckState(QtCore.Qt.Checked if glob else QtCore.Qt.Unchecked) + + def get_parameter(self): + if self.is_linked: + try: + p = float(self._linetext) + except ValueError: + 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(): + try: + lb = float(self.lineEdit.text().replace(',', '.')) + except ValueError: + lb = None + + try: + rb = float(self.lineEdit_2.text().replace(',', '.')) + except ValueError: + rb = None + else: + lb = rb = None + + bounds = (lb, rb) + + return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked(), self.is_linked + + @QtCore.pyqtSlot(bool) + def set_fixed(self, state: bool): + # self.global_checkbox.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() + def link_parameter(self, linkto=None): + if linkto is None: + action = self.sender() + else: + 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: + new_text = f'Linked to {action.parentWidget().title()}.{action.text()}' + self._linetext = self.parameter_line.text() + self.parameter_line.setText(new_text) + self.parameter_line.setEnabled(False) + self.global_checkbox.hide() + 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.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): + def __init__(self, types=None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + if types is None: + types = [] + + self.comboBox.blockSignals(True) + self.comboBox.addItems(types) + self.comboBox.addItem('New group...') + self.comboBox.blockSignals(False) + + self.frame.hide() + + @QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged') + def new_group(self, idx: int): + if idx == self.comboBox.count() - 1: + self.frame.show() + else: + self.lineEdit_2.clear() + self.frame.hide() + + @QtCore.pyqtSlot(name='on_toolButton_clicked') + def accept_group(self): + self.comboBox.insertItem(self.comboBox.count() - 1, self.lineEdit_2.text()) + self.comboBox.setCurrentIndex(self.comboBox.count() - 2) + + def accept(self): + if self.lineEdit.text(): + self.close() + + +class FitModelTree(QtWidgets.QTreeWidget): + icons = ['plus', 'mal_icon', 'minus_icon', 'geteilt_icon'] + + treeChanged = QtCore.pyqtSignal() + itemRemoved = QtCore.pyqtSignal(int) + + counterRole = QtCore.Qt.UserRole + 1 + operatorRole = QtCore.Qt.UserRole + 2 + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setHeaderHidden(True) + self.setDragEnabled(True) + self.setDragDropMode(QtWidgets.QTreeWidget.InternalMove) + self.setDefaultDropAction(QtCore.Qt.MoveAction) + + self.itemSelectionChanged.connect(lambda: self.treeChanged.emit()) + + def keyPressEvent(self, evt): + operators = [QtCore.Qt.Key_Plus, QtCore.Qt.Key_Asterisk, + QtCore.Qt.Key_Minus, QtCore.Qt.Key_Slash] + + if evt.key() == QtCore.Qt.Key_Delete: + for item in self.selectedItems(): + self.remove_function(item) + + elif evt.key() == QtCore.Qt.Key_Space: + for item in self.treeWidget.selectedItems(): + item.setCheckState(0, QtCore.Qt.Checked) if item.checkState( + 0) == QtCore.Qt.Unchecked else item.setCheckState(0, QtCore.Qt.Unchecked) + + elif evt.key() in operators: + idx = operators.index(evt.key()) + for item in self.selectedItems(): + item.setData(0, self.operatorRole, idx) + item.setIcon(0, get_icon(self.icons[idx])) + + else: + super().keyPressEvent(evt) + + def dropEvent(self, evt: QtGui.QDropEvent): + super().dropEvent(evt) + self.treeChanged.emit() + + def remove_function(self, item: QtWidgets.QTreeWidgetItem): + """ + Remove function and children from tree and dictionary + """ + while item.childCount(): + self.remove_function(item.child(0)) + + if item.parent(): + item.parent().removeChild(item) + else: + self.invisibleRootItem().removeChild(item) + + idx = item.data(0, self.counterRole) + self.itemRemoved.emit(idx) + + def add_function(self, idx: int, cnt: int, op: int, name: str, color: Union[QtGui.QColor, str, tuple], + parent: QtWidgets.QTreeWidgetItem = None, children: list = None, active: bool = True, **kwargs): + """ + Add function to tree and dictionary of functions. + """ + if not isinstance(color, QtGui.QColor): + if isinstance(color, tuple): + color = QtGui.QColor.fromRgbF(*color) + else: + color = QtGui.QColor(color) + + it = QtWidgets.QTreeWidgetItem() + it.setData(0, QtCore.Qt.UserRole, idx) + it.setData(0, self.counterRole, cnt) + it.setData(0, self.operatorRole, op) + it.setText(0, name) + it.setForeground(0, QtGui.QBrush(color)) + + it.setIcon(0, get_icon(self.icons[op])) + it.setCheckState(0, QtCore.Qt.Checked if active else QtCore.Qt.Unchecked) + + if parent is None: + self.addTopLevelItem(it) + else: + parent.addChild(it) + + if children is not None: + for c in children: + self.add_function(**c, parent=it) + + self.setCurrentIndex(self.indexFromItem(it, 0)) + + def sizeHint(self): + w = super().sizeHint().width() + return QtCore.QSize(w, 100) + + def get_selected(self): + try: + it = self.selectedItems()[0] + function_nr = it.data(0, QtCore.Qt.UserRole) + idx = it.data(0, self.counterRole) + + except IndexError: + function_nr = None + idx = None + + return function_nr, idx + + def get_functions(self, full=True, parent=None, pos=-1, return_pos=False): + """ + Create nested list of functions in tree. Parameters saved are idx (Index of function in list of all functions), + cnt (counter of number to associate with functione values), ops (+, -, *, /), and maybe children. + """ + if parent is None: + parent = self.invisibleRootItem() + + funcs = [] + for i in range(parent.childCount()): + pos += 1 + it = parent.child(i) + + child = { + 'idx': it.data(0, QtCore.Qt.UserRole), + 'op': it.data(0, self.operatorRole), + 'pos': pos, + 'active': (it.checkState(0) == QtCore.Qt.Checked), + 'children': [] + } + + if full: + child['name'] = it.text(0) + child['cnt'] = it.data(0, self.counterRole) + child['color'] = it.foreground(0).color().getRgbF() + + if it.childCount(): + child['children'], pos = self.get_functions(full=full, parent=it, pos=pos, return_pos=True) + + funcs.append(child) + + if return_pos: + return funcs, pos + else: + return funcs + + +class FitTableWidget(QtWidgets.QTableWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.horizontalHeader().hide() + self.verticalHeader().hide() + self.setColumnCount(2) + self.setSelectionBehavior(QtWidgets.QTableWidget.SelectRows) + self.horizontalHeader().setStretchLastSection(True) + self.hideColumn(1) + + def add_model(self, idx: str): + model_count = 0 + for r in range(self.rowCount()): + cb = self.cellWidget(r, 1) + cb.addItem('Model ' + str(idx), userData=idx) + model_count = cb.count() + + if model_count > 2: + if self.isColumnHidden(1): + self.showColumn(1) + self.resizeColumnToContents(0) + self.setColumnWidth(1, self.columnWidth(0) - self.columnWidth(1)) + + def remove_model(self, idx: str): + model_count = 0 + for r in range(self.rowCount()): + cb = self.cellWidget(r, 1) + if cb.currentData() == idx: + cb.setCurrentIndex(0) + cb.removeItem(cb.findData(idx)) + model_count = cb.count() + + if model_count == 2: + self.hideColumn(1) + self.resizeColumnToContents(0) + + def load(self, set_ids: list): + self.blockSignals(True) + + while self.rowCount(): + self.removeRow(0) + + for (sid, name) in set_ids: + item = QtWidgets.QTableWidgetItem(name) + item.setCheckState(QtCore.Qt.Checked) + item.setData(QtCore.Qt.UserRole+1, sid) + row = self.rowCount() + self.setRowCount(row+1) + self.setItem(row, 0, item) + + item2 = QtWidgets.QTableWidgetItem('') + self.setItem(row, 1, item2) + cb = QtWidgets.QComboBox() + cb.addItem('Default') + self.setCellWidget(row, 1, cb) + + self.blockSignals(False) + + def collect_data(self, default=None, include_name=False): + + data = {} + + for i in range(self.rowCount()): + item = self.item(i, 0) + if item.checkState() == QtCore.Qt.Checked: + mod = self.cellWidget(i, 1).currentData() + if mod is None: + mod = default + + if include_name: + arg = (item.data(QtCore.Qt.UserRole+1), item.text()) + else: + arg = item.data(QtCore.Qt.UserRole+1) + + if mod not in data: + data[mod] = [] + data[mod].append(arg) + + return data + + def data_list(self, include_name: bool = True) -> list: + ret_val = [] + for i in range(self.rowCount()): + item = self.item(i, 0) + if include_name: + ret_val.append((item.data(QtCore.Qt.UserRole+1), item.text())) + else: + ret_val.append(item.data(QtCore.Qt.UserRole+1)) + + return ret_val diff --git a/nmreval/gui_qt/fit/fit_parameter.py b/nmreval/gui_qt/fit/fit_parameter.py new file mode 100644 index 0000000..96ff18b --- /dev/null +++ b/nmreval/gui_qt/fit/fit_parameter.py @@ -0,0 +1,250 @@ +from ...utils.text import convert +from ..Qt import QtWidgets, QtCore, QtGui +from .._py.fitfuncwidget import Ui_FormFit +from ..lib.forms import FormWidget, SelectionWidget +from .fit_forms import FitModelWidget + + +class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit): + value_requested = QtCore.pyqtSignal(int) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.func = None + self.func_idx = None + self.max_width = QtCore.QSize(0, 0) + self.global_parameter = [] + self.data_parameter = [] + self.glob_values = None + self.data_values = {} + + self.scrollwidget.setLayout(QtWidgets.QVBoxLayout()) + self.scrollwidget2.setLayout(QtWidgets.QVBoxLayout()) + + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent): + if isinstance(evt, QtGui.QKeyEvent): + if (evt.key() == QtCore.Qt.Key_Right) and \ + (evt.modifiers() == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier): + self.change_single_parameter(src.value, sender=src) + self.select_next_preview(1) + + return True + + elif (evt.key() == QtCore.Qt.Key_Left) and \ + (evt.modifiers() == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier): + self.change_single_parameter(src.value, sender=src) + self.select_next_preview(-1) + + return True + + return super().eventFilter(src, evt) + + def load(self, data): + self.comboBox.blockSignals(True) + while self.comboBox.count(): + self.comboBox.removeItem(0) + + for sid, name in data: + self.comboBox.addItem(name, userData=sid) + self._make_parameter(sid) + self.comboBox.blockSignals(False) + + def set_function(self, func, idx): + self.func = func + self.func_idx = idx + + self.glob_values = [1] * len(func.params) + + for k, v in enumerate(func.params): + name = convert(v) + widgt = FitModelWidget(label=name, parent=self.scrollwidget) + widgt.parameter_pos = k + widgt.func_idx = idx + try: + widgt.set_bounds(*func.bounds[k], False) + except (AttributeError, IndexError): + pass + + size = widgt.parametername.sizeHint() + if self.max_width.width() < size.width(): + self.max_width = size + + widgt.state_changed.connect(self.set_global) + widgt.value_requested.connect(self.look_for_value) + widgt.value_changed.connect(self.change_global_parameter) + + self.global_parameter.append(widgt) + self.scrollwidget.layout().addWidget(widgt) + + widgt2 = FormWidget(name=name, fixable=False, parent=self.scrollwidget2) + widgt2.valueChanged.connect(self.change_single_parameter) + widgt2.installEventFilter(self) + self.scrollwidget2.layout().addWidget(widgt2) + self.data_parameter.append(widgt2) + + for w1, w2 in zip(self.global_parameter, self.data_parameter): + w1.parametername.setFixedSize(self.max_width) + w1.checkBox.setFixedSize(self.max_width) + w2.label.setFixedSize(self.max_width) + + if hasattr(func, 'choices') and func.choices is not None: + cbox = func.choices + for c in cbox: + widgt = SelectionWidget(*c) + widgt.selectionChanged.connect(self.change_global_choice) + self.global_parameter.append(widgt) + self.glob_values.append(widgt.value) + self.scrollwidget.layout().addWidget(widgt) + + widgt2 = SelectionWidget(*c) + widgt2.selectionChanged.connect(self.change_single_choice) + self.data_parameter.append(widgt2) + self.scrollwidget2.layout().addWidget(widgt2) + + for i in range(self.comboBox.count()): + self._make_parameter(self.comboBox.itemData(i)) + + self.scrollwidget.layout().addStretch(1) + self.scrollwidget2.layout().addStretch(1) + + def set_links(self, parameter): + for w in self.global_parameter: + if isinstance(w, FitModelWidget): + w.add_links(parameter) + + @QtCore.pyqtSlot(str) + def change_global_parameter(self, value: str): + idx = self.global_parameter.index(self.sender()) + self.glob_values[idx] = float(value) + if self.data_values[self.comboBox.currentData()][idx] is None: + self.data_parameter[idx].blockSignals(True) + self.data_parameter[idx].value = value + self.data_parameter[idx].blockSignals(False) + + @QtCore.pyqtSlot(str, object) + def change_global_choice(self, argname, value): + idx = self.global_parameter.index(self.sender()) + self.glob_values[idx] = value + if self.data_values[self.comboBox.currentData()][idx] is None: + self.data_parameter[idx].blockSignals(True) + self.data_parameter[idx].value = value + self.data_parameter[idx].blockSignals(False) + + def change_single_parameter(self, value, sender=None): + if sender is None: + sender = self.sender() + idx = self.data_parameter.index(sender) + self.data_values[self.comboBox.currentData()][idx] = value + + def change_single_choice(self, argname, value, sender=None): + if sender is None: + sender = self.sender() + idx = self.data_parameter.index(sender) + self.data_values[self.comboBox.currentData()][idx] = value + + @QtCore.pyqtSlot(object) + def look_for_value(self, sender): + self.value_requested.emit(self.global_parameter.index(sender)) + + @QtCore.pyqtSlot() + def set_global(self): + # disable single parameter if it is set global, enable if global is unset + widget = self.sender() + idx = self.global_parameter.index(widget) + enable = (widget.global_checkbox.checkState() == QtCore.Qt.Unchecked) and (widget.is_linked is None) + self.data_parameter[idx].setEnabled(enable) + + def select_next_preview(self, direction): + curr_idx = self.comboBox.currentIndex() + next_idx = (curr_idx + direction) % self.comboBox.count() + self.comboBox.setCurrentIndex(next_idx) + + @QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged') + def change_data(self, idx: int): + # new dataset is selected, look for locally set parameter else use global values + sid = self.comboBox.itemData(idx) + if sid not in self.data_values: + self._make_parameter(sid) + + for i, value in enumerate(self.data_values[sid]): + w = self.data_parameter[i] + w.blockSignals(True) + if value is None: + w.value = self.glob_values[i] + else: + w.value = value + w.blockSignals(False) + + def _make_parameter(self, sid): + if sid not in self.data_values: + self.data_values[sid] = [None] * len(self.data_parameter) + + def get_parameter(self, use_func=None): + bds = [] + is_global = [] + is_fixed = [] + globs = [] + is_linked = [] + + for g in self.global_parameter: + if isinstance(g, FitModelWidget): + p_i, bds_i, fixed_i, global_i, link_i = g.get_parameter() + + globs.append(p_i) + bds.append(bds_i) + is_fixed.append(fixed_i) + is_global.append(global_i) + is_linked.append(link_i) + + lb, ub = list(zip(*bds)) + + data_parameter = {} + if use_func is None: + use_func = list(self.data_values.keys()) + + global_p = None + for sid, parameter in self.data_values.items(): + if sid not in use_func: + continue + + kw_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)): + if isinstance(g, FitModelWidget): + if (p_i is None) or is_global[i]: + p.append(globs[i]) + if is_global[i]: + 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: + p.append(p_i) + + try: + if p[i] > ub[i]: + raise ValueError(f'Parameter {g.name} is outside bounds ({lb[i]}, {ub[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: + if p_i is None: + kw_p[g.argname] = g.value + else: + kw_p[g.argname] = p_i + + data_parameter[sid] = (p, kw_p) + + return data_parameter, lb, ub, is_fixed, global_p, is_linked diff --git a/nmreval/gui_qt/fit/fitfunction.py b/nmreval/gui_qt/fit/fitfunction.py new file mode 100644 index 0000000..bff72d2 --- /dev/null +++ b/nmreval/gui_qt/fit/fitfunction.py @@ -0,0 +1,240 @@ +from itertools import cycle, count +from typing import List, Tuple, Union + +from nmreval.configs import config_paths +from ... import models +from ...lib.importer import find_models +from ...lib.colors import BaseColor, Tab10 +from ...utils.text import convert +from ..Qt import QtWidgets, QtCore, QtGui +from .._py.fitfunctionwidget import Ui_Form +from .fit_forms import FitModelTree +from ..lib import get_icon + + +class QFunctionWidget(QtWidgets.QWidget, Ui_Form): + func_cnt = count() + func_colors = cycle(Tab10) + op_names = ['plus_icon', 'mal_icon', 'minus_icon', 'geteilt_icon'] + + newFunction = QtCore.pyqtSignal(int, int) + treeChanged = QtCore.pyqtSignal() + itemRemoved = QtCore.pyqtSignal(int) + showFunction = QtCore.pyqtSignal(int) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + self.functree = FitModelTree() + self.widget.setText('Model structure') + self.widget.addWidget(self.functree) + + self._types = [] + self.functions = find_models(models) + try: + self.functions += find_models(config_paths() / 'usermodels.py') + except FileNotFoundError: + pass + + for m in self.functions: + try: + m.type + except AttributeError: + m.type = 'Other' + + if m.type not in self._types: + self._types.append(m.type) + + self.typecomboBox.addItems(sorted(self._types)) + + self.functree.treeChanged.connect(lambda: self.treeChanged.emit()) + self.functree.itemRemoved.connect(self.remove_function) + + self.iscomplex = False + self.complex_widget.hide() + + for i, op_icon in enumerate(self.op_names): + self.operator_combobox.setItemIcon(i, get_icon(op_icon)) + + def __len__(self): + return self.use_combobox.count() + + @QtCore.pyqtSlot(int, name='on_typecomboBox_currentIndexChanged') + def change_group(self, idx: int): + """ + Change items in fitcombobox to new entries + """ + self.fitcomboBox.blockSignals(True) + while self.fitcomboBox.count(): + self.fitcomboBox.removeItem(0) + + selected_type = self.typecomboBox.itemText(idx) + for m in self.functions: + if m.type == selected_type: + self.fitcomboBox.addItem(m.name, userData=self.functions.index(m)) + self.fitcomboBox.blockSignals(False) + self.on_fitcomboBox_currentIndexChanged(0) + + @QtCore.pyqtSlot(int, name='on_fitcomboBox_currentIndexChanged') + def change_function(self, idx: int): + """ + Display new equation on changing function + """ + index = self.fitcomboBox.itemData(idx) + if self.functions: + fitfunc = self.functions[index] + try: + self.fitequation.setText(convert(fitfunc.equation)) + except AttributeError: + self.fitequation.setText('') + + @QtCore.pyqtSlot(name='on_use_function_button_clicked') + def new_function(self): + idx = self.fitcomboBox.itemData(self.fitcomboBox.currentIndex()) + cnt = next(self.func_cnt) + op = self.operator_combobox.currentIndex() + name = self.functions[idx].name + col = next(self.func_colors) + + self.newFunction.emit(idx, cnt) + + self.add_function(idx, cnt, op, name, col) + + def add_function(self, idx: int, cnt: int, op: int, + name: str, color: Union[str, Tuple[float, float, float], BaseColor], **kwargs): + """ + Add function to tree and dictionary of functions. + """ + if isinstance(color, BaseColor): + qcolor = QtGui.QColor.fromRgbF(*color.rgb(normed=True)) + elif isinstance(color, tuple): + qcolor = QtGui.QColor.fromRgbF(*color) + else: + qcolor = QtGui.QColor(color) + self.functree.add_function(idx, cnt, op, name, qcolor, **kwargs) + + self.use_combobox.addItem(name, userData=cnt) + + self.use_combobox.setItemData(self.use_combobox.count()-1, color, QtCore.Qt.DecorationRole) + self.use_combobox.setCurrentIndex(self.use_combobox.count() - 1) + + f = self.functions[idx] + if hasattr(f, 'iscomplex') and f.iscomplex: + self.iscomplex = True + self.complex_widget.show() + + @QtCore.pyqtSlot(int) + def remove_function(self, idx: int): + iterator = QtWidgets.QTreeWidgetItemIterator(self.functree) + self.iscomplex = False + while iterator.value(): + item = iterator.value() + f = self.functions[item.data(0, QtCore.Qt.UserRole)] + if hasattr(f, 'iscomplex') and f.iscomplex: + self.iscomplex = True + break + + iterator += 1 + self.complex_widget.setVisible(self.iscomplex) + + for i in range(self.use_combobox.count()): + if idx == self.use_combobox.itemData(i): + self.use_combobox.removeItem(i) + break + + self.itemRemoved.emit(idx) + + @QtCore.pyqtSlot(int, name='on_use_combobox_currentIndexChanged') + def show_parameter(self, idx: int): + if self.use_combobox.count(): + self.showFunction.emit(self.use_combobox.itemData(idx, QtCore.Qt.UserRole)) + + def get_selected(self): + function_nr, idx = self.functree.get_selected() + + if function_nr is not None: + return self.functions[function_nr], idx + else: + return None, None + + def get_functions(self, full: bool = True, clsname=False, include_all: bool = True): + """ + Create nested list of functions in tree. Parameters saved are idx (Index of function in list of all functions), + cnt (counter of number to associate with functione values), ops (+, -, *, /), and maybe children. + """ + + used_functions = self.functree.get_functions(full=full) + self._prepare_function_for_model(used_functions, full=full, clsname=clsname, include_all=include_all) + + return used_functions + + def _prepare_function_for_model(self, func_list: List[dict], + full: bool = True, clsname: bool = False, include_all: bool = True): + + for func_args in func_list: + is_active = func_args.get('active') + if (not is_active) and (not include_all): + continue + + if not clsname: + func_args['func'] = self.functions[func_args['idx']] + else: + func_args['func'] = self.functions[func_args['idx']].name + + if not full: + func_args.pop('active') + func_args.pop('idx') + + if func_args['children']: + self._prepare_function_for_model(func_args['children'], + full=full, clsname=clsname, + include_all=include_all) + + def get_parameter_list(self): + all_parameters = {} + + iterator = QtWidgets.QTreeWidgetItemIterator(self.functree) + while iterator.value(): + item = iterator.value() + f = self.functions[item.data(0, QtCore.Qt.UserRole)] + cnt = item.data(0, self.functree.counterRole) + all_parameters[f'{f.name}_{cnt}'] = [(convert(pp, new='str'), (cnt, i)) for i, pp in enumerate(f.params)] + + iterator += 1 + + return all_parameters + + def get_complex_state(self): + return self.complex_comboBox.currentIndex() if self.iscomplex else None + + def set_complex_state(self, state): + if state is not None: + self.complex_comboBox.setCurrentIndex(state) + + def clear(self): + self.functree.blockSignals(True) + self.functree.clear() + self.functree.blockSignals(False) + + self.use_combobox.blockSignals(True) + self.use_combobox.clear() + self.use_combobox.blockSignals(False) + + self.complex_comboBox.setCurrentIndex(0) + self.complex_widget.hide() + + +if __name__ == '__main__': + import sys + from numpy.random import choice + + app = QtWidgets.QApplication(sys.argv) + qw = choice(QtWidgets.QStyleFactory.keys()) + + app.setStyle(QtWidgets.QStyleFactory.create(qw)) + + fd = QFunctionWidget() + fd.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/fit/fitwindow.py b/nmreval/gui_qt/fit/fitwindow.py new file mode 100644 index 0000000..55099a9 --- /dev/null +++ b/nmreval/gui_qt/fit/fitwindow.py @@ -0,0 +1,455 @@ +from itertools import count, cycle +from string import ascii_letters + +from pyqtgraph import PlotDataItem, mkPen + +from nmreval.gui_qt.lib.pg_objects import PlotItem +from ...fit._meta import MultiModel, ModelFactory +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.fitdialog import Ui_FitDialog +from .fit_forms import FitTableWidget +from .fit_parameter import QFitParameterWidget + + +class QFitDialog(QtWidgets.QWidget, Ui_FitDialog): + func_cnt = count() + model_cnt = cycle(ascii_letters) + preview_num = 201 + + preview_emit = QtCore.pyqtSignal(dict, int, bool) + fitStartSig = QtCore.pyqtSignal(dict, list, dict) + abortFit = QtCore.pyqtSignal() + + def __init__(self, mgmt=None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.parameters = {} + self.preview_lines = [] + self._current_function = None + self.function_widgets = {} + self._management = mgmt + + self._current_model = next(QFitDialog.model_cnt) + self.show_combobox.setItemData(0, self._current_model, QtCore.Qt.UserRole) + self.default_combobox.setItemData(0, self._current_model, QtCore.Qt.UserRole) + + self.data_table = FitTableWidget(self.data_widget) + self.data_widget.addWidget(self.data_table) + self.data_widget.setText('Data') + + self.models = {} + self._func_list = {} + self._complex = {} + + self.connected_figure = '' + + self.model_frame.hide() + self.preview_button.hide() + + self.abort_button.clicked.connect(lambda: self.abortFit.emit()) + + self.functionwidget.newFunction.connect(self.add_function) + self.functionwidget.showFunction.connect(self.show_function_parameter) + self.functionwidget.itemRemoved.connect(self.remove_function) + + @QtCore.pyqtSlot(int, int) + def add_function(self, function_idx: int, function_id: int): + self.show_function_parameter(function_id, function_idx) + self.newmodel_button.setEnabled(True) + + @QtCore.pyqtSlot(int) + def remove_function(self, idx: int): + """ + Remove function and children from tree and dictionary + """ + w = self.function_widgets[idx] + self.stackedWidget.removeWidget(w) + w.deleteLater() + del self.function_widgets[idx] + + if len(self.functionwidget) == 0: + # empty model + self.newmodel_button.setEnabled(False) + self.deletemodel_button.setEnabled(False) + self._current_function = None + + else: + self._current_function = self.functionwidget.use_combobox.currentData() + + @QtCore.pyqtSlot(int) + def show_function_parameter(self, function_id: int, function_idx: int = None): + """ + Display parameter associated with selected function. + """ + if function_id in self.function_widgets: + dialog = self.function_widgets[function_id] + + else: + # create new widget for function + if function_idx is not None: + function = self.functionwidget.functions[function_idx] + else: + raise ValueError('No function index given') + + if function is None: + return + + dialog = QFitParameterWidget() + data_names = self.data_table.data_list(include_name=True) + + dialog.set_function(function, function_idx) + dialog.load(data_names) + dialog.value_requested.connect(self.look_value) + + self.stackedWidget.addWidget(dialog) + self.function_widgets[function_id] = dialog + + self.stackedWidget.setCurrentWidget(dialog) + + # collect parameter names etc. to allow linkage + self._func_list[self._current_model] = self.functionwidget.get_parameter_list() + dialog.set_links(self._func_list) + + # show same tab (general parameter/Data parameter) + tab_idx = 0 + if self._current_function is not None: + tab_idx = self.function_widgets[self._current_function].tabWidget.currentIndex() + dialog.tabWidget.setCurrentIndex(tab_idx) + + self._current_function = function_id + + def look_value(self, idx): + func_widget = self.function_widgets[self._current_function] + set_ids = [func_widget.comboBox.itemData(i) for i in range(func_widget.comboBox.count())] + for s in set_ids: + func_widget.data_values[s][idx] = self._management[s].value + func_widget.change_data(func_widget.comboBox.currentIndex()) + + def get_functions(self): + """ update functions, parameters""" + self.models[self._current_model] = self.functionwidget.get_functions() + self._complex[self._current_model] = self.functionwidget.get_complex_state() + self._func_list[self._current_model] = self.functionwidget.get_parameter_list() + + def load(self, ids: list): + """ + Add name and id of dataset to list. + """ + self.data_table.load(ids) + if self.models: + for m in self.models.keys(): + self.data_table.add_model(m) + else: + self.data_table.add_model(self._current_model) + + for dialog in self.function_widgets.values(): + dialog.load(ids) + + @QtCore.pyqtSlot(name='on_newmodel_button_clicked') + def make_new_model(self): + """ + Save model with all its functions in dictionary and adjust gui. + """ + self.deletemodel_button.setEnabled(True) + self.model_frame.show() + idx = next(QFitDialog.model_cnt) + + self.data_table.add_model(idx) + + self.default_combobox.addItem('Model '+idx, userData=idx) + self.show_combobox.addItem('Model '+idx, userData=idx) + self.show_combobox.setItemData(self.show_combobox.count()-1, idx, QtCore.Qt.UserRole) + self.show_combobox.setCurrentIndex(self.show_combobox.count()-1) + + self._current_model = idx + self.stackedWidget.setCurrentIndex(0) + + @QtCore.pyqtSlot(int, name='on_show_combobox_currentIndexChanged') + def change_model(self, idx: int): + """ + Save old model and display new model. + """ + self.get_functions() + self.functionwidget.clear() + + self._current_model = self.show_combobox.itemData(idx, QtCore.Qt.UserRole) + if self._current_model in self.models and len(self.models[self._current_model]): + for el in self.models[self._current_model]: + self.functionwidget.add_function(**el) + self.functionwidget.set_complex_state(self._complex[self._current_model]) + else: + self.stackedWidget.setCurrentIndex(0) + + @QtCore.pyqtSlot(name='on_deletemodel_button_clicked') + def remove_model(self): + model_id = self._current_model + + self.show_combobox.removeItem(self.show_combobox.findData(model_id)) + self.default_combobox.removeItem(self.default_combobox.findData(model_id)) + + for m in self.models[model_id]: + func_id = m['cnt'] + self.stackedWidget.removeWidget(self.function_widgets[func_id]) + + self.function_widgets.pop(func_id) + + self._complex.pop(model_id) + self._func_list.pop(model_id) + self.models.pop(model_id) + + self.data_table.remove_model(model_id) + + if len(self.models) == 1: + self.model_frame.hide() + + def _prepare(self, model: list, function_use=None, parameter=None, add_idx=False, cnt=0): + if parameter is None: + parameter = {'parameter': {}, 'lb': (), 'ub': (), 'var': [], + 'glob': {'idx': [], 'p': [], 'var': [], 'lb': [], 'ub': []}, + 'links': [], 'color': []} + + for i, f in enumerate(model): + if not f['active']: + continue + try: + p, lb, ub, var, glob, links = self.function_widgets[f['cnt']].get_parameter(function_use) + except ValueError as e: + _ = QtWidgets.QMessageBox().warning(self, 'Invalid value', str(e), + QtWidgets.QMessageBox.Ok) + return None, -1 + + p_len = len(parameter['lb']) + + parameter['lb'] += lb + parameter['ub'] += ub + parameter['var'] += var + parameter['links'] += links + parameter['color'] += [f['color']] + + for p_k, v_k in p.items(): + if add_idx: + kw_k = {f'{k}_{cnt}': v for k, v in v_k[1].items()} + else: + kw_k = v_k[1] + + if p_k in parameter['parameter']: + params, kw = parameter['parameter'][p_k] + params += v_k[0] + kw.update(kw_k) + else: + parameter['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: + cnt += 1 + + if f['children']: + # recurse for children + child_parameter, cnt = self._prepare(f['children'], parameter=parameter, add_idx=add_idx, cnt=cnt) + + return parameter, cnt + + @QtCore.pyqtSlot(name='on_fit_button_clicked') + def start_fit(self): + self.get_functions() + + data = self.data_table.collect_data(default=self.default_combobox.currentData()) + + func_dict = {} + for k, mod in self.models.items(): + func, order, param_len = ModelFactory.create_from_list(mod) + + if func is None: + continue + + if k in data: + parameter, _ = self._prepare(mod, function_use=data[k], add_idx=isinstance(func, MultiModel)) + if parameter is None: + return + + parameter['func'] = func + parameter['order'] = order + parameter['len'] = param_len + if self._complex[k] is None: + parameter['complex'] = self._complex[k] + else: + parameter['complex'] = ['complex', 'real', 'imag'][self._complex[k]] + + func_dict[k] = parameter + + replaceable = [] + for k, v in func_dict.items(): + for i, link_i in enumerate(v['links']): + if link_i is None: + continue + + rep_model, rep_func, rep_pos = link_i + try: + f = func_dict[rep_model] + except KeyError: + QtWidgets.QMessageBox().warning(self, 'Invalid value', + 'Parameter cannot be linked: Model is unused', + QtWidgets.QMessageBox.Ok) + return + + try: + f_idx = f['order'].index(rep_func) + except ValueError: + QtWidgets.QMessageBox().warning(self, 'Invalid value', + 'Parameter cannot be linked: ' + 'Function is probably not checked or deleted', + QtWidgets.QMessageBox.Ok) + return + + repl_idx = sum(f['len'][:f_idx])+rep_pos + if repl_idx not in f['glob']['idx']: + _ = QtWidgets.QMessageBox().warning(self, 'Invalid value', + 'Parameter cannot be linked: ' + 'Destination is not a global parameter.', + QtWidgets.QMessageBox.Ok) + return + + replaceable.append((k, i, rep_model, repl_idx)) + + replace_value = None + for p_k in f['parameter'].values(): + replace_value = p_k[0][repl_idx] + break + + if replace_value is not None: + for p_k in v['parameter'].values(): + p_k[0][i] = replace_value + + weight = ['None', 'y', 'y2', 'Deltay'][self.weight_combobox.currentIndex()] + + fit_args = {'we': weight} + + if func_dict: + self.fitStartSig.emit(func_dict, replaceable, fit_args) + + return func_dict + + @QtCore.pyqtSlot(int, name='on_preview_checkbox_stateChanged') + def show_preview(self, state: int): + print('state', state) + if state: + self.preview_button.show() + self.preview_checkbox.setText('') + + self._prepare_preview() + + else: + self.preview_emit.emit({}, -1, False) + self.preview_lines = [] + self.preview_button.hide() + self.preview_checkbox.setText('Preview') + + @QtCore.pyqtSlot(name='on_preview_button_clicked') + def _prepare_preview(self): + self.get_functions() + + default_model = self.default_combobox.currentData() + data = self.data_table.collect_data(default=default_model) + + func_dict = {} + for k, mod in self.models.items(): + func, order, param_len = ModelFactory.create_from_list(mod) + multiple_funcs = isinstance(func, MultiModel) + + if k in data: + parameter, _ = self._prepare(mod, function_use=data[k], add_idx=multiple_funcs) + parameter['func'] = func + parameter['order'] = order + parameter['len'] = param_len + + func_dict[k] = parameter + + for v in func_dict.values(): + for i, link_i in enumerate(v['links']): + if link_i is None: + continue + + rep_model, rep_func, rep_pos = link_i + f = func_dict[rep_model] + f_idx = f['order'].index(rep_func) + repl_idx = sum(f['len'][:f_idx]) + rep_pos + + replace_value = None + for p_k in f['parameter'].values(): + replace_value = p_k[0][repl_idx] + break + + if replace_value is not None: + for p_k in v['parameter'].values(): + p_k[0][i] = replace_value + + self.preview_emit.emit(func_dict, QFitDialog.preview_num, True) + + def make_previews(self, x, models_parameters: dict): + self.preview_lines = [] + + for k, model in models_parameters.items(): + f = model['func'] + is_complex = self._complex[k] + + parameters = model['parameter'] + color = model['color'] + + for p, kwargs in parameters.values(): + y = f.func(x, *p, **kwargs) + if is_complex is None: + self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3))) + + elif is_complex == 0: + 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))) + elif is_complex == 1: + self.preview_lines.append(PlotItem(x=x, y=y.real, pen=mkPen(width=3))) + else: + self.preview_lines.append(PlotItem(x=x, y=y.imag, pen=mkPen(width=3))) + + if isinstance(f, MultiModel): + for i, s in enumerate(f.subs(x, *p, **kwargs)): + pen_i = mkPen(QtGui.QColor.fromRgbF(*color[i])) + if is_complex is None: + self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i)) + elif is_complex == 0: + self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i)) + self.preview_lines.append(PlotItem(x=x, y=s.imag, pen=pen_i)) + elif is_complex == 1: + self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i)) + else: + self.preview_lines.append(PlotItem(x=x, y=s.imag, pen=pen_i)) + + return self.preview_lines + + def closeEvent(self, evt: QtGui.QCloseEvent): + self.preview_emit.emit({}, -1, False) + self.preview_lines = [] + + super().closeEvent(evt) + + +if __name__ == '__main__': + import sys + from numpy.random import choice + + app = QtWidgets.QApplication(sys.argv) + # while qw == 'QtCurve': + qw = choice(QtWidgets.QStyleFactory.keys()) + app.setStyle(QtWidgets.QStyleFactory.create(qw)) + + fd = QFitDialog() + + fd.load([('fff', 'testtesttesttest'), + ('ggg', 'testtesttesttesttest'), + ('hhh', 'testtesttesttesttesttest')]) + fd.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/fit/function_creation_dialog.py b/nmreval/gui_qt/fit/function_creation_dialog.py new file mode 100644 index 0000000..0a9f07b --- /dev/null +++ b/nmreval/gui_qt/fit/function_creation_dialog.py @@ -0,0 +1,247 @@ +import re + +import numexpr as ne +import numpy as np + +from nmreval.gui_qt.Qt import QtCore, QtWidgets +from nmreval.gui_qt._py.fitcreationdialog import Ui_Dialog + + +_numexpr_funcs = [] +for k, _ in ne.expressions.functions.items(): + pat = k + r'\(' + _numexpr_funcs.append((re.compile(pat), 'np.' + k + '(')) + + +class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog): + classCreated = QtCore.pyqtSignal(object) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.namespace_widget.make_namespace() + + self.tableWidget.itemChanged.connect(self.update_function) + + self.groupBox.toggled.connect(self.change_visibility) + self.groupBox_2.toggled.connect(self.change_visibility) + self.groupBox_3.toggled.connect(self.change_visibility) + self.groupBox_4.toggled.connect(self.change_visibility) + + self.groupBox.setChecked(True) + + def __call__(self, *args, **kwargs): + for w in [self.lineEdit_4, self.lineEdit, self.lineEdit_3, self.lineEdit_2, + self.parameterLineEdit, self.externalParametersLineEdit]: + w.clear() + + def check(self): + self.name = self.name_lineedit.text() + self.group = self.group_lineedit.text() + self.eq = str(self.lineEdit.text()) + self.p = str(self.parameterLineEdit.text()).split() + self.func = str(self.lineEdit_4.text()) + self._func_string = '' + + error = [] + for k, v in [('Name', self.name), ('Group', self.group), ('Parameters', self.p), ('Function', self.func)]: + if not v: + error.append('Empty ' + str(k)) + if self.name: + if self.name[0].isdigit(): + error.append('Name starts with digit') + if self.p: + if set(self.p) & set(self.ext_p): + error.append('Duplicate entries: {}'.format(list(set(self.p) & set(self.ext_p)))) + if self.p and self.func: + p_test = np.ones((len(self.p)+len(self.ext_p))) + _x = np.arange(2) + namespace = {'x': _x} + for i, pp in enumerate(p_test): + namespace[f'p_{i}'] = pp + self._func_string = self.func + '' + self._func_string = self._func_string.replace('[', '_').replace(']', '') + try: + ne.evaluate(self._func_string, local_dict=namespace) + except KeyError: + error.append(f'Incorrect evaluation {self.func}') + + if error: + QtWidgets.QMessageBox().warning(self, 'Invalid entries', '\n'.join(error)) + else: + return True + + def accept(self): + self.confirm() + super().accept() + + def confirm(self): + print(f' name = {self.name_lineedit.text()}') + group_type = self.group_lineedit.text() + if group_type: + print(f' group = "{group_type}"') + else: + print(' group = "User-defined"') + var = [] + for row in range(self.tableWidget.rowCount()): + var.append(self.tableWidget.item(row, 1).text()) + if var: + print(' params = [r"', end='') + print('", r"'.join(var) + '"]') + else: + print(' params = []') + + print('\n@staticmethod') + print(self.label.text()) + import inspect + for k, v in self.namespace_widget.namespace.flatten().items(): + if inspect.isfunction(v): + print(k, inspect.getmodule(v)) + print(k, [cc[1] for cc in inspect.getmembers(v) if cc[0] == '__qualname__']) + else: + print(k, v) + print(self.plainTextEdit.toPlainText()) + + @QtCore.pyqtSlot(name='on_parameter_button_clicked') + def add_variable(self): + self.tableWidget.blockSignals(True) + row = self.tableWidget.rowCount() + self.tableWidget.setRowCount(row+1) + + self.tableWidget.setItem(row, 0, QtWidgets.QTableWidgetItem('p'+str(row))) + self.tableWidget.setItem(row, 1, QtWidgets.QTableWidgetItem('p_{'+str(row)+'}')) + self.tableWidget.setItem(row, 2, QtWidgets.QTableWidgetItem('--')) + self.tableWidget.setItem(row, 3, QtWidgets.QTableWidgetItem('--')) + self.tableWidget.blockSignals(False) + self.update_function(None) + + @QtCore.pyqtSlot(name='on_selection_button_clicked') + def add_choice(self): + cnt = self.tabWidget.count() + self.tabWidget.addTab(ChoiceWidget(self), 'choice' + str(cnt)) + self.tabWidget.setCurrentIndex(cnt) + + def register(self): + i = 0 + basename = self.name.replace(' ', '') + classname = basename + # while classname in _userfits: + # classname = basename + '_' + str(i) + # i += 1 + c = register_class(classname, self.name, self.group, self.p, self.eq, self._func_string) + self.classCreated.emit(c) + return classname, c + + def save(self, cname): + t = '\n# Created automatically\n' \ + 'class {cname:}(object):\n'\ + ' name = "{name:}"\n' \ + ' type = "{group:}"\n'\ + ' equation = "{eq:}"\n'\ + ' params = {p:}\n' \ + ' ext_params = {ep:}\n\n' \ + ' @staticmethod\n' \ + ' def func(p, x):\n' \ + ' return {func:}\n' + f_string = self.func + for pat, repl in _numexpr_funcs: + f_string = re.sub(pat, repl, f_string) + + @QtCore.pyqtSlot(QtWidgets.QTableWidgetItem) + @QtCore.pyqtSlot(str) + def update_function(self, _): + var = [] + for row in range(self.tableWidget.rowCount()): + var.append(self.tableWidget.item(row, 0).text()) + + if self.use_nuclei.isChecked(): + var.append('nucleus=2.67522128e8') + + # for row in range(self.selection_combobox.count()): + # var.append(self.selection_combobox.itemText(row) + '=') + + self.label.setText('def func(x, ' + ', '.join(var) + '):') + + def change_visibility(self): + sender = self.sender() + + for gb in [self.groupBox, self.groupBox_2, self.groupBox_3, self.groupBox_4]: + gb.blockSignals(True) + gb.setChecked(sender == gb) + gb.blockSignals(False) + + self.widget_2.setVisible(sender == self.groupBox) + self.widget_3.setVisible(sender == self.groupBox_2) + self.widget.setVisible(sender == self.groupBox_3) + self.namespace_widget.setVisible(sender == self.groupBox_4) + + +class ChoiceWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self._init_ui() + + def _init_ui(self): + layout = QtWidgets.QGridLayout() + layout.setContentsMargins(3, 3, 3, 3) + layout.setHorizontalSpacing(6) + + self.label = QtWidgets.QLabel('Name', parent=self) + layout.addWidget(self.label, 0, 0) + self.name_line = QtWidgets.QLineEdit(self) + layout.addWidget(self.name_line, 0, 1) + + self.label_2 = QtWidgets.QLabel('Displayed name', parent=self) + layout.addWidget(self.label_2, 0, 2) + self.display_line = QtWidgets.QLineEdit(self) + layout.addWidget(self.display_line, 0, 3) + + self.label_3 = QtWidgets.QLabel('Type', parent=self) + layout.addWidget(self.label_3) + self.types = QtWidgets.QComboBox(self) + self.types.addItems(['str', 'int', 'float']) + layout.addWidget(self.types) + + self.add_button = QtWidgets.QPushButton('Add option') + layout.addWidget(self.add_button) + + self.table = QtWidgets.QTableWidget(self) + self.table.setColumnCount(2) + self.table.setHorizontalHeaderLabels(['Name', 'Value']) + layout.addWidget(self.table, 2, 0, 1, 4) + + self.setLayout(layout) + + +def register_class(cname, name, group, p, eq, func): + c = type(cname, (), {}) + c.name = name + c.type = group + c.params = p + c.equation = eq + c.func = func_decorator(func) + + return c + + +def func_decorator(f_string): + # we need this decorator because the result is used in a class + + def wrapped_f(*args): + namespace = {'x': args[1]} + for i, pp in enumerate(args[0]): + namespace['p_{}'.format(i)] = pp + return ne.evaluate(f_string, local_dict=namespace) + + return wrapped_f + + +if __name__ == '__main__': + import sys + + app = QtWidgets.QApplication([]) + w = QUserFitCreator() + w.show() + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/fit/result.py b/nmreval/gui_qt/fit/result.py new file mode 100644 index 0000000..3e9ede7 --- /dev/null +++ b/nmreval/gui_qt/fit/result.py @@ -0,0 +1,245 @@ +from math import isnan + +from numpy import r_ +from pyqtgraph import mkBrush + +from ..lib.utils import RdBuCMap +from ...utils.text import convert +from ..Qt import QtWidgets, QtGui, QtCore +from .._py.fitresult import Ui_Dialog +from ..lib.pg_objects import PlotItem + + +class QFitResult(QtWidgets.QDialog, Ui_Dialog): + closed = QtCore.pyqtSignal(dict, list) + redoFit = QtCore.pyqtSignal(dict) + + def __init__(self, results: list, management, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self._management = management + + self._prevs = {} + self._models = {} + + for (res, parts) in results: + idx = res.idx + print(parts) + data_k = management.data[idx] + + if res.name not in self._models: + self._models[res.name] = [] + + self._models[res.name].append(idx) + + self._prevs[idx] = [] + for fit in data_k.get_fits(): + 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=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color='r'), pen=None) + self.residplot.addItem(self.resid_graph) + self.residplot.setLabel('left', 'Residual') + + self.fitplot = self.graphicsView.addPlot(row=1, col=0) + self.data_graph = PlotItem(x=[], y=[], symbol='o', symbolPen=None, symbolBrush=mkBrush(color='r'), pen=None) + self.fitplot.addItem(self.data_graph) + self.fitplot.setLabel('left', 'Function') + + self.fit_graph = PlotItem(x=[], y=[]) + self.fitplot.addItem(self.fit_graph) + + 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.horizontalHeader().sectionClicked.connect(self.show_results) + self.logy_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(y=bool(x))) + + def add_graphs(self, graphs: list): + self.graph_comboBox.clear() + for (graph_id, graph_name) in graphs: + self.graph_comboBox.addItem(graph_name, userData=graph_id) + + @QtCore.pyqtSlot(int, name='on_graph_checkBox_stateChanged') + def change_graph(self, state: int): + self.graph_comboBox.setEnabled(state == QtCore.Qt.Unchecked) + + @QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged') + def set_parameter(self, idx: int): + model_name = self.sets_comboBox.itemText(idx) + sets = self._models[model_name] + self.param_tableWidget.setColumnCount(len(sets)) + + r = self._results[sets[0]] + self.param_tableWidget.setRowCount(len(r.parameter)) + + for i, pval in enumerate(r.parameter.values()): + name = pval.full_name + p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'html', brackets=False)) + self.param_tableWidget.setVerticalHeaderItem(i, p_header) + + for i, set_id in enumerate(sets): + data_i = self._management[set_id] + 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(0) + + @QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged') + @QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged') + def change_opts(self, _): + idx = self.sets_comboBox.currentIndex() + + self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked, + self.del_prev_checkBox.checkState() == QtCore.Qt.Checked) + + def show_results(self, idx: int): + set_id = self.param_tableWidget.horizontalHeaderItem(idx).data(QtCore.Qt.UserRole) + self.set_plot(set_id) + self.set_correlation(set_id) + self.set_statistics(set_id) + + def set_plot(self, idx: str): + res = self._results[idx] + iscomplex = res.iscomplex + + self.resid_graph.setData(x=res.x_data, y=res.residual) + if iscomplex == 'complex': + self.data_graph.setData(x=r_[res.x_data, res.x_data], + y=r_[res.y_data.real, res.y_data.imag]) + self.fit_graph.setData(x=r_[res.x, res.x], + y=r_[res.y.real, res.y.imag]) + else: + self.data_graph.setData(x=res.x_data, y=res.y_data) + self.fit_graph.setData(x=res.x, y=res.y) + + self.fitplot.setLogMode(x=res.islog) + self.residplot.setLogMode(x=res.islog) + + def set_correlation(self, idx: str): + while self.corr_tableWidget.rowCount(): + self.corr_tableWidget.removeRow(0) + + res = self._results[idx] + c = res.correlation_list() + for pi, pj, corr, pcorr in c: + cnt = self.corr_tableWidget.rowCount() + self.corr_tableWidget.insertRow(cnt) + self.corr_tableWidget.setItem(cnt, 0, QtWidgets.QTableWidgetItem(convert(pi, old='tex', new='html'))) + self.corr_tableWidget.setItem(cnt, 1, QtWidgets.QTableWidgetItem(convert(pj, old='tex', new='html'))) + + for i, val in enumerate([corr, pcorr]): + if isnan(val): + val = 1000. + val_item = QtWidgets.QTableWidgetItem(f'{val:.4g}') + val_item.setBackground(self.cmap.color(val)) + if abs(val) > 0.75: + val_item.setForeground(QtGui.QColor('white')) + self.corr_tableWidget.setItem(cnt, i+2, val_item) + + self.corr_tableWidget.resizeColumnsToContents() + + def set_statistics(self, idx: str): + while self.stats_tableWidget.rowCount(): + self.stats_tableWidget.removeRow(0) + + res = self._results[idx] + + self.stats_tableWidget.setColumnCount(1 + len(self._prevs[idx])) + self.stats_tableWidget.setRowCount(len(res.statistics)+3) + + it = QtWidgets.QTableWidgetItem(f'{res.dof}') + it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF')) + self.stats_tableWidget.setItem(0, 0, it) + + for col, (name, _, dof) in enumerate(self._prevs[idx], start=1): + self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name)) + it = QtWidgets.QTableWidgetItem(f'{dof}') + it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + self.stats_tableWidget.setItem(0, col, it) + + for row, (k, v) in enumerate(res.statistics.items(), start=1): + self.stats_tableWidget.setVerticalHeaderItem(row, QtWidgets.QTableWidgetItem(k)) + it = QtWidgets.QTableWidgetItem(f'{v:.4f}') + it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + self.stats_tableWidget.setItem(row, 0, it) + + best_idx = -1 + best_val = v + for col, (_, stats, _) in enumerate(self._prevs[idx], start=1): + if k in ['adj. R^2', 'R^2']: + best_idx = col if best_val < stats[k] else max(0, best_idx) + else: + best_idx = col if best_val > stats[k] else max(0, best_idx) + it = QtWidgets.QTableWidgetItem(f'{stats[k]:.4f}') + it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + self.stats_tableWidget.setItem(row, col, it) + + if best_idx > -1: + self.stats_tableWidget.item(row, best_idx).setBackground(QtGui.QColor('green')) + self.stats_tableWidget.item(row, best_idx).setForeground(QtGui.QColor('white')) + + row = self.stats_tableWidget.rowCount() - 2 + self.stats_tableWidget.setVerticalHeaderItem(row, QtWidgets.QTableWidgetItem('F')) + self.stats_tableWidget.setItem(row, 0, QtWidgets.QTableWidgetItem('-')) + + self.stats_tableWidget.setVerticalHeaderItem(row+1, QtWidgets.QTableWidgetItem('Pr(>F)')) + self.stats_tableWidget.setItem(row+1, 0, QtWidgets.QTableWidgetItem('-')) + + for col, (_, stats, dof) in enumerate(self._prevs[idx], start=1): + f_value, prob_f = res.f_test(stats['chi^2'], dof) + it = QtWidgets.QTableWidgetItem(f'{f_value:.4g}') + it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + self.corr_tableWidget.setItem(row, col, it) + + it = QtWidgets.QTableWidgetItem(f'{prob_f:.4g}') + it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable) + if prob_f < 0.05: + it.setBackground(QtGui.QColor('green')) + it.setForeground(QtGui.QColor('white')) + self.stats_tableWidget.setItem(row+1, col, it) + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton) + def on_buttonBox_clicked(self, button: QtWidgets.QAbstractButton): + button_type = self.buttonBox.standardButton(button) + + if button_type == self.buttonBox.Retry: + self.redoFit.emit(self._results) + + elif button_type == self.buttonBox.Ok: + graph = '' if self.graph_checkBox.checkState() == QtCore.Qt.Checked else self.graph_comboBox.currentData() + subplots = self.partial_checkBox.checkState() == QtCore.Qt.Checked + self._opts.extend([graph, subplots]) + self.closed.emit(self._results, self._opts) + + self.accept() + + else: + self.reject() diff --git a/nmreval/gui_qt/graphs/__init__.py b/nmreval/gui_qt/graphs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmreval/gui_qt/graphs/graphwindow.py b/nmreval/gui_qt/graphs/graphwindow.py new file mode 100644 index 0000000..fab3989 --- /dev/null +++ b/nmreval/gui_qt/graphs/graphwindow.py @@ -0,0 +1,705 @@ +import itertools +import os +import uuid + +from math import isnan +from typing import List, Union + +from numpy import errstate, floor, log10 +from pyqtgraph import GraphicsObject, getConfigOption, mkColor + +from ..lib.pg_objects import RegionItem +from ...utils.text import convert +from ..Qt import QtCore, QtWidgets, QtGui +from .._py.graph import Ui_GraphWindow +from ..lib import make_action_icons +from ..lib.configurations import GraceMsgBox + + +class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow): + mousePositionChanged = QtCore.pyqtSignal(float, float) + mouseDoubleClicked = QtCore.pyqtSignal() + positionClicked = QtCore.pyqtSignal(tuple, bool) + aboutToClose = QtCore.pyqtSignal(str) + + counter = itertools.count() + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self._bgcolor = mkColor(getConfigOption('background')) + self._fgcolor = mkColor(getConfigOption('foreground')) + self._prev_colors = mkColor('k'), mkColor('w') + + self._init_gui() + + make_action_icons(self) + + self.id = str(uuid.uuid4()) + + self.sets = [] + self.active = [] + + self.real_plots = {} + self.imag_plots = {} + self.error_plots = {} + + self._special_needs = [] + self.closable = True + + self.log = [False, False] + + self.scene = self.plotItem.scene() + self.scene.sigMouseMoved.connect(self.move_mouse) + + self.checkBox.stateChanged.connect(lambda x: self.legend.setVisible(x == QtCore.Qt.Checked)) + self.label_button.toggled.connect(lambda x: self.label_widget.setVisible(x)) + self.limit_button.toggled.connect(lambda x: self.limit_widget.setVisible(x)) + self.gridbutton.toggled.connect(lambda x: self.graphic.showGrid(x=x, y=x)) + self.logx_button.toggled.connect(lambda x: self.set_logmode(xmode=x)) + self.logy_button.toggled.connect(lambda x: self.set_logmode(ymode=x)) + self.graphic.plotItem.vb.sigRangeChanged.connect(self.update_limits) + self.listWidget.itemChanged.connect(self.show_legend) + + # reconnect "Export..." in context menu to our function + self.scene.contextMenu[0].disconnect() + self.scene.contextMenu[0].triggered.connect(self.export) + + def _init_gui(self): + self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter))) + + self.label_widget.hide() + self.limit_widget.hide() + self.listWidget.hide() + self.checkBox.hide() + + self.plotItem = self.graphic.plotItem + for orient in ['top', 'bottom', 'left', 'right']: + self.plotItem.showAxis(orient) + ax = self.plotItem.getAxis(orient) + ax.enableAutoSIPrefix(False) + if orient == 'top': + ax.setStyle(showValues=False) + ax.setHeight(10) + elif orient == 'right': + ax.setStyle(showValues=False) + ax.setWidth(10) + + self.legend = self.plotItem.addLegend() + self.legend.setVisible(True) + # self.legend.setBrush(color=self._bgcolor) + self.legend.layout.setContentsMargins(1, 1, 1, 1) + + self.plotItem.setMenuEnabled(False, True) + self.plotItem.ctrl.logXCheck.blockSignals(True) + self.plotItem.ctrl.logYCheck.blockSignals(True) + + for lineedit in [self.xmin_lineedit, self.xmax_lineedit, self.ymin_lineedit, self.ymax_lineedit]: + lineedit.setValidator(QtGui.QDoubleValidator()) + + def __contains__(self, item: str): + return item in self.sets + + def __iter__(self): + return iter(self.active) + + def __len__(self): + return len(self.active) + + def curves(self): + for a in self.active: + if self.real_button.isChecked(): + if self.error_plots[a] is not None: + yield self.real_plots[a], self.error_plots[a] + else: + yield self.real_plots[a], + + if self.imag_button.isChecked() and self.imag_plots[a] is not None: + yield self.imag_plots[a], + + @property + def title(self): + return self.windowTitle() + + @title.setter + def title(self, value): + self.setWindowTitle(str(value)) + + @property + def ranges(self): + r = self.plotItem.getViewBox().viewRange() + for i in [0, 1]: + if self.log[i]: + r[i] = tuple([10**x for x in r[i]]) + else: + r[i] = tuple(r[i]) + + return tuple(r) + + def add(self, name: Union[str, List], plots: List): + if isinstance(name, str): + name = [name] + plots = [plots] + + for (real_plot, imag_plot, err_plot), n in zip(plots, name): + toplevel = len(self.sets) + self.sets.append(n) + + real_plot.setZValue(2*toplevel+1) + if imag_plot: + imag_plot.setZValue(2*toplevel+1) + if err_plot: + err_plot.setZValue(2*toplevel) + + self.real_plots[n] = real_plot + self.imag_plots[n] = imag_plot + self.error_plots[n] = err_plot + + list_item = QtWidgets.QListWidgetItem(real_plot.opts.get('name', '')) + list_item.setData(QtCore.Qt.UserRole, n) + list_item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable) + list_item.setCheckState(QtCore.Qt.Checked) + self.listWidget.addItem(list_item) + + self.show_item(name) + + def remove(self, name: Union[str, List]): + if isinstance(name, str): + name = [name] + + for n in name: + self.sets.remove(n) + + for plot in [self.real_plots, self.imag_plots, self.error_plots]: + self.graphic.removeItem(plot[n]) + + if n in self.active: + self.active.remove(n) + + # remove from label list + self.listWidget.blockSignals(True) + + for i in range(self.listWidget.count()-1, 0, -1): + item = self.listWidget.item(i) + if item.data(QtCore.Qt.UserRole) in name: + self.listWidget.takeItem(i) + + self.listWidget.blockSignals(False) + + self._update_zorder() + self.show_legend() + + def move_sets(self, sets, position): + move_plots = [] + move_items = [] + + self.listWidget.blockSignals(True) + + for s in sets: + idx = self.sets.index(s) + move_plots.append(self.sets.pop(idx)) + move_items.append(self.listWidget.takeItem(idx)) + + if position == -1: + self.sets.extend(move_plots) + for it in move_items: + self.listWidget.addItem(it) + else: + self.sets = self.sets[:position] + move_plots + self.sets[position:] + for it in move_items[::-1]: + self.listWidget.insertItem(position, it) + + self.listWidget.blockSignals(False) + self._update_zorder() + + def show_item(self, idlist: list): + if len(self.sets) == 0: + return + + for a in idlist: + if a not in self.active: + self.active.append(a) + + if self.imag_button.isChecked(): + item = self.imag_plots[a] + if (item is not None) and (item not in self.graphic.items()): + self.graphic.addItem(item) + + if self.real_button.isChecked(): + item = self.real_plots[a] + if item not in self.graphic.items(): + self.graphic.addItem(item) + + if self.error_button.isChecked(): + item = self.error_plots[a] + if (item is not None) and (item not in self.graphic.items()): + self.graphic.addItem(item) + + self.show_legend() + + def hide_item(self, idlist: list): + if len(self.sets) == 0: + return + + for r in idlist: + if r in self.active: + self.active.remove(r) + + for plt in [self.real_plots, self.imag_plots, self.error_plots]: + item = plt[r] + if item in self.graphic.items(): + self.graphic.removeItem(item) + + @QtCore.pyqtSlot(bool, name='on_imag_button_toggled') + @QtCore.pyqtSlot(bool, name='on_real_button_toggled') + def set_imag_visible(self, visible: bool): + if self.sender() == self.real_button: + plots = self.real_plots + other = self.imag_plots + if self.error_button.isChecked() and not visible: + self.error_button.setChecked(False) + else: + plots = self.imag_plots + other = self.real_plots + + if visible: + func = self.graphic.addItem + else: + func = self.graphic.removeItem + + for a in self.active: + item = plots[a] + other_item = other[a] + if (item is not None) and (other_item is not None): + func(item) + + self.show_legend() + + @QtCore.pyqtSlot(bool, name='on_error_button_toggled') + def show_errorbar(self, visible: bool): + if visible and not self.real_button.isChecked(): + # no errorbars without points + self.error_button.blockSignals(True) + self.error_button.setChecked(False) + self.error_button.blockSignals(False) + return + + if visible: + for a in self.active: + item = self.error_plots[a] + if (item is not None) and (item not in self.graphic.items()): + self.graphic.addItem(item) + else: + for a in self.active: + item = self.error_plots[a] + if (item is not None) and (item in self.graphic.items()): + self.graphic.removeItem(item) + + def add_external(self, item): + if isinstance(item, RegionItem) and item.first: + # Give regions nice values on first addition to a graph + x, _ = self.ranges + + if item.mode == 'mid': + onset = item.getRegion()[0] + if self.log[0]: + delta = log10(x[1]/x[0])/20 + span = (onset / 10**delta , onset * 10**delta) + else: + delta = x[1]-x[0] + span = (onset-delta/20, onset + delta/20) + elif item.mode == 'half': + span = (0.75*x[0]+0.25*x[1], 0.25*x[0]+0.75*x[1]) + else: + span = item.getRegion() + + item.setRegion(span) + item.first = False + + if item in self.graphic.items(): + return False + + if not hasattr(item, 'setLogMode'): + self._special_needs.append(item) + + self.graphic.addItem(item) + item.setZValue(1000) + + return True + + @QtCore.pyqtSlot(GraphicsObject) + def remove_external(self, item): + if item not in self.graphic.items(): + return False + + if item in self._special_needs: + self._special_needs.remove(item) + + self.graphic.removeItem(item) + + return True + + def closeEvent(self, evt: QtGui.QCloseEvent): + if not self.closable: + evt.ignore() + return + + res = QtWidgets.QMessageBox.Yes + if len(self.sets) != 0: + res = QtWidgets.QMessageBox.question(self, 'Plot not empty', 'Graph is not empty. Deleting with all data?', + QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) + + if res == QtWidgets.QMessageBox.Yes: + self.aboutToClose.emit(self.id) + evt.accept() + else: + evt.ignore() + + def move_mouse(self, evt): + vb = self.plotItem.getViewBox() + if self.plotItem.sceneBoundingRect().contains(evt): + pos = vb.mapSceneToView(evt) + if self.log[0]: + try: + _x = 10**(pos.x()) + except OverflowError: + _x = pos.x() + else: + _x = pos.x() + + if self.log[1]: + try: + _y = 10**(pos.y()) + except OverflowError: + _y = pos.y() + else: + _y = pos.y() + self.mousePositionChanged.emit(_x, _y) + + @QtCore.pyqtSlot(name='on_title_lineedit_returnPressed') + @QtCore.pyqtSlot(name='on_xaxis_linedit_returnPressed') + @QtCore.pyqtSlot(name='on_yaxis_linedit_returnPressed') + def labels_changed(self): + label = {self.title_lineedit: 'title', self.xaxis_linedit: 'x', self.yaxis_linedit: 'y'}[self.sender()] + self.set_label(**{label: self.sender().text()}) + + def set_label(self, x=None, y=None, title=None): + if title is not None: + self.plotItem.setTitle(convert(title, old='tex', new='html'), **{'size': '10pt', 'color': self._fgcolor}) + + if x is not None: + self.plotItem.setLabel('bottom', convert(x, old='tex', new='html'), + **{'font-size': '10pt', 'color': self._fgcolor.name()}) + + if y is not None: + self.plotItem.setLabel('left', convert(y, old='tex', new='html'), + **{'font-size': '10pt', 'color': self._fgcolor.name()}) + + def set_logmode(self, xmode: bool = None, ymode: bool = None): + r = self.ranges + + if xmode is None: + xmode = self.plotItem.ctrl.logXCheck.isChecked() + else: + self.plotItem.ctrl.logXCheck.setCheckState(xmode) + + if ymode is None: + ymode = self.plotItem.ctrl.logYCheck.isChecked() + else: + self.plotItem.ctrl.logYCheck.setCheckState(ymode) + + self.log = [xmode, ymode] + + for item in self._special_needs: + item.logmode[0] = self.log[:] + + self.plotItem.updateLogMode() + + self.set_range(x=r[0], y=r[1]) + + def enable_picking(self, enabled: bool): + if enabled: + self.scene.sigMouseClicked.connect(self.position_picked) + else: + try: + self.scene.sigMouseClicked.disconnect() + except TypeError: + pass + + def position_picked(self, evt): + vb = self.graphic.plotItem.vb + + if self.graphic.plotItem.sceneBoundingRect().contains(evt.scenePos()) and evt.button() == 1: + pos = vb.mapSceneToView(evt.scenePos()) + _x, _y = pos.x(), pos.y() + + if self.log[0]: + _x = 10**_x + + if self.log[1]: + _y = 10**_y + + self.positionClicked.emit((_x, _y), evt.double()) + + @QtCore.pyqtSlot(name='on_apply_button_clicked') + def set_range(self, x: tuple = None, y: tuple = None): + if x is None: + x = float(self.xmin_lineedit.text()), float(self.xmax_lineedit.text()) + x = min(x), max(x) + + if y is None: + y = float(self.ymin_lineedit.text()), float(self.ymax_lineedit.text()) + y = min(y), max(y) + + for log, xy, func in zip(self.log, (x, y), (self.graphic.setXRange, self.graphic.setYRange)): + if log: + with errstate(all='ignore'): + xy = [log10(val) for val in xy] + + if isnan(xy[1]): + xy = [-1, 1] + elif isnan(xy[0]): + xy[0] = xy[1]-4 + + func(xy[0], xy[1], padding=0) + + @QtCore.pyqtSlot(object) + def update_limits(self, _): + r = self.ranges + self.xmin_lineedit.setText('%.5g' % r[0][0]) + self.xmax_lineedit.setText('%.5g' % r[0][1]) + + self.ymin_lineedit.setText('%.5g' % r[1][0]) + self.ymax_lineedit.setText('%.5g' % r[1][1]) + + def _update_zorder(self): + for i, sid in enumerate(self.sets): + plt = self.real_plots[sid] + if plt.zValue() != 2*i+1: + plt.setZValue(2*i+1) + if self.imag_plots[sid] is not None: + self.imag_plots[sid].setZValue(2*i+1) + if self.error_plots[sid] is not None: + self.error_plots[sid].setZValue(2*i) + + self.show_legend() + + @QtCore.pyqtSlot(bool, name='on_legend_button_toggled') + def show_legend_item_list(self, visible: bool): + self.listWidget.setVisible(visible) + self.checkBox.setVisible(visible) + + def update_legend(self, sid, name): + self.listWidget.blockSignals(True) + + for i in range(self.listWidget.count()): + item = self.listWidget.item(i) + if item.data(QtCore.Qt.UserRole) == sid: + item.setText(convert(name, old='tex', new='html')) + + self.listWidget.blockSignals(False) + self.show_legend() + + def show_legend(self): + if not self.legend.isVisible(): + return + + self.legend.clear() + + for i, sid in enumerate(self.sets): + item = self.real_plots[sid] + other_item = self.imag_plots[sid] + # should legend be visible? is either real part or imaginary part shown? + if self.listWidget.item(i).checkState() and \ + (item in self.graphic.items() or other_item in self.graphic.items()): + self.legend.addItem(item, convert(item.opts.get('name', ''), old='tex', new='html')) + + def export(self): + filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)' + for imgformat in QtGui.QImageWriter.supportedImageFormats(): + str_format = imgformat.data().decode('utf-8') + filters += ';;' + str_format.upper() + ' (*.' + str_format + ')' + + outfile, _ = QtWidgets.QFileDialog.getSaveFileName(self, caption='Export graphic', filter=filters, + options=QtWidgets.QFileDialog.DontConfirmOverwrite) + if outfile: + _, suffix = os.path.splitext(outfile) + if suffix == '': + QtWidgets.QMessageBox.warning(self, 'No file extension', + 'No file extension found, graphic was not saved.') + return + + if suffix == '.agr': + res = 0 + if os.path.exists(outfile): + res = GraceMsgBox(outfile, parent=self).exec() + if res == -1: + return + + opts = self.export_graphics() + + from ..io.exporters import GraceExporter + if res == 0: + mode = 'w' + elif res == 1: + mode = 'a' + else: + mode = res-2 + + GraceExporter(opts).export(outfile, mode=mode) + + else: + if os.path.exists(outfile): + if QtWidgets.QMessageBox.warning(self, 'Export graphic', + f'{os.path.split(outfile)[1]} already exists.\n' + f'Do you REALLY want to replace it?', + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, + QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: + return + + bg_color = self._bgcolor + fg_color = self._fgcolor + self.set_color(foreground='k', background='w') + + if suffix == '.pdf': + from ..io.exporters import PDFPrintExporter + PDFPrintExporter(self.graphic).export(outfile) + + + elif suffix == '.svg': + from pyqtgraph.exporters import SVGExporter + SVGExporter(self.scene).export(outfile) + + else: + from pyqtgraph.exporters import ImageExporter + + ImageExporter(self.scene).export(outfile) + + self.set_color(foreground=fg_color, background=bg_color) + + def export_graphics(self): + dic = self.get_state() + dic['items'] = [] + + for item in self.curves(): + item_dic = item[0].get_data_opts() + if len(item) == 2: + # plot can show errorbars + item_dic['yerr'] = item[1].opts['topData'] + + if item_dic: + dic['items'].append(item_dic) + + return dic + + def get_state(self) -> dict: + dic = { + 'id': self.id, + 'limits': (self.ranges[0], self.ranges[1]), + 'ticks': (), + 'labels': (self.plotItem.getAxis('bottom').labelText, + self.plotItem.getAxis('left').labelText, + self.plotItem.titleLabel.text, + self.title), + 'log': self.log, + 'grid': self.gridbutton.isChecked(), + 'legend': self.legend.isVisible(), + 'plots': (self.real_button.isChecked(), self.imag_button.isChecked(), self.error_button.isChecked()), + 'children': self.sets, + 'active': self.active, + } + + in_legend = [] + for i in range(self.listWidget.count()): + in_legend.append(bool(self.listWidget.item(i).checkState())) + dic['in_legend'] = in_legend + + # bottomLeft gives top left corner + l_topleft = self.plotItem.vb.itemBoundingRect(self.legend).bottomLeft() + legend_origin = [l_topleft.x(), l_topleft.y()] + for i in [0, 1]: + if self.log[i]: + legend_origin[i] = 10**legend_origin[i] + dic['legend_pos'] = legend_origin + + for i, ax in enumerate(['bottom', 'left']): + if self.log[i]: + major = 10 + minor = 9 + else: + vmin, vmax = dic['limits'][i][0], dic['limits'][i][1] + dist = vmax - vmin + scale = 10**floor(log10(abs(dist))) + steps = [0.1, 0.2, 0.25, 0.5, 1., 2., 2.5, 5., 10., 20., 50., 100.] + for step_i in steps: + if dist / step_i / scale <= 10: + break + major = step_i * scale + minor = 1 + + dic['ticks'] += (major, minor), + + return dic + + @staticmethod + def set_state(state): + graph = QGraphWindow() + graph.id = state.get('id', graph.id) + + graph.plotItem.setLabel('bottom', state['labels'][0], **{'font-size': '10pt', 'color': graph._fgcolor.name()}) + graph.plotItem.setLabel('left', state['labels'][1], **{'font-size': '10pt', 'color': graph._fgcolor.name()}) + graph.plotItem.setTitle(state['labels'][2], **{'size': '10pt', 'color': graph._fgcolor.name()}) + graph.setWindowTitle(state['labels'][3]) + + graph.graphic.showGrid(x=state['grid'], y=state['grid']) + + graph.checkBox.setCheckState(QtCore.Qt.Checked if state['legend'] else QtCore.Qt.Unchecked) + + graph.real_button.setChecked(state['plots'][0]) + graph.imag_button.setChecked(state['plots'][1]) + graph.error_button.setChecked(state['plots'][2]) + + graph.set_range(x=state['limits'][0], y=state['limits'][1]) + graph.logx_button.setChecked(state['log'][0]) + graph.logy_button.setChecked(state['log'][1]) + + return graph + + def set_color(self, foreground=None, background=None): + if background is not None: + self._bgcolor = mkColor(background) + self.graphic.setBackground(self._bgcolor) + self.legend.setBrush(self._bgcolor) + + if foreground is not None: + self._fgcolor = mkColor(foreground) + + for ax in ['left', 'bottom']: + pen = self.plotItem.getAxis(ax).pen() + pen.setColor(self._fgcolor) + + self.plotItem.getAxis(ax).setPen(pen) + self.plotItem.getAxis(ax).setTextPen(pen) + + self.legend.setLabelTextColor(self._fgcolor) + if self.legend.isVisible(): + self.show_legend() + + title = self.plotItem.titleLabel.text + if title is not None: + self.plotItem.setTitle(title, **{'size': '10pt', 'color': self._fgcolor}) + + x = self.plotItem.getAxis('bottom').labelText + if x is not None: + self.plotItem.setLabel('bottom', x, **{'font-size': '10pt', 'color': self._fgcolor.name()}) + + y = self.plotItem.getAxis('left').labelText + if y is not None: + self.plotItem.setLabel('left', y, **{'font-size': '10pt', 'color': self._fgcolor.name()}) + + @QtCore.pyqtSlot(bool, name='on_bwbutton_toggled') + def change_background(self, _): + temp = self._fgcolor, self._bgcolor + self.set_color(foreground=self._prev_colors[0], background=self._prev_colors[1]) + self._prev_colors = temp diff --git a/nmreval/gui_qt/graphs/guide_lines.py b/nmreval/gui_qt/graphs/guide_lines.py new file mode 100644 index 0000000..e821f55 --- /dev/null +++ b/nmreval/gui_qt/graphs/guide_lines.py @@ -0,0 +1,166 @@ +from pyqtgraph import InfiniteLine + +from ..Qt import QtWidgets, QtCore, QtGui +from .._py.guidelinewidget import Ui_Form +from ..lib.pg_objects import LogInfiniteLine + + +class LineWidget(QtWidgets.QWidget, Ui_Form): + line_created = QtCore.pyqtSignal(object, str) + line_deleted = QtCore.pyqtSignal(object, str) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.lines = {} + self.comments = {} + + self.vh_pos_lineEdit.setValidator(QtGui.QDoubleValidator()) + + self.tableWidget.installEventFilter(self) + + @QtCore.pyqtSlot(name='on_pushButton_clicked') + def make_line(self): + invalid = True + + idx = self.mode_comboBox.currentIndex() + try: + pos = float(self.vh_pos_lineEdit.text()) + # Vertical: idx=0; horizontal: idx = 1 + angle = 90*abs(1-idx) + invalid = False + except ValueError: + pos = None + angle = None + pass + + if invalid: + QtWidgets.QMessageBox().information(self, 'Invalid input', 'Input is not a valid number') + return + + qcolor = QtGui.QColor.fromRgb(*self.color_comboBox.value.rgb()) + comment = self.comment_lineEdit.text() + line = LogInfiniteLine(pos=pos, angle=angle, movable=self.drag_checkBox.isChecked(), pen=qcolor) + line.sigPositionChanged.connect(self.move_line) + + self.make_table_row(pos, angle, qcolor, comment) + + graph_id = self.graph_comboBox.currentData() + try: + self.lines[graph_id].append(line) + self.comments[graph_id].append(comment) + except KeyError: + self.lines[graph_id] = [line] + self.comments[graph_id] = [comment] + + self.line_created.emit(line, graph_id) + + def set_graphs(self, graphs: list): + for graph_id, name in graphs: + self.graph_comboBox.addItem(name, userData=graph_id) + + def remove_graph(self, graph_id: str): + idx = self.graph_comboBox.findData(graph_id) + if idx != -1: + self.graph_comboBox.removeItem(idx) + + if graph_id in self.lines: + del self.lines[graph_id] + + @QtCore.pyqtSlot(int, name='on_graph_comboBox_currentIndexChanged') + def change_graph(self, idx: int): + self.tableWidget.clear() + self.tableWidget.setRowCount(0) + + graph_id = self.graph_comboBox.itemData(idx) + if graph_id in self.lines: + lines = self.lines[graph_id] + comments = self.comments[graph_id] + for i, line in enumerate(lines): + self.make_table_row(line.pos(), line.angle, line.pen.color(), comments[i]) + + def make_table_row(self, position, angle, color, comment): + if angle == 0: + try: + pos_label = 'x = ' + str(position.y()) + except AttributeError: + pos_label = 'x = {position}' + + elif angle == 90: + try: + pos_label = f'y = {position.x()}' + except AttributeError: + pos_label = f'y = {position}' + + else: + raise ValueError('Only horizontal or vertical lines are supported') + + item = QtWidgets.QTableWidgetItem(pos_label) + item.setFlags(QtCore.Qt.ItemIsSelectable) + item.setForeground(QtGui.QBrush(QtGui.QColor('black'))) + + row_count = self.tableWidget.rowCount() + self.tableWidget.setRowCount(row_count+1) + self.tableWidget.setItem(row_count, 0, item) + + item2 = QtWidgets.QTableWidgetItem(comment) + self.tableWidget.setItem(row_count, 1, item2) + + colitem = QtWidgets.QTableWidgetItem(' ') + colitem.setBackground(QtGui.QBrush(color)) + colitem.setFlags(QtCore.Qt.ItemIsSelectable) + self.tableWidget.setVerticalHeaderItem(row_count, colitem) + + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent) -> bool: + if evt.type() == QtCore.QEvent.KeyPress: + if evt.key() == QtCore.Qt.Key_Delete: + self.delete_line() + return True + + return super().eventFilter(src, evt) + + def delete_line(self): + remove_rows = sorted([item.row() for item in self.tableWidget.selectedItems()]) + graph_id = self.graph_comboBox.currentData() + current_lines = self.lines[graph_id] + + print(remove_rows) + for i in reversed(remove_rows): + print(i) + self.tableWidget.removeRow(i) + self.line_deleted.emit(current_lines[i], graph_id) + + current_lines.pop(i) + self.comments[graph_id].pop(i) + + @QtCore.pyqtSlot(object) + def move_line(self, line: InfiniteLine): + current_idx = self.graph_comboBox.currentData() + graphs = self.lines[current_idx] + i = -1 + for i, line_i in enumerate(graphs): + if line == line_i: + break + pos = line.value() + text_item = self.tableWidget.item(i, 0) + text_item.setText(text_item.text()[:4]+f'{pos:.4g}') + + +if __name__ == '__main__': + import sys + from numpy.random import choice + + app = QtWidgets.QApplication([]) + + qw = 'QtCurve' + while qw == 'QtCurve': + qw = choice(QtWidgets.QStyleFactory.keys()) + print(qw) + app.setStyle(QtWidgets.QStyleFactory.create(qw)) + + mplQt = LineWidget() + mplQt.show() + mplQt.set_graphs([('a', 'a'), ('b', 'b')]) + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/graphs/movedialog.py b/nmreval/gui_qt/graphs/movedialog.py new file mode 100644 index 0000000..5b7378b --- /dev/null +++ b/nmreval/gui_qt/graphs/movedialog.py @@ -0,0 +1,89 @@ +from ..Qt import QtCore, QtWidgets +from .._py.move_dialog import Ui_MoveDialog + + +class QMover(QtWidgets.QDialog, Ui_MoveDialog): + moveData = QtCore.pyqtSignal(list, str, str) + copyData = QtCore.pyqtSignal(list, str) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.entries = {} + + self.fromcomboBox.currentIndexChanged.connect(self.change_graph) + self.buttonBox.clicked.connect(self.button_clicked) + + def setup(self, entries: dict): + self.fromcomboBox.blockSignals(True) + self.tocomboBox.blockSignals(True) + for k, v in entries.items(): + self.entries[k[0]] = v + self.fromcomboBox.addItem(k[1], userData=k[0]) + self.tocomboBox.addItem(k[1], userData=k[0]) + self.fromcomboBox.blockSignals(False) + self.tocomboBox.blockSignals(False) + self.change_graph(0) + + @QtCore.pyqtSlot(int) + def change_graph(self, idx: int): + self.listWidget.clear() + idd = self.fromcomboBox.itemData(idx) + if idd is not None: + for i, j in self.entries[idd]: + it = QtWidgets.QListWidgetItem(j) + it.setData(QtCore.Qt.UserRole, i) + self.listWidget.addItem(it) + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton) + def button_clicked(self, btn: QtWidgets.QAbstractButton): + if self.buttonBox.buttonRole(btn) in [QtWidgets.QDialogButtonBox.ApplyRole, + QtWidgets.QDialogButtonBox.AcceptRole]: + from_graph = self.fromcomboBox.itemData(self.fromcomboBox.currentIndex()) + to_graph = self.tocomboBox.itemData(self.tocomboBox.currentIndex()) + if from_graph != to_graph: + moving = [] + for idx in self.listWidget.selectedIndexes(): + it = self.listWidget.itemFromIndex(idx) + moving.append(it.data(QtCore.Qt.UserRole)) + it_data = (it.data(QtCore.Qt.UserRole), it.text()) + if self.move_button.isChecked(): + self.entries[from_graph].remove(it_data) + self.entries[to_graph].append(it_data) + self.change_graph(self.fromcomboBox.currentIndex()) + + if self.move_button.isChecked(): + self.moveData.emit(moving, to_graph, from_graph) + else: + self.copyData.emit(moving, to_graph) + + if self.buttonBox.buttonRole(btn) == QtWidgets.QDialogButtonBox.AcceptRole: + self.close() + + elif self.buttonBox.buttonRole(btn) == QtWidgets.QDialogButtonBox.RejectRole: + self.close() + else: + return + + def close(self): + self.listWidget.clear() + self.tocomboBox.clear() + self.fromcomboBox.clear() + self.entries = {} + + super().close() + + +if __name__ == '__main__': + import sys + global propData + + app = QtWidgets.QApplication([]) + + m = QMover() + m.setup({('a', '1'): ('z', '100'), ('b', '2'): ('y', '99'), ('c', '3'): ('x', '98')}) + + m.show() + + sys.exit(app.exec()) \ No newline at end of file diff --git a/nmreval/gui_qt/io/__init__.py b/nmreval/gui_qt/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmreval/gui_qt/io/asciireader.py b/nmreval/gui_qt/io/asciireader.py new file mode 100644 index 0000000..fd6472a --- /dev/null +++ b/nmreval/gui_qt/io/asciireader.py @@ -0,0 +1,182 @@ +from ...io.asciireader import AsciiReader + +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.asciidialog import Ui_ascii_reader + + +class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): + data_read = QtCore.pyqtSignal(list) + file_ext = ['.dat', '.txt'] + skip = False + + def __init__(self, fname=None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.reader = AsciiReader(fname) + + self.ascii_table.horizontalHeader().setStretchLastSection(True) + self.buttonbox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.apply) + self.buttonbox.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.accept) + + self.changestaggeredrange(0) + + self.ascii_table.contextMenuEvent = self.ctx_table + self.ascii_table.horizontalHeader().setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.ascii_table.horizontalHeader().customContextMenuRequested.connect(self.ctx_table) + + def __call__(self, fname, *args, **kwargs): + + for i in [self.stag_lineEdit, self.start_lineedit, self.end_lineedit, + self.plainTextEdit_2, self.ascii_table, self.delay_lineedit]: + i.clear() + self.checkBox_2.setChecked(False) + self.checkBox.setChecked(False) + self.plainTextEdit_2.show() + + self.reader = AsciiReader(fname) + self.set_gui() + + if QAsciiReader.skip: + self.accept() + else: + self.skippy_checkbox.blockSignals(True) + self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.skippy_checkbox.blockSignals(False) + + return self + + def set_gui(self): + for text in self.reader.header: + self.plainTextEdit_2.appendPlainText(text) + + self.ascii_table.setRowCount(self.reader.shape[0]) + self.ascii_table.setColumnCount(self.reader.shape[1]) + try: + last_header_line = self.reader.header[-1].split() + if len(last_header_line) == self.reader.shape[1]: + self.ascii_table.setHorizontalHeaderLabels(last_header_line) + except IndexError: + self.plainTextEdit_2.hide() + + for i, line in enumerate(self.reader.data): + for j, field in enumerate(line): + it = QtWidgets.QTableWidgetItem(field) + self.ascii_table.setItem(i, j, it) + + self.ascii_table.resizeColumnsToContents() + + if self.reader.delays is not None: + set_string = ''.join(str(d) + '\n' for d in self.reader.delays) + self.plainTextEdit.setPlainText(set_string) + self.delay_lineedit.setText(str(len(self.reader.delays))) + + def ctx_table(self, _): + menu = QtWidgets.QMenu(self) + x_action = QtWidgets.QAction('Set as x', self) + x_action.triggered.connect(lambda: self.set_columns('x')) + y_action = QtWidgets.QAction('Set as y', self) + y_action.triggered.connect(lambda: self.set_columns('y')) + menu.addActions([x_action, y_action]) + if self.label_5.isVisible(): + yerr_action = QtWidgets.QAction('Set as \u0394y', self) + yerr_action.triggered.connect(lambda: self.set_columns('yerr')) + menu.addAction(yerr_action) + menu.popup(QtGui.QCursor.pos()) + + def set_columns(self, mode): + cols = ' '.join([str(s.column()+1) for s in self.ascii_table.selectionModel().selectedColumns()]) + try: + lineedit = {'x': self.x_lineedit, 'y': self.y_lineedit, 'yerr': self.lineEdit}[mode] + lineedit.setText(cols) + except KeyError: + pass + + @QtCore.pyqtSlot(int, name='on_checkBox_2_stateChanged') + def changestaggeredrange(self, evt): + if evt == 2: + self.stag_lineEdit.show() + self.stag_lineEdit.setEnabled(True) + else: + self.stag_lineEdit.hide() + self.stag_lineEdit.setDisabled(True) + + @QtCore.pyqtSlot(name='on_pushButton_clicked') + def calc_delays(self): + self.reader.calc_delays(float(self.start_lineedit.text()), float(self.end_lineedit.text()), + int(self.delay_lineedit.text()), log=self.checkBox.isChecked(), + stagg=self.checkBox_2.isChecked(), stag_size=int(self.stag_lineEdit.text())) + + set_string = ''.join(str(d) + '\n' for d in self.reader.delays) + self.plainTextEdit.setPlainText(set_string) + + def update_header(self, text, comment='#'): + text = text.replace(comment, '').lstrip().rstrip() + self.plainTextEdit_2.appendPlainText(text) + + @QtCore.pyqtSlot() + def on_plainTextEdit_textChanged(self): + new_delays = str(self.plainTextEdit.toPlainText()).rstrip('\n').split('\n') + self.delay_lineedit.setText(str(len(new_delays))) + if new_delays[0] == '': + new_delays = None + else: + for k, v in enumerate(new_delays): + try: + new_delays[k] = float(v) + except ValueError: + new_delays[k] = -1 + self.reader.delays = new_delays + + @QtCore.pyqtSlot() + def accept(self): + if self.apply(): + self.close() + + def apply(self): + # default row for x is the first row, it will be superseded if an integer number is given. + + try: + x = [int(self.x_lineedit.text())-1] + except ValueError: + x = None + try: + y = [int(t)-1 for t in str(self.y_lineedit.text()).split(' ')] + except ValueError: + y = None + try: + y_err = [int(t)-1 for t in str(self.lineEdit.text()).split(' ')] + except ValueError: + y_err = None + + ret_dic = self.reader.export(xidx=x, yidx=y, yerridx=y_err, + mode=self.buttonGroup.checkedButton().text()) + + self.data_read.emit(ret_dic) + + return True + + @staticmethod + def _update_dic(key, value, old_dic): + old_dic_keys = list(old_dic.keys()) + if key in old_dic_keys: + counter = 0 + replace_key = key + str(counter) + while replace_key in old_dic_keys: + counter += 1 + replace_key = key + str(counter) + else: + replace_key = key + old_dic[replace_key] = value + + @QtCore.pyqtSlot(int, name='on_buttonGroup_buttonClicked') + def show_error(self, val): + if val == -2: + self.widget.show() + else: + self.lineEdit.setText('') + self.widget.hide() + + @QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged') + def skip_next_dial(self, _): + QAsciiReader.skip = self.skippy_checkbox.isChecked() diff --git a/nmreval/gui_qt/io/bdsreader.py b/nmreval/gui_qt/io/bdsreader.py new file mode 100644 index 0000000..8ecc837 --- /dev/null +++ b/nmreval/gui_qt/io/bdsreader.py @@ -0,0 +1,66 @@ +from ...io.bds_reader import BDSReader + +from ..Qt import QtCore, QtWidgets +from .._py.bdsdialog import Ui_Dialog + + +class QBDSReader(QtWidgets.QDialog, Ui_Dialog): + data_read = QtCore.pyqtSignal(list) + file_ext = ['.eps'] + + def __init__(self, fname: str = None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + if fname is not None: + self.reader = BDSReader(fname) + self.setup_gui() + + def __call__(self, fname: str): + self.reader = BDSReader(fname) + self.setup_gui() + + return self + + def setup_gui(self): + self.listWidget.clear() + + for temp in self.reader.set_temp: + item = QtWidgets.QListWidgetItem(str(temp)) + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable) + item.setCheckState(QtCore.Qt.Checked) + self.listWidget.addItem(item) + + def accept(self): + data = [] + + temps = [] + for i in range(self.listWidget.count()): + item = self.listWidget.item(i) + if item.checkState() == QtCore.Qt.Checked: + temps.append(i) + + for (mode, cb) in [('epsilon', self.eps_checkBox), + ('sigma', self.cond_checkBox), + ('modulus', self.modul_checkBox)]: + + if cb.checkState() == QtCore.Qt.Checked: + values = self.reader.export(mode=mode) + for t in temps: + data.append(values[t]) + + self.data_read.emit(data) + + super().accept() + + +if __name__ == '__main__': + import sys + + app = QtWidgets.QApplication(sys.argv) + + reader = QBDSReader() + reader('/autohome/dominik/nmreval/testdata/ZWEITECHANCE_EGD4H2O.EPS') + reader.show() + sys.exit(app.exec()) + diff --git a/nmreval/gui_qt/io/dscreader.py b/nmreval/gui_qt/io/dscreader.py new file mode 100644 index 0000000..e11eae5 --- /dev/null +++ b/nmreval/gui_qt/io/dscreader.py @@ -0,0 +1,273 @@ +from pathlib import Path +from typing import Union + +import numpy as np +from pyqtgraph import PlotDataItem + +from nmreval.data.points import Points +from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample + +from nmreval.gui_qt.Qt import QtWidgets, QtCore +from nmreval.gui_qt._py.dscfile_dialog import Ui_Dialog + + +class QDSCReader(QtWidgets.QDialog, Ui_Dialog): + data_read = QtCore.pyqtSignal(list) + file_ext = ['.txt', '.dsc'] + + def __init__(self, sample= None, empty=None, reference=None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.calibrator = DSCCalibrator() + self.current_run = (-1, 'h') + self.sample_idx = None + self.fname = None + + self.raw_graph.setLabel('bottom', text='T', units='K') + self.raw_graph.setTitle('Raw data') + self.raw_graph.addLegend() + self.raw_sample = PlotDataItem(x=[], y=[], name='Sample') + self.empty_sample = PlotDataItem(x=[], y=[], pen={'dash': (5, 5)}, name='Empty') + self.raw_graph.addItem(self.raw_sample) + self.raw_graph.addItem(self.empty_sample) + + self.end_graph.setTitle('End result') + self.end_graph.setLabel('bottom', text='T', units='K') + self.baseline_sample = PlotDataItem(x=[], y=[]) + self.end_graph.addItem(self.baseline_sample) + + self.baseline_graph.setTitle('Time dependence') + self.baseline_graph.setLabel('bottom', text='t', units='min') + self.drift_sample = PlotDataItem(x=[], y=[]) + self.slope_graph = PlotDataItem(x=[], y=[], pen={'color': 'r', 'dash': (5, 5)}) + self.baseline_graph.addItem(self.drift_sample) + self.baseline_graph.addItem(self.slope_graph) + + self.calib_graph.setTitle('Calibration') + self.calib_graph.setLabel('bottom', text='T', units='K') + self.ref_plotitems = [] + + self.sample = None + if sample is not None: + self.add_sample(sample) + + self.empty = None + if empty is not None: + self.add_empty(empty=empty) + + self.references = [] + if reference is not None: + if isinstance(reference, (str, Path, DSCSample)): + reference = [reference] + for r in reference: + self.add_reference(ref=r) + + def add_sample(self, fname: Union[Path, str]): + self.sample = self.calibrator.set_measurement(fname, mode='sample') + self.fname = self.sample.fname + + self.step_listWidget.clear() + + for opts in self.sample.steps: + item = QtWidgets.QListWidgetItem() + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable) + item.setCheckState(QtCore.Qt.Unchecked) + + if opts[0] == 'i': + item.setFlags(QtCore.Qt.NoItemFlags) + item.setText(f'{opts[1]:.2f} K for {opts[1] / 60:.0f} min') + else: + 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') + + self.step_listWidget.addItem(item) + + @QtCore.pyqtSlot(name='on_loadempty_button_clicked') + def add_empty(self, empty=None): + if empty is None: + empty, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self.fname.parent)) + + if empty: + self.empty = self.calibrator.set_measurement(empty, mode='empty') + self.empty_label.setText(str(self.empty.fname.name)) + + self.update_plots() + + @QtCore.pyqtSlot(name='on_delempty_button_clicked') + def remove_empty(self): + self.empty_label.setText('Empty measurement') + self.calibrator.empty = None + + self.update_plots() + + @QtCore.pyqtSlot(name='on_ref_add_pushButton_clicked') + def add_reference(self, ref=None): + if ref is None: + ref, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self.fname.parent)) + + if ref: + ref = self.calibrator.set_measurement(ref, mode='reference') + + self.references.append(ref) + item = QtWidgets.QTableWidgetItem(str(ref.fname.name)) + item.setData(QtCore.Qt.UserRole, ref.fname) + item.setFlags(QtCore.Qt.ItemIsEnabled) + + rowcnt = self.reference_tableWidget.rowCount() + self.reference_tableWidget.setRowCount(rowcnt+1) + self.reference_tableWidget.setItem(rowcnt, 0, item) + self.reference_tableWidget.setCellWidget(rowcnt, 1, ReferenceComboBox()) + self.reference_tableWidget.resizeColumnsToContents() + + self.update_plots() + + @QtCore.pyqtSlot(name='on_ref_remove_pushButton_clicked') + def remove_reference(self): + idx = self.reference_tableWidget.currentRow() + self.calibrator.remove_reference(self.reference_tableWidget.item(idx, 0).data(QtCore.Qt.UserRole)) + + self.reference_tableWidget.removeRow(idx) + + print(self.calibrator.reference) + + self.update_plots() + + @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name='on_step_listWidget_itemChanged') + def select_run(self, item: QtWidgets.QListWidgetItem): + + idx = self.step_listWidget.indexFromItem(item).row() + self.step_listWidget.blockSignals(True) + for row in range(self.step_listWidget.count()): + if idx == row: + continue + self.step_listWidget.item(row).setCheckState(QtCore.Qt.Unchecked) + self.step_listWidget.blockSignals(False) + + if item.checkState() == QtCore.Qt.Checked: + mode, rate, _, _ = self.sample.steps[idx] + self.current_run = (rate, mode) + self.sample_idx = idx + self.update_plots() + else: + self.current_run = (-1, 'h') + self.sample_idx = None + self.clear_plots() + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked') + def update_plots(self, _=None): + if self.sample_idx is None: + return + + slope_type = {self.none_radioButton: None, + self.isotherm_radioButton: 'iso', + self.slope_radioButton: 'curve'}[self.buttonGroup.checkedButton()] + + rate = self.current_run[0] + try: + raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx, + slope=slope_type) + except ValueError as e: + _msg = QtWidgets.QMessageBox.warning(self, 'No rate found', e.args[0]) + return + + self.raw_sample.setData(x=raw_sample[0], y=raw_sample[1]) + self.drift_sample.setData(x=drift_value[0]/60., y=drift_value[1]) + self.slope_graph.setData(x=slope[0]/60., y=slope[1]) + + if empty_data is not None: + self.empty_sample.setData(x=empty_data[0], y=empty_data[1]) + + self.calibrator.ref_list = [] + for row in range(self.reference_tableWidget.rowCount()): + self.calibrator.ref_list.append(self.reference_tableWidget.cellWidget(row, 1).get_reference()) + + self.calib_graph.clear() + + calib_x, calib_y, regions = self.calibrator.get_calibration(rate) + + if calib_x is not None: + for ref_zoom, onset, grad_points in regions: + ref_plot = PlotDataItem(x=ref_zoom[0], y=ref_zoom[1]) + self.calib_graph.addItem(ref_plot) + + self.calib_graph.addItem(PlotDataItem(x=grad_points[0], y=grad_points[1], + symbol='x')) + + view_limits = -np.max(ref_zoom[1])/10, np.max(ref_zoom[1]) * 1.1 + cut_idx, = np.where((onset < view_limits[1]) & (onset > view_limits[0])) + self.calib_graph.addItem(PlotDataItem(x=ref_zoom[0, cut_idx], y=onset[cut_idx], + pen={'color': 'r', 'dash': (2, 5)})) + self.calib_graph.addItem(PlotDataItem(x=[ref_zoom[0, 0], ref_zoom[0, -1]], y=[0, 0], + pen={'color': 'r', 'dash': (2, 5)})) + + else: + calib_x = [1, 0] + calib_y = 1 + + if self.cp_checkBox.isChecked(): + self.baseline_sample.setData(x=calib_x[0]*sample_data[0]+calib_x[1], y=calib_y*sample_data[1]) + else: + self.baseline_sample.setData(x=calib_x[0]*sample_data[0]+calib_x[1], y=sample_data[1]) + + def clear_plots(self): + for plot in [self.raw_sample, self.baseline_sample, self.empty_sample, self.drift_sample, self.slope_graph]: + plot.setData(x=[], y=[]) + + self.calib_graph.clear() + + def accept(self): + run_list = [] + for i in range(self.step_listWidget.count()): + if self.step_listWidget.item(i).checkState() == QtCore.Qt.Checked: + run_list.append(i) + + if self.baseline_groupBox.isChecked(): + empty = self.empty + slope = {self.none_radioButton: None, + self.isotherm_radioButton: 'iso', + self.slope_radioButton: 'heat'}[self.buttonGroup.checkedButton()] + else: + empty = None + slope = None + + if self.reference_groupBox.isChecked(): + calibrations = [] + for j, ref in enumerate(self.references): + calibrations.append((ref, self.reference_tableWidget.cellWidget(j, 1).get_reference())) + else: + calibrations = None + + new_vals = [] + + for run in run_list: + mode, rate, _, _ = self.sample.steps[run] + xy = self.sample.curve(rate, mode, empty=empty, slope=slope, calibration=calibrations) + new_vals.append(Points(xy[0], xy[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})')) + + self.data_read.emit(new_vals) + + super().accept() + + +class ReferenceComboBox(QtWidgets.QComboBox): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.references = [Cyclohexane] + for ref in self.references: + self.addItem(ref.name) + + def get_reference(self): + return self.references[self.currentIndex()] + + +if __name__ == '__main__': + import sys + app = QtWidgets.QApplication(sys.argv) + + reader = QDSCReader(sample='/autohome/dominik/nmreval/testdata/dsc/20m_LiTFSI_H2O.txt', + empty='/autohome/dominik/nmreval/testdata/dsc/leer.txt', + reference='/autohome/dominik/nmreval/testdata/dsc/cyclohexan_10K-min.txt') + reader.show() + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/io/exporters.py b/nmreval/gui_qt/io/exporters.py new file mode 100644 index 0000000..bbca4a6 --- /dev/null +++ b/nmreval/gui_qt/io/exporters.py @@ -0,0 +1,143 @@ +from numpy import c_ + +from ...io.graceeditor import GraceEditor +from ...lib.colors import Colors +from ...lib.lines import LineStyle +from ...lib.symbols import SymbolStyle +from ...utils.text import convert + +from ..Qt import QtGui, QtCore, QtPrintSupport + + +class GraceExporter: + def __init__(self, kwargs: dict): + self.__agr = None + self.__opts = kwargs + + def export(self, outfile: str, mode: (int, str) = 'w'): + if mode == 'w': + self.__agr = GraceEditor() + else: + self.__agr = GraceEditor(outfile) + + if isinstance(mode, str): + new_g = self.__agr.new_graph() + + new_g.set_limits(x=self.__opts['limits'][0], y=self.__opts['limits'][1]) + new_g.set_log(x=self.__opts['log'][0], y=self.__opts['log'][1]) + + new_g.set_onoff('legend', self.__opts['legend']) + new_g.set_property(**{'title': '"' + convert(self.__opts['labels'][2], old="html", new="agr") + '"', + 'legend loctype': 'view', + 'legend': ', '.join(str(i) for i in new_g.world_to_view(self.__opts['legend_pos']))}) + + for i, ax in enumerate('xy'): + new_g.set_axis_property(ax, **{'label': f'"{convert(self.__opts["labels"][i], old="html", new="agr")}"', + 'tick major': self.__opts['ticks'][i][0], + 'tick minor ticks': self.__opts['ticks'][i][1],}) + new_g.set_axis_onoff(ax, 'tick major grid', self.__opts['grid']) + g_idx = new_g.idx + else: + g_idx = mode + + colors = self.__agr.colors + + new_colors = [] + for item in self.__opts['items']: + new_s = self.__agr.new_set(g_idx) + + sc = item['symbolcolor'] + c_num = -1 + for i, (_, rgb) in colors.items(): + if rgb == sc: + c_num = i + break + + if c_num == -1: + c_num = max(colors.keys()) + colors[c_num + 1] = (f'color{c_num + 1}', sc) + new_colors.append((c_num + 1, f'color{c_num + 1}', sc)) + + new_s.set_symbol(**{'symbol': item['symbol'].value, 'size': item['symbolsize'] / 10., 'color': c_num, + 'fill color': c_num, 'fill pattern': 1}) + new_s.set_onoff('errorbar', self.__opts['plots'][2]) + + lc = item['linecolor'] + c_num = -1 + for c_num, (_, rgb) in colors.items(): + if rgb == lc: + break + + if c_num == -1: + c_num = max(colors.keys()) + colors[c_num + 1] = () + new_colors.append((c_num, f'color{c_num + 1}', sc)) + + new_s.set_line(**{'color': c_num, 'linewidth': item['linewidth'], + 'linestyle': item['linestyle'].to_agr()}) + new_s.set_property(comment='"' + item['name'] + '"', legend='"' + item['name'] + '"') + + data = self.__agr.dataset(g_idx, new_s.idx) + if 'yerr' in item: + data.type = 'xydy' + data.data = c_[item['x'], item['y'], item['yerr']] + new_s.set_property(**{'errorbar color': c_num}) + else: + data.data = c_[item['x'], item['y']] + + for c in new_colors: + self.__agr.set_color(c[1], c[2], idx=c[0]) + + self.__agr.write(outfile) + + +class PDFPrintExporter: + def __init__(self, graphview): + self.graphic = graphview + + def export(self, outfile): + printer = QtPrintSupport.QPrinter() + + printer.setOutputFormat(printer.PdfFormat) + printer.setOutputFileName(outfile) + + printer.setPaperSize(QtCore.QSizeF(self.graphic.width(), self.graphic.height()), + printer.DevicePixel) + printer.setPageMargins(0, 0, 0, 0, printer.DevicePixel) + + painter = QtGui.QPainter(printer) + self.graphic.render(painter) + painter.end() + + +def pgitem_to_dic(item): + x, y = item.xData, item.yData + if (x is None) or (len(x) == 0): + return + + opts = item.opts + item_dic = { + 'x': x, 'y': y, + 'name': opts['name'], + 'symbolsize': opts['symbolSize'] + } + + if opts['symbol'] is None: + item_dic['symbol'] = SymbolStyle.No + item_dic['symbolcolor'] = Colors.Black + else: + item_dic['symbol'] = SymbolStyle.from_str(opts['symbol']) + item_dic['symbolcolor'] = Colors.from_rgb(*opts['symbolBrush'].color().getRgbF()[:3]) + + pen = opts['pen'] + + if pen is not None: + item_dic['linestyle'] = LineStyle(pen.style()) + item_dic['linecolor'] = Colors.from_rgb(*pen.color().getRgbF()[:3]) + item_dic['linewidth'] = pen.widthF() + else: + item_dic['linestyle'] = LineStyle.No + item_dic['linecolor'] = item_dic['symbolcolor'] + item_dic['linewidth'] = 0.0 + + return item_dic diff --git a/nmreval/gui_qt/io/fcbatchreader.py b/nmreval/gui_qt/io/fcbatchreader.py new file mode 100644 index 0000000..e94a750 --- /dev/null +++ b/nmreval/gui_qt/io/fcbatchreader.py @@ -0,0 +1,116 @@ +import pathlib + +from ...io.fcbatchreader import FCReader +from ..lib.utils import busy_cursor +from ..Qt import QtCore, QtWidgets, QtGui +from .._py.fcreader import Ui_FCEval_dialog + + +class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog): + data_read = QtCore.pyqtSignal(list, str) + + def __init__(self, path=None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + if path is None: + path = pathlib.Path().home() + self.path = path + + self.start_lineedit.setValidator(QtGui.QDoubleValidator()) + self.stop_lineedit.setValidator(QtGui.QDoubleValidator()) + + self.listWidget.installEventFilter(self) + + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent) -> bool: + # intercept key press in listwidget to allow deletion with Del + if evt.type() == QtCore.QEvent.KeyPress: + if evt.key() == QtCore.Qt.Key_Delete: + self.listWidget.takeItem(self.listWidget.currentRow()) + return True + + return super().eventFilter(src, evt) + + @QtCore.pyqtSlot(int, name='on_region_checkBox_stateChanged') + def use_region(self, state: int): + self.start_lineedit.setEnabled(state == QtCore.Qt.Checked) + self.stop_lineedit.setEnabled(state == QtCore.Qt.Checked) + + @QtCore.pyqtSlot(name='on_file_pushbutton_clicked') + @QtCore.pyqtSlot(name='on_dir_pushbutton_clicked') + def get_input(self): + if self.sender() == self.file_pushbutton: + infiles, _ = QtWidgets.QFileDialog.getOpenFileNames(caption='Select HDF files', + directory=str(self.path), + filter='HDF files (*.h5)') + if infiles: + self.listWidget.addItems(infiles) + self.label.setText(str(pathlib.Path(infiles[-1]).parent)) + else: + infiles = QtWidgets.QFileDialog.getExistingDirectory(caption='Select input directory', + directory=str(self.path), + options=QtWidgets.QFileDialog.ShowDirsOnly) + + if infiles: + self.listWidget.addItem(infiles) + self.label.setText(str(pathlib.Path(infiles).parent)) + + @QtCore.pyqtSlot(name='on_savebutton_clicked') + def save_path(self): + outfile = QtWidgets.QFileDialog.getExistingDirectory(self, caption='Select directory', + directory=self.label.text(), + options=QtWidgets.QFileDialog.ShowDirsOnly) + if outfile: + self.label.setText(outfile) + + def accept(self): + items = [self.listWidget.item(i).text() for i in range(self.listWidget.count())] + if items: + with busy_cursor(): + self.read(items) + + self.close() + + def read(self, items): + region = (None, None) + if self.region_box.isChecked(): + start = None + if self.start_lineedit.text(): + start = float(self.start_lineedit.text()) + + stop = None + if self.stop_lineedit.text(): + stop = float(self.stop_lineedit.text()) + region = (start, stop) + + fc_eval = FCReader(items) + try: + fc_eval.load_magnetization(region=region, overwrite=self.overwrite_cb.isChecked()) + except OSError as e: + QtWidgets.QMessageBox.warning(self, 'Mssing data', e.strerror) + self.statelabel.setText('') + return + + fc_eval.fit(kww=self.kww_checkbox.isChecked(), save_fits=True, save_fig=True) + + save_variables = [] + for name, cb in [('t1', self.t1_cb), ('beta', self.beta_cb), ('m0', self.m0_cb), ('off', self.off_cb)]: + if cb.isChecked(): + save_variables.append(name) + + ret_vals = [] + ret_vals.extend(fc_eval.get_parameter(path=self.label.text(), kind='temp', parameter=save_variables)) + + grp = '' + if self.graph_checkbox.isChecked(): + grp = self.graph_comboBox.currentData(QtCore.Qt.UserRole) + + self.data_read.emit(ret_vals, grp) + + +if __name__ == '__main__': + import sys + app = QtWidgets.QApplication([]) + qfc = QFCReader() + qfc.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/io/filedialog.py b/nmreval/gui_qt/io/filedialog.py new file mode 100644 index 0000000..30306b6 --- /dev/null +++ b/nmreval/gui_qt/io/filedialog.py @@ -0,0 +1,88 @@ +from ..Qt import QtWidgets, QtCore + + +class _FileDialog(QtWidgets.QFileDialog): + def __init__(self, directory=None, caption=None, filters='', parent=None): + super().__init__(parent=parent) + + self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True) + + self.setWindowTitle(caption) + self.setDirectory(str(directory)) + self.setNameFilters(filters.split(';;')) + + file_tree = self.findChild(QtWidgets.QTreeView, 'treeView') + file_tree.setSortingEnabled(True) + for i in range(file_tree.header().count()): + file_tree.header().setSectionResizeMode(i, 0) + file_tree.model().sort(0) + + file_list = self.findChild(QtWidgets.QListView, 'listView') + file_list.model().sort(0) + + line = QtWidgets.QFrame(self) + line.setFrameShape(line.HLine) + line.setFrameShadow(line.Sunken) + self.layout().addWidget(line, self.layout().rowCount(), 0, 1, self.layout().columnCount()) + + +class OpenFileDialog(_FileDialog): + def __init__(self, directory=None, caption=None, filters='', parent=None): + super().__init__(directory=directory, caption=caption, filters=filters, parent=parent) + + self.setFileMode(QtWidgets.QFileDialog.ExistingFiles) + + self.checkBox = QtWidgets.QCheckBox(self) + self.checkBox.setChecked(False) + self.checkBox.setText('Use existing graph') + self.checkBox.stateChanged.connect(self.checkbox_state) + self.layout().addWidget(self.checkBox) + + self.comboBox = QtWidgets.QComboBox(self) + self.comboBox.setEnabled(False) + self.layout().addWidget(self.comboBox) + + self.add_to_graph = None + + def set_graphs(self, graphs): + self.comboBox.blockSignals(True) + for gid, name in graphs: + self.comboBox.addItem(name, userData=gid) + self.comboBox.blockSignals(False) + + if not self.comboBox.count(): + self.comboBox.hide() + self.checkBox.hide() + + def checkbox_state(self, checked: QtCore.Qt.CheckState): + if checked == QtCore.Qt.Checked: + self.comboBox.setEnabled(True) + self.add_to_graph = self.comboBox.currentData() + else: + self.comboBox.setEnabled(False) + self.add_to_graph = None + + @QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged') + def graph_changed(self, idx: int): + self.add_to_graph = self.comboBox.itemData(idx) + + +class SaveDirectoryDialog(_FileDialog): + def __init__(self, directory=None, filters='', parent=None): + super().__init__(directory=directory, filters=filters, parent=parent) + + self.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False) + self.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + + self.label = QtWidgets.QLabel(self) + self.label.setTextFormat(QtCore.Qt.RichText) + self.label.setText('Use <label> as placeholder in filename. (e.g. t1_<label>.dat)') + self.layout().addWidget(self.label, self.layout().rowCount(), 0, 1, self.layout().columnCount()) + + self.checkBox = QtWidgets.QCheckBox(self) + self.checkBox.setChecked(True) + self.checkBox.setText('Replace spaces with underscore') + self.layout().addWidget(self.checkBox, self.layout().rowCount(), 0, 1, self.layout().columnCount()) + + self.setWindowTitle('Save') + self.setNameFilters(['All files (*.*)', 'Session file (*.nmr)', 'Text file (*.dat)', 'HDF file (*.h5)', 'Grace files (*.agr)']) diff --git a/nmreval/gui_qt/io/filereaders.py b/nmreval/gui_qt/io/filereaders.py new file mode 100755 index 0000000..27d54ec --- /dev/null +++ b/nmreval/gui_qt/io/filereaders.py @@ -0,0 +1,133 @@ +from pathlib import Path +import struct +from typing import List, Union + +from ..Qt import QtCore + +from .asciireader import QAsciiReader +from .hdfreader import QHdfViewer +from .bdsreader import QBDSReader +from .gracereader import QGraceReader +from .dscreader import QDSCReader +from .nmrreader import QNMRReader + + +class QFileReader(QtCore.QObject): + data_read = QtCore.pyqtSignal([list], [dict]) + + def __init__(self, manager=None): + QtCore.QObject.__init__(self) + + self.select = 'all' + self.data = [] + self.filenames = None + self.extensions = set() + + self.reader = {} + + for ext, reader in [ + ('txt', QAsciiReader), ('dsc', QDSCReader), ('agr', QGraceReader), + ('bds', QBDSReader), ('hdf', QHdfViewer), ('nmr', QNMRReader) + ]: + self.register(ext, reader) + + def __call__(self, files: Union[List[str], str]) -> List: + self.data = [] + if isinstance(files, str): + self.filenames = [files] + else: + self.filenames = files + + return self.readfiles(files) + + def readfiles(self, fname: Union[List[str], str]) -> List: + if not isinstance(fname, list): + fname = [fname] + + for f in fname: + f = Path(f) + dtype = self.guess_type(f) + if dtype in self.reader: + r = self.reader[dtype] + else: + raise ValueError(f'Unknown type for file {f}') + + if r(f) is not None: + # If QAsciiReader.skip = True it accepts automatically and returns None + r(f).exec() + + self.data_read.emit(self.data) + + try: + self.reader['txt'].skip = False + except KeyError: + pass + + return self.data + + def readtnt(self, fname): + """ Special treatment for tnt. If data is a single measurement, skip dialog """ + raise NotImplementedError + # tntreader = self.reader[2] + # tntreader(fname) + # if not tntreader.reader.onedimensional: + # tntreader.exec() + + @QtCore.pyqtSlot(list) + def _update_dic(self, other: list): + self.data.extend(other) + + def register(self, key, new_reader): + if key in self.reader: + return self.reader[key] + r = new_reader() + self.reader[key] = r + r.data_read.connect(self._update_dic) + self.extensions.update(r.file_ext) + + return r + + def guess_type(self, fname: Path) -> str: + ext = fname.suffix.lower() + + if ext in self.extensions: + if ext in ('.dat', '.txt'): + with fname.open('r', encoding='iso-8859-15') as fp: + line = fp.readline() + if line.strip().startswith('Filename: C:\\'): + return 'dsc' + + return 'txt' + + if ext in ('.h5', '.hdf', '.hdf5'): + return 'hdf' + + if ext == '.eps': + return 'bds' + + return ext[1:] # remove dot + + else: + with fname.open('rb') as fp: + s16 = fp.read(16) + + # copied from whichdb (Python 2.7) to look for magic value of dbhash + (magic,) = struct.unpack("=l", s16[-4:]) + if magic in (0x00061561, 0x61150600): + return 'nmr' + + else: + ret_types = ('nmr', 'bds', 'tnt', 'agr', 'dsc') + strings = (b'NMREVAL', b'NOVOCONTROL', b'TNT1.005', b'# Grace project', b'Filename:\tC:') + + (magic,) = struct.unpack('<16s', s16) + for dtype, startstring in zip(ret_types, strings): + if magic.startswith(startstring): + return dtype + + # nothing matched, text file is best guess + return 'txt' + + +if __name__ == '__main__': + pass diff --git a/nmreval/gui_qt/io/gracereader.py b/nmreval/gui_qt/io/gracereader.py new file mode 100644 index 0000000..7261571 --- /dev/null +++ b/nmreval/gui_qt/io/gracereader.py @@ -0,0 +1,110 @@ +from ...lib.lines import LineStyle +from ...lib.symbols import SymbolStyle +from ...data.points import Points +from ...io.graceeditor import GraceEditor + +from ..Qt import QtCore, QtWidgets, QtGui +from .._py.gracereader import Ui_Dialog + + +class QGraceReader(QtWidgets.QDialog, Ui_Dialog): + data_read = QtCore.pyqtSignal(list) + file_ext = ['.agr'] + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + self._reader = GraceEditor() + self.treeWidget.itemClicked.connect(self.show_property) + + self.treeWidget.installEventFilter(self) + + def __call__(self, fname, *args, **kwargs): + self.read(fname) + + return self + + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent): + if evt.type() == QtCore.QEvent.KeyPress: + if evt.key() == QtCore.Qt.Key_Space: + iterator = QtWidgets.QTreeWidgetItemIterator(self.treeWidget) + while iterator.value(): + item = iterator.value() + if item.parent() is not None and item.isSelected(): + item.setCheckState(0, + QtCore.Qt.Checked if item.checkState(0) == QtCore.Qt.Unchecked else + QtCore.Qt.Unchecked) + + iterator += 1 + + return True + + return super().eventFilter(src, evt) + + def read(self, fname): + self._reader.parse(fname) + + for graphs in self._reader.graphs: + item = QtWidgets.QTreeWidgetItem([f'Graph {graphs.idx} (Title "{graphs.get_property("title")}")']) + for gset in graphs.set: + item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {gset.get_property("legend")}, ' + f'shape: {self._reader.dataset(graphs.idx, gset.idx).shape})']) + item_2.setCheckState(0, QtCore.Qt.Checked) + item_2.setData(0, QtCore.Qt.UserRole, (graphs.idx, gset.idx)) + item.addChild(item_2) + + self.treeWidget.addTopLevelItem(item) + self.treeWidget.expandAll() + + @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int) + def show_property(self, item: QtWidgets.QTreeWidgetItem, col: int): + keys = item.data(col, QtCore.Qt.UserRole) + if keys is not None: + cols = self._reader.colors + + self.tableWidget.item(0, 1).setText(SymbolStyle(self._reader.get_property(*keys, 'symbol')).name) + sym_col = cols[self._reader.get_property(*keys, 'symbol fill color')][1] + self.tableWidget.item(1, 1).setBackground(QtGui.QBrush(QtGui.QColor(*sym_col))) + + self.tableWidget.item(2, 1).setText(LineStyle(self._reader.get_property(*keys, 'line linestyle')).name) + + line_col = cols[self._reader.get_property(*keys, 'line color')][1] + self.tableWidget.item(3, 1).setBackground(QtGui.QBrush(QtGui.QColor(*line_col))) + + def accept(self): + data = [] + iterator = QtWidgets.QTreeWidgetItemIterator(self.treeWidget, + QtWidgets.QTreeWidgetItemIterator.NoChildren | + QtWidgets.QTreeWidgetItemIterator.Checked) + + while iterator.value(): + item = iterator.value() + key = (item.data(0, QtCore.Qt.UserRole)) + s = self._reader.dataset(*key) + label = self._reader.get_property(*key, 'legend').replace('"', '') + # label = self._reader.graphs[key[0]].sets[key[1]]['legend'].replace('"', '') + sd = s.data + if s.type == 'xydy': + data.append(Points(x=sd[:, 0], y=sd[:, 1], y_err=sd[:, 2], name=label)) + else: + data.append(Points(x=sd[:, 0], y=sd[:, 1], name=label)) + + iterator += 1 + + self.data_read.emit(data) + + self.close() + + def close(self): + self._reader.clear() + super().close() + + +if __name__ == '__main__': + import sys + app = QtWidgets.QApplication(sys.argv) + + reader = QGraceReader() + reader.read('/autohome/dominik/nmreval/testdata/02_relax_2.agr') + reader.show() + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/io/hdfreader.py b/nmreval/gui_qt/io/hdfreader.py new file mode 100644 index 0000000..e0c673c --- /dev/null +++ b/nmreval/gui_qt/io/hdfreader.py @@ -0,0 +1,196 @@ +from ...io.hdfreader import HdfReader + +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.hdftree import Ui_Hdf_Dialog + + +class QHdfViewer(QtWidgets.QDialog, Ui_Hdf_Dialog): + data_read = QtCore.pyqtSignal(list) + file_ext = ['.h5', '.hdf', '.hdf5'] + + def __init__(self, fname: str = None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.treewidget = HDFTreeWidget(self) + self.verticalLayout_3.addWidget(self.treewidget) + + self.variables = VarTable(self) + self.widget_2.addWidget(self.variables) + self.widget_2.setText('Variables') + + self._reader = HdfReader() + + if fname is not None: + self.__call__(fname) + + def __call__(self, fname, *args, **kwargs): + self.treewidget.clear() + for cb in [self.comboBox, self.comboBox_2]: + cb.clear() + cb.addItem('Automatic for the people') + cb.show() + + self._reader(filename=fname) + self._fill_boxes() + self._populate_tree(self._reader, self.treewidget.invisibleRootItem()) + + if self.comboBox.count() == 1: + self.comboBox.hide() + self.comboBox_2.hide() + + self.treewidget.expandToDepth(0) + self.treewidget.resizeColumnToContents(1) + + return self + + def _populate_tree(self, node, item): + self.treewidget.blockSignals(True) + + # add varied parameter to comboboxes + for key, value in node.title_parameter[1].items(): + if isinstance(value, list): + idx = self.comboBox.findText(key) + if idx == -1: + self.comboBox.addItem(key) + self.comboBox_2.addItem(key) + + if node.children is not None: + for child in node.children.values(): + label_item = QtWidgets.QTreeWidgetItem([child.name]) + label_item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable) + label_item.setCheckState(0, QtCore.Qt.Unchecked) + if child.type == 'signal': + label_item.setBackground(0, QtGui.QBrush(QtGui.QColor('cyan'))) + label_item.setCheckState(1, QtCore.Qt.Unchecked) + elif child.type == 'points': + label_item.setBackground(0, QtGui.QBrush(QtGui.QColor('red'))) + item.addChild(label_item) + self._populate_tree(child, label_item) + + self.treewidget.blockSignals(False) + + def _fill_boxes(self): + for k, v in self._reader.parameter.items(): + if isinstance(v, list): + idx = self.comboBox.findText(k) + if idx == -1: + self.comboBox.addItem(k) + self.comboBox_2.addItem(k) + + self.variables.populate(self._reader.parameter) + + @QtCore.pyqtSlot() + def get_selected(self): + + if self.comboBox.currentIndex() == 0: + value = None + else: + value = self.comboBox.currentText() + + if self.comboBox_2.currentIndex() == 0: + group = None + else: + group = self.comboBox_2.currentText() + + iterator = QtWidgets.QTreeWidgetItemIterator(self.treewidget, QtWidgets.QTreeWidgetItemIterator.NoChildren) + selected = [] + while iterator.value(): + item = iterator.value() + iterator += 1 + if item.checkState(0) == QtCore.Qt.Checked: + path = item.text(0) + parent = item.parent() + while parent is not None: + path = parent.text(0) + '/' + path + parent = parent.parent() + + if item.checkState(1) == QtCore.Qt.Checked: + selected.extend(self._reader.get_selected(path, flag='spectrum', value=value, group=group)) + else: + selected.extend(self._reader.get_selected(path, value=value, group=group)) + + self.data_read.emit(selected) + + def accept(self): + self.get_selected() + self.close() + + +class VarTable(QtWidgets.QTableWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setColumnCount(2) + self.setHorizontalHeaderLabels(['Key', 'Value']) + self.horizontalHeader().setStretchLastSection(True) + self.verticalHeader().setVisible(False) + self.horizontalHeader().setVisible(True) + + def populate(self, vars: dict): + self.setHorizontalHeaderLabels(['Key', 'Value']) + self.setRowCount(len(vars)) + for i, (k, v) in enumerate(vars.items()): + key_item = QtWidgets.QTableWidgetItem(k) + key_item.setFlags(QtCore.Qt.NoItemFlags) + key_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0))) + if isinstance(v, list): + val = f'<{len(v)} values>' + if len(v) < 40: + tip = '\n'.join(str(p) for p in v) + else: + tip = '\n'.join(str(p) for p in v[:40]) + tip += '\n...' + else: + val = str(v) + tip = val + value_item = QtWidgets.QTableWidgetItem(val) + value_item.setFlags(QtCore.Qt.NoItemFlags) + value_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0))) + value_item.setToolTip(tip) + self.setItem(i, 0, key_item) + self.setItem(i, 1, value_item) + + +class HDFTreeWidget(QtWidgets.QTreeWidget): + def __init__(self, parent): + super().__init__(parent=parent) + + self.setHeaderLabels(['Data', 'Spectrum?']) + self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) + self.header().setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed) + self.header().setStretchLastSection(False) + self.header().setCascadingSectionResizes(True) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems) + + self.itemChanged.connect(self.change_item) + + def keyPressEvent(self, evt: QtGui.QKeyEvent): + if evt.key() == QtCore.Qt.Key_Space: + for idx in self.selectedIndexes(): + item = self.itemFromIndex(idx) + col = idx.column() + cs = item.checkState(col) + item.setCheckState(col, QtCore.Qt.Unchecked if cs == QtCore.Qt.Checked else QtCore.Qt.Checked) + else: + super().keyPressEvent(evt) + + @staticmethod + def change_item(item: QtWidgets.QTreeWidgetItem, column: int): + state = item.checkState(column) + for i in range(item.childCount()): + child = item.child(i) + child.setCheckState(column, state) + + +if __name__ == '__main__': + import sys + + app = QtWidgets.QApplication([]) + # w = QHdfViewer('/autohome/dominik/nmreval/testdata/fc_test/abc.h5') + w = QHdfViewer('/autohome/dominik/nmreval/testdata/L8_313K_17O_GG_KG_2020-12-02_1535.h5') + + w.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/io/nmrreader.py b/nmreval/gui_qt/io/nmrreader.py new file mode 100644 index 0000000..6e2a374 --- /dev/null +++ b/nmreval/gui_qt/io/nmrreader.py @@ -0,0 +1,34 @@ +from struct import unpack + +from ...io.nmrreader import NMRReader + +from ..Qt import QtCore + + +class QNMRReader(QtCore.QObject): + data_read = QtCore.pyqtSignal(list) + file_ext = ['.nmr'] + + def __init__(self): + super().__init__() + self._reader = NMRReader() + + def __call__(self, fname): + with fname.open('rb') as fp: + s16 = fp.read(16) + + # copied from whichdb (Python 2.7) to look for magic value of dbhash + (magic,) = unpack("=l", s16[-4:]) + if magic in (0x00061561, 0x61150600): + self._reader(fname, '-1') + + else: + (magic,) = unpack('<16s', s16) + if magic.startswith(b'NMREVAL'): + self._reader(fname, str(magic[7:].rstrip(b'\x00'), 'utf8')) + + return self + + def exec(self): + data = self._reader.make_data() + self.data_read.emit([data]) diff --git a/nmreval/gui_qt/io/save_fitparameter.py b/nmreval/gui_qt/io/save_fitparameter.py new file mode 100644 index 0000000..4c7b63a --- /dev/null +++ b/nmreval/gui_qt/io/save_fitparameter.py @@ -0,0 +1,78 @@ +from ..Qt import QtWidgets, QtCore, QtGui +from .._py.save_fit_parameter import Ui_fitparameter_save_dialog + + +class QSaveFit(QtWidgets.QDialog, Ui_fitparameter_save_dialog): + def __init__(self, path: str = None, parent=None): + super().__init__(parent=parent) + + self.pnames = None + self.outpath = path + + self.setupUi(self) + + self.tableWidget.cellDoubleClicked.connect(self.clicketyclick) + self.tableWidget.setColumnCount(2) + + self.save_path_line.textEdited.connect(lambda: self.change_path(self.save_path_line.text())) + self.save_path_button.clicked.connect(self.set_save_path) + + self.header_edit.hide() + self.header_checkBox.stateChanged.connect(lambda state: self.header_edit.setVisible(state)) + + self.missing_value_line.setValidator(QtGui.QDoubleValidator()) + + def set_parameter(self, fits: dict): + for model_name, parameter in fits.items(): + for p in parameter: + item = QtWidgets.QTableWidgetItem(p) + item2 = QtWidgets.QTableWidgetItem(model_name) + item2.setToolTip(model_name) + row = self.tableWidget.rowCount() + self.tableWidget.setRowCount(row+1) + self.tableWidget.setItem(row, 0, item) + self.tableWidget.setItem(row, 1, item2) + + self.tableWidget.resizeColumnsToContents() + + @QtCore.pyqtSlot(int, int) + def clicketyclick(self, row: int, _): + if self.col_line.text(): + self.col_line.setText(self.col_line.text() + '; ' + self.tableWidget.item(row, 0).text()) + else: + self.col_line.setText(self.tableWidget.item(row, 0).text()) + + def set_save_path(self): + fname, _ = QtWidgets.QFileDialog.getSaveFileName(options=QtWidgets.QFileDialog.DontConfirmOverwrite, + directory=self.outpath) + + if fname: + self.outpath = fname + self.save_path_line.setText(fname) + + def add_header(self, idx: int): + self.textEdit.setVisible(idx != 0) + + @QtCore.pyqtSlot(name='on_usage_button_clicked') + def show_usage(self): + _ = QtWidgets.QMessageBox.information(self, 'Usage', + 'Separate each column by semicolon.\n' + 'Use p_err to save error of parameter p.\n' + 'If a column shall contain different parameters use {p1/p2}.\n' + 'KWW mean is calculated by mean(τ,β), KWW peak by peak(τ,β).') + + def accept(self): + super().accept() + + +if __name__ == '__main__': + import sys + + app = QtWidgets.QApplication([]) + + dialog = QSaveFit() + dialog.set_parameter({'fit1': ['p1', 'p2'], 'fit2': ['p2', 'p3']}) + + dialog.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/io/tntreader.py b/nmreval/gui_qt/io/tntreader.py new file mode 100644 index 0000000..cd97c76 --- /dev/null +++ b/nmreval/gui_qt/io/tntreader.py @@ -0,0 +1,109 @@ +from ...io.tntreader import TNTReader + +from ..Qt import QtCore, QtWidgets +from .._py.tntdialog import Ui_tntdialog + + +class QTNTReader(QtWidgets.QDialog, Ui_tntdialog): + data_read = QtCore.pyqtSignal(dict) + file_ext = ['.tnt'] + + def __init__(self, fname=None, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self.reader = TNTReader(fname) + + self.frame.hide() + + if fname is not None: + if self.reader.onedimensional: + self.export() + else: + self.set_gui() + + def __call__(self, fname, *args, **kwargs): + self.reader(fname) + for s in [self.widget, self.widget_2, self.widget_3]: + s.__call__() + + self.frame.hide() + self.frame_2.show() + self.unknown_delay_combobox.clear() + + if self.reader.onedimensional: + self.export() + else: + self.set_gui() + + return self + + def set_gui(self): + self.label_3.setText(' x '.join(map(str, self.reader.shape))) + for i, w in enumerate([self.widget, self.widget_2, self.widget_3], start=1): + if self.reader.shape[i] == 1: + w.hide() + else: + w.label.setText('Dimension {}'.format(i+1)) + w.dim = i + w.add_items([x[0] for x in self.reader.delays.items() if len(x[1]) == self.reader.shape[i]]) + w.get_data.connect(self.show_delays) + w.newDelay.connect(self.calc_delays) + for x in self.reader.delays.items(): + if len(x[1]) in self.reader.shape[1:]: + continue + else: + self.unknown_delay_combobox.addItem(x[0]) + if self.unknown_delay_combobox.count() == 0: + self.frame_2.hide() + + @QtCore.pyqtSlot(str) + def show_delays(self, label): + vals = self.reader.delays[str(label)] + self.sender().vals = '\n'.join(map(str, vals)) + + def calc_delays(self): + self.frame.show() + self._caller = self.sender() + + @QtCore.pyqtSlot(name='on_pushButton_2_clicked') + def cancel_delay(self): + self.frame.hide() + + @QtCore.pyqtSlot(name='on_pushButton_clicked') + def add_delay(self): + try: + s = float(self.start_lineedit.text()) + except ValueError: + return + try: + e = float(self.end_lineedit.text()) + except ValueError: + return + d = [] + for w in [self.widget, self.widget_2, self.widget_3]: + if w.isVisible(): + d.append(w.comboBox.currentText()) + self.reader.add_delay(s, e, self._caller.dim, self.lineEdit.text(), + staggered=self.checkBox_2.isChecked(), stag_steps=int(self.spinBox.value()), + log=self.checkBox.isChecked()) + self._caller.add_items(self.lineEdit.text()) + self.frame.hide() + + def export(self): + d = [] + for w in [self.widget, self.widget_2, self.widget_3]: + if w.isVisible(): + d.append(w.comboBox.currentText()) + else: + d.append(None) + + ret_dic = self.reader.export(d) + + self.data_read.emit(ret_dic) + self.close() + + return ret_dic + + def accept(self): + self.export() diff --git a/nmreval/gui_qt/lib/__init__.py b/nmreval/gui_qt/lib/__init__.py new file mode 100644 index 0000000..f273183 --- /dev/null +++ b/nmreval/gui_qt/lib/__init__.py @@ -0,0 +1,76 @@ +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 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 + + dirname = 'resources.icons.%s_light' % icon_type + 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 diff --git a/nmreval/gui_qt/lib/codeeditor.py b/nmreval/gui_qt/lib/codeeditor.py new file mode 100644 index 0000000..245a9e1 --- /dev/null +++ b/nmreval/gui_qt/lib/codeeditor.py @@ -0,0 +1,262 @@ +# CodeEditor based on QT example, Python syntax highlighter found on Python site + + +from ..Qt import QtGui, QtCore, QtWidgets + + +def _make_textformats(color, style=''): + """Return a QTextCharFormat with the given attributes. + """ + _color = QtGui.QColor() + _color.setNamedColor(color) + + _format = QtGui.QTextCharFormat() + _format.setForeground(_color) + if 'bold' in style: + _format.setFontWeight(QtGui.QFont.Bold) + if 'italic' in style: + _format.setFontItalic(True) + + return _format + + +# Syntax styles that can be shared by all languages +STYLES = { + 'keyword': _make_textformats('blue'), + 'operator': _make_textformats('black'), + 'brace': _make_textformats('black'), + 'defclass': _make_textformats('black', 'bold'), + 'string': _make_textformats('darkGreen'), + 'comment': _make_textformats('gray', 'italic'), + 'self': _make_textformats('brown', 'italic'), + 'property': _make_textformats('brown'), + 'numbers': _make_textformats('darkRed'), +} + + +class PythonHighlighter(QtGui.QSyntaxHighlighter): + """ + Syntax highlighter for the Python language. + """ + # Python keywords + keywords = [ + 'and', 'assert', 'break', 'class', 'continue', 'def', + 'del', 'elif', 'else', 'except', 'exec', 'finally', + 'for', 'from', 'global', 'if', 'import', 'in', + 'is', 'lambda', 'not', 'or', 'pass', 'print', + 'raise', 'return', 'try', 'while', 'yield', + 'None', 'True', 'False', 'object' + ] + + def __init__(self, document): + super().__init__(document) + + # Multi-line strings (expression, flag, style) + # FIXME: The triple-quotes in these two lines will mess up the + # syntax highlighting from this point onward + self.tri_single = (QtCore.QRegExp("r'{3}"), 1, STYLES['string']) + self.tri_double = (QtCore.QRegExp('r?"{3}'), 2, STYLES['string']) + + rules = [] + + # Keyword, operator, and brace rules + rules += [(rf'\b{w}\b', 0, STYLES['keyword']) for w in PythonHighlighter.keywords] + + # Other rules + rules += [ + # 'self' + (r'\bself\b', 0, STYLES['self']), + + # 'def' followed by an identifier + (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']), + # 'class' followed by an identifier + (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']), + # @ followed by a word + (r'\s*@(\w+)\s*', 0, STYLES['property']), + + # Numeric literals + (r'\b[+-]?\d+[lL]?\b', 0, STYLES['numbers']), + (r'\b[+-]?0[xX][\dA-Fa-f]+[lL]?\b', 0, STYLES['numbers']), + (r'\b[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b', 0, STYLES['numbers']), + + + # Double-quoted string, possibly containing escape sequences + (r'[rf]?"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']), + # Single-quoted string, possibly containing escape sequences + (r"[rf]?'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']), + + # From '#' until a newline + (r'#[^\n]*', 0, STYLES['comment']), + ] + + # Build a QRegExp for each pattern + self.rules = [(QtCore.QRegExp(pat), index, fmt) for (pat, index, fmt) in rules] + + def highlightBlock(self, text): + """ + Apply syntax highlighting to the given block of text. + """ + # Do other syntax formatting + for expression, nth, rule in self.rules: + index = expression.indexIn(text, 0) + + while index >= 0: + # We actually want the index of the nth match + index = expression.pos(nth) + length = len(expression.cap(nth)) + self.setFormat(index, length, rule) + index = expression.indexIn(text, index + length) + + self.setCurrentBlockState(0) + + # Do multi-line strings + in_multiline = self.match_multiline(text, *self.tri_single) + if not in_multiline: + in_multiline = self.match_multiline(text, *self.tri_double) + + def match_multiline(self, text, delimiter, in_state, style): + """ + Highlighting of multi-line strings. ``delimiter`` should be a + ``QRegExp`` for triple-single-quotes or triple-double-quotes, and + ``in_state`` should be a unique integer to represent the corresponding + state changes when inside those strings. Returns True if we're still + inside a multi-line string when this function is finished. + """ + # If inside triple-single quotes, start at 0 + if self.previousBlockState() == in_state: + start = 0 + add = 0 + # Otherwise, look for the delimiter on this line + else: + start = delimiter.indexIn(text) + # Move past this match + add = delimiter.matchedLength() + + # As long as there's a delimiter match on this line... + while start >= 0: + # Look for the ending delimiter + end = delimiter.indexIn(text, start + add) + # Ending delimiter on this line? + if end >= add: + length = end - start + add + delimiter.matchedLength() + self.setCurrentBlockState(0) + # No; multi-line string + else: + self.setCurrentBlockState(in_state) + try: + length = text.length() - start + add + except AttributeError: + length = len(text) - start + add + # Apply formatting + self.setFormat(start, length, style) + # Look for the next match + start = delimiter.indexIn(text, start + length) + + # Return True if still inside a multi-line string, False otherwise + if self.currentBlockState() == in_state: + return True + else: + return False + + +class LineNumbers(QtWidgets.QWidget): + def __init__(self, editor): + super().__init__(editor) + self.editor = editor + + def sizeHint(self): + return QtCore.QSize(self.editor.width_linenumber, 0) + + def paintEvent(self, event): + self.editor.paintevent_linenumber(event) + + +class CodeEditor(QtWidgets.QPlainTextEdit): + # more or less a direct translation of the Qt example + def __init__(self, parent): + super().__init__(parent) + + self.current_linenumber = LineNumbers(self) + + self.blockCountChanged.connect(self.update_width_linenumber) + self.updateRequest.connect(self.update_current_area) + self.cursorPositionChanged.connect(self.highlight_current_line) + self.update_width_linenumber(0) + + self.highlight = PythonHighlighter(self.document()) + + def keyPressEvent(self, evt): + if evt.key() == QtCore.Qt.Key_Tab: + # use spaces instead of tab + self.insertPlainText(' '*4) + elif evt.key() == QtCore.Qt.Key_Insert: + self.setOverwriteMode(not self.overwriteMode()) + else: + super().keyPressEvent(evt) + + @property + def width_linenumber(self): + digits = 1 + count = max(1, self.blockCount()) + while count >= 10: + count /= 10 + digits += 1 + space = 6 + self.fontMetrics().width('9') * digits + + return space + + def update_width_linenumber(self, _): + self.setViewportMargins(self.width_linenumber, 0, 0, 0) + + def update_current_area(self, rect, dy): + if dy: + self.current_linenumber.scroll(0, dy) + else: + self.current_linenumber.update(0, rect.y(), self.current_linenumber.width(), rect.height()) + if rect.contains(self.viewport().rect()): + self.update_width_linenumber(0) + + def resizeEvent(self, evt): + super().resizeEvent(evt) + + cr = self.contentsRect() + self.current_linenumber.setGeometry(QtCore.QRect(cr.left(), cr.top(), self.width_linenumber, cr.height())) + + def paintevent_linenumber(self, evt): + painter = QtGui.QPainter(self.current_linenumber) + painter.fillRect(evt.rect(), QtCore.Qt.lightGray) + + block = self.firstVisibleBlock() + block_number = block.blockNumber() + top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() + bottom = top + self.blockBoundingRect(block).height() + + # Just to make sure I use the right font + height = self.fontMetrics().height() + while block.isValid() and (top <= evt.rect().bottom()): + if block.isVisible() and (bottom >= evt.rect().top()): + number = str(block_number + 1) + painter.setPen(QtCore.Qt.black) + painter.drawText(0, top, self.current_linenumber.width() - 3, height, + QtCore.Qt.AlignRight, number) + + block = block.next() + top = bottom + bottom = top + self.blockBoundingRect(block).height() + block_number += 1 + + def highlight_current_line(self): + extra_selections = [] + + if not self.isReadOnly(): + selection = QtWidgets.QTextEdit.ExtraSelection() + + line_color = QtGui.QColor(QtCore.Qt.yellow).lighter(180) + + selection.format.setBackground(line_color) + selection.format.setProperty(QtGui.QTextFormat.FullWidthSelection, True) + selection.cursor = self.textCursor() + selection.cursor.clearSelection() + extra_selections.append(selection) + + self.setExtraSelections(extra_selections) diff --git a/nmreval/gui_qt/lib/color_dialog.py b/nmreval/gui_qt/lib/color_dialog.py new file mode 100644 index 0000000..0c51f64 --- /dev/null +++ b/nmreval/gui_qt/lib/color_dialog.py @@ -0,0 +1,89 @@ +from nmreval.configs import config_paths +from nmreval.gui_qt.Qt import QtWidgets, QtCore, QtGui +from nmreval.gui_qt._py.color_palette import Ui_Dialog +from nmreval.lib.colors import Colors, get_palettes + + +class PaletteDialog(QtWidgets.QDialog, Ui_Dialog): + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self._palettes = get_palettes() + self.palette_combobox.addItems(self._palettes.keys()) + + self.colorlist.installEventFilter(self) + + def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent) -> bool: + if evt.type() == evt.KeyPress: + if evt.key() == QtCore.Qt.Key_Delete: + self.colorlist.takeItem(self.colorlist.currentRow()) + + return True + + return super().eventFilter(src, evt) + + @QtCore.pyqtSlot(name='on_add_palette_button_pressed') + @QtCore.pyqtSlot(name='on_append_palette_button_pressed') + def set_palette(self, clear=True): + if self.sender() == self.add_palette_button or clear: + self.colorlist.clear() + + for color in self._palettes[self.palette_combobox.currentText()]: + item = QtWidgets.QListWidgetItem(color.name) + item.setData(QtCore.Qt.DecorationRole, QtGui.QColor.fromRgbF(*color.rgb(normed=True))) + self.colorlist.addItem(item) + + @QtCore.pyqtSlot(name='on_add_color_button_pressed') + def add_color(self): + color = self.color_combobox.value + item = QtWidgets.QListWidgetItem(color.name) + item.setData(QtCore.Qt.DecorationRole, QtGui.QColor.fromRgbF(*color.rgb(normed=True))) + self.colorlist.addItem(item) + + @QtCore.pyqtSlot(name='on_save_button_pressed') + def save_new_palette(self): + if not self.colorlist.count(): + _ = QtWidgets.QMessageBox.warning(self, 'Error', 'No colors to save.') + return + + if not self.new_name_edit.text(): + _ = QtWidgets.QMessageBox.warning(self, 'Error', 'New colors have no name.') + return + + color_name = self.new_name_edit.text() + if color_name in self._palettes: + _ = QtWidgets.QMessageBox.warning(self, 'Error', 'Name already used.') + return + + color_file = config_paths() / 'colorschemes.cfg' + + new_palette = [] + with color_file.open('a') as f: + f.write('#-- %s\n' % color_name) + for i in range(self.colorlist.count()): + item = self.colorlist.item(i) + r, g, b = item.data(QtCore.Qt.DecorationRole).getRgbF()[:3] + new_palette.append(Colors.from_rgb(r, g, b, normed=True)) + f.write('{r*255:.1f}, {g*255:.1f}, {b*255:.1f}\n') + f.write('%.1f, %.1f}, %.1f}\n' % (r*255, g*255, b*255)) + f.write('\n') + + self._palettes[color_name] = new_palette + self.palette_combobox.addItem(color_name) + + def load_palette(self, name: str): + idx = self.palette_combobox.findText(name) + if idx != -1: + self.palette_combobox.setCurrentIndex(idx) + self.set_palette(clear=True) + + +if __name__ == '__main__': + import sys + app = QtWidgets.QApplication([]) + d = PaletteDialog() + d.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/lib/configurations.py b/nmreval/gui_qt/lib/configurations.py new file mode 100644 index 0000000..0bc114f --- /dev/null +++ b/nmreval/gui_qt/lib/configurations.py @@ -0,0 +1,156 @@ +import os + +from ...configs import * +from ...io.graceeditor import GraceEditor + +from ..Qt import QtWidgets, QtGui +from .._py.agroptiondialog import Ui_Dialog + + +class QGraceSettings(QtWidgets.QDialog, Ui_Dialog): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + self._default_path = config_paths().joinpath('Default.agr') + self._default = GraceEditor(self._default_path) + + self.setup_ui() + + def setup_ui(self): + page = self._default.size + self.widthDoubleSpinBox.setValue(page[0]) + self.heightDoubleSpinBox.setValue(page[1]) + + view = self._default.get_property(0, 'view') + self.leftMarginDoubleSpinBox.setValue(self._default.convert(view[0], direction='abs')) + self.bottomMarginDoubleSpinBox.setValue(self._default.convert(view[1], direction='abs')) + self.rightMarginDoubleSpinBox.setValue(page[0]-self._default.convert(view[2], direction='abs')) + self.topMarginDoubleSpinBox.setValue(page[1]-self._default.convert(view[3], direction='abs')) + + +class GraceMsgBox(QtWidgets.QDialog): + def __init__(self, fname, parent=None): + super(GraceMsgBox, self).__init__(parent=parent) + + agr = GraceEditor() + agr.parse(fname) + + layout = QtWidgets.QGridLayout() + layout.setContentsMargins(13, 13, 13, 13) + self.setLayout(layout) + + label = QtWidgets.QLabel('%s already exists. Select one of the options or cancel:' % os.path.split(fname)[1]) + layout.addWidget(label, 1, 1, 1, 2) + + self.button_grp = QtWidgets.QButtonGroup(self) + + self.overwrite_radiobutton = QtWidgets.QRadioButton('Overwrite file', parent=self) + self.overwrite_radiobutton.setChecked(True) + self.button_grp.addButton(self.overwrite_radiobutton, id=0) + layout.addWidget(self.overwrite_radiobutton, 2, 1, 1, 2) + + self.new_graph_button = QtWidgets.QRadioButton('Create new graph', parent=self) + self.button_grp.addButton(self.new_graph_button, id=1) + layout.addWidget(self.new_graph_button, 3, 1, 1, 2) + + self.addgraph_button = QtWidgets.QRadioButton('Add sets to graph', parent=self) + self.button_grp.addButton(self.addgraph_button, id=3) + layout.addWidget(self.addgraph_button, 4, 1, 1, 1) + + self.graph_combobox = QtWidgets.QComboBox(self) + self.graph_combobox.addItems(['G' + str(g.idx) for g in agr.graphs]) + layout.addWidget(self.graph_combobox, 4, 2, 1, 1) + + self.buttonbox = QtWidgets.QDialogButtonBox(self) + self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) + layout.addWidget(self.buttonbox, 5, 1, 1, 2) + + self.buttonbox.rejected.connect(self.reject) + self.buttonbox.accepted.connect(self.accept) + + def accept(self) -> None: + super().accept() + self.setResult(self.button_grp.checkedId()) + if self.button_grp.checkedId() == 3: + self.setResult(int(self.graph_combobox.currentText()[1:]) + 2) + + def reject(self) -> None: + super().reject() + self.setResult(-1) + + +class GeneralConfiguration(QtWidgets.QDialog): + def __init__(self, parent=None): + super().__init__(parent=parent) + + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(3, 3, 3, 3) + + intro = QtWidgets.QLabel('Changes become active after restart.') + layout.addWidget(intro) + + parser = read_configuration() + for sec in parser.sections(): + group = QtWidgets.QGroupBox(sec, self) + + layout2 = QtWidgets.QGridLayout() + layout2.setContentsMargins(3, 3, 3, 3) + row = 0 + for key, value in parser.items(sec): + label = QtWidgets.QLabel(key.capitalize(), self) + layout2.addWidget(label, row, 0) + if (sec, key) in allowed_values: + edit = QtWidgets.QComboBox(self) + edit.addItems(allowed_values[(sec, key)]) + edit.setCurrentIndex(edit.findText(value)) + else: + edit = QtWidgets.QLineEdit(self) + edit.setText(value) + try: + _ = float(value) + edit.setValidator(QtGui.QDoubleValidator()) + except ValueError: + pass + + layout2.addWidget(edit, row, 1) + row += 1 + + group.setLayout(layout2) + + layout.addWidget(group) + + self.buttonbox = QtWidgets.QDialogButtonBox(self) + self.buttonbox.setStandardButtons(self.buttonbox.Ok|self.buttonbox.Cancel) + self.buttonbox.rejected.connect(self.close) + self.buttonbox.accepted.connect(self.accept) + layout.addWidget(self.buttonbox) + + self.setLayout(layout) + + def accept(self): + options = {} + for section in self.findChildren(QtWidgets.QGroupBox): + args = {} + + layout = section.layout() + for row in range(layout.rowCount()): + key = layout.itemAtPosition(row, 0).widget().text() + + value_widget = layout.itemAtPosition(row, 1).widget() + if isinstance(value_widget, QtWidgets.QComboBox): + value = value_widget.currentText() + elif isinstance(value_widget, QtWidgets.QLineEdit): + value = value_widget.text() + elif isinstance(value_widget, QtWidgets.QDoubleSpinBox): + value = value_widget.text() + else: + raise TypeError('Config key %s has unknown type %s' % (key, repr(value_widget))) + + args[key] = value + + options[section.title()] = args + + write_configuration(options) + + super().accept() diff --git a/nmreval/gui_qt/lib/decorators.py b/nmreval/gui_qt/lib/decorators.py new file mode 100644 index 0000000..9f40149 --- /dev/null +++ b/nmreval/gui_qt/lib/decorators.py @@ -0,0 +1,55 @@ +from functools import wraps + +from ..Qt import QtWidgets + + +def update_indexes(func): + + @wraps(func) + def wrapped(self, *args, **kwargs): + ret_val = func(self, *args, **kwargs) + self.blockSignals(True) + + iterator = QtWidgets.QTreeWidgetItemIterator(self) + i = j = 0 + while iterator.value(): + item = iterator.value() + if item is not None: + if item.parent() is None: + item.setText(1, 'g[{}]'.format(i)) + i += 1 + j = 0 + else: + item.setText(1, '.s[{}]'.format(j)) + j += 1 + iterator += 1 + + self.blockSignals(False) + + return ret_val + + return wrapped + + +def plot_update(func): + + @wraps(func) + def wrapped(self, *args, **kwargs): + ret_val = func(self, *args, **kwargs) + m = self._data.mask + _x = self._data.x + _y = self._data.y + + self.plot_real.setData(x=_x[m], y=_y.real[m], name=self._data.name) + if self.plot_imag is not None: + self.plot_imag.setData(x=_x[m], y=_y.imag[m], name=self._data.name) + + if self.plot_error is not None: + _y_err = self._data.y_err + self.plot_error.setData(x=_x[m], y=_y.real[m], top=_y_err[m], bottom=_y_err[m]) + + self.dataChanged.emit(self.id) + + return ret_val + + return wrapped diff --git a/nmreval/gui_qt/lib/delegates.py b/nmreval/gui_qt/lib/delegates.py new file mode 100644 index 0000000..50f79eb --- /dev/null +++ b/nmreval/gui_qt/lib/delegates.py @@ -0,0 +1,235 @@ +import re + +from ..Qt import QtWidgets, QtGui, QtCore + +from ...lib.colors import BaseColor, Colors +from ...lib.lines import LineStyle +from ...lib.symbols import SymbolStyle, make_symbol_pixmap + + +class PropertyDelegate(QtWidgets.QStyledItemDelegate): + def paint(self, painter: QtGui.QPainter, options: QtWidgets.QStyleOptionViewItem, idx: QtCore.QModelIndex): + r = idx.data(QtCore.Qt.DisplayRole) + if r is not None: + if isinstance(r, BaseColor): + painter.save() + c = QtGui.QColor(*r.value) + rect = options.rect + painter.fillRect(rect, QtGui.QBrush(c)) + painter.restore() + + elif isinstance(r, LineStyle): + painter.save() + pen = QtGui.QPen() + pal = QtGui.QGuiApplication.palette() + pen.setColor(pal.color(QtGui.QPalette.Text)) + pen.setWidth(2) + pen.setStyle(r.value) + pen.setCapStyle(QtCore.Qt.RoundCap) + painter.setPen(pen) + + rect = options.rect + rect.adjust(5, 0, -5, 0) + mid = (rect.bottom()+rect.top()) / 2 + painter.drawLine(rect.left(), mid, rect.right(), mid) + painter.restore() + + elif isinstance(r, SymbolStyle): + painter.save() + pen = QtGui.QPen() + pal = QtGui.QGuiApplication.palette() + pen.setColor(pal.color(QtGui.QPalette.Text)) + painter.setPen(pen) + + pm = make_symbol_pixmap(r) + painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, (options.rect.height()-pm.height())/2), pm) + + style = QtWidgets.QApplication.style() + text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None) + text_rect.adjust(5+pm.width(), 0, 0, 0) + painter.drawText(text_rect, options.displayAlignment, r.name) + + painter.restore() + + else: + super().paint(painter, options, idx) + + def createEditor(self, parent: QtWidgets.QWidget, + options: QtWidgets.QStyleOptionViewItem, idx: QtCore.QModelIndex) -> QtWidgets.QWidget: + data = idx.data() + if isinstance(data, BaseColor): + editor = ColorListEditor(parent) + + elif isinstance(data, LineStyle): + editor = LineStyleEditor(parent) + + elif isinstance(data, SymbolStyle): + editor = SymbolStyleEditor(parent) + + elif isinstance(data, float): + editor = SpinBoxEditor(parent) + + else: + editor = super().createEditor(parent, options, idx) + + return editor + + def setEditorData(self, editor: QtWidgets.QWidget, idx: QtCore.QModelIndex): + data = idx.data() + if isinstance(data, (BaseColor, LineStyle, SymbolStyle)): + editor.value = data + else: + super().setEditorData(editor, idx) + + def setModelData(self, editor: QtWidgets.QWidget, model: QtCore.QAbstractItemModel, idx: QtCore.QModelIndex): + data = idx.data() + if isinstance(data, (BaseColor, LineStyle, SymbolStyle)): + model.setData(idx, editor.value) + else: + super().setModelData(editor, model, idx) + + +class ColorListEditor(QtWidgets.QComboBox): + def __init__(self, parent=None): + super().__init__(parent) + self.populateList() + + @QtCore.pyqtProperty(BaseColor, user=True) + def value(self): + return self.itemData(self.currentIndex()) + + @value.setter + def value(self, val): + for i in range(self.count()): + if val == self.itemData(i): + self.setCurrentIndex(i) + break + + def populateList(self): + for i, colorName in enumerate(Colors): + color = QtGui.QColor(*colorName.value) + self.insertItem(i, colorName.name) + self.setItemData(i, colorName) + px = QtGui.QPixmap(self.iconSize()) + px.fill(color) + self.setItemData(i, QtGui.QIcon(px), QtCore.Qt.DecorationRole) + + +class SpinBoxEditor(QtWidgets.QDoubleSpinBox): + def __init__(self, parent=None): + super().__init__(parent) + self.setValue(1.0) + self.setSingleStep(0.1) + self.setDecimals(1) + + @QtCore.pyqtProperty(float, user=True) + def value(self): + return super().value() + + @value.setter + def value(self, val): + super().setValue(val) + + +class LineStyleEditor(QtWidgets.QComboBox): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setItemDelegate(LineStyleDelegate()) + self.populate() + + @QtCore.pyqtProperty(int, user=True) + def value(self): + return self.itemData(self.currentIndex()) + + @value.setter + def value(self, val): + for i in range(self.count()): + if val == self.itemData(i): + self.setCurrentIndex(i) + break + + def populate(self): + for i, style in enumerate(LineStyle): + self.insertItem(i, re.sub(r'([A-Z])', r' \g<1>', style.name)) + self.setItemData(i, style) + + def paintEvent(self, evt: QtGui.QPaintEvent): + if self.currentData() is not None: + painter = QtWidgets.QStylePainter(self) + opt = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(opt) + painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt) + pen = QtGui.QPen() + pal = QtGui.QGuiApplication.palette() + pen.setColor(pal.color(QtGui.QPalette.Text)) + pen.setWidth(2) + pen.setStyle(self.currentData().value) + pen.setCapStyle(QtCore.Qt.RoundCap) + painter.setPen(pen) + + rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, + opt, QtWidgets.QStyle.SC_ComboBoxEditField, None) + rect.adjust(+10, 0, -10, 0) + mid = (rect.bottom() + rect.top()) / 2 + painter.drawLine(rect.left(), mid, rect.right(), mid) + painter.end() + else: + super().paintEvent(evt) + + +class LineStyleDelegate(QtWidgets.QStyledItemDelegate): + def paint(self, painter, option, index): + data = index.data(QtCore.Qt.UserRole) + + if data is not None: + pen = QtGui.QPen() + pal = QtGui.QGuiApplication.palette() + pen.setColor(pal.color(QtGui.QPalette.Text)) + pen.setWidth(2) + pen.setStyle(data.value) + pen.setCapStyle(QtCore.Qt.RoundCap) + painter.setPen(pen) + + rect = option.rect + rect.adjust(+10, 0, -10, 0) + mid = (rect.bottom()+rect.top()) / 2 + painter.drawLine(rect.left(), mid, rect.right(), mid) + else: + QtWidgets.QStyledItemDelegate.paint(self, painter, option, index) + + +class SymbolStyleEditor(QtWidgets.QComboBox): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.populate() + + @QtCore.pyqtProperty(SymbolStyle, user=True) + def value(self): + return self.itemData(self.currentIndex()) + + @value.setter + def value(self, val): + for i in range(self.count()): + if val == self.itemData(i): + self.setCurrentIndex(i) + break + + def populate(self): + for i, s in enumerate(SymbolStyle): + self.insertItem(i, re.sub(r'([A-Z])', r' \g<1>', s.name)) + self.setItemData(i, s) + self.setItemData(i, make_symbol_pixmap(s), QtCore.Qt.DecorationRole) + + +class HeaderDelegate(QtWidgets.QStyledItemDelegate): + def paint(self, painter: QtGui.QPainter, option: QtWidgets.QStyleOptionViewItem, index: QtCore.QModelIndex): + + header_option = QtWidgets.QStyleOptionHeader() + header_option.rect = option.rect + + style = QtWidgets.QApplication.style() + style.drawControl(QtWidgets.QStyle.CE_HeaderSection, header_option, painter) + if option.state & QtWidgets.QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) \ No newline at end of file diff --git a/nmreval/gui_qt/lib/expandablewidget.py b/nmreval/gui_qt/lib/expandablewidget.py new file mode 100644 index 0000000..29bfd82 --- /dev/null +++ b/nmreval/gui_qt/lib/expandablewidget.py @@ -0,0 +1,65 @@ +from ..Qt import QtWidgets, QtCore + + +class ExpandableWidget(QtWidgets.QWidget): + expansionChanged = QtCore.pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self._init_ui() + + self._widget = None + self._expanded = False + + self.toolButton.clicked.connect(self.changeVisibility) + + def _init_ui(self): + self.verticalLayout = QtWidgets.QVBoxLayout(self) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + + self.toolButton = QtWidgets.QToolButton(self) + self.toolButton.setArrowType(QtCore.Qt.RightArrow) + self.toolButton.setStyleSheet('border: 0') + self.toolButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.horizontalLayout.addWidget(self.toolButton) + + self.line = QtWidgets.QFrame(self) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.horizontalLayout.addWidget(self.line) + + self.verticalLayout.addLayout(self.horizontalLayout) + + def addWidget(self, widget: QtWidgets.QWidget): + self._widget = widget + self._widget.setVisible(self._expanded) + self.layout().addWidget(widget) + + def setText(self, text: str): + self.toolButton.setText(text) + + def isExpanded(self): + return self._expanded + + def changeVisibility(self): + if not self._widget: + return + + self.setExpansion(not self._expanded) + + self.expansionChanged.emit() + + def setExpansion(self, state: bool): + self.blockSignals(True) + if state: + self.toolButton.setArrowType(QtCore.Qt.DownArrow) + else: + self.toolButton.setArrowType(QtCore.Qt.RightArrow) + + self._expanded = state + self._widget.setVisible(state) + self.blockSignals(False) diff --git a/nmreval/gui_qt/lib/forms.py b/nmreval/gui_qt/lib/forms.py new file mode 100644 index 0000000..fb3058d --- /dev/null +++ b/nmreval/gui_qt/lib/forms.py @@ -0,0 +1,402 @@ +from numpy import inf + +from ...utils.text import convert + +from ..Qt import QtGui, QtCore, QtWidgets +from .._py.mean_form import Ui_mean_form + + +class QDelayWidget(QtWidgets.QWidget): + get_data = QtCore.pyqtSignal(str) + newDelay = QtCore.pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.vals = '' + self.dim = 0 + self.plainTextEdit.setVisible(False) + + def __call__(self): + self.vals = '' + self.dim = 0 + self.comboBox.clear() + self.comboBox.blockSignals(True) + self.comboBox.addItem('New list...') + self.comboBox.blockSignals(True) + self.plainTextEdit.setVisible(False) + + def add_items(self, item): + self.comboBox.blockSignals(True) + if isinstance(item, list): + self.comboBox.insertItems(0, item) + else: + self.comboBox.insertItem(0, item) + self.comboBox.setCurrentIndex(0) + self.comboBox.blockSignals(False) + + @QtCore.pyqtSlot(name='on_toolButton_clicked') + def show_values(self): + if self.plainTextEdit.isVisible(): + self.plainTextEdit.clear() + self.plainTextEdit.setVisible(False) + elif self.comboBox.currentText() == 'New list...': + self.newDelay.emit() + else: + self.get_data.emit(self.comboBox.currentText()) + self.plainTextEdit.setVisible(True) + self.plainTextEdit.setPlainText(self.vals) + + +class LineEdit(QtWidgets.QLineEdit): + values_requested = QtCore.pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent=parent) + + def contextMenuEvent(self, evt): + menu = self.createStandardContextMenu() + request_action = menu.addAction('Use value of set(s)') + + action = menu.exec(evt.globalPos()) + + if action == request_action: + self.values_requested.emit() + + +class LineEditPost(QtWidgets.QLineEdit): + values_requested = QtCore.pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.suffix = '' + self.prefix = '' + + self.editingFinished.connect(self.add_fixes) + + def add_fixes(self): + self.setText(self.prefix+super().text()+self.suffix) + + def text(self): + text = super().text() + if text.startswith(self.prefix): + text = text[len(self.prefix):] + if text.endswith(self.suffix): + text = text[:-len(self.suffix)] + + return text + + +class FormWidget(QtWidgets.QWidget): + types = {'float': (float, QtGui.QDoubleValidator), + 'int': (int, QtGui.QIntValidator), + 'str': (str, lambda: 0)} + + valueChanged = QtCore.pyqtSignal(object) + stateChanged = QtCore.pyqtSignal(bool) + + def __init__(self, name: str, validator: str = 'float', fixable: bool = False, parent=None): + super().__init__(parent=parent) + + self._init_ui() + + self._name = name + + self._type = FormWidget.types[validator][0] + self.vals.setValidator(FormWidget.types[validator][1]()) + self.vals.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0) + + self.label.setText(convert(name)) + + self._checkable = fixable + self.checkBox.setVisible(fixable) + self.checkBox.stateChanged.connect(lambda x: self.stateChanged.emit(True if x else False)) + + def _init_ui(self): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.setSpacing(2) + + self.label = QtWidgets.QLabel(self) + layout.addWidget(self.label) + + layout.addSpacerItem(QtWidgets.QSpacerItem(20, 20, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum)) + + self.vals = QtWidgets.QLineEdit(self) + layout.addWidget(self.vals) + + self.checkBox = QtWidgets.QCheckBox('Fix?', self) + layout.addWidget(self.checkBox) + + @property + def value(self): + try: + return self._type(self.vals.text().replace(',', '.')) + except ValueError: + return {float: 0.0, int: 0, str: ''}[self._type] + + @value.setter + def value(self, val): + self.vals.setText(str(val)) + + def setChecked(self, enable): + if self._checkable: + self.checkBox.setCheckState(QtCore.Qt.Checked if enable else QtCore.Qt.Unchecked) + else: + print(f'Parameter {self._name} is not variable') + + def isChecked(self): + return self.checkBox.isChecked() + + +class SelectionWidget(QtWidgets.QWidget): + selectionChanged = QtCore.pyqtSignal(str, object) + + def __init__(self, label: str, argname: str, opts: dict, parent=None): + super().__init__(parent=parent) + + self._init_ui() + self.label.setText(convert(label)) + for k in opts.keys(): + self.comboBox.addItem(k) + + self.argname = argname + self.options = opts + + self.comboBox.currentIndexChanged.connect(lambda idx: self.selectionChanged.emit(self.argname, self.value)) + + def _init_ui(self): + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(1, 1, 1, 1) + layout.setSpacing(2) + + self.label = QtWidgets.QLabel(self) + layout.addWidget(self.label) + + layout.addSpacerItem(QtWidgets.QSpacerItem(65, 20, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Minimum)) + + self.comboBox = QtWidgets.QComboBox(self) + layout.addWidget(self.comboBox) + self.setLayout(layout) + + @property + def value(self): + try: + return float(self.options[str(self.comboBox.currentText())]) + except ValueError: + return str(self.options[str(self.comboBox.currentText())]) + + def get_parameter(self): + return str(self.comboBox.currentText()) + + @value.setter + def value(self, val): + key = [k for k, v in self.options.items() if v == val][0] + self.comboBox.setCurrentIndex(self.comboBox.findText(key)) + + +class Widget(QtWidgets.QWidget, Ui_mean_form): + valueChanged = QtCore.pyqtSignal() + + def __init__(self, name: str, tree: dict, collapsing: bool = False, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self._tree = {} + + self._collapse = collapsing + + self.label.setText(convert(name)) + self.lineEdit.setValidator(QtGui.QDoubleValidator()) + + self.set_graphs(tree) + self.change_graph(0) + + if self._collapse: + self.digit_checkbox.hide() + self.frame.hide() + self.data_checkbox.stateChanged.connect(self.collapse_widgets) + else: + self.data_checkbox.stateChanged.connect(self.change_mode) + self.digit_checkbox.stateChanged.connect(self.change_mode) + + def set_graphs(self, graph: dict): + self.graph_combobox.blockSignals(True) + self._tree.clear() + for key, (name, _) in graph.items(): + self.graph_combobox.addItem(name, userData=key) + self.graph_combobox.blockSignals(False) + self._tree.update(graph) + self.change_graph(0) + + @QtCore.pyqtSlot(int, name='on_graph_combobox_currentIndexChanged') + def change_graph(self, idx: int): + self.set_combobox.clear() + + key = self.graph_combobox.itemData(idx, QtCore.Qt.UserRole) + if key is not None: + for set_key, set_name in self._tree[key][1]: + self.set_combobox.addItem(set_name, userData=set_key) + + def on_lineEdit_textChanged(self, text=''): + if text: + self.valueChanged.emit() + + @property + def value(self): + if self.data_checkbox.isChecked(): + return self.set_combobox.currentData() + else: + try: + return float(self.lineEdit.text()) + except ValueError: + return + + @QtCore.pyqtSlot(int) + def change_mode(self, state: int): + box = self.sender() + other_box = self.data_checkbox if box == self.digit_checkbox else self.digit_checkbox + + if (state == QtCore.Qt.Unchecked) and (other_box.checkState() == QtCore.Qt.Unchecked): + box.blockSignals(True) + box.setCheckState(QtCore.Qt.Checked) + box.blockSignals(False) + return + + other_box.blockSignals(True) + other_box.setChecked(False) + other_box.blockSignals(False) + + self.valueChanged.emit() + + @QtCore.pyqtSlot(int) + def collapse_widgets(self, state: int): + data_is_checked = state == QtCore.Qt.Checked + self.frame.setVisible(data_is_checked) + self.lineEdit.setVisible(not data_is_checked) + + +class CheckBoxHeader(QtWidgets.QHeaderView): + clicked = QtCore.pyqtSignal(int, bool) + + _x_offset = 3 + _y_offset = 0 # This value is calculated later, based on the height of the paint rect + _width = 20 + _height = 20 + + def __init__(self, column_indices, orientation=QtCore.Qt.Horizontal, parent=None): + super().__init__(orientation, parent) + self.setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + # self.setClickable(True) + + if isinstance(column_indices, list) or isinstance(column_indices, tuple): + self.column_indices = column_indices + elif isinstance(column_indices, int): + self.column_indices = [column_indices] + else: + raise RuntimeError('column_indices must be a list, tuple or integer') + + self.isChecked = {} + for column in self.column_indices: + self.isChecked[column] = 0 + + def paintSection(self, painter, rect, logicalIndex): + painter.save() + super().paintSection(painter, rect, logicalIndex) + painter.restore() + + self._y_offset = int((rect.height()-self._width)/2.) + + if logicalIndex in self.column_indices: + option = QtWidgets.QStyleOptionButton() + option.rect = QtCore.QRect(rect.x() + self._x_offset, rect.y() + self._y_offset, self._width, self._height) + option.state = QtWidgets.QStyle.State_Enabled | QtWidgets.QStyle.State_Active + if self.isChecked[logicalIndex] == 2: + option.state |= QtWidgets.QStyle.State_NoChange + elif self.isChecked[logicalIndex]: + option.state |= QtWidgets.QStyle.State_On + else: + option.state |= QtWidgets.QStyle.State_Off + + self.style().drawControl(QtWidgets.QStyle.CE_CheckBox,option,painter) + + def updateCheckState(self, index, state): + self.isChecked[index] = state + self.viewport().update() + + def mousePressEvent(self, event): + index = self.logicalIndexAt(event.pos()) + if 0 <= index < self.count(): + x = self.sectionPosition(index) + if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width and self._y_offset < event.pos().y() < self._y_offset + self._height: + if self.isChecked[index] == 1: + self.isChecked[index] = 0 + else: + self.isChecked[index] = 1 + + self.clicked.emit(index, self.isChecked[index]) + self.viewport().update() + else: + super().mousePressEvent(event) + else: + super().mousePressEvent(event) + + +class CustomRangeDialog(QtWidgets.QDialog): + """ Simple dialog to enter x and y limits to fit regions""" + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.gl = QtWidgets.QGridLayout() + self.gl.addWidget(QtWidgets.QLabel('Leave empty for complete range'), 0, 0, 1, 4) + + self._limits = [[], []] + for i, orient in enumerate(['x', 'y'], start=1): + self.gl.addWidget(QtWidgets.QLabel(orient + ' from'), i, 0) + self.gl.addWidget(QtWidgets.QLabel('to'), i, 2) + for j in [0, 1]: + lim = QtWidgets.QLineEdit() + lim.setValidator(QtGui.QDoubleValidator()) + self._limits[i-1].append(lim) + self.gl.addWidget(lim, i, 2*j+1) + + self.buttonbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok) + self.buttonbox.accepted.connect(self.accept) + + self.gl.addWidget(self.buttonbox, 3, 0, 1, 4) + self.setLayout(self.gl) + + @property + def limits(self): + ret_val = [] + for orient in self._limits: + for i, w in enumerate(orient): + val = w.text() + if val == '': + ret_val.append(-inf if i == 0 else inf) + else: + ret_val.append(float(val)) + return ret_val + + +class ElideComboBox(QtWidgets.QComboBox): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.view().setTextElideMode(QtCore.Qt.ElideRight) + + def paintEvent(self, evt: QtGui.QPaintEvent): + opt = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(opt) + + painter = QtWidgets.QStylePainter(self) + painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, opt) + + rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, opt, QtWidgets.QStyle.SC_ComboBoxEditField, self) + + opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width()) + painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt) diff --git a/nmreval/gui_qt/lib/gol.py b/nmreval/gui_qt/lib/gol.py new file mode 100644 index 0000000..4212e71 --- /dev/null +++ b/nmreval/gui_qt/lib/gol.py @@ -0,0 +1,390 @@ +from __future__ import annotations + +import numbers +import numpy as np +import sys +from itertools import accumulate + +from ..Qt import QtWidgets, QtGui, QtCore +from .._py.gol import Ui_Form + + +def circle(radius): + pxl = [] + for x in range(int(np.ceil(radius/np.sqrt(2)))): + y = round(np.sqrt(radius**2-x**2)) + pxl.extend([[x, y], [y, x], [x, -y], [y, -x], + [-x, -y], [-y, -x], [-x, y], [-y, x]]) + + return np.array(pxl) + + +def square(a): + pxl = [] + pxl.extend(list(zip(range(-a, a+1), [a]*(2*a+1)))) + pxl.extend(list(zip(range(-a, a+1), [-a]*(2*a+1)))) + pxl.extend(list(zip([a]*(2*a+1), range(-a, a+1)))) + pxl.extend(list(zip([-a]*(2*a+1), range(-a, a+1)))) + + return np.array(pxl) + + +def diamond(a): + pxl = [] + for x in range(int(a+1)): + y = a-x + pxl.extend([[x, y], [-x, y], [x, -y], [-x, -y]]) + + print(np.array(pxl).shape) + + return np.array(pxl) + + +def plus(a): + pxl = np.zeros((4*int(a)+2, 2), dtype=int) + pxl[:2*int(a)+1, 0] = np.arange(-a, a+1) + pxl[2*int(a)+1:, 1] = np.arange(-a, a+1) + + return pxl + +# birth, survival +predefined_rules = { + 'Conway': ('23', '3'), + 'Life34': ('34', '34'), + 'Coagulation': ('235678', '378'), + 'Corrosion': ('12345', '45'), + 'Long life': ('5', '345'), + 'Maze': ('12345', '3'), + 'Coral': ('45678', '3'), + 'Pseudo life': ('238', '357'), + 'Flakes': ('012345678', '3'), + 'Gnarl': ('1', '1'), + 'Fabric': ('12', '1'), + 'Assimilation': ('4567', '345'), + 'Diamoeba': ('5678', '35678'), + 'High life': ('23', '36'), + 'More Maze': ('1235', '3'), + 'Replicator': ('1357', '1357'), + 'Seed': ('', '2'), + 'Serviette': ('', '234'), + 'More coagulation': ('235678', '3678'), + 'Domino': ('125', '36'), + 'Anneal': ('35678', '4678'), +} + + +class GameOfLife: + colors = [ + [31, 119, 180], + [255, 127, 14], + [44, 160, 44], + [214, 39, 40], + [148, 103, 189], + [140, 86, 75] + ] + + def __init__(self, + size: tuple = (400, 400), + pattern: np.ndarray = None, + fill: float | list[float] = 0.05, + num_pop: int = 1, + rules: str | tuple = ('23', '3') + ): + self.populations = num_pop if pattern is None else 1 + self._size = size + + self._world = np.zeros(shape=(*self._size, self.populations), dtype=np.uint8) + self._neighbors = np.zeros(shape=self._world.shape, dtype=np.uint8) + self._drawing = 255 * np.zeros(shape=(*self._size, 3), dtype=np.uint8) + + self.fill = np.zeros(self.populations) + self._populate(fill, pattern) + + print(rules) + if isinstance(rules, str): + try: + b_rule, s_rule = predefined_rules[rules] + except KeyError: + raise ValueError('Rule is not predefined') + else: + b_rule, s_rule = rules + + self.survival_condition = np.array([int(c) for c in s_rule]) + self.birth_condition = np.array([int(c) for c in b_rule]) + + # indexes for neighbors + self._neighbor_idx = [ + [[slice(None), slice(1, None)], [slice(None), slice(0, -1)]], # N (:, 1:), S (:, _-1) + [[slice(1, None), slice(1, None)], [slice(0, -1), slice(0, -1)]], # NE (1:, 1:), SW (:-1, :-1) + [[slice(1, None), slice(None)], [slice(0, -1), slice(None)]], # E (1:, :), W (:-1, :) + [[slice(1, None), slice(0, -1)], [slice(0, -1), slice(1, None)]] # SE (1:, :-1), NW (:-1:, 1:) + ] + + def _populate(self, fill, pattern): + if pattern is None: + if isinstance(fill, numbers.Number): + fill = [fill]*self.populations + + prob = (np.random.default_rng().random(size=self._size)) + lower_lim = 0 + for i, upper_lim in enumerate(accumulate(fill)): + self._world[:, :, i] = (lower_lim <= prob) & (prob < upper_lim) + lower_lim = upper_lim + + else: + pattern = np.asarray(pattern) + + x_step = self._size[0]//(self.populations+1) + y_step = self._size[1]//(self.populations+1) + + for i in range(self.populations): + self._world[-pattern[:, 0]+(i+1)*x_step, pattern[:, 1]+(i+1)*y_step, i] = 1 + + for i in range(self.populations): + self.fill[i] = self._world[:, :, i].sum() / (self._size[0]*self._size[1]) + + def tick(self): + n = self._neighbors + w = self._world + + n[...] = 0 + for idx_1, idx_2 in self._neighbor_idx: + n[tuple(idx_1)] += w[tuple(idx_2)] + n[tuple(idx_2)] += w[tuple(idx_1)] + + birth = ((np.in1d(n, self.birth_condition).reshape(n.shape)) & (w.sum(axis=-1) == 0)[:, :, None]) + survive = ((np.in1d(n, self.survival_condition).reshape(n.shape)) & (w == 1)) + + w[...] = 0 + w[birth | survive] = 1 + + def draw(self, shade: int): + if shade == 0: + self._drawing[...] = 0 + elif shade == 1: + self._drawing -= (self._drawing/4).astype(np.uint8) + self._drawing = self._drawing.clip(0, 127) + + for i in range(self.populations): + self._drawing[(self._world[:, :, i] == 1)] = self.colors[i] + self.fill[i] = self._world[:, :, i].sum() / (self._size[0]*self._size[1]) + + return self._drawing + + +class QGameOfLife(QtWidgets.QDialog, Ui_Form): + SPEEDS = [0.5, 1, 2, 5, 7.5, 10, 12.5, 15, 20, 25, 30, 40, 50] + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.setupUi(self) + + self._size = (100, 100) + self.game = None + self._step = 0 + self._shading = 1 + self._speed = 5 + + self.timer = QtCore.QTimer() + self.timer.setInterval(100) + self.timer.timeout.connect(self.tick) + + self._init_ui() + + def _init_ui(self): + self.item = None + self.scene = QtWidgets.QGraphicsScene() + self.item = self.scene.addPixmap(QtGui.QPixmap()) + self.view.setScene(self.scene) + + self.rule_cb.addItems(list(predefined_rules.keys())) + + self.random_widgets = [] + for _ in range(6): + w = QSliderText(15, parent=self) + w.slider.valueChanged.connect(self.set_max_population) + self.verticalLayout.addWidget(w) + self.random_widgets.append(w) + + self.birth_line.setValidator(QtGui.QIntValidator()) + self.survival_line.setValidator(QtGui.QIntValidator()) + + for w in self.random_widgets[1:] + [self.object_widget]: + w.hide() + + self.setGeometry(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, + self.size(), QtWidgets.qApp.desktop().availableGeometry())) + + self.view.resizeEvent = self.resizeEvent + + @QtCore.pyqtSlot(int) + def on_object_combobox_currentIndexChanged(self, idx: int): + for w in self.random_widgets + [self.object_widget, self.rand_button_wdgt]: + w.hide() + + if idx == 0: + self.random_widgets[0].show() + self.rand_button_wdgt.show() + else: + self.object_widget.show() + + @QtCore.pyqtSlot() + def on_add_random_button_clicked(self): + if self.object_combobox.currentIndex() != 0: + return + + for w in self.random_widgets[1:]: + if not w.isVisible(): + w.show() + break + + @QtCore.pyqtSlot() + def on_remove_random_button_clicked(self): + if self.object_combobox.currentIndex() != 0: + return + + for w in reversed(self.random_widgets[1:]): + if w.isVisible(): + w.hide() + break + + @QtCore.pyqtSlot(str) + def on_rule_cb_currentIndexChanged(self, entry: str): + rule = predefined_rules[entry] + self.birth_line.setText(rule[1]) + self.survival_line.setText(rule[0]) + + @QtCore.pyqtSlot(int) + def set_max_population(self, _: int): + over_population = -100 + num_tribes = -1 + for w in self.random_widgets: + if w.isVisible(): + over_population += w.slider.value() + num_tribes += 1 + + if over_population > 0: + for w in self.random_widgets: + if w == self.sender() or w.isHidden(): + continue + w.setValue(max(0, int(w.slider.value()-over_population/num_tribes))) + + @QtCore.pyqtSlot() + def on_start_button_clicked(self): + self.pause_button.setChecked(False) + self._step = 0 + self.current_step.setText(f'{self._step} steps') + self._size = (int(self.height_box.value()), int(self.width_box.value())) + + pattern = None + num_pop = 0 + fill = [] + pattern_size = self.object_size.value() + if 2*pattern_size >= max(self._size): + pattern_size = int(np.floor(max(self._size[0]-1, self._size[1]-1) / 2)) + + idx = self.object_combobox.currentIndex() + if idx == 0: + for w in self.random_widgets: + if w.isVisible(): + num_pop += 1 + fill.append(w.slider.value()/100) + else: + pattern = [None, circle, square, diamond, plus][idx](pattern_size) + + self.game = GameOfLife(self._size, pattern=pattern, fill=fill, num_pop=num_pop, + rules=(self.birth_line.text(), self.survival_line.text())) + self.draw() + self.view.fitInView(self.item, QtCore.Qt.KeepAspectRatio) + + self.timer.start() + + def tick(self): + self.game.tick() + self.draw() + + self._step += 1 + self.current_step.setText(f'{self._step} steps') + self.cover_label.setText('\n'.join([f'Color {i+1}: {f*100:.2f} %' for i, f in enumerate(self.game.fill)])) + + @QtCore.pyqtSlot() + def on_faster_button_clicked(self): + self._speed = min(self._speed+1, len(QGameOfLife.SPEEDS)-1) + new_speed = QGameOfLife.SPEEDS[self._speed] + self.timer.setInterval(int(1000/new_speed)) + self.velocity_label.setText(f'{new_speed:.1f} steps/s') + + @QtCore.pyqtSlot() + def on_slower_button_clicked(self): + self._speed = max(self._speed-1, 0) + new_speed = QGameOfLife.SPEEDS[self._speed] + self.timer.setInterval(int(1000/new_speed)) + self.velocity_label.setText(f'{new_speed:.1f} steps/s') + + @QtCore.pyqtSlot() + def on_pause_button_clicked(self): + if self.pause_button.isChecked(): + self.timer.stop() + else: + self.timer.start() + + @QtCore.pyqtSlot(QtWidgets.QAbstractButton) + def on_buttonGroup_buttonClicked(self, button): + self._shading = [self.radioButton, self.vanish_shadow, self.full_shadow].index(button) + + def draw(self): + bitmap = self.game.draw(shade=self._shading) + h, w, c = bitmap.shape + image = QtGui.QImage(bitmap.tobytes(), w, h, w*c, QtGui.QImage.Format_RGB888) + self.scene.removeItem(self.item) + pixmap = QtGui.QPixmap.fromImage(image) + self.item = self.scene.addPixmap(pixmap) + + @QtCore.pyqtSlot(int) + def on_hide_button_stateChanged(self, state: int): + self.option_frame.setVisible(not state) + self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio) + + def resizeEvent(self, evt): + super().resizeEvent(evt) + self.view.fitInView(self.item, QtCore.Qt.KeepAspectRatio) + + def showEvent(self, evt): + super().showEvent(evt) + self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio) + + +class QSliderText(QtWidgets.QWidget): + def __init__(self, value: int, parent=None): + super().__init__(parent=parent) + layout = QtWidgets.QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.slider = QtWidgets.QSlider(self) + self.slider.setOrientation(QtCore.Qt.Horizontal) + self.slider.setMaximum(100) + self.slider.setTickPosition(self.slider.TicksBothSides) + self.slider.setTickInterval(5) + + self.value = QtWidgets.QLabel(self) + + self.slider.valueChanged.connect(lambda x: self.value.setText(f'{x} %')) + + layout.addWidget(self.slider) + layout.addWidget(self.value) + + self.setLayout(layout) + + self.setValue(value) + + def setValue(self, value: int): + self.slider.setValue(value) + self.value.setText(f'{value} %') + + +if __name__ == "__main__": + application = QtWidgets.QApplication(sys.argv) + qGameOfLife = QGameOfLife() + qGameOfLife.show() + sys.exit(application.exec()) diff --git a/nmreval/gui_qt/lib/namespace.py b/nmreval/gui_qt/lib/namespace.py new file mode 100644 index 0000000..9229176 --- /dev/null +++ b/nmreval/gui_qt/lib/namespace.py @@ -0,0 +1,211 @@ +import inspect +import re +from collections import namedtuple + +import numpy as np + +from ... import models +from ...configs import config_paths +from ...lib.importer import find_models, import_ +from ...utils import constants as constants +from ...utils.text import convert +from ..Qt import QtWidgets, QtCore +from .._py.namespace_widget import Ui_Form + + +class Namespace: + def __init__(self, fitfuncs=False, const=False, basic=False): + self.namespace = {} + self.groupings = {} + self.top_levels = {} + + if basic: + self.add_namespace({'x': (None, 'x values'), '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)'), + 'tan': (np.tan, 'Tangens', 'tan(PIKA)'), 'ln': (np.log, 'Natural Logarithm', 'ln(PIKA)'), + 'log': (np.log10, 'Logarithm (base 10)', 'log(PIKA)'), + 'exp': (np.exp, 'Exponential', 'exp(PIKA)'), 'sqrt': (np.sqrt, 'Root', 'sqrt(PIKA)'), + '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)')}, + parents=('Basic', 'Functions')) + + self.add_namespace({'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: + self.add_namespace({'e': (constants.e, 'e / As'), 'eps0': (constants.epsilon0, 'epsilon0 / As/Vm'), + 'Eu': (constants.Eu,), 'h': (constants.h, 'h / eVs'), + 'hbar': (constants.hbar, 'hbar / eVs'), 'kB': (constants.kB, 'kB / eV/K'), + '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()}, + parents=('Constants', 'Magnetogyric ratios (in 1/(sT))')) + + if fitfuncs: + self.make_dict_from_fitmodule(models) + try: + usermodels = import_(config_paths() / 'usermodels.py') + self.make_dict_from_fitmodule(usermodels, parent='User-defined') + except FileNotFoundError: + pass + + def __str__(self): + ret = '\n'.join([f'{k}: {v}' for k, v in self.groupings.items()]) + return ret + + def make_dict_from_fitmodule(self, module, parent='Fit function'): + fitfunc_dict = {} + for funcs in find_models(module): + name = funcs.name + if funcs.type not in fitfunc_dict: + fitfunc_dict[funcs.type] = {} + + func_list = fitfunc_dict[funcs.type] + + func_args = 'x, ' + ', '.join(convert(p, old='latex', new='str', brackets=False) for p in funcs.params) + + # keywords arguments need names in function + sig = inspect.signature(funcs.func) + for p in sig.parameters.values(): + if p.default != p.empty: + func_args += ', ' + str(p) + + func_list[f"{funcs.__name__}"] = (funcs.func, name, f"{funcs.__name__}({func_args})") + + for k, v in fitfunc_dict.items(): + self.add_namespace(v, parents=(parent, k)) + + def add_namespace(self, namespace, parents): + if parents[0] in self.top_levels: + if parents[1] not in self.top_levels[parents[0]]: + self.top_levels[parents[0]].append(parents[1]) + else: + self.top_levels[parents[0]] = [parents[1]] + if (parents[0], parents[1]) not in self.groupings: + self.groupings[(parents[0], parents[1])] = list(namespace.keys()) + else: + self.groupings[(parents[0], parents[1])].extend(list(namespace.keys())) + + self.namespace.update(namespace) + + def flatten(self): + ret_dic = {} + graph = namedtuple('graphs', ['s']) + sets = namedtuple('sets', ['x', 'y', 'y_err', 'value', 'fit'], defaults=(None,)) + + gs = re.compile(r'g\[(\d+)].s\[(\d+)].(x|y(?:_err)*|value|fit)') + + for k, v in self.namespace.items(): + m = gs.match(k) + if m: + if 'g' not in ret_dic: + ret_dic['g'] = {} + + g_num, s_num, pos = m.groups() + if int(g_num) not in ret_dic['g']: + ret_dic['g'][int(g_num)] = graph({}) + + gg = ret_dic['g'][int(g_num)] + if int(s_num) not in gg.s: + gg.s[int(s_num)] = sets('', '', '', '') + + ss = gg.s[int(s_num)] + if pos == 'fit': + if ss.fit is None: + gg.s[int(s_num)] = ss._replace(**{pos: {}}) + ss = gg.s[int(s_num)] + ss.fit[k[m.end()+2:-2]] = v[0] + + else: + ret_dic['g'][int(g_num)].s[int(s_num)] = ss._replace(**{pos: v[0]}) + + else: + ret_dic[k] = v[0] + + return ret_dic + + +class QNamespaceWidget(QtWidgets.QWidget, Ui_Form): + selected = QtCore.pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setupUi(self) + self.namespace = None + + def make_namespace(self): + self.set_namespace(Namespace(fitfuncs=True, const=True, basic=True)) + + @QtCore.pyqtSlot(int, name='on_groups_comboBox_currentIndexChanged') + def show_subgroup(self, idx: int): + name = self.groups_comboBox.itemText(idx) + + self.subgroups_comboBox.blockSignals(True) + self.subgroups_comboBox.clear() + for item in self.namespace.top_levels[name]: + self.subgroups_comboBox.addItem(item) + self.subgroups_comboBox.blockSignals(False) + + self.show_namespace(0) + + @QtCore.pyqtSlot(int, name='on_subgroups_comboBox_currentIndexChanged') + def show_namespace(self, idx: int): + subspace = self.namespace.groupings[(self.groups_comboBox.currentText(), self.subgroups_comboBox.itemText(idx))] + + self.namespace_table.clear() + self.namespace_table.setRowCount(0) + + for entry in subspace: + key_item = QtWidgets.QTableWidgetItem(entry) + key_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + + vals = self.namespace.namespace[entry] + + if len(vals) < 3: + alias = entry + else: + alias = vals[2] + + if len(vals) < 2: + display = entry + else: + display = vals[1] + + value_item = QtWidgets.QTableWidgetItem(display) + value_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + + key_item.setData(QtCore.Qt.UserRole, alias) + value_item.setData(QtCore.Qt.UserRole, alias) + + row = self.namespace_table.rowCount() + self.namespace_table.setRowCount(row+1) + self.namespace_table.setItem(row, 0, key_item) + self.namespace_table.setItem(row, 1, value_item) + + self.namespace_table.resizeColumnsToContents() + + def set_namespace(self, namespace): + self.namespace = namespace + + self.groups_comboBox.blockSignals(True) + self.groups_comboBox.clear() + for k in self.namespace.top_levels.keys(): + self.groups_comboBox.addItem(k) + self.groups_comboBox.blockSignals(False) + + self.show_subgroup(0) + + @QtCore.pyqtSlot(QtWidgets.QTableWidgetItem, name='on_namespace_table_itemDoubleClicked') + def item_selected(self, item: QtWidgets.QTableWidgetItem): + self.selected.emit(item.data(QtCore.Qt.UserRole)) diff --git a/nmreval/gui_qt/lib/pg_objects.py b/nmreval/gui_qt/lib/pg_objects.py new file mode 100644 index 0000000..7fa9851 --- /dev/null +++ b/nmreval/gui_qt/lib/pg_objects.py @@ -0,0 +1,435 @@ +import numpy as np +from pyqtgraph import ( + InfiniteLine, + ErrorBarItem, + LinearRegionItem, mkBrush, + mkColor, mkPen, + PlotDataItem +) + +from ...lib.colors import BaseColor, Colors +from ...lib.lines import LineStyle +from ...lib.symbols import SymbolStyle + +from ..Qt import QtCore, QtGui + +""" +Subclasses of pyqtgraph items, mostly to take care of log-scaling. +pyqtgraph looks for function "setLogMode" for logarithmic axes, so needs to be implemented. +""" + + +class LogInfiniteLine(InfiniteLine): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.logmode = [False, False] + + def setLogMode(self, xmode, ymode): + """ + Does only work for vertical and horizontal lines + """ + if self.logmode == [xmode, ymode]: + return + + new_p = list(self.p[:]) + if (self.angle == 90) and (self.logmode[0] != xmode): + if xmode: + new_p[0] = np.log10(new_p[0]+np.finfo(float).eps) + else: + new_p[0] = 10**new_p[0] + + if (self.angle == 0) and (self.logmode[1] != ymode): + if ymode: + new_p[1] = np.log10(new_p[1]+np.finfo(float).eps) + else: + new_p[1] = 10**new_p[1] + + self.logmode = [xmode, ymode] + + if np.all(np.isfinite(new_p)): + self.setPos(new_p) + else: + self.setPos(self.p) + self.sigPositionChanged.emit(self) + + def setValue(self, v): + if isinstance(v, QtCore.QPointF): + v = [v.x(), v.y()] + + with np.errstate(divide='ignore'): + if isinstance(v, (list, tuple)): + for i in [0, 1]: + if self.logmode[i]: + v[i] = np.log10(v[i]+np.finfo(float).eps) + else: + if self.angle == 90: + if self.logmode[0]: + v = [np.log10(v+np.finfo(float).eps), 0] + else: + v = [v, 0] + elif self.angle == 0: + if self.logmode[1]: + v = [0, np.log10(v+np.finfo(float).eps)] + else: + v = [0, v] + else: + raise ValueError('LogInfiniteLine: Diagonal lines need two values') + + self.setPos(v) + + def value(self): + p = self.getPos() + if self.angle == 0: + return 10**p[1] if self.logmode[1] else p[1] + elif self.angle == 90: + return 10**p[0] if self.logmode[0] else p[0] + else: + if self.logmode[0]: + p[0] = 10**p[0] + if self.logmode[1]: + p[1] = 10**p[1] + return p + + +class ErrorBars(ErrorBarItem): + def __init__(self, **opts): + self.log = [False, False] + + opts['xData'] = opts.get('x', None) + opts['yData'] = opts.get('y', None) + opts['topData'] = opts.get('top', None) + opts['bottomData'] = opts.get('bottom', None) + + super().__init__(**opts) + + def setLogMode(self, x_mode, y_mode): + if self.log == [x_mode, y_mode]: + return + + self._make_log_scale(x_mode, y_mode) + + self.log[0] = x_mode + self.log[1] = y_mode + + super().setData() + + def setData(self, **opts): + self.opts.update(opts) + + self.opts['xData'] = opts.get('x', self.opts['xData']) + self.opts['yData'] = opts.get('y', self.opts['yData']) + self.opts['topData'] = opts.get('top', self.opts['topData']) + self.opts['bottomData'] = opts.get('bottom', self.opts['bottomData']) + + if any(self.log): + self._make_log_scale(*self.log) + + super().setData() + + def _make_log_scale(self, x_mode, y_mode): + _x = self.opts['xData'] + _xmask = np.logical_not(np.isnan(_x)) + + if x_mode: + with np.errstate(all='ignore'): + _x = np.log10(_x) + _xmask = np.logical_not(np.isnan(_x)) + + _y = self.opts['yData'] + _ymask = np.ones(_y.size, dtype=bool) + _top = self.opts['topData'] + _bottom = self.opts['bottomData'] + + if y_mode: + with np.errstate(all='ignore'): + logtop = np.log10(self.opts['topData']+_y) + logbottom = np.log10(_y-self.opts['bottomData']) + + _y = np.log10(_y) + _ymask = np.logical_not(np.isnan(_y)) + + logbottom[logbottom == -np.inf] = _y[logbottom == -np.inf] + _bottom = np.nan_to_num(np.maximum(_y-logbottom, 0)) + logtop[logtop == -np.inf] = _y[logtop == -np.inf] + _top = np.nan_to_num(np.maximum(logtop-_y, 0)) + + _mask = np.logical_and(_xmask, _ymask) + + self.opts['x'] = _x[_mask] + self.opts['y'] = _y[_mask] + self.opts['top'] = _top[_mask] + self.opts['bottom'] = _bottom[_mask] + + +class PlotItem(PlotDataItem): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.opts['linecolor'] = Colors.Black + self.opts['symbolcolor'] = Colors.Black + + if self.opts['pen'] is not None: + pen = self.opts['pen'] + if isinstance(pen, tuple): + self.opts['linecolor'] = pen + else: + c = pen.color() + self.opts['linecolor'] = c.red(), c.green(), c.blue() + + if self.symbol != SymbolStyle.No: + c = self.opts['symbolBrush'].color() + self.opts['symbolcolor'] = c.red(), c.green(), c.blue() + + def __getitem__(self, item): + return self.opts.get(item, None) + + @property + def symbol(self): + return SymbolStyle.from_str(self.opts['symbol']) + + @property + def symbolcolor(self): + sc = self.opts['symbolcolor'] + if isinstance(sc, tuple): + return Colors(sc) + elif isinstance(sc, str): + return Colors.from_str(sc) + else: + return sc + + @property + def symbolsize(self): + return self.opts['symbolSize'] + + @property + def linestyle(self) -> LineStyle: + pen = self.opts['pen'] + if pen is None: + return LineStyle.No + else: + return LineStyle(pen.style()) + + @property + def linewidth(self) -> float: + pen = self.opts['pen'] + if pen is None: + return 1. + else: + return pen.widthF() + + @property + def linecolor(self) -> Colors: + lc = self.opts['linecolor'] + if isinstance(lc, tuple): + return Colors(lc) + elif isinstance(lc, str): + return Colors.from_str(lc) + else: + return lc + + def updateItems(self): + """ + We override this function so that curves with nan/inf values can be displayed. + Newer versions close this bug differently (https://github.com/pyqtgraph/pyqtgraph/pull/1058) + but this works somewhat. + """ + + curveArgs = {} + for k, v in [('pen', 'pen'), ('shadowPen', 'shadowPen'), ('fillLevel', 'fillLevel'), + ('fillOutline', 'fillOutline'), ('fillBrush', 'brush'), ('antialias', 'antialias'), + ('connect', 'connect'), ('stepMode', 'stepMode')]: + curveArgs[v] = self.opts[k] + + scatterArgs = {} + for k, v in [('symbolPen', 'pen'), ('symbolBrush', 'brush'), ('symbol', 'symbol'), ('symbolSize', 'size'), + ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]: + if k in self.opts: + scatterArgs[v] = self.opts[k] + + x, y = self.getData() + if x is None: + x = [] + if y is None: + y = [] + # scatterArgs['mask'] = self.dataMask + + if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None): + is_finite = np.isfinite(x) & np.isfinite(y) + all_finite = np.all(is_finite) + if not all_finite: + # remove all bad values + x = x[is_finite] + y = y[is_finite] + self.curve.setData(x=x, y=y, **curveArgs) + self.curve.show() + else: + self.curve.hide() + + if scatterArgs['symbol'] is not None: + if self.opts.get('stepMode', False) is True: + x = 0.5 * (x[:-1] + x[1:]) + self.scatter.setData(x=x, y=y, **scatterArgs) + self.scatter.show() + else: + self.scatter.hide() + + def set_symbol(self, symbol=None, size=None, color=None): + if symbol is not None: + if isinstance(symbol, int): + self.setSymbol(SymbolStyle(symbol).to_str()) + elif isinstance(symbol, SymbolStyle): + self.setSymbol(symbol.to_str()) + else: + self.setSymbol(symbol) + + if color is not None: + self.set_color(color, symbol=True) + + if size is not None: + self.setSymbolSize(size) + + def set_color(self, color, symbol=False, line=False): + if isinstance(color, BaseColor): + color = color.rgb() + elif isinstance(color, QtGui.QColor): + color = color.getRgb()[:3] + + if symbol: + self.setSymbolBrush(mkBrush(color)) + self.setSymbolPen(mkPen(color=color)) + self.opts['symbolcolor'] = color + + if line: + pen = self.opts['pen'] + self.opts['linecolor'] = color + if pen is not None: + pen.setColor(mkColor(color)) + self.opts['pen'] = pen + self.updateItems() + + def set_line(self, style=None, width=None, color=None): + pen = self.opts['pen'] + if pen is None: + pen = mkPen(style=QtCore.Qt.NoPen) + + if width is not None: + pen.setWidthF(width) + + if style is not None: + if isinstance(style, LineStyle): + style = style.value + + pen.setStyle(style) + + self.opts['pen'] = pen + self.updateItems() + + if color is not None: + self.set_color(color, symbol=False, line=True) + + def get_data_opts(self) -> dict: + x, y = self.xData, self.yData + if (x is None) or (len(x) == 0): + return {} + + opts = self.opts + item_dic = { + 'x': x, 'y': y, + 'name': opts['name'], + 'symbolsize': opts['symbolSize'] + } + + if opts['symbol'] is None: + item_dic['symbol'] = SymbolStyle.No + item_dic['symbolcolor'] = Colors.Black + else: + item_dic['symbol'] = SymbolStyle.from_str(opts['symbol']) + item_dic['symbolcolor'] = opts['symbolcolor'] + + pen = opts['pen'] + + if pen is not None: + item_dic['linestyle'] = LineStyle(pen.style()) + item_dic['linecolor'] = opts['linecolor'] + item_dic['linewidth'] = pen.widthF() + else: + item_dic['linestyle'] = LineStyle.No + item_dic['linecolor'] = item_dic['symbolcolor'] + item_dic['linewidth'] = 0.0 + + return item_dic + + +class RegionItem(LinearRegionItem): + def __init__(self, *args, **kwargs): + self.mode = kwargs.pop('mode', 'half') + + super().__init__(*args, **kwargs) + + self.logmode = False + self.first = True + + def setLogMode(self, xmode, _): + if self.logmode == xmode: + return + + if xmode: + new_region = [np.log10(self.lines[0].value()), np.log10(self.lines[1].value())] + + if np.isnan(new_region[1]): + new_region[1] = self.lines[1].value() + + if np.isnan(new_region[0]): + new_region[0] = new_region[1]/10. + + else: + new_region = [10**self.lines[0].value(), 10**self.lines[1].value()] + + self.logmode = xmode + self.setRegion(new_region) + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + if axis == self._orientation_axis[self.orientation]: + r = self.getRegion() + if self.logmode: + r = np.log10(r[0]), np.log10(r[1]) + return r + else: + return None + + def getRegion(self): + region = super().getRegion() + if self.logmode: + return 10**region[0], 10**region[1] + else: + return region + + def boundingRect(self): + # overwrite to draw correct rect in logmode + + br = self.viewRect() # bounds of containing ViewBox mapped to local coords. + + rng = self.getRegion() + if self.logmode: + rng = np.log10(rng[0]), np.log10(rng[1]) + + if self.orientation in ('vertical', LinearRegionItem.Vertical): + br.setLeft(rng[0]) + br.setRight(rng[1]) + length = br.height() + br.setBottom(br.top() + length * self.span[1]) + br.setTop(br.top() + length * self.span[0]) + else: + br.setTop(rng[0]) + br.setBottom(rng[1]) + length = br.width() + br.setRight(br.left() + length * self.span[1]) + br.setLeft(br.left() + length * self.span[0]) + + br = br.normalized() + + if self._bounds != br: + self._bounds = br + self.prepareGeometryChange() + + return br diff --git a/nmreval/gui_qt/lib/randpok.py b/nmreval/gui_qt/lib/randpok.py new file mode 100644 index 0000000..55de7de --- /dev/null +++ b/nmreval/gui_qt/lib/randpok.py @@ -0,0 +1,119 @@ +import sys +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 = '
{}'.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}') + + +if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + w = QPokemon(number=807) + w.show() + + sys.exit(app.exec()) diff --git a/nmreval/gui_qt/lib/stuff.py b/nmreval/gui_qt/lib/stuff.py new file mode 100644 index 0000000..59a1207 --- /dev/null +++ b/nmreval/gui_qt/lib/stuff.py @@ -0,0 +1,502 @@ +import random +import sys +import numpy as np + +from nmreval.gui_qt.Qt import QtWidgets, QtCore, QtGui + + +__all__ = ['Game'] + + +class Game(QtWidgets.QDialog): + def __init__(self, mode, parent=None): + super().__init__(parent=parent) + + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(3, 3, 3, 3) + + self.label = QtWidgets.QLabel(self) + layout.addWidget(self.label) + + self.startbutton = QtWidgets.QPushButton('Start', self) + self.startbutton.clicked.connect(self.start) + layout.addWidget(self.startbutton) + + if mode == 'tetris': + self._setup_tetris() + else: + self._setup_snake() + + layout.addWidget(self.board) + self.setStyleSheet(""" + Board { + border: 5px solid black; + } + QPushButton { + font-weight: bold; + } + """) + + self.setLayout(layout) + + self.board.new_status.connect(self.new_message) + + def _setup_tetris(self): + self.board = TetrisBoard(self) + self.setGeometry(200, 100, 276, 546+self.startbutton.height()+self.label.height()) + self.setWindowTitle('Totally not Tetris') + + def _setup_snake(self): + self.board = SnakeBoard(self) + self.setGeometry(200, 100, 406, 406+self.startbutton.height()+self.label.height()) + self.setWindowTitle('Snakey') + + def start(self): + + self.board.start() + + @QtCore.pyqtSlot(str) + def new_message(self, msg: str): + self.label.setText(msg) + + +class Board(QtWidgets.QFrame): + new_status = QtCore.pyqtSignal(str) + + SPEED = 1000 + WIDTH = 10 + HEIGHT = 10 + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.timer = QtCore.QTimer(self) + self.timer.timeout.connect(self.next_move) + + self.score = 0 + self._speed = self.SPEED + self._ispaused = False + self._isdead = True + + self.setFrameStyle(QtWidgets.QFrame.Box | QtWidgets.QFrame.Raised) + self.setLineWidth(3) + + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + + def _init_game(self): + raise NotImplementedError + + @property + def cellwidth(self): + return int(self.contentsRect().width() // self.WIDTH) + + # square height + @property + def cellheight(self): + return int(self.contentsRect().height() // self.HEIGHT) + + def start(self): + if self._isdead: + self._init_game() + self.new_status.emit(f'Score: {self.score}') + self.timer.start(self._speed) + self._isdead = False + self.setFocus() + + def stop(self, msg): + self.new_status.emit(f'Score {self.score} // ' + msg) + self._isdead = True + self.timer.stop() + + def pause(self): + if self._ispaused and not self._isdead: + self.new_status.emit(f'Score {self.score}') + self.timer.start(self._speed) + else: + self.new_status.emit(f'Score {self.score} // Paused') + self.timer.stop() + + self._ispaused = not self._ispaused + + def next_move(self): + raise NotImplementedError + + def draw_square(self, painter, x, y, color): + color = QtGui.QColor(color) + painter.fillRect(x+1, y+1, self.cellwidth-2, self.cellheight-2, color) + + def draw_circle(self, painter, x, y, color): + painter.save() + + color = QtGui.QColor(color) + painter.setPen(QtGui.QPen(color)) + painter.setBrush(QtGui.QBrush(color)) + painter.drawEllipse(x+1, y+1, self.cellwidth, self.cellheight) + + painter.restore() + + def keyPressEvent(self, evt): + if evt.key() == QtCore.Qt.Key_P: + self.pause() + else: + super().keyPressEvent(evt) + + +class SnakeBoard(Board): + SPEED = 100 + WIDTH = 30 + HEIGHT = 30 + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.snake = [[int(SnakeBoard.WIDTH//2), int(SnakeBoard.HEIGHT//2)], + [int(SnakeBoard.WIDTH//2)+1, int(SnakeBoard.HEIGHT//2)]] + self.current_x_head, self.current_y_head = self.snake[0] + self.direction = 'l' + + self.food = None + self.grow_snake = False + + def _init_game(self): + self.snake = [[int(SnakeBoard.WIDTH//2), int(SnakeBoard.HEIGHT//2)], + [int(SnakeBoard.WIDTH//2)+1, int(SnakeBoard.HEIGHT//2)]] + self.current_x_head, self.current_y_head = self.snake[0] + self.direction = 'l' + + self.food = None + self.grow_snake = False + self.new_food() + + def next_move(self): + self.snake_move() + self.got_food() + self.check_death() + self.update() + + def snake_move(self): + if self.direction == 'l': + self.current_x_head -= 1 + elif self.direction == 'r': + self.current_x_head += 1 + + # y increases top to bottom + elif self.direction == 'u': + self.current_y_head -= 1 + elif self.direction == 'd': + self.current_y_head += 1 + + head = [self.current_x_head, self.current_y_head] + self.snake.insert(0, head) + + if not self.grow_snake: + self.snake.pop() + else: + self.new_status.emit(f'Score: {self.score}') + self.grow_snake = False + + def got_food(self): + head = self.snake[0] + if self.food == head: + self.new_food() + self.grow_snake = True + self.score += 1 + + def new_food(self): + x = random.randint(3, SnakeBoard.WIDTH-3) + y = random.randint(3, SnakeBoard.HEIGHT-3) + + while [x, y] == self.snake[0]: + x = random.randint(3, SnakeBoard.WIDTH-3) + y = random.randint(3, SnakeBoard.HEIGHT-3) + + self.food = [x, y] + + def check_death(self): + rip_message = '' + is_dead = False + if (self.current_x_head < 1) or (self.current_x_head > SnakeBoard.WIDTH-2) or \ + (self.current_y_head < 1) or (self.current_y_head > SnakeBoard.HEIGHT-2): + rip_message = 'Snake found wall :(' + is_dead = True + + head = self.snake[0] + for snake_i in self.snake[1:]: + if snake_i == head: + rip_message = 'Snake bit itself :(' + is_dead = True + break + + if is_dead: + self.stop(rip_message) + + def keyPressEvent(self, event): + key = event.key() + if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_A): + if self.direction != 'r': + self.direction = 'l' + + elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_D): + if self.direction != 'l': + self.direction = 'r' + + elif key in (QtCore.Qt.Key_Down, QtCore.Qt.Key_S): + if self.direction != 'u': + self.direction = 'd' + + elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_W): + if self.direction != 'd': + self.direction = 'u' + + else: + return super().keyPressEvent(event) + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + + rect = self.contentsRect() + boardtop = rect.bottom() - SnakeBoard.HEIGHT * self.cellheight + boardleft = rect.left() + + for pos in self.snake: + self.draw_circle(painter, + int(boardleft+pos[0]*self.cellwidth), + int(boardtop+pos[1]*self.cellheight), + 'blue') + if self.food is not None: + self.draw_square(painter, + int(boardleft+self.food[0]*self.cellwidth), + int(boardtop+self.food[1]*self.cellheight), + 'orange') + + +class TetrisBoard(Board): + WIDTH = 10 + HEIGHT = 20 + SPEED = 300 + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self._shapes = { + 1: ZShape, + 2: SShape, + 3: Line, + 4: TShape, + 5: Square, + 6: MirrorL + } + + self._init_game() + + def _init_game(self): + self.curr_x = 0 + self.curr_y = 0 + self.curr_piece = None + self.board = np.zeros((TetrisBoard.WIDTH, TetrisBoard.HEIGHT + 2), dtype=int) + + def next_move(self): + if self.curr_piece is None: + self.make_new_piece() + else: + self.move_down() + + def try_move(self, piece, x, y): + if piece is None: + return False + + if x+piece.x.min() < 0 or x+piece.x.max() >= TetrisBoard.WIDTH: + return False + + if y-piece.y.max() < 0 or y-piece.y.min() >= TetrisBoard.HEIGHT+2: + return False + + if np.any(self.board[piece.x+x, y-piece.y]) != 0: + return False + + if piece != self.curr_piece: + self.curr_piece = piece + + self.curr_x = x + self.curr_y = y + + self.update() + + return True + + def make_new_piece(self): + new_piece = self._shapes[random.randint(1, len(self._shapes))]() + + startx = TetrisBoard.WIDTH//2 + starty = TetrisBoard.HEIGHT+2 - 1 + new_piece.y.min() + if not self.try_move(new_piece, startx, starty): + self.stop('Game over :(') + + def move_down(self): + if not self.try_move(self.curr_piece, self.curr_x, self.curr_y-1): + self.final_destination_reached() + + def drop_to_bottom(self): + new_y = self.curr_y + + while new_y > 0: + if not self.try_move(self.curr_piece, self.curr_x, new_y-1): + break + new_y -= 1 + + self.final_destination_reached() + + def final_destination_reached(self): + x = self.curr_x+self.curr_piece.x + y = self.curr_y-self.curr_piece.y + self.board[x, y] = next(k for k, v in self._shapes.items() if isinstance(self.curr_piece, v)) + + self.remove_lines() + + self.curr_piece = None + self.make_new_piece() + + def remove_lines(self): + full_rows = np.where(np.all(self.board, axis=0))[0] + num_rows = len(full_rows) + + if num_rows: + temp = np.zeros_like(self.board) + temp[:, :temp.shape[1]-num_rows] = np.delete(self.board, full_rows, axis=1) + self.board = temp + + self.score += num_rows + self.new_status.emit(f'Lines: {self.score}') + + if self.score % 10 == 0: + self._speed += 0.9 + self.timer.setInterval(int(self._speed)) + + self.update() + + def keyPressEvent(self, event): + key = event.key() + + if self.curr_piece is None: + return super().keyPressEvent(event) + + if key == QtCore.Qt.Key_Left: + self.try_move(self.curr_piece, self.curr_x-1, self.curr_y) + + elif key == QtCore.Qt.Key_Right: + self.try_move(self.curr_piece, self.curr_x+1, self.curr_y) + + elif key == QtCore.Qt.Key_Down: + if not self.try_move(self.curr_piece.rotate(), self.curr_x, self.curr_y): + self.curr_piece.rotate(clockwise=False) + + elif key == QtCore.Qt.Key_Up: + if not self.try_move(self.curr_piece.rotate(clockwise=False), self.curr_x, self.curr_y): + self.curr_piece.rotate() + + elif key == QtCore.Qt.Key_Space: + self.drop_to_bottom() + + else: + super().keyPressEvent(event) + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + rect = self.contentsRect() + board_top = rect.bottom() - TetrisBoard.HEIGHT*self.cellheight + + for i in range(TetrisBoard.WIDTH): + for j in range(TetrisBoard.HEIGHT): + shape = self.board[i, j] + + if shape: + color = self._shapes[shape].color + self.draw_square(painter, + rect.left() + i*self.cellwidth, + board_top + (TetrisBoard.HEIGHT-j-1)*self.cellheight, color) + + if self.curr_piece is not None: + x = self.curr_x + self.curr_piece.x + y = self.curr_y - self.curr_piece.y + + for i in range(4): + if TetrisBoard.HEIGHT < y[i]+1: + continue + + self.draw_square(painter, rect.left() + x[i] * self.cellwidth, + board_top + (TetrisBoard.HEIGHT-y[i]-1) * self.cellheight, + self.curr_piece.color) + + +class Tetromino: + SHAPE = np.array([[0], [0]]) + color = None + + def __init__(self): + self.shape = self.SHAPE + + def rotate(self, clockwise: bool = True): + if clockwise: + self.shape = np.vstack((-self.shape[1], self.shape[0])) + else: + self.shape = np.vstack((self.shape[1], -self.shape[0])) + + return self + + @property + def x(self): + return self.shape[0] + + @property + def y(self): + return self.shape[1] + + +class ZShape(Tetromino): + SHAPE = np.array([[0, 0, -1, -1], + [-1, 0, 0, 1]]) + color = 'green' + + +class SShape(Tetromino): + SHAPE = np.array([[0, 0, 1, 1], + [-1, 0, 0, 1]]) + color = 'purple' + + +class Line(Tetromino): + SHAPE = np.array([[0, 0, 0, 0], + [-1, 0, 1, 2]]) + color = 'red' + + +class TShape(Tetromino): + SHAPE = np.array([[-1, 0, 1, 0], + [0, 0, 0, 1]]) + color = 'orange' + + +class Square(Tetromino): + SHAPE = np.array([[0, 1, 0, 1], + [0, 0, 1, 1]]) + color = 'yellow' + + +class LShape(Tetromino): + SHAPE = np.array([[-1, 0, 0, 0], + [1, -1, 0, 1]]) + color = 'blue' + + +class MirrorL(Tetromino): + SHAPE = np.array([[1, 0, 0, 0], + [-1, -1, 0, 1]]) + color = 'lightGray' + + +if __name__ == '__main__': + app = QtWidgets.QApplication([]) + tetris = Game('snake') + tetris.show() + sys.exit(app.exec_()) diff --git a/nmreval/gui_qt/lib/styles.py b/nmreval/gui_qt/lib/styles.py new file mode 100644 index 0000000..d453f50 --- /dev/null +++ b/nmreval/gui_qt/lib/styles.py @@ -0,0 +1,102 @@ +from . import HAS_IMPORTLIB_RESOURCE +from ..Qt import QtGui, QtWidgets + + +class DarkPalette(QtGui.QPalette): + def __init__(self): + super().__init__() + + self.setColor(QtGui.QPalette.Base, QtGui.QColor(42, 42, 42)) + self.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(66, 66, 66)) + self.setColor(QtGui.QPalette.Window, QtGui.QColor(93, 93, 93)) + self.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(93, 93, 93)) + self.setColor(QtGui.QPalette.Button, QtGui.QColor(93, 93, 93)) + + self.setColor(QtGui.QPalette.WindowText, QtGui.QColor(220, 220, 220)) + self.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(220, 220, 220)) + self.setColor(QtGui.QPalette.Text, QtGui.QColor(220, 220, 220)) + self.setColor(QtGui.QPalette.BrightText, QtGui.QColor(220, 220, 220)) + self.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(220, 220, 220)) + self.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(220, 220, 220)) + + self.setColor(QtGui.QPalette.Shadow, QtGui.QColor(20, 20, 20)) + self.setColor(QtGui.QPalette.Dark, QtGui.QColor(35, 35, 35)) + + self.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218)) + self.setColor(QtGui.QPalette.Link, QtGui.QColor(220, 220, 220)) + self.setColor(QtGui.QPalette.LinkVisited, QtGui.QColor(108, 180, 218)) + + # disabled + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, QtGui.QColor(80, 80, 80)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Window, QtGui.QColor(80, 80, 80)) + + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(127, 127, 127)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(127, 127, 127)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, QtGui.QColor(127, 127, 127)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(127, 127, 127)) + + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, QtGui.QColor(80, 80, 80)) + + +class LightPalette(QtGui.QPalette): + def __init__(self): + super().__init__() + + self.setColor(QtGui.QPalette.Base, QtGui.QColor(237, 237, 237)) + self.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(225, 225, 225)) + self.setColor(QtGui.QPalette.Window, QtGui.QColor(240, 240, 240)) + self.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(240, 240, 240)) + self.setColor(QtGui.QPalette.Button, QtGui.QColor(240, 240, 240)) + + self.setColor(QtGui.QPalette.WindowText, QtGui.QColor(0, 0, 0)) + self.setColor(QtGui.QPalette.Text, QtGui.QColor(0, 0, 0)) + self.setColor(QtGui.QPalette.BrightText, QtGui.QColor(0, 0, 0)) + self.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(0, 0, 0)) + self.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(0, 0, 0)) + self.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(0, 0, 0)) + + self.setColor(QtGui.QPalette.Shadow, QtGui.QColor(20, 20, 20)) + self.setColor(QtGui.QPalette.Dark, QtGui.QColor(225, 225, 225)) + + self.setColor(QtGui.QPalette.Highlight, QtGui.QColor(218, 66, 42)) + self.setColor(QtGui.QPalette.Link, QtGui.QColor(0, 162, 232)) + self.setColor(QtGui.QPalette.LinkVisited, QtGui.QColor(222, 222, 222)) + + # disabled + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Base, QtGui.QColor(115, 115, 115)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Window, QtGui.QColor(115, 115, 115)) + + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, QtGui.QColor(115, 115, 115)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text, QtGui.QColor(115, 115, 115)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, QtGui.QColor(115, 115, 115)) + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, QtGui.QColor(115, 115, 115)) + + self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Highlight, QtGui.QColor(190, 190, 190)) + + +class MyProxyStyle(QtWidgets.QProxyStyle): + def __init__(self, color): + super().__init__() + + if color == 'dark': + self._palette = DarkPalette() + else: + self._palette = LightPalette() + + def polish(self, obj): + if isinstance(obj, QtGui.QPalette): + return self._palette + + elif isinstance(obj, QtWidgets.QApplication): + if HAS_IMPORTLIB_RESOURCE: + from importlib.resources import path + with path('resources.icons', 'style.qss') as fp: + with fp.open('r') as f: + obj.setStyleSheet(f.read()) + else: + from pkg_resources import resource_filename + with open(resource_filename('resources.icons', 'style.qss'), 'r') as f: + obj.setStyleSheet(f.read()) + + else: + return super().polish(obj) diff --git a/nmreval/gui_qt/lib/tables.py b/nmreval/gui_qt/lib/tables.py new file mode 100644 index 0000000..15195c4 --- /dev/null +++ b/nmreval/gui_qt/lib/tables.py @@ -0,0 +1,31 @@ +from ..Qt import QtWidgets, QtGui, QtCore + + +class TreeWidget(QtWidgets.QTreeWidget): + def keyPressEvent(self, evt: QtGui.QKeyEvent): + if evt.key() == QtCore.Qt.Key_Space: + sets = [] + from_parent = [] + + for idx in self.selectedIndexes(): + if idx.column() != 0: + continue + item = self.itemFromIndex(idx) + + if item.parent() is None: + is_selected = item.checkState(0) + self.blockSignals(True) + for i in range(item.childCount()): + child = item.child(i) + # child.setCheckState(0, is_selected) + from_parent.append(child) + self.blockSignals(False) + item.setCheckState(0, QtCore.Qt.Unchecked if is_selected == QtCore.Qt.Checked else QtCore.Qt.Checked) + else: + sets.append(item) + for it in sets: + if it in from_parent: + continue + it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked) + else: + super().keyPressEvent(evt) \ No newline at end of file diff --git a/nmreval/gui_qt/lib/undos.py b/nmreval/gui_qt/lib/undos.py new file mode 100644 index 0000000..d2231af --- /dev/null +++ b/nmreval/gui_qt/lib/undos.py @@ -0,0 +1,245 @@ +import copy + +from numpy import argsort + +from ..Qt import QtWidgets, QtCore +from ..data.container import FitContainer +from ..graphs.graphwindow import QGraphWindow + + +class ApodizationCommand(QtWidgets.QUndoCommand): + def __init__(self, data, apod_values: list, apod_func: object): + super().__init__('Apodization') + + self.__data = data + self.__y = copy.deepcopy(data.data.y) + self.__apod_func = apod_func + self.__apod_values = apod_values + + def undo(self): + # doing a copy (again) to ensure two different objects + self.__data.y = copy.deepcopy(self.__y) + + def redo(self): + self.__data.apply('ap', (self.__apod_values, self.__apod_func)) + + +class CutCommand(QtWidgets.QUndoCommand): + def __init__(self, data, *limits): + super().__init__('Apodization') + + self.__data = data + self.__data_data = copy.deepcopy(data.data) + self.__limits = limits + + def undo(self): + # doing a copy (again) to ensure two different objects + self.__data.data = copy.deepcopy(self.__data_data) + + def redo(self): + self.__data.apply('cut', self.__limits) + + +class PhaseCommand(QtWidgets.QUndoCommand): + def __init__(self, data, ph0: float, ph1: float, pvt: float): + super().__init__('Phase correction') + + self.__phase = (ph0, ph1, pvt) + self.__data = data + + def undo(self): + self.__data.apply('ph', (-self.__phase[0], -self.__phase[1], self.__phase[2])) + + def redo(self): + self.__data.apply('ph', self.__phase) + + +class ShiftCommand(QtWidgets.QUndoCommand): + def __init__(self, data, value, mode): + super().__init__('Fourier') + + self.__data = data + self.__original = copy.deepcopy(self.__data.data) + self.__args = (value, mode) + + def undo(self): + self.__data.data = copy.deepcopy(self.__original) + + def redo(self): + self.__data.apply('ls', self.__args) + + +class NormCommand(QtWidgets.QUndoCommand): + def __init__(self, data, mode): + super().__init__('Normalize') + + self.__data = data + self.__mode = mode + self.__scale = 1. + + def undo(self): + self.__data.y *= self.__scale + self.__data.y_err *= self.__scale + + def redo(self): + max_value = self.__data.y.max() + self.__data.apply('norm', (self.__mode,)) + self.__scale = max_value / self.__data.y.max() + + +class CenterCommand(QtWidgets.QUndoCommand): + def __init__(self, data): + super().__init__('Normalize') + + self.__data = data + self.__offset = 0. + + def undo(self): + _x = self.__data.data.x + _x += self.__offset + self.__data.x = _x + + def redo(self): + x0 = self.__data.x[0] + self.__data.apply('center', ()) + self.__offset = x0 - self.__data.x[0] + + +class ZerofillCommand(QtWidgets.QUndoCommand): + def __init__(self, data): + super().__init__('Zero filling') + + self.__data = data + + def undo(self): + self.__data.apply('zf', (-1,)) + + def redo(self): + self.__data.apply('zf', (1,)) + + +class BaselineCommand(QtWidgets.QUndoCommand): + def __init__(self, data): + super().__init__('Baseline correction') + + self.__baseline = None + self.__data = data + + def undo(self): + self.__data.y += self.__baseline + + def redo(self): + y_prev = self.__data.y[-1] + self.__data.apply('bl', tuple()) + self.__baseline = y_prev - self.__data.y[-1] + + +class BaselineSplineCommand(QtWidgets.QUndoCommand): + def __init__(self, data, baseline): + super().__init__('Baseline correction') + + self.__baseline = baseline + self.__data = data + + def undo(self): + self.__data.apply('bls', (-self.__baseline,)) + + def redo(self): + self.__data.apply('bls', (self.__baseline,)) + + +class FourierCommand(QtWidgets.QUndoCommand): + def __init__(self, data): + super().__init__('Fourier') + + self.__data = data + self.__original = copy.deepcopy(self.__data.data) + + def undo(self): + self.__data.data = copy.deepcopy(self.__original) + + def redo(self): + self.__data.apply('ft', tuple()) + + +class SortCommand(QtWidgets.QUndoCommand): + def __init__(self, data): + super().__init__('Sort') + + self.__data = data + self.__sort = None + + def undo(self): + self.__data.unsort(self.__sort) + + def redo(self): + self.__sort = argsort(argsort(self.__data.data.x)) + self.__data.apply('sort', tuple()) + + +class DeleteGraphCommand(QtWidgets.QUndoCommand): + def __init__(self, container: dict, key: str, + signal1: QtCore.pyqtSignal, signal2: QtCore.pyqtSignal): + super().__init__('Delete graph') + # Deletion of GraphWindow is more complicated because C++ object is destroyed + + self.__container = container + _value = self.__container[key] + self.__value = self.__container[key].get_state() + self.__key = key + self.__signal_add = signal1 + self.__signal_remove = signal2 + + def redo(self): + self.__signal_remove.emit(self.__key) + del self.__container[self.__key] + + def undo(self): + q = QGraphWindow().set_state(self.__value) + self.__container[self.__key] = q + self.__signal_add.emit(self.__key) + + +class DeleteCommand(QtWidgets.QUndoCommand): + def __init__(self, container, key, signal1, signal2): + super().__init__('Delete data') + + self.__container = container + self.__value = self.__container[key] + self.__key = key + self.__signal_add = signal1 + self.__signal_remove = signal2 + + def redo(self): + self.__signal_remove.emit(self.__key) + if isinstance(self.__value, FitContainer): + try: + self.__container[self.__value.fitted_key]._fits.remove(self.__key) + except KeyError: + pass + del self.__container[self.__key] + + def undo(self): + self.__container[self.__key] = self.__value + if isinstance(self.__value, FitContainer): + try: + self.__container[self.__value.fitted_key]._fits.append(self.__key) + except KeyError: + pass + + self.__signal_add.emit([self.__key], self.__value.graph) + + +class EvalCommand(QtWidgets.QUndoCommand): + def __init__(self, container: dict, key: str, new_data, title: str): + super().__init__(title) + self.__container = container + self.__value = copy.deepcopy(self.__container[key].data) + self.__replacement = new_data + self.__key = key + + def redo(self): + self.__container[self.__key].data = self.__replacement + + def undo(self): + self.__container[self.__key].data = self.__value diff --git a/nmreval/gui_qt/lib/usermodeleditor.py b/nmreval/gui_qt/lib/usermodeleditor.py new file mode 100644 index 0000000..ab02c45 --- /dev/null +++ b/nmreval/gui_qt/lib/usermodeleditor.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +from pathlib import Path + +from ..Qt import QtWidgets, QtCore, QtGui +from ..lib.codeeditor import CodeEditor + + +class QUsermodelEditor(QtWidgets.QMainWindow): + modelsChanged = QtCore.pyqtSignal() + + def __init__(self, fname: str | Path = None, parent=None): + super().__init__(parent=parent) + + self._init_gui() + + self.fname = None + self._dir = None + if fname is not None: + self.read_file(fname) + + def _init_gui(self): + self.centralwidget = QtWidgets.QWidget(self) + + layout = QtWidgets.QVBoxLayout(self.centralwidget) + layout.setContentsMargins(3, 3, 3, 3) + layout.setSpacing(3) + + self.edit_field = CodeEditor(self.centralwidget) + font = QtGui.QFont('default') + font.setStyleHint(font.Monospace) + font.setPointSize(10) + self.edit_field.setFont(font) + + layout.addWidget(self.edit_field) + self.setCentralWidget(self.centralwidget) + + self.statusbar = QtWidgets.QStatusBar(self) + self.setStatusBar(self.statusbar) + + self.menubar = self.menuBar() + + self.menuFile = QtWidgets.QMenu('File', self.menubar) + self.menubar.addMenu(self.menuFile) + + self.menuFile.addAction('Open...', self.open_file, QtGui.QKeySequence.Open) + self.menuFile.addAction('Save', self.overwrite_file, QtGui.QKeySequence.Save) + self.menuFile.addAction('Save as...', self.save_file, QtGui.QKeySequence('Ctrl+Shift+S')) + self.menuFile.addSeparator() + self.menuFile.addAction('Close', self.close, QtGui.QKeySequence.Quit) + + self.resize(800, 600) + self.setGeometry(QtWidgets.QStyle.alignedRect( + QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, + self.size(), QtWidgets.qApp.desktop().availableGeometry() + )) + + @property + def is_modified(self): + return self.edit_field.document().isModified() + + @is_modified.setter + def is_modified(self, val: bool): + self.edit_field.document().setModified(val) + + @QtCore.pyqtSlot() + def open_file(self): + overwrite = self.changes_saved + + if overwrite: + fname, _ = QtWidgets.QFileDialog.getOpenFileName(directory=str(self._dir)) + if fname: + self.read_file(fname) + + def read_file(self, fname: str | Path): + self.set_fname_opts(fname) + + with self.fname.open('r') as f: + self.edit_field.setPlainText(f.read()) + + def set_fname_opts(self, fname: str | Path): + self.fname = Path(fname) + self._dir = self.fname.parent + self.setWindowTitle('Edit ' + str(fname)) + + @property + def changes_saved(self) -> bool: + if not self.is_modified: + return True + + ret = QtWidgets.QMessageBox.question(self, 'Time to think', + '

The document was modified.

\n' + '

Do you want to save changes?

', + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | + QtWidgets.QMessageBox.Cancel) + if ret == QtWidgets.QMessageBox.Yes: + self.save_file() + + if ret == QtWidgets.QMessageBox.No: + self.is_modified = False + + return not self.is_modified + + @QtCore.pyqtSlot() + def save_file(self): + outfile, _ = QtWidgets.QFileDialog().getSaveFileName(parent=self, caption='Save file', directory=str(self._dir)) + + if outfile: + with open(outfile, 'w') as f: + f.write(self.edit_field.toPlainText()) + + self.set_fname_opts(outfile) + + self.is_modified = False + + return self.is_modified + + @QtCore.pyqtSlot() + def overwrite_file(self): + if self.fname is not None: + with self.fname.open('w') as f: + f.write(self.edit_field.toPlainText()) + + self.modelsChanged.emit() + + self.is_modified = False + + def closeEvent(self, evt: QtGui.QCloseEvent): + if not self.changes_saved: + evt.ignore() + else: + super().closeEvent(evt) + + +if __name__ == '__main__': + import sys + app = QtWidgets.QApplication([]) + + win = QUsermodelEditor('/autohome/dominik/.nmreval/myfitmodels.py') + win.show() + + sys.exit(app.exec()) + diff --git a/nmreval/gui_qt/lib/utils.py b/nmreval/gui_qt/lib/utils.py new file mode 100644 index 0000000..adba102 --- /dev/null +++ b/nmreval/gui_qt/lib/utils.py @@ -0,0 +1,93 @@ +from math import inf +from contextlib import contextmanager +from numpy import linspace +from scipy.interpolate import interp1d + +from ..Qt import QtGui, QtWidgets + + +@contextmanager +def busy_cursor(): + try: + cursor = QtGui.QCursor(QtGui.QPixmap('/autohome/dominik/Downloads/slowbro.gif')) + QtWidgets.QApplication.setOverrideCursor(cursor) + yield + + finally: + QtWidgets.QApplication.restoreOverrideCursor() + + +class SciSpinBox(QtWidgets.QDoubleSpinBox): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.validator = QtGui.QDoubleValidator(self) + + self.setMinimum(-inf) + self.setMaximum(inf) + self.setDecimals(1000) + self.precision = 0.001 + + self._prev_value = float(self.lineEdit().text()) + + def valueFromText(self, text: str) -> float: + try: + self._prev_value = float(self.cleanText()) + except ValueError: + pass + return self._prev_value + + def textFromValue(self, value: float) -> str: + if value == 0: + return '0' + else: + return f'{value:.3e}' + + def stepBy(self, step: int): + self._prev_value = self.value() + + new_value = self._prev_value + if new_value != 0.0: + new_value *= 10**(step/19.) + else: + new_value = 0.001 + + self.setValue(new_value) + self.lineEdit().setText(f'{new_value:.3e}') + + def validate(self, text, pos): + return self.validator.validate(text, pos) + + +class RdBuCMap: + # taken from Excel sheet from colorbrewer.org + _rdbu = [ + (103, 0, 31), + (178, 24, 43), + (214, 96, 77), + (244, 165, 130), + (253, 219, 199), + (247, 247, 247), + (209, 229, 240), + (146, 197, 222), + (67, 147, 195), + (33, 102, 172), + (5, 48, 97) + ] + + def __init__(self, vmin=-1., vmax=1.): + self.min = vmin + self.max = vmax + + self.spline = [interp1d(linspace(self.max, self.min, num=11), [rgb[i] for rgb in RdBuCMap._rdbu]) + for i in range(3)] + + def color(self, val: float): + if val > self.max: + col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[0]) + elif val < self.min: + col = QtGui.QColor.fromRgb(*RdBuCMap._rdbu[-1]) + else: + col = QtGui.QColor.fromRgb(*(float(self.spline[i](val)) for i in range(3))) + + return col diff --git a/nmreval/gui_qt/main/__init__.py b/nmreval/gui_qt/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nmreval/gui_qt/main/mainwindow.py b/nmreval/gui_qt/main/mainwindow.py new file mode 100644 index 0000000..2747138 --- /dev/null +++ b/nmreval/gui_qt/main/mainwindow.py @@ -0,0 +1,922 @@ +import pathlib +from pathlib import Path +from typing import List, Tuple + +from numpy import geomspace, linspace +from pyqtgraph import ViewBox, PlotDataItem + +from .management import UpperManagement +from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets +from ..data.shift_graphs import QShift +from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog +from ..fit.result import QFitResult +from ..graphs.graphwindow import QGraphWindow +from ..graphs.movedialog import QMover +from ..io.fcbatchreader import QFCReader +from ..io.filedialog import OpenFileDialog, SaveDirectoryDialog +from ..lib import get_icon, make_action_icons +from ..lib.pg_objects import RegionItem +from ..math.evaluation import QEvalDialog +from ..math.interpol import InterpolDialog +from ..math.mean_dialog import QMeanTimes +from ..math.smooth import QSmooth +from ..nmr.coupling_calc import QCoupCalcDialog +from ..nmr.t1_from_tau import QRelaxCalc +from .._py.basewindow import Ui_BaseWindow +from ...configs import * + + +class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow): + closeSignal = QtCore.pyqtSignal() + openpoints = QtCore.pyqtSignal(dict) + save_ses_sig = QtCore.pyqtSignal(str) + rest_ses_sig = QtCore.pyqtSignal(str) + + def __init__(self, parents=None, path=None): + super().__init__(parent=parents) + + if path is None: + self.path = Path.home() + else: + self.path = Path(path) + + self.read_state() + + self.management = UpperManagement(self) + + self.fitlimitvalues = [None, None] + self.fitpreview = [] + self._fit_plot_id = None + self.savefitdialog = None + self.eval = None + self.editor = None + + self.movedialog = QMover(self) + + self.current_graph_widget = None + self.current_plotitem = None + self._block_window_change = False + + self.fname = None + self.tim = QtCore.QTimer() + + self.settings = QtCore.QSettings('NMREVal', 'settings') + self._init_gui() + self._init_signals() + + def _init_gui(self): + self.setupUi(self) + make_action_icons(self) + self.setWindowIcon(get_icon('logo')) + + self.norm_toolbutton = QtWidgets.QToolButton(self) + self.norm_toolbutton.setMenu(self.menuNormalize) + self.norm_toolbutton.setPopupMode(self.norm_toolbutton.InstantPopup) + self.norm_toolbutton.setIcon(get_icon('normal')) + self.toolbar_edit.addWidget(self.norm_toolbutton) + + self.fitlim_button = QtWidgets.QToolButton(self) + self.fitlim_button.setMenu(self.menuLimits) + self.fitlim_button.setPopupMode(self.fitlim_button.InstantPopup) + self.fitlim_button.setIcon(get_icon('fit_region')) + self.toolBar_fit.addWidget(self.fitlim_button) + + self.area.dragEnterEvent = self.dragEnterEvent + + while self.tabWidget.count() > 2: + self.tabWidget.removeTab(self.tabWidget.count()-1) + + # Prevent closing "data" and "values" + for i in [0, 1]: + self.tabWidget.tabBar().tabButton(i, QtWidgets.QTabBar.ButtonPosition.RightSide).resize(0, 0) + + self.setAcceptDrops(True) + + self.mousepos = QtWidgets.QLabel('') + self.status = QtWidgets.QLabel('') + self.statusBar.addWidget(self.status) + self.statusBar.addWidget(self.mousepos) + + self.fitregion = RegionItem() + self._values_plot = PlotDataItem(x=[], y=[], symbolSize=30, symbol='x', + pen=None, symbolPen='#d526b5', symbolBrush='#d526b5') + + self._fit_plot_id = None + + self.setGeometry(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, + self.size(), QtWidgets.qApp.desktop().availableGeometry())) + + self.datawidget.management = self.management + + self.ac_group = QtWidgets.QActionGroup(self) + self.ac_group.addAction(self.action_lm_fit) + self.ac_group.addAction(self.action_nm_fit) + self.ac_group.addAction(self.action_odr_fit) + + self.ac_group2 = QtWidgets.QActionGroup(self) + self.ac_group2.addAction(self.action_no_range) + self.ac_group2.addAction(self.action_x_range) + self.ac_group2.addAction(self.action_custom_range) + + def _init_signals(self): + self.actionRedo = self.management.undostack.createRedoAction(self) + icon = QtGui.QIcon.fromTheme("edit-redo") + self.actionRedo.setIcon(icon) + self.actionRedo.setShortcuts(QtGui.QKeySequence.Redo) + self.menuData.insertAction(self.action_new_set, self.actionRedo) + + self.actionUndo = self.management.undostack.createUndoAction(self) + self.actionUndo.setShortcuts(QtGui.QKeySequence.Undo) + icon = QtGui.QIcon.fromTheme("edit-undo") + self.actionUndo.setIcon(icon) + 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.ac_group2.triggered.connect(self.change_fit_limits) + + self.t1action.triggered.connect(lambda: self._show_tab('t1_temp')) + self.action_edit.triggered.connect(lambda: self._show_tab('signal')) + self.actionPick_position.triggered.connect(lambda: self._show_tab('pick')) + self.actionIntegrate.triggered.connect(lambda: self._show_tab('integrate')) + self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit')) + + self.action_new_set.triggered.connect(self.management.create_empty) + + self.datawidget.keyChanged.connect(self.management.change_keys) + self.datawidget.tree.deleteItem.connect(self.management.delete_sets) + self.datawidget.tree.moveItem.connect(self.management.move_sets) + self.datawidget.tree.copyItem.connect(self.management.copy_sets) + self.datawidget.graph_toolButton.clicked.connect(self.new_graph) + self.datawidget.empty_toolButton.clicked.connect(self.management.create_empty) + self.datawidget.func_toolButton.clicked.connect(self.make_data_from_function) + self.datawidget.tree.stateChanged.connect(self.management.change_visibility) + self.datawidget.startShowProperty.connect(self.management.get_properties) + self.datawidget.propertyChanged.connect(self.management.update_property) + self.datawidget.tree.saveFits.connect(self.save_fit_parameter) + + self.management.newData.connect(self.show_new_data) + self.management.newGraph.connect(self.new_graph) + self.management.dataChanged.connect(self.update_data) + self.management.deleteData.connect(self.delete_data) + self.management.deleteGraph.connect(self.remove_graph) + self.management.restoreGraph.connect(self.set_graph) + self.management.properties_collected.connect(self.datawidget.set_properties) + self.management.unset_state.connect(lambda x: self.datawidget.uncheck_sets(x)) + self.management.fitFinished.connect(self.show_fit_results) + + self.fit_dialog._management = self.management + self.fit_dialog.preview_emit.connect(self.show_fit_preview) + self.fit_dialog.fitStartSig.connect(self.start_fit) + self.fit_dialog.abortFit.connect(lambda : self.management.stopFit.emit()) + + self.movedialog.moveData.connect(self.move_sets) + self.movedialog.copyData.connect(self.management.copy_sets) + + self.ptsselectwidget.points_selected.connect(self.management.extract_points) + + self.t1tauwidget.newData.connect(self.management.add_new_data) + + self.editsignalwidget.do_something.connect(self.management.apply) + self.editsignalwidget.preview_triggered.connect(self.do_preview) + + self.action_sort_pts.triggered.connect(lambda: self.management.apply('sort', ())) + self.action_calc_eps_derivative.triggered.connect(self.management.bds_deriv) + self.action_magnitude.triggered.connect(self.management.calc_magn) + self.actionCenterMax.triggered.connect(lambda: self.management.apply('center', ())) + + self.valuewidget.requestData.connect(self.show_data_values) + self.valuewidget.itemChanged.connect(self.management.set_values) + self.valuewidget.itemDeleted.connect(self.management.remove_values) + self.valuewidget.itemAdded.connect(self.management.append) + self.valuewidget.maskSignal.connect(self.management.mask_value) + self.valuewidget.values_selected.connect(self.plot_selected_values) + + self.actionMaximize.triggered.connect(lambda: self.current_graph_widget.showMaximized()) + self.actionNext_window.triggered.connect(lambda: self.area.activateNextSubWindow()) + self.actionPrevious.triggered.connect(lambda: self.area.activatePreviousSubWindow()) + + self.closeSignal.connect(self.close) + + self.action_norm_max.triggered.connect(lambda: self.management.apply('norm', ('max',))) + self.action_norm_max_abs.triggered.connect(lambda: self.management.apply('norm', ('maxabs',))) + self.action_norm_first.triggered.connect(lambda: self.management.apply('norm', ('first',))) + self.action_norm_last.triggered.connect(lambda: self.management.apply('norm', ('last',))) + self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',))) + self.action_cut.triggered.connect(lambda: self.management.cut()) + + self.actionConcatenate_sets.triggered.connect(lambda : self.management.cat()) + + @QtCore.pyqtSlot(name='on_action_open_triggered') + def open(self): + filedialog = OpenFileDialog(directory=self.path, caption='Open files', + filters='All files (*.*);;' + 'Program session (*.nmr);;' + 'HDF files (*.h5);;' + 'Text files (*.txt *.dat);;' + 'Novocontrol Alpha (*.EPS);;' + 'TecMag files (*.tnt);;' + 'Grace files (*.agr)') + + filedialog.set_graphs(self.management.graphs.list()) + + filedialog.exec() + fname = filedialog.selectedFiles() + + if fname: + self.path = Path(fname[0]).parent + self.management.load_files(fname, new_plot=filedialog.add_to_graph) + + @QtCore.pyqtSlot(name='on_actionOpen_FC_triggered') + def read_fc(self): + reader = QFCReader(path=self.path, parent=self) + reader.data_read.connect(self.management.add_new_data) + reader.exec() + + del reader + + @QtCore.pyqtSlot(name='on_actionPrint_triggered') + def print(self): + QtPrintSupport.QPrintDialog().exec() + + @QtCore.pyqtSlot(name='on_actionExportData_triggered') + @QtCore.pyqtSlot(name='on_actionSave_triggered') + def save(self): + save_dialog = SaveDirectoryDialog( + directory=str(self.path), parent=self, + ) + + mode = save_dialog.exec() + if mode == QtWidgets.QDialog.Accepted: + path = save_dialog.selectedFiles() + selected_filter = save_dialog.selectedNameFilter() + + if path: + self.management.save(path[0], selected_filter) + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(list) + def save_fit_parameter(self, fit_sets: List[str] = None): + fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save fit parameter', directory=str(self.path), + filter='All files(*, *);;Text files(*.dat *.txt)') + + if fname: + self.management.save_fit_parameter(fname, fit_sets=fit_sets) + + @QtCore.pyqtSlot(name='on_actionExportGraphic_triggered') + def export_graphic(self): + self.current_graph_widget.export() + + @QtCore.pyqtSlot(name='on_actionNew_window_triggered') + def new_graph(self): + w = QGraphWindow() + self.management.graphs[w.id] = w + self.set_graph(w.id) + + if self.eval is not None: + self.eval.set_graphs(self.management.graphs.list()) + + return w.id + + @QtCore.pyqtSlot(list, str) + def show_new_data(self, sets: list, graph: str): + if len(sets) == 0: + return + + if graph == '': + graph = self.new_graph() + + self.management.plots_to_graph(sets, graph) + + for idd in sets: + new_item = self.management[idd] + self.datawidget.blockSignals(True) + self.datawidget.add_item(new_item.id, new_item.name, graph) + self.datawidget.blockSignals(False) + + if graph == self.fit_dialog.connected_figure: + self.fit_dialog.load(self.management.graphs.active(graph)) + + @QtCore.pyqtSlot(name='on_actionDelete_window_triggered') + def delete_windows(self): + self.management.delete_sets() + + @QtCore.pyqtSlot(str) + def remove_graph(self, gid: str): + print(gid, self.current_graph_widget) + self.datawidget.remove_item(gid) + w = None + for w in self.area.subWindowList(): + wdgt = w.widget() + if wdgt.id == gid: + wdgt.disconnect() + if wdgt == self.current_graph_widget: + if self.ptsselectwidget.connected_figure == gid: + self.ptsselectwidget.connected_figure = None + self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget)) + + if self.t1tauwidget.connected_figure == gid: + self.t1tauwidget.connected_figure = None + self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget)) + + if self.fit_dialog.connected_figure == gid: + self.fit_dialog.connected_figure = None + for item in self.fit_dialog.preview_lines: + self.current_graph_widget.remove_external(item) + + if self.valuewidget.graph_shown == gid: + self.tabWidget.setCurrentIndex(0) + + self.current_graph_widget = None + self.management.current_graph = '' + self.current_plotitem = None + + break + + if w is not None: + w.close() + + if self.current_graph_widget is None: + self.area.activateNextSubWindow() + + @QtCore.pyqtSlot(str) + def set_graph(self, key: str): + w = self.management.graphs[key] + + subwindow = self.area.addSubWindow(w) + subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandMove, True) + subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandResize, True) + subwindow.setMinimumHeight(400) + subwindow.setMinimumWidth(600) + + self.datawidget.blockSignals(True) + self.datawidget.tree.blockSignals(True) + self.datawidget.add_graph(w.id, w.title) + self.datawidget.tree.blockSignals(False) + self.datawidget.blockSignals(False) + + w.mousePositionChanged.connect(self.mousemoved) + w.aboutToClose.connect(self.delete_windows) + w.positionClicked.connect(self.point_selected) + w.show() + + graph_list = self.management.graphs.list() + self.t1tauwidget.set_graphs(graph_list) + self.ptsselectwidget.set_graphs(graph_list) + + @QtCore.pyqtSlot(QtWidgets.QMdiSubWindow, name='on_area_subWindowActivated') + 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""" + if wd is not None: + if self.current_graph_widget is not None: + self.current_graph_widget.closable = True + + if self.ptsselectwidget.isVisible(): + self._select_ptswidget(False, False, False) + + 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 + + pick = False + block = False + if self.ptsselectwidget.isVisible(): + pick, block = self._select_ptswidget(True, pick, block) + if self.fit_dialog.isVisible(): + block = self._select_fitwidget(True, block) + + 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_actionCascade_windows_triggered') + @QtCore.pyqtSlot(name='on_actionTile_triggered') + def change_window_size(self): + if self.sender() == self.actionCascade_windows: + self.area.cascadeSubWindows() + elif self.sender() == self.actionTile: + self.area.tileSubWindows() + + @QtCore.pyqtSlot(name='on_actionChange_datatypes_triggered') + def type_change_dialog(self): + from ..data.conversion import ConversionDialog + + dialog = ConversionDialog(self) + dialog.set_graphs(self.management.graphs.tree()) + dialog.convertSets.connect(self.management.convert_sets) + + _ = dialog.exec() + + dialog.disconnect() + + def _set_pick_block(self, pick: bool, block: bool): + self.current_graph_widget.enable_picking(pick) + self.current_graph_widget.closable = not block + + @QtCore.pyqtSlot(int, name='on_tabWidget_currentChanged') + def toggle_tabs(self, idx: int): + widget = self.tabWidget.widget(idx) + if self.current_graph_widget is None: + if self.tabWidget.currentIndex() > 1: + self.tabWidget.removeTab(self.tabWidget.indexOf(widget)) + self.tabWidget.setCurrentIndex(0) + return + + if self.current_graph_widget is not None: + self.current_graph_widget.enable_picking(False) + + pick_required, block_window = self._select_ptswidget(widget == self.ptsselectwidget, False, False) + self._select_valuewidget(widget == self.valuewidget) + 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) + + self._set_pick_block(pick_required, block_window) + + def _select_ptswidget(self, onoff: bool, pick_required: bool, block_window: bool) -> Tuple[bool, bool]: + if self.current_graph_widget is None: + return pick_required, block_window + + if onoff: # point selection + for line in self.ptsselectwidget.pts_lines: + self.current_graph_widget.add_external(line) + self.ptsselectwidget.point_removed.connect(self.current_graph_widget.remove_external) + self.ptsselectwidget.connected_figure = self.management.current_graph + pick_required = True + else: + if self.ptsselectwidget.connected_figure: + g = self.management.graphs[self.ptsselectwidget.connected_figure] + for line in self.ptsselectwidget.pts_lines: + g.remove_external(line) + # self.ptsselectwidget.clear() + + return pick_required, block_window + + @QtCore.pyqtSlot(int, name='on_tabWidget_tabCloseRequested') + def close_tab(self, idx: int): + if idx == 0: + pass + else: + self.tabWidget.setCurrentIndex(0) + self.tabWidget.removeTab(idx) + + def _show_tab(self, mode: str): + widget, name = { + 't1_temp': (self.t1tauwidget, 'T1 mininmon'), + 'signal': (self.editsignalwidget, 'Signals'), + 'pick': (self.ptsselectwidget, 'Pick points'), + 'fit': (self.fit_dialog, 'Fit') + }[mode] + + for idx in range(self.tabWidget.count()): + if self.tabWidget.widget(idx) == widget: + self.tabWidget.setCurrentIndex(idx) + return + + self.tabWidget.addTab(widget, name) + self.tabWidget.setCurrentIndex(self.tabWidget.count()-1) + + def _select_valuewidget(self, onoff: bool): + if onoff: # Values + self.valuewidget(self.management.graphs.tree()) + self.valuewidget.connected_figure = self.management.current_graph + if self.valuewidget.graph_shown is not None: + self.management.graphs[self.valuewidget.graph_shown].add_external(self._values_plot) + else: + if self.valuewidget.graph_shown is not None: + self.management.graphs[self.valuewidget.graph_shown].remove_external(self._values_plot) + + def _select_integralwidget(self, onoff: bool, pick_required: bool): + if self.current_graph_widget is None: + return pick_required + + if onoff: + self.integralwidget(self.current_graph_widget.title, + self.management.graphs.current_sets(self.management.current_graph)) + self.integralwidget.item_deleted.connect(self.current_graph_widget.remove_external) + self.integralwidget.connected_figure = self.management.current_graph + pick_required = True + else: + if self.integralwidget.connected_figure: + g = self.management.graphs[self.integralwidget.connected_figure] + for line in self.integralwidget.lines: + g.remove_external(line[0]) + g.remove_external(line[1]) + self.integralwidget.clear() + + return pick_required + + def _select_t1tauwidget(self, onoff: bool, pick_required: bool, block_window: bool): + if onoff: # tau from t1 + if self.current_graph_widget is None: + return + + idx = self.tabWidget.indexOf(self.t1tauwidget) + if len(self.current_graph_widget) != 1: + QtWidgets.QMessageBox.information(self, 'Too many datasets', + 'Only one T1 curve can be evaluated at once') + self.tabWidget.removeTab(idx) + self.tabWidget.setCurrentIndex(0) + return + + self.tabWidget.setTabText(idx, 'T1 min (%s)' % {self.current_graph_widget.title}) + + self.t1tauwidget.set_graphs(self.management.graphs.list()) + self.t1tauwidget.set_data(*self.management.get_data(self.current_graph_widget.active[0], xy_only=True), + name=self.management[self.current_graph_widget.active[0]].name) + self.t1tauwidget.connected_figure = self.management.current_graph + self.current_graph_widget.add_external(self.t1tauwidget.min_pos) + self.current_graph_widget.add_external(self.t1tauwidget.parabola) + pick_required = True + block_window = True + else: + if self.t1tauwidget.connected_figure: + g = self.management.graphs[self.t1tauwidget.connected_figure] + g.remove_external(self.t1tauwidget.min_pos) + g.remove_external(self.t1tauwidget.parabola) + + return pick_required, block_window + + @QtCore.pyqtSlot(str) + def get_data(self, key: str): + self.sender().set_data(self.management[key]) + + @QtCore.pyqtSlot(name='on_actionCalculateT1_triggered') + def show_t1calc_dialog(self): + dialog = QRelaxCalc(self) + dialog.set_graphs(self.management.graphs.tree(key_only=True)) + dialog.newData.connect(self.management.calc_relaxation) + dialog.show() + + @QtCore.pyqtSlot(name='on_actionSkip_points_triggered') + def skip_pts_dialog(self): + from ..math.skipping import QSkipDialog + + dial = QSkipDialog(self) + dial.exec() + + self.management.skip_points(**dial.get_arguments()) + + @QtCore.pyqtSlot(str, name='on_action_coup_calc_triggered') + def coupling_dialog(self): + dialog = QCoupCalcDialog(self) + dialog.show() + + @QtCore.pyqtSlot(name='on_action_mean_t1_triggered') + def mean_dialog(self): + gnames = self.management.graphs.tree(key_only=True) + dialog = QMeanTimes(gnames, parent=self) + dialog.newValues.connect(self.management.calc_mean) + dialog.show() + + @QtCore.pyqtSlot(name='on_actionRunning_values_triggered') + def smooth_dialog(self): + dialog = QSmooth(parent=self) + dialog.newValues.connect(self.management.smooth_data) + dialog.show() + + @QtCore.pyqtSlot(name='on_actionInterpolation_triggered') + def interpol_dialog(self): + if self.current_graph_widget is None: + return + + gnames = self.management.graphs.tree() + dialog = InterpolDialog(parent=self) + dialog.set_data(gnames, self.current_graph_widget.id) + dialog.new_data.connect(self.management.interpolate_data) + dialog.show() + + @QtCore.pyqtSlot(name='on_action_calc_triggered') + def open_eval_dialog(self): + if self.eval is None: + self.eval = QEvalDialog(parent=self) + self.eval.do_eval.connect(self.management.eval_expression) + self.eval.do_calc.connect(self.management.create_from_function) + + self.eval.set_mode('e') + self.eval.set_namespace(self.management.get_namespace()) + self.eval.add_data(self.management.active_sets) + self.eval.set_graphs(self.management.graphs.list()) + + self.eval.exec() + + @QtCore.pyqtSlot(name='on_actionAddlines_triggered') + def make_data_from_function(self): + if self.eval is None: + self.eval = QEvalDialog(parent=self) + self.eval.do_eval.connect(self.management.eval_expression) + self.eval.do_calc.connect(self.management.create_from_function) + + self.eval.set_mode('c') + self.eval.set_namespace(self.management.get_namespace()) + self.eval.set_graphs(self.management.graphs.list()) + + self.eval.exec() + + @QtCore.pyqtSlot(name='on_actionDerivation_triggered') + @QtCore.pyqtSlot(name='on_actionIntegration_triggered') + @QtCore.pyqtSlot(name='on_actionFilon_triggered') + def int_diff_ft(self): + sets = self.management.active_sets + mode = {self.actionIntegration: 'int', + self.actionDerivation: 'diff', + self.actionFilon: 'logft'}[self.sender()] + + if sets: + from ..math.integrate_derive import QDeriveIntegrate + + dialog = QDeriveIntegrate(mode) + dialog.add_graphs(self.management.graphs.list()) + dialog.set_sets(sets) + + res = dialog.exec() + + if res: + options = dialog.get_options() + mode = options['mode'] + if mode in ['i', 'd']: + self.management.integrate(**options) + elif mode == 'l': + self.management.logft(**options) + else: + raise ValueError('Unknown mode %s, not `i`, `d`, `l`.' % str(mode)) + + @QtCore.pyqtSlot(str) + def update_data(self, sid: str): + if self.valuewidget.shown_set == sid: + self.show_data_values(sid) + + self.datawidget.set_name(sid, self.management[sid].name) + + @QtCore.pyqtSlot(str) + def delete_data(self, sid): + print('remove', sid) + if self.valuewidget.shown_set == sid: + self.tabWidget.setCurrentIndex(0) + + self.datawidget.remove_item(sid) + + @QtCore.pyqtSlot(name='on_actionBaseline_triggered') + def baseline_dialog(self): + if not self.current_graph_widget: + return + + if len(self.current_graph_widget) != 1: + QtWidgets.QMessageBox.information(self, 'Invalid number of sets', + 'Baseline correction can only applied to one set at a time.') + return + + for sid in self.current_graph_widget.active: + data_mode = self.management[sid].mode + if data_mode in ['spectrum']: + editor = QBaselineDialog(self) + editor.add_data(*self.management.get_data(sid, xy_only=True)) + editor.finished.connect(self.management.apply) + editor.exec() + + @QtCore.pyqtSlot(str) + def do_preview(self, mode): + if mode == 'ap': + dialog = QApodDialog(parent=self) + elif mode == 'ph': + dialog = QPhasedialog(parent=self) + else: + raise ValueError('Unknown preview mode %s' % str(mode)) + + for sid in self.current_graph_widget.active: + data_mode = self.management[sid].mode + tobeadded = False + if (data_mode == 'fid') or (data_mode == 'spectrum' and mode == 'ph'): + tobeadded = True + + if tobeadded: + dialog.add_data(*self.management.get_data(sid, xy_only=True)) + + if dialog.exec() == QtWidgets.QDialog.Accepted: + self.management.apply(mode, dialog.get_value()) + + @QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered') + def move_sets_dialog(self): + gnames = self.management.graphs.tree() + self.movedialog.setup(gnames) + self.movedialog.exec() + + @QtCore.pyqtSlot(list, str, str) + def move_sets(self, sets, dest, src): + self.management.move_sets(sets, dest, src) + for s in sets: + self.datawidget.tree.move_sets(s, dest, src) + + @QtCore.pyqtSlot(str) + def show_data_values(self, sid: str): + if sid == '': + return + + data, mask = self.management.get_data(sid) + self.valuewidget.set_data(data, mask) + + def plot_selected_values(self, gid: str, x: list, y: list): + self._values_plot.setData(x=x, y=y) + if gid != self.valuewidget.connected_figure: + self.management.graphs[self.valuewidget.connected_figure].remove_external(self._values_plot) + self.management.graphs[gid].add_external(self._values_plot) + self.valuewidget.connected_figure = gid + + @QtCore.pyqtSlot(object, str) + def item_to_graph(self, item, graph_id): + self.management.graphs[graph_id].add_external(item) + + @QtCore.pyqtSlot(object, str) + def item_from_graph(self, item, graph_id): + self.management.graphs[graph_id].remove_external(item) + + def closeEvent(self, evt): + # self._write_settings() + self.close() + + @QtCore.pyqtSlot(int) + def request_data(self, idx): + idd = self.datawidget.get_indexes(idx=idx-1) + try: + x = self.management[idd].x + y = self.management[idd].y + ret_val = (x, y) + except KeyError: + ret_val = None + + self.sender().receive_data(ret_val) + + return ret_val + + @QtCore.pyqtSlot(tuple, bool) + def point_selected(self, pos, double): + w = self.tabWidget.currentWidget() + if w == self.ptsselectwidget: + line = self.ptsselectwidget.add(pos, double) + self.current_graph_widget.add_external(line) + + elif w == self.t1tauwidget: + self.t1tauwidget.t1min_picked(pos) + + def _select_fitwidget(self, onoff: bool, block_window: bool): + if self.current_graph_widget is not None: + print('select', self.current_graph_widget.id) + if onoff: + if self.management.active_sets: + self.fit_dialog.connected_figure = self.management.current_graph + self.fit_dialog.load(self.management.active_sets) + for item in self.fit_dialog.preview_lines: + self.current_graph_widget.add_external(item) + if self.action_custom_range.isChecked(): + self.current_graph_widget.add_external(self.fitregion) + + block_window = True + else: + for item in self.fit_dialog.preview_lines: + self.current_graph_widget.remove_external(item) + self.current_graph_widget.remove_external(self.fitregion) + + return block_window + + @QtCore.pyqtSlot(QtWidgets.QAction) + def change_fit_limits(self, action: QtWidgets.QAction): + if action == self.action_custom_range and self.fit_dialog.isVisible(): + self.current_graph_widget.add_external(self.fitregion) + else: + self.current_graph_widget.remove_external(self.fitregion) + + def start_fit(self, parameter, links, fit_options): + fit_options['limits'] = { + self.action_no_range: 'none', + self.action_x_range: 'x', + self.action_custom_range: self.fitregion.getRegion() + }[self.ac_group2.checkedAction()] + + fit_options['fit_mode'] = { + self.action_lm_fit: 'lsq', + self.action_nm_fit: 'nm', + self.action_odr_fit: 'odr' + }[self.ac_group.checkedAction()] + + self.fit_dialog.fit_button.setEnabled(False) + self.management.start_fit(parameter, links, fit_options) + + @QtCore.pyqtSlot(dict, int, bool) + def show_fit_preview(self, funcs: dict, num: int, show: bool): + if self.fit_dialog.connected_figure is None: + return + + g = self.management.graphs[self.fit_dialog.connected_figure] + for item in self.fit_dialog.preview_lines: + g.remove_external(item) + + if show: + x_lim, _ = g.ranges + space = geomspace if g.log[0] else linspace + x = space(1.01*x_lim[0], 0.99*x_lim[1], num=num) + + self.fit_dialog.make_previews(x, funcs) + + for item in self.fit_dialog.preview_lines: + g.add_external(item) + + self.raise_() + + @QtCore.pyqtSlot(list) + def show_fit_results(self, results: list): + self.fit_dialog.fit_button.setEnabled(True) + if results: + res_dialog = QFitResult(results, self.management, parent=self) + res_dialog.add_graphs(self.management.graphs.list()) + res_dialog.closed.connect(self.management.make_fits) + res_dialog.redoFit.connect(self.management.redo_fits) + res_dialog.show() + + @QtCore.pyqtSlot(name='on_actionFunction_editor_triggered') + def edit_models(self): + if self.editor is None: + from ..lib.usermodeleditor import QUsermodelEditor + + self.editor = QUsermodelEditor(config_paths() / 'usermodels.py', parent=self) + self.editor.modelsChanged.connect(self.update_fitmodels) + self.editor.setWindowModality(QtCore.Qt.ApplicationModal) + self.editor.show() + + @QtCore.pyqtSlot(name='on_actionShift_triggered') + def shift_dialog(self): + s = QShift(self) + s.set_graphs(self.management.graphs.list()) + for key, name in self.management.active_sets: + data = self.management.data[key] + s.add_item(key, name, data.x, data.y) + s.valuesChanged.connect(self.management.shift_scale) + s.show() + + def update_fitmodels(self): + pass + + @staticmethod + @QtCore.pyqtSlot(name='on_actionDocumentation_triggered') + def open_doc(): + docpath = '/autohome/dominik/auswerteprogramm3/doc/_build/html/index.html' + import webbrowser + webbrowser.open(docpath) + + def dropEvent(self, evt): + if evt.mimeData().hasUrls(): + files = [str(url.toLocalFile()) for url in evt.mimeData().urls()] + self.management.load_files(files) + + def dragEnterEvent(self, evt): + evt.accept() + + @QtCore.pyqtSlot(bool, name='on_actionMouse_behaviour_toggled') + def change_mouse_mode(self, is_checked): + if is_checked: + self.current_plotitem.plotItem.vb.setMouseMode(ViewBox.RectMode) + else: + self.current_plotitem.plotItem.vb.setMouseMode(ViewBox.PanMode) + + def mousemoved(self, xpos, ypos): + self.mousepos.setText('x={:.3g}; y={:.3g}'.format(xpos, ypos)) + + @QtCore.pyqtSlot(name='on_actionSnake_triggered') + @QtCore.pyqtSlot(name='on_actionTetris_triggered') + @QtCore.pyqtSlot(name='on_actionLife_triggered') + def spannung_spiel_und_spass(self): + + if self.sender() == self.actionLife: + from ..lib.gol import QGameOfLife + game = QGameOfLife(parent=self) + game.setWindowModality(QtCore.Qt.NonModal) + game.show() + + else: + from ..lib.stuff import Game + if self.sender() == self.actionSnake: + gtype = 'snake' + else: + gtype = 'tetris' + + game = Game(gtype, parent=self) + game.show() + + @QtCore.pyqtSlot(name='on_actionConfiguration_triggered') + def open_configuration(self): + from ..lib.configurations import GeneralConfiguration + dialog = GeneralConfiguration(self) + dialog.show() + + def close(self): + write_state({'recent_path': str(self.path)}) + + super().close() + + def read_state(self): + opts = read_state() + self.path = pathlib.Path(opts.get('recent_path', Path.home())) diff --git a/nmreval/gui_qt/main/management.py b/nmreval/gui_qt/main/management.py new file mode 100644 index 0000000..5968933 --- /dev/null +++ b/nmreval/gui_qt/main/management.py @@ -0,0 +1,1074 @@ +import pathlib +import re +import uuid +from typing import List + +from ...fit import data as fit_d +from ...fit.model import Model +from ...fit.result import FitResult +from ...fit.minimizer import FitRoutine +from ...math.interpol import interpolate +from ...math.logfourier import logft +from ...math.smooth import smooth +from ...nmr.relaxation import Relaxation + +from ..lib.undos import * +from ..data.container import * +from ..io.filereaders import QFileReader +from ..lib.utils import busy_cursor + + +class GraphSignals(QtCore.QObject): + valueChanged = QtCore.pyqtSignal() + + +class GraphDict(OrderedDict): + def __init__(self, data): + super().__init__() + + self._data = data + self.signals = GraphSignals() + self.valueChanged = self.signals.valueChanged + + def __setitem__(self, key, value): + super().__setitem__(key, value) + self.valueChanged.emit() + + def __delitem__(self, key): + super().__delitem__(key) + self.valueChanged.emit() + + def tree(self, key_only=False): + ret_val = OrderedDict() + for k, g in self.items(): + if key_only: + ret_val[k] = (g.title, [(s, self._data[s].name) for s in g.sets]) + else: + ret_val[(k, g.title)] = [(s, self._data[s].name) for s in g.sets] + + return ret_val + + def list(self): + return [(k, v.title) for k, v in self.items()] + + def active(self, key: str): + if key: + return [(self._data[i].id, self._data[i].name) for i in self[key]] + else: + return [] + + def current_sets(self, key: str): + if key: + return [(self._data[i].id, self._data[i].name) for i in self[key].sets] + else: + return [] + + +class UpperManagement(QtCore.QObject): + newGraph = QtCore.pyqtSignal() + restoreGraph = QtCore.pyqtSignal(str) + deleteGraph = QtCore.pyqtSignal(str) + newData = QtCore.pyqtSignal(list, str) + deleteData = QtCore.pyqtSignal(str) + dataChanged = QtCore.pyqtSignal(str) + fitFinished = QtCore.pyqtSignal(list) + stopFit = QtCore.pyqtSignal() + properties_collected = QtCore.pyqtSignal(dict) + unset_state = QtCore.pyqtSignal(list) + + + _colors = cycle(Colors) + + _actions = { + 'ls': (ShiftCommand, 'Left shift'), + 'cut': (CutCommand, 'Cut'), + 'ap': (ApodizationCommand, 'Apodization'), + 'zf': (ZerofillCommand, 'Zerofill'), + 'ph': (PhaseCommand, 'Phase'), + 'bl': (BaselineCommand, 'Baseline'), + 'bls': (BaselineSplineCommand, 'Baseline'), + 'ft': (FourierCommand, 'Fourier'), + 'ft_pake': 'FT (de-paked)', + 'sort': (SortCommand, 'Sort'), + 'norm': (NormCommand, 'Normalize'), + 'center': (CenterCommand, 'Center on max') + } + + def __init__(self, window): + super().__init__() + + self._fit_active = False + self.fit_thread = None + self.fit_worker = None + + self.counter = 0 + self.data = OrderedDict() + self.window = window + self.current_graph = '' + self.graphs = GraphDict(self.data) + self.namespace = None + self.undostack = QtWidgets.QUndoStack() + self.deleteData.connect(self.plot_from_graph) + + def __setitem__(self, key: str, value, **kwargs): + if isinstance(value, ExperimentContainer): + item = value + item.id = key + elif isinstance(value, FitResult): + item = FitContainer(key, value, manager=self, **kwargs) + elif isinstance(value, Signal): + item = SignalContainer(key, value, manager=self, **kwargs) + else: + item = PointContainer(key, value, manager=self, **kwargs) + + item.dataChanged.connect(lambda x: self.dataChanged.emit(x)) + + self.data[key] = item + + def __getitem__(self, item): + return self.data[item] + + def __contains__(self, item): + return item in self.data + + def __iter__(self): + for k, v in self.data.items(): + yield k, v + + @property + def active_sets(self): + return self.graphs.active(self.current_graph) + + def add(self, data, **kwargs): + _id = str(uuid.uuid4()) + self.__setitem__(_id, data, **kwargs) + + return _id + + def load_files(self, fname: List[str], new_plot: str = None): + ret_dic = QFileReader(manager=self).readfiles(fname) + self.add_new_data(ret_dic, new_plot) + + def _load_session(self, sets: dict, graphs: dict): + sid = self._load_sets(sets) + + for g in graphs: + _ = g.pop('id') + graph = QGraphWindow.set_state(g) + self.graphs[graph.id] = graph + self.restoreGraph.emit(graph.id) + + children = [sid[c] for c in g['children']] + active = [sid[c] for c in g['active']] + inactive = [k for k in children if k not in active] + + self.newData.emit(children, graph.id) + + graph.active = active + graph.listWidget.blockSignals(True) + for i, l in enumerate(g['in_legend']): + graph.listWidget.item(i).setCheckState(l) + graph.listWidget.blockSignals(False) + + # set unchecked in tree and hide/show in plot + self.unset_state.emit(inactive) + self.change_visibility(active, inactive) + + def _load_sets(self, sets: dict) -> dict: + sid = {} + for _id, (data, opts) in sets.items(): + if isinstance(data, FitResult): + # for fits, _id belongs to the fitted data, not the fit + src_id = data.idx + if src_id in sid: + new_id = self.add(data, src=sid[src_id]) + self.data[sid[src_id]]._fits.append(new_id) + else: + new_id = self.add(data) + else: + new_id = self.add(data) + + sid[_id] = new_id + + for m in ['real', 'imag']: + if m in opts: + self.data[new_id].setSymbol(**opts[m][0], mode=m) + self.data[new_id].setLine(**opts[m][1], mode=m) + + return sid + + def add_new_data(self, data: list, gid: str): + sid = [] + for d in data: + if isinstance(d, tuple): + if len(d) == 2: + self._load_session(d[0], graphs=d[1]) + else: + sid.extend(list(self._load_sets(d[0]).values())) + else: + sid.append(self.add(d)) + + if sid: + gid = '' if not gid else gid + + self.newData.emit(sid, gid) + + def plots_to_graph(self, plotkeys: list, gid: str): + self.graphs[gid].add(plotkeys, [self.data[k].plots for k in plotkeys]) + for k in plotkeys: + self.data[k].graph = gid + + @QtCore.pyqtSlot(str) + def plot_from_graph(self, key: str): + self.graphs[self.data[key].graph].remove(key) + + @QtCore.pyqtSlot(list, str, str) + def move_sets(self, sets: list, dest: str, src, pos: int = -1): + if isinstance(src, str): + src = [src]*len(sets) + + for graph_id, set_id in zip(src, sets): + # move all plots to the same graph + if graph_id != dest: + self.graphs[graph_id].remove(set_id) + self.plots_to_graph([set_id], dest) + + # move to correct position + self.graphs[dest].move_sets(sets, pos) + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(list, str) + def copy_sets(self, sets: list = None, src: str = None): + if sets is None: + sets = self.graphs[self.current_graph].active[:] + + if src is None: + src = self.current_graph + + new_ids = [] + for s in sets: + copy_of_s = self.data[s].copy(full=True) + copy_of_s.id = str(uuid.uuid4()) + new_ids.append(copy_of_s.id) + self.data[copy_of_s.id] = copy_of_s + + self.newData.emit(new_ids, src) + + return new_ids + + @QtCore.pyqtSlot(list) + @QtCore.pyqtSlot(str) + def delete_sets(self, rm_sets: list = None): + rm_graphs = [] + print(rm_sets) + + if rm_sets is None: + rm_sets = self.graphs[self.current_graph].sets + [self.current_graph] + + self.undostack.beginMacro('Delete') + + for k in rm_sets[::-1]: + if k in self.data: + cmd = DeleteCommand(self.data, k, self.newData, self.deleteData) + self.undostack.push(cmd) + else: + rm_graphs.append(k) + + for k in rm_graphs: + cmd = DeleteGraphCommand(self.graphs, k, self.restoreGraph, self.deleteGraph) + self.undostack.push(cmd) + + self.undostack.endMacro() + + @QtCore.pyqtSlot() + def cat(self, src_sets=None): + joined = None + group_set = set() + name_set = set() + value_set = set() + + if src_sets is None: + src_sets = self.graphs[self.current_graph].active + for sid in src_sets: + data_i = self.data[sid] + if joined is None: + joined = data_i.copy() + else: + joined.append(data_i.x, data_i.y, data_i.y_err) + + name_set.add(data_i.name) + group_set.add(data_i.group) + value_set.add(data_i.value) + + if joined is not None: + joined.group = '/'.join(group_set) + joined.name = '/'.join(name_set) + + if len(value_set) == 1: + joined.value = value_set.pop() + else: + joined.value = 0.0 + + self.newData.emit([self.add(joined)], self.current_graph) + + def get_data(self, sid: str, xy_only=False): + """ + Return data for a given id. + Return value is tuple of [x, y, y_err] and mask if xy_only is False, [x, y] if true. + """ + d = self.data[sid] + if xy_only: + return [d.x, d.y] + + return [d.data.x, d.data.y, d.data.y_err], d.data.mask.data + + def change_visibility(self, selected: list, deselected: list): + """Change status of list of ids after status change in datawidget""" + + for s in selected: + self.graphs[self.data[s].graph].show_item([s]) + + for d in deselected: + self.graphs[self.data[d].graph].hide_item([d]) + + @QtCore.pyqtSlot(str, str) + def change_keys(self, identifier: str, name: str): + if identifier in self.data: + d = self.data[identifier] + d.name = name + self.graphs[d.graph].update_legend(identifier, name) + elif identifier in self.graphs: + self.graphs[identifier].title = name + else: + raise KeyError('Unknown ID ' + str(identifier)) + + @QtCore.pyqtSlot(str, tuple) + def apply(self, func: str, arguments: tuple): + # undos, names displayed by undo action + cmd, cmd_text = self._actions[func] + + self.undostack.beginMacro(cmd_text) + for sid in self.graphs[self.current_graph]: + single_undo = cmd(self.data[sid], *arguments) + self.undostack.push(single_undo) + self.undostack.endMacro() + + def cut(self): + xlim, _ = self.graphs[self.current_graph].ranges + self.apply('cut', xlim) + + @QtCore.pyqtSlot() + def unmask(self): + for d in self.data.values(): + d.mask = np.ones_like(d.mask, dtype=bool) + + def start_fit(self, parameter: dict, links: list, fit_options: dict): + if self._fit_active: + return + + self.__fit_options = (parameter, links, fit_options) + + fitter = FitRoutine() + models = {} + fit_limits = fit_options['limits'] + fit_mode = fit_options['fit_mode'] + we = fit_options['we'] + + for model_id, model_p in parameter.items(): + m = Model(model_p['func']) + models[model_id] = m + + m_complex = model_p['complex'] + m.set_complex(m_complex) + + for set_id, set_params in model_p['parameter'].items(): + data_i = self.data[set_id] + if we == 'Deltay': + we = data_i.y_err**2 + + if m_complex is None or m_complex == 'real': + _y = data_i.y.real + elif m_complex == 'imag' and np.iscomplexobj(self.data[set_id].y): + _y = data_i.y.imag + else: + _y = data_i.y + + _x = data_i.x + + if fit_limits == 'none': + inside = slice(None) + 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])) + + 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) + + d.set_model(m) + d.set_parameter(set_params[0], var=model_p['var'], + lb=model_p['lb'], ub=model_p['ub'], + fun_kwargs=set_params[1]) + + fitter.add_data(d) + + model_globs = model_p['glob'] + if model_globs: + m.set_global_parameter(**model_p['glob']) + + for links_i in links: + fitter.set_link_parameter((models[links_i[0]], links_i[1]), + (models[links_i[2]], links_i[3])) + + with busy_cursor(): + self.fit_worker = FitWorker(fitter, fit_mode) + self.fit_thread = QtCore.QThread() + self.fit_worker.moveToThread(self.fit_thread) + + self.fit_thread.started.connect(self.fit_worker.run) + self.fit_worker.finished.connect(self.end_fit) + self.fit_worker.finished.connect(self.fit_thread.quit) + self.fit_worker.finished.connect(self.fit_worker.deleteLater) + self.fit_thread.finished.connect(self.fit_thread.deleteLater) + + self.stopFit.connect(lambda: self.fit_worker.fitter.abort()) + + self.fit_thread.start() + + @QtCore.pyqtSlot(list, bool) + def end_fit(self, result: list, success: bool): + print('FIT FINISHED') + if success: + self.fitFinished.emit(result) + else: + QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), 'Fit failed', + 'Fit kaput with exception: \n' + "\n".join(result[0])) + self.fitFinished.emit([]) + self._fit_active = False + + @QtCore.pyqtSlot(dict) + def redo_fits(self, res: dict): + models = self.__fit_options[0] + for single_model, model_args in models.items(): + parameter = model_args['parameter'] + + for set_id, set_parameter in parameter.items(): + new_values = [v.value for v in res[set_id].parameter.values()] + parameter[set_id] = (new_values, set_parameter[1]) + self.start_fit(*self.__fit_options) + + @QtCore.pyqtSlot(dict, list) + def make_fits(self, res: dict, opts: list): + f_id_list = [] + gid = '' + + subplots = opts.pop(-1) + param_graph = opts.pop(-1) + tobedeleted = [] + for i, (k, fit) in enumerate(res.items()): + reject, delete_prev = opts[i] + if reject: + continue + + data_k = self.data[k] + if delete_prev: + tobedeleted.extend([f.id for f in data_k.get_fits()]) + data_k.set_fits([]) + + syms = data_k.plot_real.symbol + if syms == SymbolStyle.No: + color = data_k.plot_real.linecolor + else: + color = data_k.plot_real.symbolcolor + + fit.value = data_k.value + fit.group = data_k.group + + f_id = self.add(fit, color=color, src=k) + + if subplots: + print('subplots') + + f_id_list.append(f_id) + data_k.set_fits(f_id) + gid = data_k.graph + + self.delete_sets(tobedeleted) + + if f_id_list: + self.newData.emit(f_id_list, gid) + self.make_fit_parameter(f_id_list, graph_id=param_graph) + + def make_fit_parameter(self, fit_sets: List[str], graph_id: str = None): + fit_dict = self._collect_fit_parameter(fit_sets) + + if fit_dict: + p_id_list = [] + for v in fit_dict.values(): + xy = np.array(v[0]).T + p_id_list.append(self.add(Points(x=xy[0], y=xy[1], y_err=xy[2], name=v[1]))) + + if not graph_id: + graph_id = '' + + self.newData.emit(p_id_list, graph_id) + + def save_fit_parameter(self, fname: str, fit_sets: List[str] = None): + if fit_sets is None: + fit_sets = [s for (s, _) in self.active_sets] + + for set_id in fit_sets: + data = self.data[set_id] + if data.mode != 'fit': + continue + + data.data.save_parameter(fname) + + def _collect_fit_parameter(self, fit_sets: List[str]) -> dict: + fit_dict = {} + + for set_id in fit_sets: + data = self.data[set_id] + if data.mode != 'fit': + continue + + for key, pvalue in data.parameter.items(): + name = pvalue.full_name + fit_key = key + data.model_name + + if fit_key not in fit_dict: + fit_dict[fit_key] = [[], name] + + err = 0 if pvalue.error is None else pvalue.error + + fit_dict[fit_key][0].append([data.value, pvalue.value, err]) + + return fit_dict + + @QtCore.pyqtSlot(dict, str) + def extract_points(self, params: dict, gid: str): + xy_mode = params.pop('xy') + _active = self.graphs[self.current_graph].active + + new_datasets = {} + for sid in _active: + data_i = self.data[sid] + if data_i.group not in new_datasets: + new_datasets[data_i.group] = [], [] + new_x_axis, _temp = new_datasets[data_i.group] + + new_x_axis.append(data_i.value) + _temp.append(data_i.points(params)) + + key_list = [] + for label, (new_x_axis, _temp) in new_datasets.items(): + _temp = np.array(_temp) # (number of sets, number of picks, (x, y, y_err)) + num_pts = _temp.shape[1] + + for i in range(num_pts): + if xy_mode[0]: + key = self.add(Points(x=new_x_axis, y=_temp[:, i, 0], name=label)) + key_list.append(key) + + if xy_mode[1]: + key = self.add(Points(x=new_x_axis, y=_temp[:, i, 1], y_err=_temp[:, i, 2], name=label)) + key_list.append(key) + + self.newData.emit(key_list, gid) + + @QtCore.pyqtSlot(list) + def get_properties(self, sid: list) -> dict: + props = {} + for key in sid: + if key not in self.data: + continue + + props = self.data[key].get_properties() + + self.properties_collected.emit(props) + + return props + + @QtCore.pyqtSlot(list, str, str, object) + def update_property(self, sid: list, key1: str, key2: str, value: Any): + for s in sid: + self.data[s].update_property(key1, key2, value) + + def create_empty(self): + import numpy.random as random + dat = Points(x=np.arange(10), y=np.arange(10) + random.rand(10)-0.5, y_err=random.rand(10), + name='Das Sein und das Nichts') + idd = self.add(dat) + self.newData.emit([idd], self.current_graph) + + @QtCore.pyqtSlot(tuple, dict, str) + def calc_mean(self, dist_params, conversion, graph): + dist, args = dist_params + parameter = [] + + x = None + name = 'tau (%s)' % {conversion["to_"]} + value = 0. + for i, p in enumerate(args): + if isinstance(p, float): + parameter.append(p) + else: + if x is None: + x = self.data[p].x + if i == 0: + name = self.data[p].name + value = self.data[p].value + parameter.append(self.data[p].y) + + if x is None: + x = 0 + + key = self.add(Points(x, dist.convert(*parameter, **conversion), name=name, value=value)) + self.newData.emit([key], graph) + self.sender().update_graphs(self.graphs.tree(key_only=True)) + + @QtCore.pyqtSlot(list, str, bool, bool, tuple, str) + def interpolate_data(self, data_ids, mode, xlog, ylog, new_axis, dest_graph): + if len(new_axis) == 4: + start, end, steps, loggy = new_axis + if loggy: + new_x = np.logspace(np.log10(start), np.log10(end), steps) + else: + new_x = np.linspace(start, end, steps) + else: + new_x = self.data[new_axis[0]].x + + new_key = [] + for ids in data_ids: + k = self.add(interpolate(self.data[ids], new_x, xlog=xlog, ylog=ylog, kind=mode, extrapolate=True)) + new_key.append(k) + + self.newData.emit(new_key, dest_graph) + + @QtCore.pyqtSlot(int, dict) + def smooth_data(self, npoints, param_kwargs): + _active = self.graphs[self.current_graph].active + new_data = [] + for sid in _active: + try: + key = self.add(smooth(self.data[sid], npoints, **param_kwargs)) + new_data.append(key) + except Exception as e: + QtWidgets.QMessageBox().warning(self.window, + 'Smoothing failed!', + f'Smoothing failed for {self.data[sid].name} with exception:\n{e.args}') + if new_data: + self.newData.emit(new_data, self.current_graph) + + @QtCore.pyqtSlot() + def update_color(self): + UpperManagement._colors = cycle(Colors) + for i in self.active: + self.data[i].color = next(UpperManagement._colors) + + @QtCore.pyqtSlot(dict, tuple) + def shift_scale(self, values: dict, options: tuple): + copy_data, value_plot = options + + sid_list = [] + shift_y = [] + shift_x = [] + for k, v in values.items(): + d_k = self.data[k] + + if copy_data is None: + d_k.x = d_k.x*v[1][0] + v[0][0] + d_k.y = d_k.y*v[1][1] + v[0][1] + else: + new_data = d_k.copy(full=True) + new_data.update({'shift': v[0], 'scale': 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_list.append(sid) + + shift_x.append(d_k.value) + shift_y.append(v[0]+v[1]) + + self.newData.emit(sid_list, copy_data) + + if value_plot is not None: + sid_list = [] + shift_y = np.array(shift_y) + for i, (mode, default) in enumerate([('x shift', 0.), ('y shift', 0.), + ('x scale', 1.), ('y scale', 1.), ]): + if np.all(shift_y[:, i] == default): + continue + data = Points(shift_x, shift_y[:, i], name=mode) + sid_list.append(self.add(data)) + + self.newData.emit(sid_list, value_plot) + + @QtCore.pyqtSlot(list) + def convert_sets(self, src: list): + new_graph = {} + + error_list = [] + + for sets in src: + # merge: sets (real, imag, graph, type) + # normal: sets (source set, graph, type) + + graph_id = sets[-2] + new_type = [Points, FID, Spectrum, BDS][sets[-1]] + + if len(sets) == 4: + real_set, imag_set = sets[0], sets[1] + if real_set != '': + data = self.data[real_set] + new_data = new_type(data.x, data.y.real) + if imag_set != '': + imag_data = self.data[imag_set] + if len(imag_data) == len(data): + new_data.y.imag = imag_data.y.real + else: + error_list.append(f'Lengths mismatch of {data.name} ({len(data)}) and {imag_data.name} ({len(imag_data)})') + continue + + else: + data = self.data[imag_set] + new_data = new_type(data.x, np.zeros(data.x.size)) + new_data.y.imag = data.y.real + + else: + data = self.data[sets[0]] + if isinstance(data.data, new_type): + error_list.append(f'{data.name} is alreade of type {new_type.__name__}') + continue + + new_data = new_type(data.x, np.zeros(data.x.size)) + new_data.y.real = data.y.real + + new_data.update(data.opts) + new_id = self.add(data.change_type(new_data)) + if graph_id not in new_graph: + new_graph[graph_id] = [] + + new_graph[graph_id].append(new_id) + + for g, s in new_graph.items(): + self.newData.emit(s, g) + + if error_list: + err_string = "\n- ".join(error_list) + _ = QtWidgets.QMessageBox.information(QtWidgets.QWidget(), 'Something was skipped', + f'Some conversions were skipped:\n{err_string}') + + def get_namespace(self): + from ..lib.namespace import Namespace + self.namespace = Namespace(basic=True, const=True, fitfuncs=True) + + for i, g in enumerate(self.graphs.values()): + for j, sid in enumerate(g.sets): + sets = self.data[sid] + self.namespace.add_namespace(sets.get_namespace(i, j), parents=('Data', f'{sets.name} ({g.title})')) + + return self.namespace + + @QtCore.pyqtSlot(list, list, bool) + def eval_expression(self, cmds: list, set_ids: list, overwrite: bool): + ns = self.namespace.flatten() + + if overwrite: + self.undostack.beginMacro('Evaluate expression') + + failures = [] + for sid in set_ids: + data_i = self.data[sid] + try: + # use a copy of original namespace + new_data = data_i.eval_expression(cmds, dict(ns)) + if overwrite: + cmd = EvalCommand(self.data, sid, new_data, 'Evaluate expression') + self.undostack.push(cmd) + else: + new_id = self.copy_sets(sets=[sid]) + self.data[new_id[0]].data = new_data + except Exception as e: + failures.append((data_i, e)) + print(str(data_i) + ' failed with Exception: ' + ''.join(e.args)) + continue + + if overwrite: + self.undostack.endMacro() + + if failures: + err_msg = QtWidgets.QMessageBox(parent=self.sender()) + err_msg.setText('One or more errors occured during evaluation.') + err_msg.setDetailedText('\n'.join(f'{d.name} failed with error: {err.args}' for d, err in failures)) + err_msg.exec() + + self.sender().success = not failures + + self.sender().add_data(self.active_sets) + + @QtCore.pyqtSlot(list, dict) + def create_from_function(self, cmds: list, opts: dict): + ns = dict(self.namespace.flatten()) + + dtype = [Points, FID, Spectrum, BDS][opts.pop('dtype')] + + try: + for c in cmds: + exec(c, globals(), ns) + + name = opts.pop('name') + value = opts.pop('val') + graph = opts.pop('graph') + + data = dtype(x=ns['x'], y=ns['y'], y_err=ns['y_err'], name=name, value=value) + s_id = self.add(data, **opts) + self.sender().success = True + self.newData.emit([s_id], graph) + + except Exception as err: + print('Creation failed with error: ' + ', '.join(err.args)) + err_msg = QtWidgets.QMessageBox(parent=self.sender()) + err_msg.setText('One or more errors occured during evaluation.') + err_msg.setDetailedText('Creation failed with error: ' + ', '.join(err.args)) + err_msg.exec() + + self.sender().success = False + + def show_statistics(self, mode): + x, y, = [], [] + + for i, _ in self.active_sets: + _temp = self.data[i] + try: + x.append(float(_temp.name)) + except ValueError: + x.append(i) + y.append(_temp.statistic(mode)) + + @QtCore.pyqtSlot() + def calc_magn(self): + new_id = [] + for k, _ in self.active_sets: + dataset = self.data[k] + if isinstance(dataset, SignalContainer): + new_value = dataset.copy(full=True) + new_value.data = dataset.data.magnitude() + new_id.append(self.add(new_value)) + + self.newData.emit(new_id, '') + + @QtCore.pyqtSlot() + def center(self): + new_id = [] + for k, _ in self.active_sets: + new_value = self.data[k].copy(full=True) + new_value.x -= new_value.x[np.argmax(new_value.y.real)] + new_id.append(self.add(new_value)) + + self.newData.emit(new_id, '') + + def integrate(self, **kwargs): + new_sets = [] + log = kwargs['log'] + limits = kwargs.get('limits') + mode = kwargs['mode'] + + for set_id in kwargs['sets']: + data_i = self.data[set_id] + if mode == 'i': + new_data = data_i.data.integrate(log=log, limits=limits) + elif mode == 'd': + new_data = data_i.data.diff(log=log) + else: + raise ValueError(f'Unknown mode {mode}.') + + new_container = data_i.copy(full=True) + new_container.data = new_data + + new_sets.append(self.add(new_container)) + + self.newData.emit(new_sets, kwargs['graph']) + + def bds_deriv(self): + new_sets = [] + + for (set_id, _) in self.active_sets: + data_i = self.data[set_id] + diff = data_i.data.diff(log=True) + new_data = Points(x=diff.x, y=-np.pi/2*diff.y.real) + new_data.update(data_i.data.meta) + + new_sets.append(self.add(new_data, color=data_i.plot_imag.linecolor)) + + self.newData.emit(new_sets, '') + + def logft(self, **kwargs): + new_sets = [] + ft_mode = kwargs['ft_mode'] + + for set_id in kwargs['sets']: + data_i = self.data[set_id] + if ft_mode in ['cos', 'sin']: + new_data = Points(*logft(data_i.x, data_i.y, mode=ft_mode)) + else: + new_data = Signal(*logft(data_i.x, data_i.y, mode=ft_mode)) + + 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.newData.emit(new_sets, kwargs['graph']) + + def skip_points(self, offset: int, step: int, invert: bool = False, copy: bool = False): + for k, _ in self.active_sets: + src = self.data[k] + if invert: + mask = np.mod(np.arange(offset, src.x.size+offset), step) != 0 + else: + mask = np.mod(np.arange(offset, src.x.size+offset), step) == 0 + + if copy: + data = src.copy() + temp = data.mask.copy() + temp[temp] = mask + + data.remove(np.where(~temp)) + data.mask = np.ones(data.x.shape) + + idd = self.add(data) + self.newData.emit([idd], self.current_graph) + else: + src.mask[src.mask] = mask + src.mask = src.mask + + @QtCore.pyqtSlot(dict) + def calc_relaxation(self, opts: dict): + params = opts['pts'] + if len(params) == 4: + if params[3]: + _x = x1 = np.geomspace(params[0], params[1], num=params[2]) + else: + _x = x1 = np.linspace(params[0], params[1], num=params[2]) + + if opts['axis1'] in ['t', 'invt1000']: + t_p = opts['t_param'] + if len(t_p) == 2: + from ...models import Arrhenius as Func + else: + from ...models import VFT as Func + + _x = Func.func(x1, *t_p, invt=opts['axis1']) + + else: + if params[1]: + x1 = self.data[params[0]].x + _x = self.data[params[0]].y.real + else: + _x = x1 = self.data[params[0]].x + + x2 = opts['val2'] + + 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.convert(_x, *sd_param, from_=opts['tau_type'], to_='raw') + + relax = Relaxation() + relax.distribution(sd, parameter=sd_param, 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]] + relax.coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1]) + + if opts['out'] == 't1': + y = relax.t1(x2, _x) + else: + y = relax.t2(x2, _x) + + pts = Points(x1, y, name=sd.name) + pts.meta.update(opts) + + # we do not want class instances + pts.meta['coup'] = opts['coup'].name + pts.meta['spec_dens'] = sd.name + + self.newData.emit([self.add(pts)], opts['graph']) + self.sender().update_graphs(self.graphs.list()) + + def mask_value(self, idx: str, m: list): + self.data[idx].mask = m + + def remove_values(self, idx: str, m: list): + self.data[idx].remove(m) + + def set_values(self, idx: str, pos: tuple, value): + self.data[idx].setvalues(pos, value) + + def append(self, idx: str): + self.data[idx].add([0.0, 0.0, 0.0]) + + def save(self, outpath: str, extension: str, strip_spaces=False): + path = pathlib.Path(outpath) + suffix = path.suffix + + if not suffix: + m = re.match(r'[\w\s]*\(\*(\.\w+)\)', extension) + if m: + suffix = m.group(1) + path = path.with_suffix(suffix) + else: + raise ValueError('No file extension detected') + + if suffix == '.nmr': + from ...io.sessionwriter import NMRWriter + NMRWriter(self.graphs, self.data).export(path) + + return + + real_outnames = [] + for set_id, set_name in self.active_sets: + full_name = path.stem + if '