forked from IPKM/nmreval
Compare commits
248 Commits
csv_broken
...
packaging
Author | SHA1 | Date | |
---|---|---|---|
9baf1e44d3 | |||
8fb17b22fd | |||
f04cadf780 | |||
ae45ff2e55 | |||
735896e68d | |||
aff9cd31a7 | |||
8772fa9e7a | |||
9c5d91918f | |||
dbb35cdba4 | |||
070509c691 | |||
5ea1ed6d0a | |||
a1691f11a6 | |||
f956c111c2 | |||
cb2bc78a2a | |||
9e9751cd9d | |||
5a8ef8c9c7 | |||
45f5eb9bef | |||
5116622e42 | |||
661ab81d7e | |||
185dd160f1 | |||
7fe564a61e | |||
6339bdc2cc | |||
37a6d789bb | |||
cde794cb0d | |||
2a69147ed4 | |||
902a35b71a | |||
6f76349932 | |||
f9bb466cb3 | |||
a97c31325f | |||
207ee5bffd | |||
0046d04683 | |||
9463ed1e6c | |||
ea62a05bd3 | |||
c5706084bf | |||
04037d6b4d | |||
dedb130163 | |||
6ecc4a4126 | |||
067857eda2 | |||
41d90bb15f | |||
8d994bb9b4 | |||
2cbc7e8d75 | |||
1d22f22901 | |||
bd1a227e4c | |||
03d172bade | |||
e51a02d277 | |||
3af5cb0301 | |||
22f317da8d | |||
24b56cbd2a | |||
5fd52a1a44 | |||
d8e5de6b7a | |||
869901596b | |||
e4dbaf2b91 | |||
dee1271fe1 | |||
b8bab2af7b | |||
a406908a69 | |||
5e55f06723 | |||
e2e52cebde | |||
53c58b2bbb | |||
4e865cd0c6 | |||
311157a01a | |||
f0ee2073ad | |||
d2e63a5ee3 | |||
5a153585ee | |||
d17d0f251e | |||
0b8f4932b2 | |||
5d43ccb05d | |||
88a32ea7fd | |||
7febe55929 | |||
783fe505ba | |||
2fed4bb0bf | |||
ec8fafcc9c | |||
7762758958 | |||
258922772e | |||
e612c607e2 | |||
b27d9b55ff | |||
3475650899 | |||
0eca306ebe | |||
d7eaff5f0b | |||
a5216b1eed | |||
cf1565f7d3 | |||
c601e77cec | |||
63f4f82228 | |||
bae7ee2db6 | |||
dde7b7006d | |||
141e9f810a | |||
b6136bc8ce | |||
7762e299e4 | |||
ca130eaa14 | |||
2b2c6e932d | |||
402994e09b | |||
7a50028202 | |||
becc1a15a9 | |||
13e6112c99 | |||
1ff462c4b1 | |||
0cf5d7eef9 | |||
609f135855 | |||
5db6a9f2c5 | |||
3149a3f958 | |||
3321d85203 | |||
76cd4acfb0 | |||
33afc2ca94 | |||
ac039c1b6d | |||
ef21d7c7f2 | |||
3ed7368bb0 | |||
fbf4246c7e | |||
91afe8224f | |||
ead9751541 | |||
1d9b641f0a | |||
46ca50845b | |||
a310a78fb9 | |||
9756bf958b | |||
8de4a0cbd3 | |||
bc946e1027 | |||
1d9bf600ba | |||
4a04502012 | |||
6bcf42d6a8 | |||
8a96e0472d | |||
567fcfe37e | |||
058551cc72 | |||
6fd3053964 | |||
3ee95a1990 | |||
1a2f178f58 | |||
c39ffb8a51 | |||
a26595695c | |||
c9ea32629d | |||
032ffb72b1 | |||
354d5cbc99 | |||
22f8bc80ed | |||
84d588cf80 | |||
5b146433f3 | |||
1a225b2cd2 | |||
89c640e591 | |||
1703b8d514 | |||
7732544f69 | |||
988d2ccbda | |||
4268bdc5ae | |||
775b5e7e8a | |||
30e148de14 | |||
2499aac7a1 | |||
f934104587 | |||
6e976e1404 | |||
92c29bec2a | |||
9f6d4e0d0c | |||
90bd33a680 | |||
26fea8f69f | |||
25f7ff5616 | |||
bec789318d | |||
edc6af2762 | |||
eb1e657dab | |||
79a424c4bd | |||
f74dd982ba | |||
a5a9561b54 | |||
c8287c2dbf | |||
a8a7e75501 | |||
30750cc4ed | |||
af14793286 | |||
2906c17472 | |||
5590b5cd16 | |||
09e415babf | |||
4e2938b2a2 | |||
685b9d2316 | |||
b91fbf7621 | |||
8d55430246 | |||
8d06240205 | |||
65034d4518 | |||
2adf15104f | |||
cd9c85c12b | |||
84d136dd4c | |||
f60e125487 | |||
1e9a390ae2 | |||
8673e5acdb | |||
66a0e40a23 | |||
a1ab6335c5 | |||
09a2b61160 | |||
681b49a22f | |||
3dcd44c3ee | |||
6e9dd4d45f | |||
888d86d9fe | |||
67d60949b5 | |||
fcaf43b3eb | |||
49101565a3 | |||
255d7c7862 | |||
12dacb73b3 | |||
b127cc15e2 | |||
bc215ce32b | |||
996b0b2ae2 | |||
753cd06dd1 | |||
45d319834b | |||
11ed3c16eb | |||
edf858da29 | |||
e10b85b904 | |||
267554b252 | |||
233cdd9f80 | |||
15c959fd71 | |||
1378cf6ac7 | |||
a964e02612 | |||
a72e4ba833 | |||
75ec462efd | |||
adcd98fc31 | |||
d8cc99cea4 | |||
7671a56b6f | |||
8086dd5276 | |||
9479364a64 | |||
def2a99ed8 | |||
ac4a4d3b8e | |||
dd1c26e285 | |||
eba7869c4d | |||
efd5b34b13 | |||
2d472bd44e | |||
2042148d0f | |||
4a9d50e8a4 | |||
56b588293d | |||
59625c1581 | |||
bb3d5ac58b | |||
48861c3f80 | |||
8e2ebfc765 | |||
924b58beda | |||
bd8a4f16ea | |||
e41c42d573 | |||
c94231f9d9 | |||
99bb196e5c | |||
e5563d55d5 | |||
abe6ca42c7 | |||
40961a89df | |||
29518b9ea0 | |||
3793f67951 | |||
0251dea563 | |||
bc034cf4f4 | |||
3e513a1231 | |||
ead30d127a | |||
3b79c571fb | |||
ee8ea4f2c5 | |||
2c0cdfbd68 | |||
767fa5f6fb | |||
02f8a3bb31 | |||
ffecc9c873 | |||
0ec0021727 | |||
6b71de8265 | |||
43285b4bd5 | |||
2fbaa94109 | |||
ebf3e9635d | |||
79befb50f1 | |||
0adb530dc2 | |||
d7fd8395e5 | |||
75f9bc84f8 | |||
1c98676bee | |||
8a35a5e6cb | |||
df9b302f9d |
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ AppDir
|
||||
NMReval*.zsync
|
||||
.idea
|
||||
*.zs-old
|
||||
docs
|
||||
|
@ -12,7 +12,9 @@ script:
|
||||
# Copy the python application code into the AppDir
|
||||
- cp bin/evaluate.py $TARGET_APPDIR/usr/bin/
|
||||
- cp -r src/* $TARGET_APPDIR/usr/src/
|
||||
- cp src/pkm.vogel.nmreval.desktop $TARGET_APPDIR/usr/share/applications
|
||||
- cp src/resources/pkm.vogel.nmreval.desktop $TARGET_APPDIR/usr/share/applications
|
||||
# set current date as version info
|
||||
- sed -i "s/CURRENT_DATE/$(date +'%Y-%m-%d')/" $TARGET_APPDIR/usr/src/nmreval/version.py
|
||||
|
||||
|
||||
AppDir:
|
||||
@ -30,35 +32,50 @@ AppDir:
|
||||
arch: amd64
|
||||
allow_unauthenticated: true
|
||||
sources:
|
||||
- sourceline: 'deb [arch=amd64] http://mirror.infra.pkm/ bullseye main contrib non-free'
|
||||
- sourceline: 'deb [arch=amd64] http://ftp.uni-mainz.de/debian bullseye main contrib non-free'
|
||||
|
||||
include:
|
||||
# for /usr/bin/env
|
||||
- coreutils
|
||||
- dash
|
||||
- zsync
|
||||
- hicolor-icon-theme
|
||||
# - coreutils
|
||||
# - dash
|
||||
# - zsync
|
||||
# - hicolor-icon-theme
|
||||
- libatlas3-base
|
||||
- python3.9-minimal
|
||||
- gnuplot-nox
|
||||
- python3-minimal
|
||||
- python3-numpy
|
||||
- python3-scipy
|
||||
# - python3-matplotlib
|
||||
# - python-matplotlib-data
|
||||
- python3-bsddb3
|
||||
- python3-h5py
|
||||
- python3-pyqt5
|
||||
- python3-pyqtgraph
|
||||
- python3-requests
|
||||
- python3-urllib3
|
||||
# - python3-tk
|
||||
exclude:
|
||||
# lots of qt stuff we do not use
|
||||
- libqt5designer5
|
||||
- libqt5help5
|
||||
- libqt5network5
|
||||
- libqt5sql5
|
||||
- libqt5test5
|
||||
- libqt5xml5
|
||||
- qtbase5-dev-tools
|
||||
- pyqt5-dev-tools
|
||||
- libavahi-client3
|
||||
- libavahi-common-data
|
||||
- libavahi-common3
|
||||
after_bundle: |
|
||||
echo "MONSTER SED FOLLOWING...(uncomment if needed for mpl-data)"
|
||||
#sed -i s,\'/usr/share/matplotlib/mpl-data\',"f\"\{os.environ.get\('APPDIR'\,'/'\)\}/usr/share/matplotlib/mpl-data\"", ${TARGET_APPDIR}/usr/lib/python3/dist-packages/matplotlib/__init__.py
|
||||
- libwacom2
|
||||
- libwacom-common
|
||||
|
||||
files:
|
||||
exclude:
|
||||
- usr/share/man
|
||||
- usr/share/doc/*/README.*
|
||||
- usr/share/doc/*/changelog.*
|
||||
- usr/share/doc/*/NEWS.*
|
||||
- usr/share/doc/*/TODO.*
|
||||
runtime:
|
||||
# if needed, apparently replaces hardcoded location with APPDIR location
|
||||
# path_mappings:
|
||||
# - /usr/share/matplotlib/mpl-data:$APPDIR/usr/share/matplotlib/mpl-data
|
||||
version: "continuous"
|
||||
env:
|
||||
PATH: '${APPDIR}/usr/bin:${PATH}'
|
||||
|
30
Makefile
30
Makefile
@ -5,31 +5,37 @@ PYUIC = pyuic5
|
||||
PYRCC = pyrcc5
|
||||
|
||||
#Directory with ui files
|
||||
RESOURCE_DIR = resources/_ui
|
||||
RESOURCE_DIR = src/resources/_ui
|
||||
|
||||
#Directory for compiled resources
|
||||
COMPILED_DIR = nmreval/gui_qt/_py
|
||||
PYQT_DIR = src/gui_qt/_py
|
||||
|
||||
#UI files to compile, uses every *.ui found in RESOURCE_DIR
|
||||
UI_FILES = $(foreach dir, $(RESOURCE_DIR), $(notdir $(wildcard $(dir)/*.ui)))
|
||||
COMPILED_UI = $(UI_FILES:%.ui=$(COMPILED_DIR)/%.py)
|
||||
PYQT_UI = $(UI_FILES:%.ui=$(PYQT_DIR)/%.py)
|
||||
|
||||
SVG_FILES = $(foreach dir, $(RCC_DIR), $(notdir $(wildcard $(dir)/*.svg)))
|
||||
PNG_FILES = $(SVG_FILES:%.svg=$(RCC_DIR)/%.png)
|
||||
|
||||
all : ui
|
||||
|
||||
ui : $(COMPILED_UI)
|
||||
CC = gcc
|
||||
CFLAGS = -O2 -fPIC
|
||||
LDFLAGS = -shared
|
||||
|
||||
rcc: $(PNG_FILES)
|
||||
C_DIR = src/nmreval/clib
|
||||
|
||||
all : ui compile
|
||||
|
||||
ui : $(PYQT_UI)
|
||||
|
||||
rcc : $(PNG_FILES)
|
||||
|
||||
# only one C file at the moment
|
||||
compile : $(C_DIR)/integrate.c
|
||||
$(CC) $(LDFLAGS) $(CFLAGS) -o $(C_DIR)/integrate.so $<
|
||||
|
||||
|
||||
$(COMPILED_DIR)/%.py : $(RESOURCE_DIR)/%.ui
|
||||
$(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
|
||||
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import pathlib
|
||||
sys.path.append(str(pathlib.Path(__file__).absolute().parent.parent / 'src'))
|
||||
|
||||
from nmreval.configs import check_for_config
|
||||
|
||||
# does a directory for config stuff exist? create it if not
|
||||
check_for_config()
|
||||
|
||||
# pyqtgraph warns on Mac if QApplication is created when it is imported
|
||||
# import pyqtgraph
|
||||
|
||||
from nmreval.lib.logger import handle_exception
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
from gui_qt import App
|
||||
|
||||
app = App(['Team Rocket FTW!'])
|
||||
|
||||
from gui_qt.main.mainwindow import NMRMainWindow
|
||||
|
||||
mplQt = NMRMainWindow()
|
||||
mplQt.show()
|
||||
|
||||
sys.exit(app.exec())
|
@ -1,54 +1,53 @@
|
||||
[metadata]
|
||||
name = nmreval
|
||||
version = 0.1
|
||||
description = Evaluation of data
|
||||
long_description = file: README.md
|
||||
author = Dominik Demuth
|
||||
author_email = dominik.demuth@physik.tu-darmstadt.de
|
||||
install_requires = [
|
||||
'numpy',
|
||||
'scipy',
|
||||
'matplotlib',
|
||||
'bsddb3',
|
||||
'pyqtgraph',
|
||||
'pyqt',
|
||||
'h5py',
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel", "setuptools-scm"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "nmreval"
|
||||
version = "0.1"
|
||||
description = "Evaluation of NMR and orther data"
|
||||
authors = [
|
||||
{ name = "Dominik Demuth", email = "dominik.demuth@pkm.tu-darmstadt.de" },
|
||||
]
|
||||
keywords = ['nmr', 'physics', 'science']
|
||||
maintainers = [
|
||||
{ name = "Dominik Demuth", email = "dominik.demuth@pkm.tu-darmstadt.de" },
|
||||
]
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Intended Audience :: Science/Research',
|
||||
'Topic :: Scientific/Engineering :: Physics',
|
||||
'Topic :: Scientific/Engineering :: Visualization',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3 :: only',
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Topic :: Scientific/Engineering :: Physics",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3 :: only",
|
||||
"Operating System :: POSIX :: Linux"
|
||||
]
|
||||
keywords = ["nmr", "physics", "science"]
|
||||
license = { text = "BSD 3-Clause License" }
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"scipy",
|
||||
"h5py",
|
||||
"PyQt5",
|
||||
"pyqtgraph",
|
||||
]
|
||||
license = {text = "BSD 3-Clause License"}
|
||||
|
||||
[tool.setuptools]
|
||||
include_package_data = true
|
||||
[project.optional-dependencies]
|
||||
legacy = ["bsddb3"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval"
|
||||
Issues = "https://gitea.pkm.physik.tu-darmstadt.de/IPKM/nmreval/issues"
|
||||
|
||||
[tool.setuptools.packages]
|
||||
find = {}
|
||||
scripts = bin/evaluate.py
|
||||
[project.gui-scripts]
|
||||
nmreval = "gui_qt.cli:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include =[
|
||||
'nmreval*',
|
||||
'gui_qt*',
|
||||
'resources*',
|
||||
]
|
||||
|
||||
[tool.setuptools.package_data]
|
||||
* = *.txt, *.npz, *.png, *.json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
where = ["src"]
|
||||
|
@ -1,8 +0,0 @@
|
||||
matplotlib
|
||||
numpy
|
||||
scipy
|
||||
PyQt5
|
||||
h5py
|
||||
pyqtgraph
|
||||
bsddb3
|
||||
requests
|
4
setup.py
4
setup.py
@ -1,4 +0,0 @@
|
||||
# Always prefer setuptools over distutils
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '_ui/apod_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/apod_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -13,62 +14,223 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_ApodEdit(object):
|
||||
def setupUi(self, ApodEdit):
|
||||
ApodEdit.setObjectName("ApodEdit")
|
||||
ApodEdit.resize(784, 484)
|
||||
ApodEdit.resize(1144, 655)
|
||||
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.setContentsMargins(9, 9, 9, 9)
|
||||
self.gridLayout.setSpacing(3)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.graphicsView = PlotWidget(ApodEdit)
|
||||
self.freq_graph = NMRPlotWidget(ApodEdit)
|
||||
self.freq_graph.setObjectName("freq_graph")
|
||||
self.gridLayout.addWidget(self.freq_graph, 2, 2, 1, 1)
|
||||
self.time_graph = NMRPlotWidget(ApodEdit)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.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.setHeightForWidth(self.time_graph.sizePolicy().hasHeightForWidth())
|
||||
self.time_graph.setSizePolicy(sizePolicy)
|
||||
self.time_graph.setObjectName("time_graph")
|
||||
self.gridLayout.addWidget(self.time_graph, 2, 1, 1, 1)
|
||||
self.widget = QtWidgets.QWidget(ApodEdit)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.apodcombobox.sizePolicy().hasHeightForWidth())
|
||||
self.apodcombobox.setSizePolicy(sizePolicy)
|
||||
sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
|
||||
self.widget.setSizePolicy(sizePolicy)
|
||||
self.widget.setObjectName("widget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.baseline_box = QtWidgets.QCheckBox(self.widget)
|
||||
self.baseline_box.setObjectName("baseline_box")
|
||||
self.verticalLayout.addWidget(self.baseline_box)
|
||||
self.shift_box = QtWidgets.QGroupBox(self.widget)
|
||||
self.shift_box.setFlat(True)
|
||||
self.shift_box.setCheckable(True)
|
||||
self.shift_box.setChecked(False)
|
||||
self.shift_box.setObjectName("shift_box")
|
||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.shift_box)
|
||||
self.gridLayout_4.setContentsMargins(3, 3, 3, 3)
|
||||
self.gridLayout_4.setSpacing(3)
|
||||
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||
self.ls_lineedit = QtWidgets.QLineEdit(self.shift_box)
|
||||
self.ls_lineedit.setObjectName("ls_lineedit")
|
||||
self.gridLayout_4.addWidget(self.ls_lineedit, 1, 1, 1, 1)
|
||||
self.ls_spinbox = QtWidgets.QSpinBox(self.shift_box)
|
||||
self.ls_spinbox.setMaximum(999999)
|
||||
self.ls_spinbox.setObjectName("ls_spinbox")
|
||||
self.gridLayout_4.addWidget(self.ls_spinbox, 0, 1, 1, 1)
|
||||
self.ls_combobox = QtWidgets.QComboBox(self.shift_box)
|
||||
self.ls_combobox.setObjectName("ls_combobox")
|
||||
self.ls_combobox.addItem("")
|
||||
self.ls_combobox.addItem("")
|
||||
self.gridLayout_4.addWidget(self.ls_combobox, 0, 0, 2, 1)
|
||||
self.verticalLayout.addWidget(self.shift_box)
|
||||
self.apod_box = QtWidgets.QGroupBox(self.widget)
|
||||
self.apod_box.setFlat(True)
|
||||
self.apod_box.setCheckable(True)
|
||||
self.apod_box.setChecked(False)
|
||||
self.apod_box.setObjectName("apod_box")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.apod_box)
|
||||
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
|
||||
self.verticalLayout_2.setSpacing(3)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.apodcombobox = QtWidgets.QComboBox(self.apod_box)
|
||||
self.apodcombobox.setObjectName("apodcombobox")
|
||||
self.gridLayout.addWidget(self.apodcombobox, 0, 0, 1, 1)
|
||||
self.verticalLayout_2.addWidget(self.apodcombobox)
|
||||
self.eqn_label = QtWidgets.QLabel(self.apod_box)
|
||||
self.eqn_label.setIndent(3)
|
||||
self.eqn_label.setObjectName("eqn_label")
|
||||
self.verticalLayout_2.addWidget(self.eqn_label)
|
||||
self.widget_layout = QtWidgets.QHBoxLayout()
|
||||
self.widget_layout.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.verticalLayout_2.addLayout(self.widget_layout)
|
||||
self.verticalLayout.addWidget(self.apod_box)
|
||||
self.zerofill_box = QtWidgets.QGroupBox(self.widget)
|
||||
self.zerofill_box.setFlat(True)
|
||||
self.zerofill_box.setCheckable(True)
|
||||
self.zerofill_box.setChecked(False)
|
||||
self.zerofill_box.setObjectName("zerofill_box")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.zerofill_box)
|
||||
self.horizontalLayout.setContentsMargins(3, 3, 3, 3)
|
||||
self.horizontalLayout.setSpacing(3)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.label = QtWidgets.QLabel(self.zerofill_box)
|
||||
self.label.setObjectName("label")
|
||||
self.horizontalLayout.addWidget(self.label)
|
||||
self.zf_spinbox = QtWidgets.QSpinBox(self.zerofill_box)
|
||||
self.zf_spinbox.setMinimum(1)
|
||||
self.zf_spinbox.setMaximum(3)
|
||||
self.zf_spinbox.setObjectName("zf_spinbox")
|
||||
self.horizontalLayout.addWidget(self.zf_spinbox)
|
||||
self.verticalLayout.addWidget(self.zerofill_box)
|
||||
self.phase_box = QtWidgets.QGroupBox(self.widget)
|
||||
self.phase_box.setFlat(True)
|
||||
self.phase_box.setCheckable(True)
|
||||
self.phase_box.setChecked(False)
|
||||
self.phase_box.setObjectName("phase_box")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.phase_box)
|
||||
self.gridLayout_2.setContentsMargins(3, 3, 3, 3)
|
||||
self.gridLayout_2.setSpacing(3)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.label_3 = QtWidgets.QLabel(self.phase_box)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
|
||||
self.label_4 = QtWidgets.QLabel(self.phase_box)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1)
|
||||
self.ph0_spinbox = QtWidgets.QDoubleSpinBox(self.phase_box)
|
||||
self.ph0_spinbox.setWrapping(True)
|
||||
self.ph0_spinbox.setDecimals(1)
|
||||
self.ph0_spinbox.setMinimum(-180.0)
|
||||
self.ph0_spinbox.setMaximum(180.0)
|
||||
self.ph0_spinbox.setSingleStep(0.5)
|
||||
self.ph0_spinbox.setObjectName("ph0_spinbox")
|
||||
self.gridLayout_2.addWidget(self.ph0_spinbox, 0, 1, 1, 1)
|
||||
self.pivot_lineedit = QtWidgets.QLineEdit(self.phase_box)
|
||||
self.pivot_lineedit.setObjectName("pivot_lineedit")
|
||||
self.gridLayout_2.addWidget(self.pivot_lineedit, 2, 1, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(self.phase_box)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1)
|
||||
self.ph1_spinbox = QtWidgets.QDoubleSpinBox(self.phase_box)
|
||||
self.ph1_spinbox.setWrapping(True)
|
||||
self.ph1_spinbox.setDecimals(2)
|
||||
self.ph1_spinbox.setMinimum(-720.0)
|
||||
self.ph1_spinbox.setMaximum(720.0)
|
||||
self.ph1_spinbox.setSingleStep(0.05)
|
||||
self.ph1_spinbox.setObjectName("ph1_spinbox")
|
||||
self.gridLayout_2.addWidget(self.ph1_spinbox, 1, 1, 1, 1)
|
||||
self.verticalLayout.addWidget(self.phase_box)
|
||||
self.ft_box = QtWidgets.QGroupBox(self.widget)
|
||||
self.ft_box.setCheckable(True)
|
||||
self.ft_box.setChecked(False)
|
||||
self.ft_box.setObjectName("ft_box")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.ft_box)
|
||||
self.verticalLayout_3.setContentsMargins(3, 3, 3, 3)
|
||||
self.verticalLayout_3.setSpacing(3)
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.phase_before_button = QtWidgets.QRadioButton(self.ft_box)
|
||||
self.phase_before_button.setChecked(True)
|
||||
self.phase_before_button.setObjectName("phase_before_button")
|
||||
self.buttonGroup = QtWidgets.QButtonGroup(ApodEdit)
|
||||
self.buttonGroup.setObjectName("buttonGroup")
|
||||
self.buttonGroup.addButton(self.phase_before_button)
|
||||
self.verticalLayout_3.addWidget(self.phase_before_button)
|
||||
self.phase_after_button = QtWidgets.QRadioButton(self.ft_box)
|
||||
self.phase_after_button.setObjectName("phase_after_button")
|
||||
self.buttonGroup.addButton(self.phase_after_button)
|
||||
self.verticalLayout_3.addWidget(self.phase_after_button)
|
||||
self.verticalLayout.addWidget(self.ft_box)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
self.gridLayout.addWidget(self.widget, 2, 0, 1, 1)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(ApodEdit)
|
||||
self.buttonBox.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.gridLayout.addWidget(self.buttonBox, 4, 1, 1, 2)
|
||||
self.log_freq_widget = QtWidgets.QWidget(ApodEdit)
|
||||
self.log_freq_widget.setObjectName("log_freq_widget")
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.log_freq_widget)
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_3.addItem(spacerItem1)
|
||||
self.logx_freq = QtWidgets.QCheckBox(self.log_freq_widget)
|
||||
self.logx_freq.setObjectName("logx_freq")
|
||||
self.horizontalLayout_3.addWidget(self.logx_freq)
|
||||
self.logy_freq = QtWidgets.QCheckBox(self.log_freq_widget)
|
||||
self.logy_freq.setObjectName("logy_freq")
|
||||
self.horizontalLayout_3.addWidget(self.logy_freq)
|
||||
self.gridLayout.addWidget(self.log_freq_widget, 3, 2, 1, 1)
|
||||
self.logtime_widget = QtWidgets.QWidget(ApodEdit)
|
||||
self.logtime_widget.setObjectName("logtime_widget")
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.logtime_widget)
|
||||
self.horizontalLayout_2.setContentsMargins(-1, 1, -1, -1)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem2)
|
||||
self.logx_time = QtWidgets.QCheckBox(self.logtime_widget)
|
||||
self.logx_time.setObjectName("logx_time")
|
||||
self.horizontalLayout_2.addWidget(self.logx_time)
|
||||
self.logy_time = QtWidgets.QCheckBox(self.logtime_widget)
|
||||
self.logy_time.setObjectName("logy_time")
|
||||
self.horizontalLayout_2.addWidget(self.logy_time)
|
||||
self.gridLayout.addWidget(self.logtime_widget, 3, 1, 1, 1)
|
||||
|
||||
self.retranslateUi(ApodEdit)
|
||||
self.buttonBox.accepted.connect(ApodEdit.accept)
|
||||
self.buttonBox.rejected.connect(ApodEdit.close)
|
||||
self.buttonBox.accepted.connect(ApodEdit.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(ApodEdit.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(ApodEdit)
|
||||
|
||||
def retranslateUi(self, ApodEdit):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
ApodEdit.setWindowTitle(_translate("ApodEdit", "Apodization"))
|
||||
self.baseline_box.setText(_translate("ApodEdit", "Baseline"))
|
||||
self.shift_box.setTitle(_translate("ApodEdit", "Shift"))
|
||||
self.ls_lineedit.setText(_translate("ApodEdit", "0"))
|
||||
self.ls_combobox.setItemText(0, _translate("ApodEdit", "Points"))
|
||||
self.ls_combobox.setItemText(1, _translate("ApodEdit", "Seconds"))
|
||||
self.apod_box.setTitle(_translate("ApodEdit", "Apodization"))
|
||||
self.eqn_label.setText(_translate("ApodEdit", "TextLabel"))
|
||||
from pyqtgraph import PlotWidget
|
||||
self.zerofill_box.setTitle(_translate("ApodEdit", "Zero fill"))
|
||||
self.label.setText(_translate("ApodEdit", "Double length"))
|
||||
self.zf_spinbox.setSuffix(_translate("ApodEdit", "x"))
|
||||
self.phase_box.setTitle(_translate("ApodEdit", "Phase correction"))
|
||||
self.label_3.setText(_translate("ApodEdit", "Phase 1"))
|
||||
self.label_4.setText(_translate("ApodEdit", "Pivot"))
|
||||
self.pivot_lineedit.setText(_translate("ApodEdit", "0"))
|
||||
self.label_2.setText(_translate("ApodEdit", "Phase 0"))
|
||||
self.ft_box.setTitle(_translate("ApodEdit", "Fourier transform"))
|
||||
self.phase_before_button.setText(_translate("ApodEdit", "before phase correction"))
|
||||
self.phase_after_button.setText(_translate("ApodEdit", "after phase correction"))
|
||||
self.logx_freq.setText(_translate("ApodEdit", "Log X"))
|
||||
self.logy_freq.setText(_translate("ApodEdit", "Log Y"))
|
||||
self.logx_time.setText(_translate("ApodEdit", "Log X"))
|
||||
self.logy_time.setText(_translate("ApodEdit", "Log Y"))
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/asciidialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_ascii_reader(object):
|
||||
def setupUi(self, ascii_reader):
|
||||
ascii_reader.setObjectName("ascii_reader")
|
||||
ascii_reader.resize(627, 703)
|
||||
ascii_reader.resize(665, 904)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(ascii_reader)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.tabWidget = QtWidgets.QTabWidget(ascii_reader)
|
||||
@ -28,92 +28,25 @@ class Ui_ascii_reader(object):
|
||||
self.header_widget.setObjectName("header_widget")
|
||||
self.verticalLayout_3.addWidget(self.header_widget)
|
||||
self.groupBox = QtWidgets.QGroupBox(self.tabWidgetPage1)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
|
||||
self.groupBox.setSizePolicy(sizePolicy)
|
||||
self.groupBox.setObjectName("groupBox")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
|
||||
self.gridLayout.setContentsMargins(3, 3, 3, 3)
|
||||
self.gridLayout.setHorizontalSpacing(9)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.FID_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.FID_radioButton.setAutoExclusive(True)
|
||||
self.FID_radioButton.setObjectName("FID_radioButton")
|
||||
self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader)
|
||||
self.buttonGroup.setObjectName("buttonGroup")
|
||||
self.buttonGroup.addButton(self.FID_radioButton)
|
||||
self.gridLayout.addWidget(self.FID_radioButton, 2, 1, 1, 1)
|
||||
self.x_lineedit = QtWidgets.QLineEdit(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.x_lineedit.sizePolicy().hasHeightForWidth())
|
||||
self.x_lineedit.setSizePolicy(sizePolicy)
|
||||
self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
|
||||
self.x_lineedit.setObjectName("x_lineedit")
|
||||
self.gridLayout.addWidget(self.x_lineedit, 1, 3, 1, 1)
|
||||
self.y_label = QtWidgets.QLabel(self.groupBox)
|
||||
self.y_label.setObjectName("y_label")
|
||||
self.gridLayout.addWidget(self.y_label, 2, 2, 1, 1)
|
||||
self.pts_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.pts_radioButton.setChecked(True)
|
||||
self.pts_radioButton.setAutoExclusive(True)
|
||||
self.pts_radioButton.setObjectName("pts_radioButton")
|
||||
self.buttonGroup.addButton(self.pts_radioButton)
|
||||
self.gridLayout.addWidget(self.pts_radioButton, 1, 1, 1, 1)
|
||||
self.deltay_lineEdit = QtWidgets.QLineEdit(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.deltay_lineEdit.sizePolicy().hasHeightForWidth())
|
||||
self.deltay_lineEdit.setSizePolicy(sizePolicy)
|
||||
self.deltay_lineEdit.setObjectName("deltay_lineEdit")
|
||||
self.gridLayout.addWidget(self.deltay_lineEdit, 3, 3, 1, 1)
|
||||
self.label_5 = QtWidgets.QLabel(self.groupBox)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.gridLayout.addWidget(self.label_5, 3, 2, 1, 1)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.groupBox)
|
||||
self.horizontalLayout_4.setContentsMargins(3, 3, 3, 3)
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.verticalLayout_6 = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout_6.setObjectName("verticalLayout_6")
|
||||
self.column_checkBox = QtWidgets.QCheckBox(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.column_checkBox.sizePolicy().hasHeightForWidth())
|
||||
self.column_checkBox.setSizePolicy(sizePolicy)
|
||||
self.column_checkBox.setObjectName("column_checkBox")
|
||||
self.gridLayout.addWidget(self.column_checkBox, 0, 0, 1, 1)
|
||||
self.label = QtWidgets.QLabel(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
|
||||
self.label.setSizePolicy(sizePolicy)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 0, 1, 1, 1)
|
||||
self.label_7 = QtWidgets.QLabel(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth())
|
||||
self.label_7.setSizePolicy(sizePolicy)
|
||||
self.label_7.setObjectName("label_7")
|
||||
self.gridLayout.addWidget(self.label_7, 0, 2, 1, 2)
|
||||
self.y_lineedit = QtWidgets.QLineEdit(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.y_lineedit.sizePolicy().hasHeightForWidth())
|
||||
self.y_lineedit.setSizePolicy(sizePolicy)
|
||||
self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
|
||||
self.y_lineedit.setObjectName("y_lineedit")
|
||||
self.gridLayout.addWidget(self.y_lineedit, 2, 3, 1, 1)
|
||||
self.spectrum_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.spectrum_radioButton.setObjectName("spectrum_radioButton")
|
||||
self.buttonGroup.addButton(self.spectrum_radioButton)
|
||||
self.gridLayout.addWidget(self.spectrum_radioButton, 3, 1, 1, 1)
|
||||
self.x_label = QtWidgets.QLabel(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.x_label.sizePolicy().hasHeightForWidth())
|
||||
self.x_label.setSizePolicy(sizePolicy)
|
||||
self.x_label.setObjectName("x_label")
|
||||
self.gridLayout.addWidget(self.x_label, 1, 2, 1, 1)
|
||||
self.verticalLayout_6.addWidget(self.column_checkBox)
|
||||
self.line_spinBox = QtWidgets.QSpinBox(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@ -122,17 +55,158 @@ class Ui_ascii_reader(object):
|
||||
self.line_spinBox.setSizePolicy(sizePolicy)
|
||||
self.line_spinBox.setMinimum(1)
|
||||
self.line_spinBox.setObjectName("line_spinBox")
|
||||
self.gridLayout.addWidget(self.line_spinBox, 1, 0, 1, 1)
|
||||
self.verticalLayout_6.addWidget(self.line_spinBox)
|
||||
self.label_6 = QtWidgets.QLabel(self.groupBox)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.verticalLayout_6.addWidget(self.label_6)
|
||||
self.preview_spinBox = QtWidgets.QSpinBox(self.groupBox)
|
||||
self.preview_spinBox.setMinimum(1)
|
||||
self.preview_spinBox.setProperty("value", 10)
|
||||
self.preview_spinBox.setObjectName("preview_spinBox")
|
||||
self.gridLayout.addWidget(self.preview_spinBox, 3, 0, 1, 1)
|
||||
self.label_6 = QtWidgets.QLabel(self.groupBox)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.gridLayout.addWidget(self.label_6, 2, 0, 1, 1)
|
||||
self.verticalLayout_6.addWidget(self.preview_spinBox)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_6.addItem(spacerItem)
|
||||
self.horizontalLayout_4.addLayout(self.verticalLayout_6)
|
||||
self.verticalLayout_5 = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout_5.setObjectName("verticalLayout_5")
|
||||
self.label = QtWidgets.QLabel(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
|
||||
self.label.setSizePolicy(sizePolicy)
|
||||
self.label.setObjectName("label")
|
||||
self.verticalLayout_5.addWidget(self.label)
|
||||
self.pts_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.pts_radioButton.setChecked(True)
|
||||
self.pts_radioButton.setAutoExclusive(True)
|
||||
self.pts_radioButton.setObjectName("pts_radioButton")
|
||||
self.buttonGroup = QtWidgets.QButtonGroup(ascii_reader)
|
||||
self.buttonGroup.setObjectName("buttonGroup")
|
||||
self.buttonGroup.addButton(self.pts_radioButton)
|
||||
self.verticalLayout_5.addWidget(self.pts_radioButton)
|
||||
self.dsc_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.dsc_radioButton.setObjectName("dsc_radioButton")
|
||||
self.buttonGroup.addButton(self.dsc_radioButton)
|
||||
self.verticalLayout_5.addWidget(self.dsc_radioButton)
|
||||
self.FID_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.FID_radioButton.setAutoExclusive(True)
|
||||
self.FID_radioButton.setObjectName("FID_radioButton")
|
||||
self.buttonGroup.addButton(self.FID_radioButton)
|
||||
self.verticalLayout_5.addWidget(self.FID_radioButton)
|
||||
self.spectrum_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.spectrum_radioButton.setObjectName("spectrum_radioButton")
|
||||
self.buttonGroup.addButton(self.spectrum_radioButton)
|
||||
self.verticalLayout_5.addWidget(self.spectrum_radioButton)
|
||||
self.bds_radioButton = QtWidgets.QRadioButton(self.groupBox)
|
||||
self.bds_radioButton.setObjectName("bds_radioButton")
|
||||
self.buttonGroup.addButton(self.bds_radioButton)
|
||||
self.verticalLayout_5.addWidget(self.bds_radioButton)
|
||||
self.horizontalLayout_4.addLayout(self.verticalLayout_5)
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout()
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.deltay_lineEdit = QtWidgets.QLineEdit(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.deltay_lineEdit.sizePolicy().hasHeightForWidth())
|
||||
self.deltay_lineEdit.setSizePolicy(sizePolicy)
|
||||
self.deltay_lineEdit.setObjectName("deltay_lineEdit")
|
||||
self.gridLayout_2.addWidget(self.deltay_lineEdit, 3, 1, 1, 1)
|
||||
self.label_7 = QtWidgets.QLabel(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth())
|
||||
self.label_7.setSizePolicy(sizePolicy)
|
||||
self.label_7.setObjectName("label_7")
|
||||
self.gridLayout_2.addWidget(self.label_7, 0, 0, 1, 2)
|
||||
self.y_lineedit = QtWidgets.QLineEdit(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.y_lineedit.sizePolicy().hasHeightForWidth())
|
||||
self.y_lineedit.setSizePolicy(sizePolicy)
|
||||
self.y_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
|
||||
self.y_lineedit.setObjectName("y_lineedit")
|
||||
self.gridLayout_2.addWidget(self.y_lineedit, 2, 1, 1, 1)
|
||||
self.y_label = QtWidgets.QLabel(self.groupBox)
|
||||
self.y_label.setObjectName("y_label")
|
||||
self.gridLayout_2.addWidget(self.y_label, 2, 0, 1, 1)
|
||||
self.x_lineedit = QtWidgets.QLineEdit(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.x_lineedit.sizePolicy().hasHeightForWidth())
|
||||
self.x_lineedit.setSizePolicy(sizePolicy)
|
||||
self.x_lineedit.setInputMethodHints(QtCore.Qt.ImhFormattedNumbersOnly|QtCore.Qt.ImhPreferNumbers)
|
||||
self.x_lineedit.setObjectName("x_lineedit")
|
||||
self.gridLayout_2.addWidget(self.x_lineedit, 1, 1, 1, 1)
|
||||
self.label_5 = QtWidgets.QLabel(self.groupBox)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.gridLayout_2.addWidget(self.label_5, 3, 0, 1, 1)
|
||||
self.x_label = QtWidgets.QLabel(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.x_label.sizePolicy().hasHeightForWidth())
|
||||
self.x_label.setSizePolicy(sizePolicy)
|
||||
self.x_label.setObjectName("x_label")
|
||||
self.gridLayout_2.addWidget(self.x_label, 1, 0, 1, 1)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_2.addItem(spacerItem1, 4, 1, 1, 1)
|
||||
self.horizontalLayout_4.addLayout(self.gridLayout_2)
|
||||
self.verticalLayout_3.addWidget(self.groupBox)
|
||||
self.dsdfsf = QtWidgets.QLabel(self.tabWidgetPage1)
|
||||
self.dsdfsf.setObjectName("dsdfsf")
|
||||
self.verticalLayout_3.addWidget(self.dsdfsf)
|
||||
self.gridLayout = QtWidgets.QGridLayout()
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.re_match_index = QtWidgets.QSpinBox(self.tabWidgetPage1)
|
||||
self.re_match_index.setMinimum(1)
|
||||
self.re_match_index.setObjectName("re_match_index")
|
||||
self.gridLayout.addWidget(self.re_match_index, 1, 3, 1, 1)
|
||||
self.label_9 = QtWidgets.QLabel(self.tabWidgetPage1)
|
||||
self.label_9.setObjectName("label_9")
|
||||
self.gridLayout.addWidget(self.label_9, 1, 2, 1, 1)
|
||||
self.regex_input = QtWidgets.QLineEdit(self.tabWidgetPage1)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.regex_input.sizePolicy().hasHeightForWidth())
|
||||
self.regex_input.setSizePolicy(sizePolicy)
|
||||
self.regex_input.setObjectName("regex_input")
|
||||
self.gridLayout.addWidget(self.regex_input, 1, 1, 1, 1)
|
||||
self.re_button = QtWidgets.QRadioButton(self.tabWidgetPage1)
|
||||
self.re_button.setChecked(True)
|
||||
self.re_button.setObjectName("re_button")
|
||||
self.buttonGroup_2 = QtWidgets.QButtonGroup(ascii_reader)
|
||||
self.buttonGroup_2.setObjectName("buttonGroup_2")
|
||||
self.buttonGroup_2.addButton(self.re_button)
|
||||
self.gridLayout.addWidget(self.re_button, 1, 0, 1, 1)
|
||||
self.custom_input = QtWidgets.QLineEdit(self.tabWidgetPage1)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.custom_input.sizePolicy().hasHeightForWidth())
|
||||
self.custom_input.setSizePolicy(sizePolicy)
|
||||
self.custom_input.setObjectName("custom_input")
|
||||
self.gridLayout.addWidget(self.custom_input, 2, 1, 1, 1)
|
||||
self.custom_button = QtWidgets.QRadioButton(self.tabWidgetPage1)
|
||||
self.custom_button.setObjectName("custom_button")
|
||||
self.buttonGroup_2.addButton(self.custom_button)
|
||||
self.gridLayout.addWidget(self.custom_button, 2, 0, 1, 1)
|
||||
self.label_8 = QtWidgets.QLabel(self.tabWidgetPage1)
|
||||
self.label_8.setWordWrap(True)
|
||||
self.label_8.setObjectName("label_8")
|
||||
self.gridLayout.addWidget(self.label_8, 0, 0, 1, 4)
|
||||
self.verticalLayout_3.addLayout(self.gridLayout)
|
||||
self.groupBox_2 = QtWidgets.QGroupBox(self.tabWidgetPage1)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
|
||||
self.groupBox_2.setSizePolicy(sizePolicy)
|
||||
self.groupBox_2.setObjectName("groupBox_2")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2)
|
||||
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
|
||||
@ -215,24 +289,19 @@ class Ui_ascii_reader(object):
|
||||
self.pushButton.setSizePolicy(sizePolicy)
|
||||
self.pushButton.setObjectName("pushButton")
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.pushButton)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.formLayout.setItem(6, QtWidgets.QFormLayout.FieldRole, spacerItem2)
|
||||
self.horizontalLayout_3.addLayout(self.formLayout)
|
||||
self.tabWidget.addTab(self.tabWidgetPage2, "")
|
||||
self.verticalLayout.addWidget(self.tabWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem1)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem3)
|
||||
self.skippy_checkbox = QtWidgets.QCheckBox(ascii_reader)
|
||||
self.skippy_checkbox.setObjectName("skippy_checkbox")
|
||||
self.horizontalLayout_2.addWidget(self.skippy_checkbox)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_2.addItem(spacerItem2)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_2)
|
||||
self.buttonbox = QtWidgets.QDialogButtonBox(ascii_reader)
|
||||
self.buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
@ -247,14 +316,20 @@ class Ui_ascii_reader(object):
|
||||
ascii_reader.setTabOrder(self.column_checkBox, self.line_spinBox)
|
||||
ascii_reader.setTabOrder(self.line_spinBox, self.preview_spinBox)
|
||||
ascii_reader.setTabOrder(self.preview_spinBox, self.pts_radioButton)
|
||||
ascii_reader.setTabOrder(self.pts_radioButton, self.FID_radioButton)
|
||||
ascii_reader.setTabOrder(self.pts_radioButton, self.dsc_radioButton)
|
||||
ascii_reader.setTabOrder(self.dsc_radioButton, self.FID_radioButton)
|
||||
ascii_reader.setTabOrder(self.FID_radioButton, self.spectrum_radioButton)
|
||||
ascii_reader.setTabOrder(self.spectrum_radioButton, self.x_lineedit)
|
||||
ascii_reader.setTabOrder(self.spectrum_radioButton, self.bds_radioButton)
|
||||
ascii_reader.setTabOrder(self.bds_radioButton, self.x_lineedit)
|
||||
ascii_reader.setTabOrder(self.x_lineedit, self.y_lineedit)
|
||||
ascii_reader.setTabOrder(self.y_lineedit, self.deltay_lineEdit)
|
||||
ascii_reader.setTabOrder(self.deltay_lineEdit, self.ascii_table)
|
||||
ascii_reader.setTabOrder(self.ascii_table, self.skippy_checkbox)
|
||||
ascii_reader.setTabOrder(self.skippy_checkbox, self.delay_textfield)
|
||||
ascii_reader.setTabOrder(self.deltay_lineEdit, self.re_button)
|
||||
ascii_reader.setTabOrder(self.re_button, self.regex_input)
|
||||
ascii_reader.setTabOrder(self.regex_input, self.re_match_index)
|
||||
ascii_reader.setTabOrder(self.re_match_index, self.custom_button)
|
||||
ascii_reader.setTabOrder(self.custom_button, self.custom_input)
|
||||
ascii_reader.setTabOrder(self.custom_input, self.ascii_table)
|
||||
ascii_reader.setTabOrder(self.ascii_table, self.delay_textfield)
|
||||
ascii_reader.setTabOrder(self.delay_textfield, self.delay_lineedit)
|
||||
ascii_reader.setTabOrder(self.delay_lineedit, self.start_lineedit)
|
||||
ascii_reader.setTabOrder(self.start_lineedit, self.end_lineedit)
|
||||
@ -262,24 +337,33 @@ class Ui_ascii_reader(object):
|
||||
ascii_reader.setTabOrder(self.log_checkBox, self.staggered_checkBox)
|
||||
ascii_reader.setTabOrder(self.staggered_checkBox, self.stag_lineEdit)
|
||||
ascii_reader.setTabOrder(self.stag_lineEdit, self.pushButton)
|
||||
ascii_reader.setTabOrder(self.pushButton, self.skippy_checkbox)
|
||||
|
||||
def retranslateUi(self, ascii_reader):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
ascii_reader.setWindowTitle(_translate("ascii_reader", "Read text file"))
|
||||
self.groupBox.setTitle(_translate("ascii_reader", "Options"))
|
||||
self.FID_radioButton.setText(_translate("ascii_reader", "FID"))
|
||||
self.x_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which column is used as x-value.</p></body></html>"))
|
||||
self.y_label.setText(_translate("ascii_reader", "y"))
|
||||
self.pts_radioButton.setText(_translate("ascii_reader", "Points"))
|
||||
self.label_5.setText(_translate("ascii_reader", "<html><head/><body><p>Δy</p></body></html>"))
|
||||
self.column_checkBox.setText(_translate("ascii_reader", "Column name"))
|
||||
self.label.setText(_translate("ascii_reader", "Import as"))
|
||||
self.label_7.setText(_translate("ascii_reader", "Use columns as"))
|
||||
self.y_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which columns are read for y-values. (\'Points\': Every number creates a new data set;\'FID\'/\'Spectrum\': Numbers at even positions are used for real parts, at odd positions for imaginary parts.)</p></body></html>"))
|
||||
self.spectrum_radioButton.setText(_translate("ascii_reader", "Spectrum"))
|
||||
self.x_label.setText(_translate("ascii_reader", "x"))
|
||||
self.line_spinBox.setPrefix(_translate("ascii_reader", "header line "))
|
||||
self.label_6.setText(_translate("ascii_reader", "Preview length"))
|
||||
self.label.setText(_translate("ascii_reader", "Import as"))
|
||||
self.pts_radioButton.setText(_translate("ascii_reader", "Points"))
|
||||
self.dsc_radioButton.setText(_translate("ascii_reader", "DSC"))
|
||||
self.FID_radioButton.setText(_translate("ascii_reader", "FID"))
|
||||
self.spectrum_radioButton.setText(_translate("ascii_reader", "Spectrum"))
|
||||
self.bds_radioButton.setText(_translate("ascii_reader", "BDS"))
|
||||
self.label_7.setText(_translate("ascii_reader", "Use columns as"))
|
||||
self.y_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which columns are used for y values.</p><p>- \'Points\': Every number creates a new data set;<br/>- \'FID\'/\'Spectrum\': Numbers at even positions are used for real parts, at odd positions for imaginary parts.</p></body></html>"))
|
||||
self.y_label.setText(_translate("ascii_reader", "y"))
|
||||
self.x_lineedit.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Specify which column is used for x values, write <span style=\" font-style:italic;\">index</span> to use row numbers (starting with 0). </p></body></html>"))
|
||||
self.label_5.setText(_translate("ascii_reader", "<html><head/><body><p>Δy</p></body></html>"))
|
||||
self.x_label.setText(_translate("ascii_reader", "x"))
|
||||
self.dsdfsf.setText(_translate("ascii_reader", "Numerical value"))
|
||||
self.label_9.setText(_translate("ascii_reader", "Match index"))
|
||||
self.regex_input.setToolTip(_translate("ascii_reader", "<html><head/><body><p>Token:<br/>[abc]: Matches any of a, b, or c<br/>[a-z]: Matches any digit in the range a-z<br/>\\d: Matches any digit in the range 0-9 (equal to [0-9}</p><p>Quantifiers:<br/>a*: 0 or more of a<br/>a*: 1 or more of a<br/>a?: 0 or 1 of a</p></body></html>"))
|
||||
self.re_button.setText(_translate("ascii_reader", "Regex"))
|
||||
self.custom_button.setText(_translate("ascii_reader", "Custom value"))
|
||||
self.label_8.setText(_translate("ascii_reader", "Filename"))
|
||||
self.groupBox_2.setTitle(_translate("ascii_reader", "Preview"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabWidgetPage1), _translate("ascii_reader", "Data"))
|
||||
self.label_2.setText(_translate("ascii_reader", "Number of delays"))
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '_ui/baseline_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/baseline_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -44,7 +45,7 @@ class Ui_SignalEdit(object):
|
||||
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)
|
||||
self.graphicsView = NMRPlotWidget(SignalEdit)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -54,11 +55,11 @@ class Ui_SignalEdit(object):
|
||||
self.gridLayout.addWidget(self.graphicsView, 0, 2, 1, 1)
|
||||
|
||||
self.retranslateUi(SignalEdit)
|
||||
self.buttonBox.accepted.connect(SignalEdit.accept)
|
||||
self.buttonBox.rejected.connect(SignalEdit.close)
|
||||
self.buttonBox.accepted.connect(SignalEdit.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(SignalEdit.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(SignalEdit)
|
||||
|
||||
def retranslateUi(self, SignalEdit):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
SignalEdit.setWindowTitle(_translate("SignalEdit", "Dialog"))
|
||||
from pyqtgraph import PlotWidget
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/basewindow.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -70,7 +70,7 @@ class Ui_BaseWindow(object):
|
||||
self.integralwidget = IntegralWidget()
|
||||
self.integralwidget.setObjectName("integralwidget")
|
||||
self.tabWidget.addTab(self.integralwidget, "")
|
||||
self.area = QtWidgets.QMdiArea(self.splitter)
|
||||
self.area = MdiAreaTile(self.splitter)
|
||||
self.area.setObjectName("area")
|
||||
self.horizontalLayout.addWidget(self.splitter)
|
||||
BaseWindow.setCentralWidget(self.centralwidget)
|
||||
@ -117,6 +117,8 @@ class Ui_BaseWindow(object):
|
||||
self.menuStuff = QtWidgets.QMenu(self.menubar)
|
||||
self.menuStuff.setTitle("")
|
||||
self.menuStuff.setObjectName("menuStuff")
|
||||
self.menuDSC = QtWidgets.QMenu(self.menubar)
|
||||
self.menuDSC.setObjectName("menuDSC")
|
||||
BaseWindow.setMenuBar(self.menubar)
|
||||
self.toolBar = QtWidgets.QToolBar(BaseWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
@ -261,6 +263,10 @@ class Ui_BaseWindow(object):
|
||||
self.actionMaximize.setObjectName("actionMaximize")
|
||||
self.actionTile = QtWidgets.QAction(BaseWindow)
|
||||
self.actionTile.setObjectName("actionTile")
|
||||
self.actionTileVertical = QtWidgets.QAction(BaseWindow)
|
||||
self.actionTileVertical.setObjectName("actionTileVertical")
|
||||
self.actionTileHorizontal = QtWidgets.QAction(BaseWindow)
|
||||
self.actionTileHorizontal.setObjectName("actionTileHorizontal")
|
||||
self.actionMinimize = QtWidgets.QAction(BaseWindow)
|
||||
self.actionMinimize.setCheckable(True)
|
||||
self.actionMinimize.setVisible(False)
|
||||
@ -358,6 +364,16 @@ class Ui_BaseWindow(object):
|
||||
self.actionBugs.setObjectName("actionBugs")
|
||||
self.actionShow_error_log = QtWidgets.QAction(BaseWindow)
|
||||
self.actionShow_error_log.setObjectName("actionShow_error_log")
|
||||
self.actionCreate_starter = QtWidgets.QAction(BaseWindow)
|
||||
self.actionCreate_starter.setObjectName("actionCreate_starter")
|
||||
self.actionAbout = QtWidgets.QAction(BaseWindow)
|
||||
self.actionAbout.setObjectName("actionAbout")
|
||||
self.actionTNMH_model = QtWidgets.QAction(BaseWindow)
|
||||
self.actionTNMH_model.setObjectName("actionTNMH_model")
|
||||
self.actionBinning = QtWidgets.QAction(BaseWindow)
|
||||
self.actionBinning.setObjectName("actionBinning")
|
||||
self.actionTNMH = QtWidgets.QAction(BaseWindow)
|
||||
self.actionTNMH.setObjectName("actionTNMH")
|
||||
self.menuSave.addAction(self.actionSave)
|
||||
self.menuSave.addAction(self.actionExportGraphic)
|
||||
self.menuSave.addAction(self.action_save_fit_parameter)
|
||||
@ -383,9 +399,9 @@ class Ui_BaseWindow(object):
|
||||
self.menuData.addSeparator()
|
||||
self.menuData.addAction(self.actionChange_datatypes)
|
||||
self.menuHelp.addAction(self.actionShow_error_log)
|
||||
self.menuHelp.addAction(self.actionDocumentation)
|
||||
self.menuHelp.addAction(self.actionUpdate)
|
||||
self.menuHelp.addAction(self.actionBugs)
|
||||
self.menuHelp.addAction(self.actionAbout)
|
||||
self.menuNormalize.addAction(self.action_norm_max)
|
||||
self.menuNormalize.addAction(self.action_norm_max_abs)
|
||||
self.menuNormalize.addSeparator()
|
||||
@ -401,6 +417,7 @@ class Ui_BaseWindow(object):
|
||||
self.menuExtra.addSeparator()
|
||||
self.menuExtra.addAction(self.menuNormalize.menuAction())
|
||||
self.menuExtra.addAction(self.actionInterpolation)
|
||||
self.menuExtra.addAction(self.actionBinning)
|
||||
self.menuExtra.addAction(self.actionRunning_values)
|
||||
self.menuExtra.addAction(self.actionShift)
|
||||
self.menuExtra.addSeparator()
|
||||
@ -422,7 +439,10 @@ class Ui_BaseWindow(object):
|
||||
self.menuOptions.addSeparator()
|
||||
self.menuOptions.addAction(self.action_colorcycle)
|
||||
self.menuOptions.addAction(self.actionConfiguration)
|
||||
self.menuOptions.addAction(self.actionCreate_starter)
|
||||
self.menuView.addAction(self.actionTile)
|
||||
self.menuView.addAction(self.actionTileVertical)
|
||||
self.menuView.addAction(self.actionTileHorizontal)
|
||||
self.menuView.addAction(self.actionCascade_windows)
|
||||
self.menuWindow.addAction(self.actionNew_window)
|
||||
self.menuWindow.addAction(self.actionDelete_window)
|
||||
@ -451,6 +471,7 @@ class Ui_BaseWindow(object):
|
||||
self.menuStuff.addAction(self.actionLife)
|
||||
self.menuStuff.addAction(self.actionTetris)
|
||||
self.menuStuff.addAction(self.actionMine)
|
||||
self.menuDSC.addAction(self.actionTNMH_model)
|
||||
self.menubar.addAction(self.menuFile.menuAction())
|
||||
self.menubar.addAction(self.menuWindow.menuAction())
|
||||
self.menubar.addAction(self.menuData.menuAction())
|
||||
@ -459,6 +480,7 @@ class Ui_BaseWindow(object):
|
||||
self.menubar.addAction(self.menuFit.menuAction())
|
||||
self.menubar.addAction(self.menuNMR.menuAction())
|
||||
self.menubar.addAction(self.menuBDS.menuAction())
|
||||
self.menubar.addAction(self.menuDSC.menuAction())
|
||||
self.menubar.addAction(self.menuOptions.menuAction())
|
||||
self.menubar.addAction(self.menuHelp.menuAction())
|
||||
self.menubar.addAction(self.menuStuff.menuAction())
|
||||
@ -511,6 +533,7 @@ class Ui_BaseWindow(object):
|
||||
self.menuNMR.setTitle(_translate("BaseWindow", "NMR"))
|
||||
self.menuBDS.setTitle(_translate("BaseWindow", "BDS"))
|
||||
self.menuSpectrum.setTitle(_translate("BaseWindow", "Spectrum"))
|
||||
self.menuDSC.setTitle(_translate("BaseWindow", "DSC"))
|
||||
self.toolBar.setWindowTitle(_translate("BaseWindow", "Main"))
|
||||
self.toolbar_edit.setWindowTitle(_translate("BaseWindow", "Math"))
|
||||
self.toolBar_nmr.setWindowTitle(_translate("BaseWindow", "NMR"))
|
||||
@ -566,6 +589,8 @@ class Ui_BaseWindow(object):
|
||||
self.actionGuide_lines.setText(_translate("BaseWindow", "Draw lines..."))
|
||||
self.actionMaximize.setText(_translate("BaseWindow", "Maximize"))
|
||||
self.actionTile.setText(_translate("BaseWindow", "Tile windows"))
|
||||
self.actionTileVertical.setText(_translate("BaseWindow", "Tile windows vertically"))
|
||||
self.actionTileHorizontal.setText(_translate("BaseWindow", "Tile windows horizontally"))
|
||||
self.actionMinimize.setText(_translate("BaseWindow", "Minimize"))
|
||||
self.actionNew_window.setText(_translate("BaseWindow", "New graph"))
|
||||
self.actionDelete_window.setText(_translate("BaseWindow", "Delete graph"))
|
||||
@ -612,6 +637,11 @@ class Ui_BaseWindow(object):
|
||||
self.action_draw_object.setText(_translate("BaseWindow", "Draw objects..."))
|
||||
self.actionBugs.setText(_translate("BaseWindow", "Bugs! Problems! Wishes!"))
|
||||
self.actionShow_error_log.setText(_translate("BaseWindow", "Show error log"))
|
||||
self.actionCreate_starter.setText(_translate("BaseWindow", "Create starter.."))
|
||||
self.actionAbout.setText(_translate("BaseWindow", "About..."))
|
||||
self.actionTNMH_model.setText(_translate("BaseWindow", "Tg , Hodge, TNMH,,,"))
|
||||
self.actionBinning.setText(_translate("BaseWindow", "Binning..."))
|
||||
self.actionTNMH.setText(_translate("BaseWindow", "TNMH..."))
|
||||
from ..data.datawidget.datawidget import DataWidget
|
||||
from ..data.integral_widget import IntegralWidget
|
||||
from ..data.point_select import PointSelectWidget
|
||||
@ -619,4 +649,5 @@ from ..data.signaledit.editsignalwidget import EditSignalWidget
|
||||
from ..data.valueeditwidget import ValueEditWidget
|
||||
from ..fit.fitwindow import QFitDialog
|
||||
from ..graphs.drawings import DrawingsWidget
|
||||
from ..lib.mdiarea import MdiAreaTile
|
||||
from ..nmr.t1widget import QT1Widget
|
||||
|
@ -1,13 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/bdsdialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/bdsdialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9.2
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
@ -16,12 +19,13 @@ class Ui_Dialog(object):
|
||||
self.gridLayout.setContentsMargins(3, 3, 3, 3)
|
||||
self.gridLayout.setSpacing(3)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.listWidget = QtWidgets.QListWidget(Dialog)
|
||||
self.listWidget = QListWidgetSelect(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.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.listWidget.setObjectName("listWidget")
|
||||
self.gridLayout.addWidget(self.listWidget, 1, 0, 2, 1)
|
||||
self.groupBox_2 = QtWidgets.QGroupBox(Dialog)
|
||||
@ -93,8 +97,8 @@ class Ui_Dialog(object):
|
||||
self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
self.buttonBox.accepted.connect(Dialog.accept)
|
||||
self.buttonBox.rejected.connect(Dialog.reject)
|
||||
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
Dialog.setTabOrder(self.freq_button, self.temp_button)
|
||||
Dialog.setTabOrder(self.temp_button, self.eps_checkBox)
|
||||
@ -117,4 +121,4 @@ class Ui_Dialog(object):
|
||||
self.temp_checkBox.setText(_translate("Dialog", "Meas. temperature"))
|
||||
self.time_checkBox.setText(_translate("Dialog", "Meas. time"))
|
||||
self.label.setText(_translate("Dialog", "Found entries"))
|
||||
|
||||
from ..lib.listwidget import QListWidgetSelect
|
||||
|
@ -1,29 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/dscfile_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/dscfile_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.9.2
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
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)
|
||||
Dialog.resize(1341, 799)
|
||||
self.verticalLayout_5 = QtWidgets.QVBoxLayout(Dialog)
|
||||
self.verticalLayout_5.setObjectName("verticalLayout_5")
|
||||
self.splitter = QtWidgets.QSplitter(Dialog)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.verticalLayoutWidget = QtWidgets.QWidget(self.splitter)
|
||||
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
|
||||
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
|
||||
self.verticalLayout_4.setContentsMargins(6, 6, 6, 6)
|
||||
self.verticalLayout_4.setObjectName("verticalLayout_4")
|
||||
self.groupBox = QtWidgets.QGroupBox(self.verticalLayoutWidget)
|
||||
self.groupBox.setFlat(False)
|
||||
self.groupBox.setObjectName("groupBox")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
|
||||
self.verticalLayout.setContentsMargins(6, 6, 6, 6)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.step_listWidget = QtWidgets.QListWidget(self.groupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.step_listWidget.sizePolicy().hasHeightForWidth())
|
||||
self.step_listWidget.setSizePolicy(sizePolicy)
|
||||
self.step_listWidget.setMinimumSize(QtCore.QSize(0, 0))
|
||||
self.step_listWidget.setObjectName("step_listWidget")
|
||||
self.verticalLayout.addWidget(self.step_listWidget)
|
||||
self.verticalLayout_4.addWidget(self.groupBox)
|
||||
self.groupBox_2 = QtWidgets.QGroupBox(self.verticalLayoutWidget)
|
||||
self.groupBox_2.setObjectName("groupBox_2")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2)
|
||||
self.verticalLayout_2.setContentsMargins(6, 6, 6, 6)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.groupBox_4 = QtWidgets.QGroupBox(self.groupBox_2)
|
||||
self.groupBox_4.setObjectName("groupBox_4")
|
||||
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox_4)
|
||||
self.verticalLayout_6.setContentsMargins(6, 6, 6, 6)
|
||||
self.verticalLayout_6.setObjectName("verticalLayout_6")
|
||||
self.empty_label = QtWidgets.QLabel(self.groupBox_4)
|
||||
self.empty_label.setObjectName("empty_label")
|
||||
self.verticalLayout_6.addWidget(self.empty_label)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setSpacing(3)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.loadempty_button = QtWidgets.QPushButton(self.groupBox_4)
|
||||
self.loadempty_button.setObjectName("loadempty_button")
|
||||
self.horizontalLayout_2.addWidget(self.loadempty_button)
|
||||
self.delempty_button = QtWidgets.QPushButton(self.groupBox_4)
|
||||
self.delempty_button.setObjectName("delempty_button")
|
||||
self.horizontalLayout_2.addWidget(self.delempty_button)
|
||||
self.verticalLayout_6.addLayout(self.horizontalLayout_2)
|
||||
self.verticalLayout_2.addWidget(self.groupBox_4)
|
||||
self.groupBox_5 = QtWidgets.QGroupBox(self.groupBox_2)
|
||||
self.groupBox_5.setObjectName("groupBox_5")
|
||||
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBox_5)
|
||||
self.verticalLayout_7.setContentsMargins(6, 6, 6, 6)
|
||||
self.verticalLayout_7.setSpacing(3)
|
||||
self.verticalLayout_7.setObjectName("verticalLayout_7")
|
||||
self.none_radioButton = QtWidgets.QRadioButton(self.groupBox_5)
|
||||
self.none_radioButton.setObjectName("none_radioButton")
|
||||
self.buttonGroup = QtWidgets.QButtonGroup(Dialog)
|
||||
self.buttonGroup.setObjectName("buttonGroup")
|
||||
self.buttonGroup.addButton(self.none_radioButton)
|
||||
self.verticalLayout_7.addWidget(self.none_radioButton)
|
||||
self.isotherm_radioButton = QtWidgets.QRadioButton(self.groupBox_5)
|
||||
self.isotherm_radioButton.setChecked(True)
|
||||
self.isotherm_radioButton.setObjectName("isotherm_radioButton")
|
||||
self.buttonGroup.addButton(self.isotherm_radioButton)
|
||||
self.verticalLayout_7.addWidget(self.isotherm_radioButton)
|
||||
self.slope_radioButton = QtWidgets.QRadioButton(self.groupBox_5)
|
||||
self.slope_radioButton.setObjectName("slope_radioButton")
|
||||
self.buttonGroup.addButton(self.slope_radioButton)
|
||||
self.verticalLayout_7.addWidget(self.slope_radioButton)
|
||||
self.widget = QtWidgets.QWidget(self.groupBox_5)
|
||||
self.widget.setMinimumSize(QtCore.QSize(0, 33))
|
||||
self.widget.setObjectName("widget")
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget)
|
||||
self.horizontalLayout_3.setContentsMargins(3, 3, 3, 3)
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.limit1_lineedit = QtWidgets.QLineEdit(self.widget)
|
||||
self.limit1_lineedit.setObjectName("limit1_lineedit")
|
||||
self.horizontalLayout_3.addWidget(self.limit1_lineedit)
|
||||
self.limit2_lineedit = QtWidgets.QLineEdit(self.widget)
|
||||
self.limit2_lineedit.setText("")
|
||||
self.limit2_lineedit.setObjectName("limit2_lineedit")
|
||||
self.horizontalLayout_3.addWidget(self.limit2_lineedit)
|
||||
self.verticalLayout_7.addWidget(self.widget)
|
||||
self.verticalLayout_2.addWidget(self.groupBox_5)
|
||||
self.verticalLayout_4.addWidget(self.groupBox_2)
|
||||
self.groupBox_3 = QtWidgets.QGroupBox(self.verticalLayoutWidget)
|
||||
self.groupBox_3.setObjectName("groupBox_3")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_3)
|
||||
self.verticalLayout_3.setContentsMargins(6, 6, 6, 6)
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.cp_checkBox = QtWidgets.QCheckBox(self.groupBox_3)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -31,36 +115,8 @@ class Ui_Dialog(object):
|
||||
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)
|
||||
self.verticalLayout_3.addWidget(self.cp_checkBox)
|
||||
self.reference_tableWidget = QtWidgets.QTableWidget(self.groupBox_3)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -75,57 +131,11 @@ class Ui_Dialog(object):
|
||||
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.verticalLayout_3.addWidget(self.reference_tableWidget)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setSpacing(3)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.ref_add_pushButton = QtWidgets.QPushButton(Dialog)
|
||||
self.ref_add_pushButton = QtWidgets.QPushButton(self.groupBox_3)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -133,7 +143,7 @@ class Ui_Dialog(object):
|
||||
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)
|
||||
self.ref_remove_pushButton = QtWidgets.QPushButton(self.groupBox_3)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -141,16 +151,22 @@ class Ui_Dialog(object):
|
||||
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.verticalLayout_3.addLayout(self.horizontalLayout)
|
||||
self.verticalLayout_4.addWidget(self.groupBox_3)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.verticalLayout_4.addItem(spacerItem)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(self.verticalLayoutWidget)
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Save)
|
||||
self.buttonBox.setCenterButtons(True)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.verticalLayout_4.addWidget(self.buttonBox)
|
||||
self.layoutWidget = QtWidgets.QWidget(self.splitter)
|
||||
self.layoutWidget.setObjectName("layoutWidget")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget)
|
||||
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.raw_graph = PlotWidget(Dialog)
|
||||
self.raw_graph = NMRPlotWidget(self.layoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -159,7 +175,7 @@ class Ui_Dialog(object):
|
||||
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)
|
||||
self.calib_graph = NMRPlotWidget(self.layoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -168,7 +184,7 @@ class Ui_Dialog(object):
|
||||
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)
|
||||
self.baseline_graph = NMRPlotWidget(self.layoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -177,7 +193,7 @@ class Ui_Dialog(object):
|
||||
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)
|
||||
self.end_graph = NMRPlotWidget(self.layoutWidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -186,28 +202,30 @@ class Ui_Dialog(object):
|
||||
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.verticalLayout_5.addWidget(self.splitter)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
self.buttonBox.accepted.connect(Dialog.accept)
|
||||
self.buttonBox.rejected.connect(Dialog.reject)
|
||||
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
|
||||
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.groupBox.setTitle(_translate("Dialog", "Detected steps"))
|
||||
self.groupBox_2.setTitle(_translate("Dialog", "Baseline corrections"))
|
||||
self.groupBox_4.setTitle(_translate("Dialog", "Empty measurement"))
|
||||
self.empty_label.setText(_translate("Dialog", "No emtpy measurement"))
|
||||
self.loadempty_button.setText(_translate("Dialog", "Load empty"))
|
||||
self.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.groupBox_5.setTitle(_translate("Dialog", "Slope correction"))
|
||||
self.none_radioButton.setText(_translate("Dialog", "None"))
|
||||
self.isotherm_radioButton.setText(_translate("Dialog", "Isotherms"))
|
||||
self.slope_radioButton.setText(_translate("Dialog", "Curve slope"))
|
||||
self.limit1_lineedit.setPlaceholderText(_translate("Dialog", "start (in min)"))
|
||||
self.limit2_lineedit.setPlaceholderText(_translate("Dialog", "stop (in min)"))
|
||||
self.groupBox_3.setTitle(_translate("Dialog", "References"))
|
||||
self.cp_checkBox.setText(_translate("Dialog", "Use reference to convert to heat capacity"))
|
||||
self.ref_add_pushButton.setText(_translate("Dialog", "Add reference"))
|
||||
self.ref_remove_pushButton.setText(_translate("Dialog", "Remove reference"))
|
||||
|
||||
from pyqtgraph import PlotWidget
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/eval_expr_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/eval_expr_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -36,14 +37,14 @@ class Ui_CalcDialog(object):
|
||||
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)
|
||||
self.listWidget = QListWidgetSelect(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.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.listWidget.setObjectName("listWidget")
|
||||
self.verticalLayout_2.addWidget(self.listWidget)
|
||||
self.overwrite_checkbox = QtWidgets.QCheckBox(self.page)
|
||||
@ -202,7 +203,7 @@ class Ui_CalcDialog(object):
|
||||
self.label_8.setBuddy(self.line_doubleSpinBox)
|
||||
|
||||
self.retranslateUi(CalcDialog)
|
||||
self.stackedWidget.setCurrentIndex(2)
|
||||
self.stackedWidget.setCurrentIndex(0)
|
||||
QtCore.QMetaObject.connectSlotsByName(CalcDialog)
|
||||
CalcDialog.setTabOrder(self.calc_edit, self.listWidget)
|
||||
CalcDialog.setTabOrder(self.listWidget, self.overwrite_checkbox)
|
||||
@ -237,4 +238,5 @@ class Ui_CalcDialog(object):
|
||||
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.listwidget import QListWidgetSelect
|
||||
from ..lib.namespace import QNamespaceWidget
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/fitdialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/fitdialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -37,7 +38,6 @@ class Ui_FitDialog(object):
|
||||
self.weight_combobox.addItem("")
|
||||
self.weight_combobox.addItem("")
|
||||
self.weight_combobox.addItem("")
|
||||
self.weight_combobox.addItem("")
|
||||
self.gridLayout_2.addWidget(self.weight_combobox, 6, 1, 1, 1)
|
||||
self.newmodel_button = QtWidgets.QPushButton(self.scrollAreaWidgetContents_2)
|
||||
self.newmodel_button.setEnabled(False)
|
||||
@ -143,7 +143,6 @@ class Ui_FitDialog(object):
|
||||
self.weight_combobox.setItemText(1, _translate("FitDialog", "y"))
|
||||
self.weight_combobox.setItemText(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"))
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/fitmodelwidget.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/fitmodelwidget.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_FitParameter(object):
|
||||
def setupUi(self, FitParameter):
|
||||
FitParameter.setObjectName("FitParameter")
|
||||
FitParameter.resize(365, 78)
|
||||
FitParameter.resize(365, 66)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -36,7 +37,7 @@ class Ui_FitParameter(object):
|
||||
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 = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.parameter_line.sizePolicy().hasHeightForWidth())
|
||||
@ -44,20 +45,12 @@ class Ui_FitParameter(object):
|
||||
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)
|
||||
|
@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/autohome/dominik/nmreval-gitea/src/resources/_ui/fitresult.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/fitresult.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.7
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -14,37 +14,105 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
Dialog.resize(864, 649)
|
||||
Dialog.resize(969, 974)
|
||||
self.gridLayout = QtWidgets.QGridLayout(Dialog)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.sets_comboBox = ElideComboBox(Dialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
self.stack = QtWidgets.QTabWidget(Dialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
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)
|
||||
sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth())
|
||||
self.stack.setSizePolicy(sizePolicy)
|
||||
self.stack.setObjectName("stack")
|
||||
self.stackPage1 = QtWidgets.QWidget()
|
||||
self.stackPage1.setObjectName("stackPage1")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.stackPage1)
|
||||
self.gridLayout_3.setContentsMargins(3, 3, 3, 3)
|
||||
self.gridLayout_3.setSpacing(3)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.logy_box = QtWidgets.QCheckBox(self.stackPage1)
|
||||
self.logy_box.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.logy_box.setObjectName("logy_box")
|
||||
self.gridLayout_3.addWidget(self.logy_box, 2, 1, 1, 1)
|
||||
self.logx_box = QtWidgets.QCheckBox(self.stackPage1)
|
||||
self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.logx_box.setObjectName("logx_box")
|
||||
self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1)
|
||||
self.graphicsView = GraphicsLayoutWidget(self.stackPage1)
|
||||
self.graphicsView.setObjectName("graphicsView")
|
||||
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2)
|
||||
self.stack.addTab(self.stackPage1, "")
|
||||
self.stackPage2 = QtWidgets.QWidget()
|
||||
self.stackPage2.setObjectName("stackPage2")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.stackPage2)
|
||||
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.stackPage2)
|
||||
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.addTab(self.stackPage2, "")
|
||||
self.stackPage3 = QtWidgets.QWidget()
|
||||
self.stackPage3.setObjectName("stackPage3")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.stackPage3)
|
||||
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.stackPage3)
|
||||
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.addTab(self.stackPage3, "")
|
||||
self.gridLayout.addWidget(self.stack, 0, 1, 5, 1)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Retry)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.gridLayout.addWidget(self.buttonBox, 6, 0, 1, 2)
|
||||
self.gridLayout.addWidget(self.buttonBox, 8, 0, 1, 2)
|
||||
self.param_tableWidget = QtWidgets.QTableWidget(Dialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.param_tableWidget.sizePolicy().hasHeightForWidth())
|
||||
self.param_tableWidget.setSizePolicy(sizePolicy)
|
||||
self.param_tableWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||
self.param_tableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
|
||||
self.param_tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.param_tableWidget.setAlternatingRowColors(True)
|
||||
self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.param_tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectColumns)
|
||||
self.param_tableWidget.setShowGrid(False)
|
||||
self.param_tableWidget.setColumnCount(0)
|
||||
self.param_tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.param_tableWidget.setColumnCount(1)
|
||||
self.param_tableWidget.setObjectName("param_tableWidget")
|
||||
self.param_tableWidget.setRowCount(0)
|
||||
self.param_tableWidget.horizontalHeader().setStretchLastSection(False)
|
||||
self.param_tableWidget.horizontalHeader().setVisible(False)
|
||||
self.param_tableWidget.horizontalHeader().setStretchLastSection(True)
|
||||
self.gridLayout.addWidget(self.param_tableWidget, 1, 0, 1, 1)
|
||||
self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog)
|
||||
self.del_prev_checkBox.setObjectName("del_prev_checkBox")
|
||||
self.gridLayout.addWidget(self.del_prev_checkBox, 3, 0, 1, 1)
|
||||
self.line = QtWidgets.QFrame(Dialog)
|
||||
self.line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.gridLayout.addWidget(self.line, 5, 0, 1, 2)
|
||||
self.groupBox = QtWidgets.QGroupBox(Dialog)
|
||||
self.groupBox.setObjectName("groupBox")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox)
|
||||
@ -112,89 +180,21 @@ class Ui_Dialog(object):
|
||||
self.partial_checkBox.setObjectName("partial_checkBox")
|
||||
self.horizontalLayout.addWidget(self.partial_checkBox)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 4)
|
||||
self.gridLayout.addWidget(self.groupBox, 5, 0, 1, 2)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setSpacing(3)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog)
|
||||
self.reject_fit_checkBox.setObjectName("reject_fit_checkBox")
|
||||
self.horizontalLayout_2.addWidget(self.reject_fit_checkBox)
|
||||
self.del_prev_checkBox = QtWidgets.QCheckBox(Dialog)
|
||||
self.del_prev_checkBox.setObjectName("del_prev_checkBox")
|
||||
self.horizontalLayout_2.addWidget(self.del_prev_checkBox)
|
||||
self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 1)
|
||||
self.line = QtWidgets.QFrame(Dialog)
|
||||
self.line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.gridLayout.addWidget(self.line, 3, 0, 1, 2)
|
||||
self.stack = QtWidgets.QTabWidget(Dialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
self.gridLayout.addWidget(self.groupBox, 7, 0, 1, 2)
|
||||
self.sets_comboBox = ElideComboBox(Dialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth())
|
||||
self.stack.setSizePolicy(sizePolicy)
|
||||
self.stack.setObjectName("stack")
|
||||
self.stackPage1 = QtWidgets.QWidget()
|
||||
self.stackPage1.setObjectName("stackPage1")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.stackPage1)
|
||||
self.gridLayout_3.setContentsMargins(3, 3, 3, 3)
|
||||
self.gridLayout_3.setSpacing(3)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.logy_box = QtWidgets.QCheckBox(self.stackPage1)
|
||||
self.logy_box.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.logy_box.setObjectName("logy_box")
|
||||
self.gridLayout_3.addWidget(self.logy_box, 2, 1, 1, 1)
|
||||
self.logx_box = QtWidgets.QCheckBox(self.stackPage1)
|
||||
self.logx_box.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.logx_box.setObjectName("logx_box")
|
||||
self.gridLayout_3.addWidget(self.logx_box, 2, 0, 1, 1)
|
||||
self.graphicsView = GraphicsLayoutWidget(self.stackPage1)
|
||||
self.graphicsView.setObjectName("graphicsView")
|
||||
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 2)
|
||||
self.stack.addTab(self.stackPage1, "")
|
||||
self.stackPage2 = QtWidgets.QWidget()
|
||||
self.stackPage2.setObjectName("stackPage2")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.stackPage2)
|
||||
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.stackPage2)
|
||||
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.addTab(self.stackPage2, "")
|
||||
self.stackPage3 = QtWidgets.QWidget()
|
||||
self.stackPage3.setObjectName("stackPage3")
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.stackPage3)
|
||||
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.stackPage3)
|
||||
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.addTab(self.stackPage3, "")
|
||||
self.gridLayout.addWidget(self.stack, 0, 1, 3, 1)
|
||||
sizePolicy.setHeightForWidth(self.sets_comboBox.sizePolicy().hasHeightForWidth())
|
||||
self.sets_comboBox.setSizePolicy(sizePolicy)
|
||||
self.sets_comboBox.setMaximumSize(QtCore.QSize(400, 16777215))
|
||||
self.sets_comboBox.setBaseSize(QtCore.QSize(200, 0))
|
||||
self.sets_comboBox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
|
||||
self.sets_comboBox.setObjectName("sets_comboBox")
|
||||
self.gridLayout.addWidget(self.sets_comboBox, 0, 0, 1, 1)
|
||||
self.reject_fit_checkBox = QtWidgets.QCheckBox(Dialog)
|
||||
self.reject_fit_checkBox.setObjectName("reject_fit_checkBox")
|
||||
self.gridLayout.addWidget(self.reject_fit_checkBox, 2, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
self.stack.setCurrentIndex(0)
|
||||
@ -203,20 +203,6 @@ class Ui_Dialog(object):
|
||||
def retranslateUi(self, Dialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Dialog.setWindowTitle(_translate("Dialog", "Fit results"))
|
||||
self.groupBox.setTitle(_translate("Dialog", "Output"))
|
||||
self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function"))
|
||||
self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves"))
|
||||
self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter"))
|
||||
self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter"))
|
||||
self.minx_line.setToolTip(_translate("Dialog", "Leave empty to start at lowest point"))
|
||||
self.minx_line.setPlaceholderText(_translate("Dialog", "min x"))
|
||||
self.maxx_line.setToolTip(_translate("Dialog", "Leave empty to start at highest point"))
|
||||
self.maxx_line.setPlaceholderText(_translate("Dialog", "max x"))
|
||||
self.numx_line.setPlaceholderText(_translate("Dialog", "# pts"))
|
||||
self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve"))
|
||||
self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions"))
|
||||
self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
|
||||
self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits"))
|
||||
self.logy_box.setText(_translate("Dialog", "logarithmic y axis"))
|
||||
self.logx_box.setText(_translate("Dialog", "logarithmic x axis"))
|
||||
self.stack.setTabText(self.stack.indexOf(self.stackPage1), _translate("Dialog", "Plot"))
|
||||
@ -230,5 +216,19 @@ class Ui_Dialog(object):
|
||||
item = self.corr_tableWidget.horizontalHeaderItem(3)
|
||||
item.setText(_translate("Dialog", "Partial Corr."))
|
||||
self.stack.setTabText(self.stack.indexOf(self.stackPage3), _translate("Dialog", "Correlations"))
|
||||
self.del_prev_checkBox.setText(_translate("Dialog", "Delete previous fits of this set"))
|
||||
self.groupBox.setTitle(_translate("Dialog", "Output"))
|
||||
self.extrapolate_box.setToolTip(_translate("Dialog", "Extrapolates only main function"))
|
||||
self.extrapolate_box.setText(_translate("Dialog", "Extrapolate curves"))
|
||||
self.parameter_checkbox.setText(_translate("Dialog", "Plot parameter"))
|
||||
self.graph_checkBox.setText(_translate("Dialog", "New graph for parameter"))
|
||||
self.minx_line.setToolTip(_translate("Dialog", "Leave empty to start at lowest point"))
|
||||
self.minx_line.setPlaceholderText(_translate("Dialog", "min x"))
|
||||
self.maxx_line.setToolTip(_translate("Dialog", "Leave empty to start at highest point"))
|
||||
self.maxx_line.setPlaceholderText(_translate("Dialog", "max x"))
|
||||
self.numx_line.setPlaceholderText(_translate("Dialog", "# pts"))
|
||||
self.curve_checkbox.setText(_translate("Dialog", "Plot fit curve"))
|
||||
self.partial_checkBox.setText(_translate("Dialog", "Plot partial functions"))
|
||||
self.reject_fit_checkBox.setText(_translate("Dialog", "Reject this fit"))
|
||||
from ..lib.forms import ElideComboBox
|
||||
from pyqtgraph import GraphicsLayoutWidget
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/graph.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/graph.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -196,19 +197,20 @@ class Ui_GraphWindow(object):
|
||||
self.gridLayout.setHorizontalSpacing(3)
|
||||
self.gridLayout.setVerticalSpacing(0)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.listWidget = QtWidgets.QListWidget(GraphWindow)
|
||||
self.listWidget = QListWidgetSelect(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.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
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)
|
||||
self.graphic = NMRPlotWidget(GraphWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -272,4 +274,5 @@ class Ui_GraphWindow(object):
|
||||
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
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
from ..lib.listwidget import QListWidgetSelect
|
||||
|
@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file '/autohome/dominik/nmreval/src/resources/_ui/integral_widget.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/integral_widget.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.4
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -26,7 +26,6 @@ class Ui_Form(object):
|
||||
self.set_combobox.setObjectName("set_combobox")
|
||||
self.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")
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/integratederive_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/integratederive_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
Dialog.resize(400, 308)
|
||||
Dialog.resize(400, 375)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.listWidget = QtWidgets.QListWidget(Dialog)
|
||||
@ -49,6 +50,9 @@ class Ui_Dialog(object):
|
||||
self.log_checkbox = QtWidgets.QCheckBox(Dialog)
|
||||
self.log_checkbox.setObjectName("log_checkbox")
|
||||
self.verticalLayout.addWidget(self.log_checkbox)
|
||||
self.freq_box = QtWidgets.QCheckBox(Dialog)
|
||||
self.freq_box.setObjectName("freq_box")
|
||||
self.verticalLayout.addWidget(self.freq_box)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
|
||||
self.horizontalLayout_2.setSpacing(3)
|
||||
@ -67,8 +71,8 @@ class Ui_Dialog(object):
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
self.buttonBox.accepted.connect(Dialog.accept)
|
||||
self.buttonBox.rejected.connect(Dialog.reject)
|
||||
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
|
||||
def retranslateUi(self, Dialog):
|
||||
@ -80,4 +84,5 @@ class Ui_Dialog(object):
|
||||
self.ft_comboBox.setItemText(1, _translate("Dialog", "Imag"))
|
||||
self.ft_comboBox.setItemText(2, _translate("Dialog", "Complex"))
|
||||
self.log_checkbox.setText(_translate("Dialog", "use logarithmic x axis"))
|
||||
self.freq_box.setText(_translate("Dialog", "return x axis as f, not omega"))
|
||||
self.newgraph_checkbox.setText(_translate("Dialog", "New graph"))
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/interpol_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/interpol_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -65,12 +66,13 @@ class Ui_Dialog(object):
|
||||
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)
|
||||
self.listWidget = QListWidgetSelect(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.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
||||
self.listWidget.setObjectName("listWidget")
|
||||
self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 2)
|
||||
self.sampling_widget = QtWidgets.QWidget(Dialog)
|
||||
@ -130,8 +132,8 @@ class Ui_Dialog(object):
|
||||
self.label_8.setBuddy(self.dest_combobox)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
self.buttonBox.accepted.connect(Dialog.accept)
|
||||
self.buttonBox.rejected.connect(Dialog.reject)
|
||||
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
Dialog.setTabOrder(self.listWidget, self.ylog_checkBox)
|
||||
Dialog.setTabOrder(self.ylog_checkBox, self.interp_comboBox)
|
||||
@ -164,3 +166,4 @@ class Ui_Dialog(object):
|
||||
self.xaxis_comboBox.setItemText(1, _translate("Dialog", "from data"))
|
||||
self.label_8.setText(_translate("Dialog", "Add interpolated data to"))
|
||||
self.xlog_checkBox.setText(_translate("Dialog", "use log(x)"))
|
||||
from ..lib.listwidget import QListWidgetSelect
|
||||
|
@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/phase_corr_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/phase_corr_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.4
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -24,7 +24,7 @@ class Ui_SignalEdit(object):
|
||||
self.gridLayout.setContentsMargins(6, 6, 6, 6)
|
||||
self.gridLayout.setSpacing(3)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.graphicsView = PlotWidget(SignalEdit)
|
||||
self.graphicsView = NMRPlotWidget(SignalEdit)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -83,8 +83,8 @@ class Ui_SignalEdit(object):
|
||||
self.gridLayout.addItem(spacerItem1, 1, 5, 1, 1)
|
||||
|
||||
self.retranslateUi(SignalEdit)
|
||||
self.buttonBox.accepted.connect(SignalEdit.accept)
|
||||
self.buttonBox.rejected.connect(SignalEdit.close)
|
||||
self.buttonBox.accepted.connect(SignalEdit.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(SignalEdit.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(SignalEdit)
|
||||
|
||||
def retranslateUi(self, SignalEdit):
|
||||
@ -94,4 +94,4 @@ class Ui_SignalEdit(object):
|
||||
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
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/shift_scale_dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/shift_scale_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -162,7 +163,7 @@ class Ui_shift_dialog(object):
|
||||
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)
|
||||
self.graphicsView = NMRPlotWidget(self.verticalFrame_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@ -267,8 +268,8 @@ class Ui_shift_dialog(object):
|
||||
|
||||
self.retranslateUi(shift_dialog)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
self.buttonBox.accepted.connect(shift_dialog.accept)
|
||||
self.buttonBox.rejected.connect(shift_dialog.reject)
|
||||
self.buttonBox.accepted.connect(shift_dialog.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(shift_dialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(shift_dialog)
|
||||
shift_dialog.setTabOrder(self.tabWidget, self.shift_table)
|
||||
shift_dialog.setTabOrder(self.shift_table, self.x_shift_spinbox)
|
||||
@ -310,5 +311,5 @@ class Ui_shift_dialog(object):
|
||||
self.overwrite_checkbox.setText(_translate("shift_dialog", "Overwrite data"))
|
||||
self.data_newgraph.setText(_translate("shift_dialog", "New graph"))
|
||||
self.values_newgraph.setText(_translate("shift_dialog", "New graph"))
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
from ..lib.spinboxes import SciSpinBox
|
||||
from pyqtgraph import PlotWidget
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/t1dialog.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/t1dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.12.3
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@ -160,6 +161,7 @@ class Ui_t1dialog(object):
|
||||
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.setEnabled(False)
|
||||
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)
|
||||
|
253
src/gui_qt/_py/tnmh_dialog.py
Normal file
253
src/gui_qt/_py/tnmh_dialog.py
Normal file
@ -0,0 +1,253 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/tnmh_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_DSCEvalDialog(object):
|
||||
def setupUi(self, DSCEvalDialog):
|
||||
DSCEvalDialog.setObjectName("DSCEvalDialog")
|
||||
DSCEvalDialog.resize(996, 712)
|
||||
self.gridLayout = QtWidgets.QGridLayout(DSCEvalDialog)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.listWidget = QListWidgetSelect(DSCEvalDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.listWidget.sizePolicy().hasHeightForWidth())
|
||||
self.listWidget.setSizePolicy(sizePolicy)
|
||||
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.listWidget.setObjectName("listWidget")
|
||||
self.gridLayout.addWidget(self.listWidget, 0, 0, 1, 1)
|
||||
self.stackedWidget = QtWidgets.QStackedWidget(DSCEvalDialog)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
|
||||
self.stackedWidget.setSizePolicy(sizePolicy)
|
||||
self.stackedWidget.setFrameShape(QtWidgets.QFrame.Box)
|
||||
self.stackedWidget.setObjectName("stackedWidget")
|
||||
self.page = QtWidgets.QWidget()
|
||||
self.page.setObjectName("page")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.page)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.gridLayout_9 = QtWidgets.QGridLayout()
|
||||
self.gridLayout_9.setObjectName("gridLayout_9")
|
||||
self.tg_export_check = QtWidgets.QCheckBox(self.page)
|
||||
self.tg_export_check.setChecked(True)
|
||||
self.tg_export_check.setObjectName("tg_export_check")
|
||||
self.gridLayout_9.addWidget(self.tg_export_check, 0, 0, 1, 1)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_9.addItem(spacerItem, 3, 0, 1, 1)
|
||||
self.tglines_export_check = QtWidgets.QCheckBox(self.page)
|
||||
self.tglines_export_check.setChecked(True)
|
||||
self.tglines_export_check.setObjectName("tglines_export_check")
|
||||
self.gridLayout_9.addWidget(self.tglines_export_check, 0, 1, 1, 1)
|
||||
self.tg_export_button = QtWidgets.QPushButton(self.page)
|
||||
self.tg_export_button.setObjectName("tg_export_button")
|
||||
self.gridLayout_9.addWidget(self.tg_export_button, 2, 0, 1, 2)
|
||||
self.gridLayout_2.addLayout(self.gridLayout_9, 2, 1, 1, 1)
|
||||
self.tg_tree = QtWidgets.QTreeWidget(self.page)
|
||||
self.tg_tree.setObjectName("tg_tree")
|
||||
self.tg_tree.headerItem().setText(0, "1")
|
||||
self.tg_tree.header().setVisible(False)
|
||||
self.gridLayout_2.addWidget(self.tg_tree, 2, 0, 1, 1)
|
||||
self.dsc_plot = NMRPlotWidget(self.page)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.dsc_plot.sizePolicy().hasHeightForWidth())
|
||||
self.dsc_plot.setSizePolicy(sizePolicy)
|
||||
self.dsc_plot.setObjectName("dsc_plot")
|
||||
self.gridLayout_2.addWidget(self.dsc_plot, 1, 0, 1, 2)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.label = QtWidgets.QLabel(self.page)
|
||||
self.label.setObjectName("label")
|
||||
self.horizontalLayout_4.addWidget(self.label)
|
||||
self.calctg_button = QtWidgets.QPushButton(self.page)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.calctg_button.sizePolicy().hasHeightForWidth())
|
||||
self.calctg_button.setSizePolicy(sizePolicy)
|
||||
self.calctg_button.setObjectName("calctg_button")
|
||||
self.horizontalLayout_4.addWidget(self.calctg_button)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_4, 0, 0, 1, 2)
|
||||
self.stackedWidget.addWidget(self.page)
|
||||
self.page_2 = QtWidgets.QWidget()
|
||||
self.page_2.setObjectName("page_2")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.page_2)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.label_4 = QtWidgets.QLabel(self.page_2)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridLayout_3.addWidget(self.label_4, 2, 0, 1, 1)
|
||||
self.tghodge_graph = NMRPlotWidget(self.page_2)
|
||||
self.tghodge_graph.setObjectName("tghodge_graph")
|
||||
self.gridLayout_3.addWidget(self.tghodge_graph, 1, 0, 1, 1)
|
||||
self.tau_plot = NMRPlotWidget(self.page_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.tau_plot.sizePolicy().hasHeightForWidth())
|
||||
self.tau_plot.setSizePolicy(sizePolicy)
|
||||
self.tau_plot.setObjectName("tau_plot")
|
||||
self.gridLayout_3.addWidget(self.tau_plot, 1, 1, 1, 1)
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.label_2 = QtWidgets.QLabel(self.page_2)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.horizontalLayout_3.addWidget(self.label_2)
|
||||
self.hodge_button = QtWidgets.QPushButton(self.page_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.hodge_button.sizePolicy().hasHeightForWidth())
|
||||
self.hodge_button.setSizePolicy(sizePolicy)
|
||||
self.hodge_button.setObjectName("hodge_button")
|
||||
self.horizontalLayout_3.addWidget(self.hodge_button)
|
||||
self.gridLayout_3.addLayout(self.horizontalLayout_3, 0, 0, 1, 2)
|
||||
self.export_hodge_button = QtWidgets.QPushButton(self.page_2)
|
||||
self.export_hodge_button.setObjectName("export_hodge_button")
|
||||
self.gridLayout_3.addWidget(self.export_hodge_button, 5, 1, 1, 1)
|
||||
self.hodge_graph_combo = QtWidgets.QComboBox(self.page_2)
|
||||
self.hodge_graph_combo.setEnabled(False)
|
||||
self.hodge_graph_combo.setObjectName("hodge_graph_combo")
|
||||
self.gridLayout_3.addWidget(self.hodge_graph_combo, 4, 1, 1, 1)
|
||||
self.hodge_graph_check = QtWidgets.QCheckBox(self.page_2)
|
||||
self.hodge_graph_check.setChecked(True)
|
||||
self.hodge_graph_check.setObjectName("hodge_graph_check")
|
||||
self.gridLayout_3.addWidget(self.hodge_graph_check, 4, 0, 1, 1)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.onset_check = QtWidgets.QCheckBox(self.page_2)
|
||||
self.onset_check.setChecked(True)
|
||||
self.onset_check.setObjectName("onset_check")
|
||||
self.horizontalLayout_5.addWidget(self.onset_check)
|
||||
self.mid_check = QtWidgets.QCheckBox(self.page_2)
|
||||
self.mid_check.setChecked(True)
|
||||
self.mid_check.setObjectName("mid_check")
|
||||
self.horizontalLayout_5.addWidget(self.mid_check)
|
||||
self.end_check = QtWidgets.QCheckBox(self.page_2)
|
||||
self.end_check.setChecked(True)
|
||||
self.end_check.setObjectName("end_check")
|
||||
self.horizontalLayout_5.addWidget(self.end_check)
|
||||
self.inflection_check = QtWidgets.QCheckBox(self.page_2)
|
||||
self.inflection_check.setChecked(True)
|
||||
self.inflection_check.setObjectName("inflection_check")
|
||||
self.horizontalLayout_5.addWidget(self.inflection_check)
|
||||
self.fictive_check = QtWidgets.QCheckBox(self.page_2)
|
||||
self.fictive_check.setChecked(True)
|
||||
self.fictive_check.setObjectName("fictive_check")
|
||||
self.horizontalLayout_5.addWidget(self.fictive_check)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout_5.addItem(spacerItem1)
|
||||
self.gridLayout_3.addLayout(self.horizontalLayout_5, 3, 0, 1, 2)
|
||||
self.stackedWidget.addWidget(self.page_2)
|
||||
self.page_3 = QtWidgets.QWidget()
|
||||
self.page_3.setObjectName("page_3")
|
||||
self.gridLayout_6 = QtWidgets.QGridLayout(self.page_3)
|
||||
self.gridLayout_6.setObjectName("gridLayout_6")
|
||||
self.tnmh_graphics = NMRPlotWidget(self.page_3)
|
||||
self.tnmh_graphics.setObjectName("tnmh_graphics")
|
||||
self.gridLayout_6.addWidget(self.tnmh_graphics, 1, 0, 1, 2)
|
||||
self.tnmh_tree = QtWidgets.QTreeWidget(self.page_3)
|
||||
self.tnmh_tree.setObjectName("tnmh_tree")
|
||||
self.tnmh_tree.headerItem().setText(0, "1")
|
||||
self.tnmh_tree.header().setVisible(False)
|
||||
self.gridLayout_6.addWidget(self.tnmh_tree, 2, 0, 1, 1)
|
||||
self.gridLayout_5 = QtWidgets.QGridLayout()
|
||||
self.gridLayout_5.setObjectName("gridLayout_5")
|
||||
self.tnmh_graph_check = QtWidgets.QCheckBox(self.page_3)
|
||||
self.tnmh_graph_check.setChecked(True)
|
||||
self.tnmh_graph_check.setObjectName("tnmh_graph_check")
|
||||
self.gridLayout_5.addWidget(self.tnmh_graph_check, 1, 0, 1, 1)
|
||||
self.tnmhfit_export_check = QtWidgets.QCheckBox(self.page_3)
|
||||
self.tnmhfit_export_check.setChecked(True)
|
||||
self.tnmhfit_export_check.setObjectName("tnmhfit_export_check")
|
||||
self.gridLayout_5.addWidget(self.tnmhfit_export_check, 0, 0, 1, 1)
|
||||
self.tnmh_export_button = QtWidgets.QPushButton(self.page_3)
|
||||
self.tnmh_export_button.setObjectName("tnmh_export_button")
|
||||
self.gridLayout_5.addWidget(self.tnmh_export_button, 2, 0, 1, 2)
|
||||
self.fictive_export_check = QtWidgets.QCheckBox(self.page_3)
|
||||
self.fictive_export_check.setChecked(True)
|
||||
self.fictive_export_check.setObjectName("fictive_export_check")
|
||||
self.gridLayout_5.addWidget(self.fictive_export_check, 0, 1, 1, 1)
|
||||
self.tnmh_graph_combo = QtWidgets.QComboBox(self.page_3)
|
||||
self.tnmh_graph_combo.setEnabled(False)
|
||||
self.tnmh_graph_combo.setObjectName("tnmh_graph_combo")
|
||||
self.gridLayout_5.addWidget(self.tnmh_graph_combo, 1, 1, 1, 1)
|
||||
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
self.gridLayout_5.addItem(spacerItem2, 3, 0, 1, 1)
|
||||
self.gridLayout_6.addLayout(self.gridLayout_5, 2, 1, 1, 1)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.label_3 = QtWidgets.QLabel(self.page_3)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.horizontalLayout_2.addWidget(self.label_3)
|
||||
self.tnhm_fitbutton = QtWidgets.QPushButton(self.page_3)
|
||||
self.tnhm_fitbutton.setObjectName("tnhm_fitbutton")
|
||||
self.horizontalLayout_2.addWidget(self.tnhm_fitbutton)
|
||||
self.gridLayout_6.addLayout(self.horizontalLayout_2, 0, 0, 1, 2)
|
||||
self.stackedWidget.addWidget(self.page_3)
|
||||
self.gridLayout.addWidget(self.stackedWidget, 0, 1, 1, 1)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem3)
|
||||
self.back_button = QtWidgets.QToolButton(DSCEvalDialog)
|
||||
self.back_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
|
||||
self.back_button.setArrowType(QtCore.Qt.LeftArrow)
|
||||
self.back_button.setObjectName("back_button")
|
||||
self.horizontalLayout.addWidget(self.back_button)
|
||||
self.next_button = QtWidgets.QToolButton(DSCEvalDialog)
|
||||
self.next_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
|
||||
self.next_button.setArrowType(QtCore.Qt.RightArrow)
|
||||
self.next_button.setObjectName("next_button")
|
||||
self.horizontalLayout.addWidget(self.next_button)
|
||||
self.close_button = QtWidgets.QPushButton(DSCEvalDialog)
|
||||
self.close_button.setObjectName("close_button")
|
||||
self.horizontalLayout.addWidget(self.close_button)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2)
|
||||
|
||||
self.retranslateUi(DSCEvalDialog)
|
||||
self.stackedWidget.setCurrentIndex(0)
|
||||
self.close_button.clicked.connect(DSCEvalDialog.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(DSCEvalDialog)
|
||||
|
||||
def retranslateUi(self, DSCEvalDialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
DSCEvalDialog.setWindowTitle(_translate("DSCEvalDialog", "To boldly go where no man has gone before"))
|
||||
self.tg_export_check.setText(_translate("DSCEvalDialog", "Export Tg"))
|
||||
self.tglines_export_check.setText(_translate("DSCEvalDialog", "Export lines"))
|
||||
self.tg_export_button.setText(_translate("DSCEvalDialog", "Export"))
|
||||
self.label.setText(_translate("DSCEvalDialog", "<html><head/><body><p><span style=\" font-weight:600;\">Glass transition</span></p></body></html>"))
|
||||
self.calctg_button.setText(_translate("DSCEvalDialog", "Calculate Tg"))
|
||||
self.label_4.setText(_translate("DSCEvalDialog", "Export tau:"))
|
||||
self.label_2.setText(_translate("DSCEvalDialog", "<html><head/><body><p><span style=\" font-weight:600;\">Hodge tau</span></p></body></html>"))
|
||||
self.hodge_button.setText(_translate("DSCEvalDialog", "Make it so."))
|
||||
self.export_hodge_button.setText(_translate("DSCEvalDialog", "Export"))
|
||||
self.hodge_graph_check.setText(_translate("DSCEvalDialog", "New graph"))
|
||||
self.onset_check.setText(_translate("DSCEvalDialog", "Onset"))
|
||||
self.mid_check.setText(_translate("DSCEvalDialog", "Midpoint"))
|
||||
self.end_check.setText(_translate("DSCEvalDialog", "End"))
|
||||
self.inflection_check.setText(_translate("DSCEvalDialog", "Inflection"))
|
||||
self.fictive_check.setText(_translate("DSCEvalDialog", "Fictive"))
|
||||
self.tnmh_graph_check.setText(_translate("DSCEvalDialog", "New graph"))
|
||||
self.tnmhfit_export_check.setText(_translate("DSCEvalDialog", "Export fit"))
|
||||
self.tnmh_export_button.setText(_translate("DSCEvalDialog", "Export"))
|
||||
self.fictive_export_check.setText(_translate("DSCEvalDialog", "Export dTf / dT"))
|
||||
self.label_3.setText(_translate("DSCEvalDialog", "<html><head/><body><p><span style=\" font-weight:600;\">dTf/dT and TNMH </span></p></body></html>"))
|
||||
self.tnhm_fitbutton.setText(_translate("DSCEvalDialog", "Engage!"))
|
||||
self.back_button.setText(_translate("DSCEvalDialog", "Back"))
|
||||
self.next_button.setText(_translate("DSCEvalDialog", "Next"))
|
||||
self.close_button.setText(_translate("DSCEvalDialog", "Close"))
|
||||
from ..lib.graph_items import NMRPlotWidget
|
||||
from ..lib.listwidget import QListWidgetSelect
|
@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resources/_ui/valueeditor.ui'
|
||||
# Form implementation generated from reading ui file 'src/resources/_ui/valueeditor.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.4
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
28
src/gui_qt/cli.py
Normal file
28
src/gui_qt/cli.py
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
def main():
|
||||
import sys
|
||||
from nmreval.configs import check_for_config
|
||||
|
||||
# does a directory for config stuff exist? create it if not
|
||||
check_for_config()
|
||||
|
||||
# pyqtgraph warns on Mac if QApplication is created when it is imported
|
||||
# import pyqtgraph
|
||||
|
||||
from nmreval.lib.logger import handle_exception
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
from gui_qt import App
|
||||
|
||||
app = App(['Team Rocket FTW!'])
|
||||
|
||||
from gui_qt.main.mainwindow import NMRMainWindow
|
||||
|
||||
mplQt = NMRMainWindow()
|
||||
mplQt.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -8,8 +8,10 @@ from pyqtgraph import mkPen
|
||||
|
||||
from nmreval.data.points import Points
|
||||
from nmreval.data.signals import Signal
|
||||
from nmreval.lib.logger import logger
|
||||
from nmreval.utils.text import convert
|
||||
from nmreval.data.bds import BDS
|
||||
from nmreval.data.dsc import DSC
|
||||
from nmreval.lib.colors import BaseColor, TUColors
|
||||
from nmreval.lib.lines import LineStyle
|
||||
from nmreval.lib.symbols import SymbolStyle, symbolcycle
|
||||
@ -32,6 +34,7 @@ class ExperimentContainer(QtCore.QObject):
|
||||
self.id = str(identifier)
|
||||
|
||||
self._fits = []
|
||||
self._relations = kwargs.get('relations', {})
|
||||
self._data = data
|
||||
self._manager = kwargs.get('manager')
|
||||
self.graph = ''
|
||||
@ -44,6 +47,7 @@ class ExperimentContainer(QtCore.QObject):
|
||||
self.actions = {}
|
||||
self._update_actions()
|
||||
|
||||
@plot_update
|
||||
def _init_plot(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@ -226,6 +230,8 @@ class ExperimentContainer(QtCore.QObject):
|
||||
return self.plot_real, self.plot_imag, self.plot_error
|
||||
|
||||
def get_state(self):
|
||||
# TODO preserve relationships
|
||||
|
||||
ret_dic = {
|
||||
'id': self.id,
|
||||
'data': self._data.get_state(),
|
||||
@ -267,6 +273,32 @@ class ExperimentContainer(QtCore.QObject):
|
||||
else:
|
||||
self._fits.extend(value)
|
||||
|
||||
def has_relation(self, relation_type):
|
||||
return relation_type in self._relations
|
||||
|
||||
def get_relation(self, relation_type: int):
|
||||
return self._relations.get(relation_type, [])
|
||||
|
||||
def add_relation(self, relation_type: int, value: str):
|
||||
if relation_type not in self._relations:
|
||||
self._relations[relation_type] = []
|
||||
|
||||
self._relations[relation_type].append(value)
|
||||
|
||||
def remove_relation(self, relation_type: int, value: str):
|
||||
if relation_type not in self._relations:
|
||||
raise ValueError(f'Relationship {relation_type!r} with id {value!r} doe not exist for {self.name}')
|
||||
|
||||
related_id_value = self._relations[relation_type]
|
||||
|
||||
idx = related_id_value.index(value)
|
||||
if idx == -1:
|
||||
raise ValueError(f'Relationship {relation_type!r} with id {value!r} doe not exist for {self.name}')
|
||||
|
||||
related_id_value.pop(idx)
|
||||
if len(related_id_value) == 0:
|
||||
self._relations.pop(relation_type)
|
||||
|
||||
def _update_actions(self):
|
||||
self.actions.update({'sort': self._data.sort,
|
||||
'cut': self._data.cut,
|
||||
@ -312,12 +344,10 @@ class ExperimentContainer(QtCore.QObject):
|
||||
err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb()))
|
||||
self.plot_error.setData(pen=err_pen)
|
||||
|
||||
elif mode == 'imag' and self.plot_imag is not None:
|
||||
if mode in ['imag', 'all'] and self.plot_imag is not None:
|
||||
self.plot_imag.set_color(color, symbol=symbol, line=line)
|
||||
else:
|
||||
print('Updating color failed for ' + str(self.id))
|
||||
|
||||
def setSymbol(self, symbol=None, color=None, size=None, mode='real'):
|
||||
def setSymbol(self, *, symbol=None, color=None, size=None, mode='real'):
|
||||
if mode in ['real', 'all']:
|
||||
self.plot_real.set_symbol(symbol=symbol, size=size, color=color)
|
||||
if color is not None and self.plot_error is not None and self.plot_real.symbol != SymbolStyle.No:
|
||||
@ -327,9 +357,9 @@ class ExperimentContainer(QtCore.QObject):
|
||||
elif mode in ['imag', 'all'] 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))
|
||||
logger.warning(f'Updating symbol failed for {self.id}')
|
||||
|
||||
def setLine(self, width=None, style=None, color=None, mode='real'):
|
||||
def setLine(self, *, width=None, style=None, color=None, mode='real'):
|
||||
if mode in ['real', 'all']:
|
||||
self.plot_real.set_line(width=width, style=style, color=color)
|
||||
if color is not None and self.plot_error is not None and self.plot_real.symbol == SymbolStyle.No:
|
||||
@ -339,7 +369,7 @@ class ExperimentContainer(QtCore.QObject):
|
||||
elif mode in ['imag', 'all'] 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))
|
||||
logger.warning(f'Updating line failed for {self.id}')
|
||||
|
||||
def update_property(self, key1: str, key2: str, value: Any):
|
||||
keykey = key2.split()
|
||||
@ -434,11 +464,21 @@ class ExperimentContainer(QtCore.QObject):
|
||||
|
||||
return offset
|
||||
|
||||
@plot_update
|
||||
def shift_scale(self, shift_factor: tuple[float, float], scaling_factor: tuple[float, float]):
|
||||
scale_x, scale_y = scaling_factor
|
||||
shift_x, shift_y = shift_factor
|
||||
self.data.x = self.data.x * scale_x + shift_x
|
||||
self.data.y = self.data.y * scale_y + shift_y
|
||||
self.data.y_err = self.data.y_err * scale_y
|
||||
|
||||
self.update({'shift': scaling_factor, 'scale': shift_factor})
|
||||
|
||||
def get_namespace(self, i: int = None, j: int = None) -> dict:
|
||||
if (i is None) and (j is None):
|
||||
prefix = ''
|
||||
else:
|
||||
prefix = 'g[%i].s[%i].' % (i, j)
|
||||
prefix = f'g[{i}].s[{j}].'
|
||||
|
||||
namespace = {prefix + 'x': (self.x, 'x values'),
|
||||
prefix + 'y': [self.y, 'y values'],
|
||||
@ -459,27 +499,50 @@ class ExperimentContainer(QtCore.QObject):
|
||||
|
||||
return namespace
|
||||
|
||||
def eval_expression(self, cmds, namespace):
|
||||
namespace.update({'x': self.x, 'y': self.y, 'y_err': self.y_err, 'value': self.value})
|
||||
def eval_expression(self, cmds, namespace, i=None, j=None):
|
||||
if i is not None:
|
||||
namespace['i'] = i
|
||||
|
||||
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()})
|
||||
if j is not None:
|
||||
namespace['j'] = j
|
||||
|
||||
namespace.update({'x': self._data.x, 'y': self._data.y, 'y_err': self._data.y_err, 'value': self.value})
|
||||
namespace['fit'] = {}
|
||||
|
||||
if isinstance(self, FitContainer):
|
||||
namespace['fit'].update({
|
||||
'%s' % convert(pname, old='latex', new='plain'): pvalue.value
|
||||
for (pname, pvalue) in self._data.parameter.items()
|
||||
})
|
||||
|
||||
elif len(self._fits) == 1:
|
||||
namespace['fit'].update({
|
||||
'%s' % convert(pname, old='tex', new='str'): pvalue.value
|
||||
for (pname, pvalue) in self._manager[self._fits[0]].parameter.items()
|
||||
})
|
||||
else:
|
||||
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()})
|
||||
namespace['fit'].update({
|
||||
"%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.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err'], replace_mask=False)
|
||||
new_data.value = namespace['value']
|
||||
|
||||
return new_data
|
||||
|
||||
def binning(self, digits: float):
|
||||
new_data = self.copy(full=True)
|
||||
new_data.data = self._data.binning(value=digits)
|
||||
|
||||
return new_data
|
||||
|
||||
|
||||
class PointContainer(ExperimentContainer):
|
||||
symbols = symbolcycle()
|
||||
@ -490,6 +553,9 @@ class PointContainer(ExperimentContainer):
|
||||
self.mode = 'pts'
|
||||
self._init_plot(**kwargs)
|
||||
|
||||
if isinstance(self._data, DSC):
|
||||
self.mode = 'dsc'
|
||||
|
||||
def _init_plot(self, **kwargs):
|
||||
self.plot_imag = None
|
||||
|
||||
@ -526,17 +592,17 @@ class PointContainer(ExperimentContainer):
|
||||
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,
|
||||
self.plot_real = PlotItem(x=self.x, y=self.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,
|
||||
self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err,
|
||||
pen=mkPen({'color': self.plot_real.symbolcolor.rgb()}))
|
||||
else:
|
||||
self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err,
|
||||
self.plot_error = ErrorBars(x=self.x, y=self.y, top=self.y_err, bottom=self.y_err,
|
||||
pen=mkPen({'color': self.plot_real.linecolor.rgb()}))
|
||||
|
||||
|
||||
@ -559,12 +625,12 @@ class FitContainer(ExperimentContainer):
|
||||
if isinstance(color, BaseColor):
|
||||
color = color.rgb()
|
||||
|
||||
self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name,
|
||||
self.plot_real = PlotItem(x=self.x, y=self.y.real, name=self.name,
|
||||
pen=mkPen({'color': color}),
|
||||
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,
|
||||
self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name,
|
||||
pen=mkPen({'color': color}),
|
||||
connect='finite', symbol=None)
|
||||
|
||||
@ -597,9 +663,9 @@ class SignalContainer(ExperimentContainer):
|
||||
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,
|
||||
self.plot_real = PlotItem(x=self.x, y=self.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,
|
||||
self.plot_imag = PlotItem(x=self.x, y=self.y.imag, name=self.name,
|
||||
symbol=None, pen=None, connect='finite')
|
||||
|
||||
color = kwargs.get('color', None)
|
||||
@ -683,3 +749,46 @@ class SignalContainer(ExperimentContainer):
|
||||
self._update_actions()
|
||||
|
||||
return self
|
||||
|
||||
@plot_update
|
||||
def edit_signal(
|
||||
self: SignalContainer,
|
||||
baseline: tuple[None | bool],
|
||||
leftshift: tuple[None | float, str],
|
||||
zerofill: tuple[None, int],
|
||||
apod: tuple[None, list[float], type[object]],
|
||||
phase: tuple[None | float, float, float],
|
||||
fourier: tuple[None | bool]
|
||||
):
|
||||
"""
|
||||
Function for EditUndoCommand to call if a timesignal or spectra must be worked on.
|
||||
This avoids to update the plot for every action we do and makes it slightly faster.
|
||||
"""
|
||||
|
||||
if baseline[0] is not None:
|
||||
self._data.baseline()
|
||||
|
||||
if leftshift[0] is not None:
|
||||
self._data.shift(*leftshift)
|
||||
|
||||
if zerofill[0] is not None:
|
||||
self._data.zerofill(*zerofill)
|
||||
|
||||
if apod[0] is not None:
|
||||
self._data.apod(*apod)
|
||||
|
||||
# ft with three options: None, True, False
|
||||
if fourier[0] is None:
|
||||
# ft None -> only phase correct
|
||||
if phase[0] is not None:
|
||||
self._data.manual_phase(*phase)
|
||||
elif fourier[0] == True:
|
||||
# ft True -> first phase correct then fft
|
||||
if phase[0] is not None:
|
||||
self._data.manual_phase(*phase)
|
||||
self.fourier()
|
||||
else:
|
||||
# ft False -> first fft then phase correct
|
||||
self.fourier()
|
||||
if phase[0] is not None:
|
||||
self._data.manual_phase(*phase)
|
@ -5,7 +5,7 @@ from nmreval.lib.colors import available_cycles
|
||||
from .properties import PropWidget
|
||||
from ...Qt import QtWidgets, QtGui, QtCore
|
||||
from ..._py.datawidget import Ui_DataWidget
|
||||
from ...lib import make_action_icons
|
||||
from ...lib.iconloading import make_action_icons
|
||||
from ...lib.delegates import HeaderDelegate
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
saveFits = QtCore.pyqtSignal(list)
|
||||
extendFits = QtCore.pyqtSignal(list)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
@ -60,15 +61,16 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
|
||||
self.update_indexes()
|
||||
|
||||
def add_item(self, items: (tuple | list[tuple]), gid: str):
|
||||
def add_item(self, items: (tuple | list[tuple]), gid: str, update: bool = True):
|
||||
if isinstance(items, tuple):
|
||||
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:
|
||||
for (idd, name, value) in items:
|
||||
item = QtWidgets.QTreeWidgetItem([name])
|
||||
item.setToolTip(0, f'Value: {value}')
|
||||
item.setData(0, QtCore.Qt.UserRole, idd)
|
||||
item.setCheckState(0, QtCore.Qt.Checked)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable |
|
||||
@ -78,11 +80,11 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
|
||||
self.resizeColumnToContents(0)
|
||||
break
|
||||
|
||||
self.update_indexes()
|
||||
if update:
|
||||
self.update_indexes()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
|
||||
def data_change(self, item: QtWidgets.QTreeWidgetItem) -> tuple[set, set]:
|
||||
def data_change(self, item: QtWidgets.QTreeWidgetItem, emit: bool = True) -> tuple[set, set]:
|
||||
idd = item.data(0, QtCore.Qt.UserRole)
|
||||
is_selected = item.checkState(0) == QtCore.Qt.Checked
|
||||
to_be_hidden = set()
|
||||
@ -140,9 +142,10 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
pass
|
||||
|
||||
else:
|
||||
self.keyChanged.emit(idd, item.text(0))
|
||||
if emit:
|
||||
self.keyChanged.emit(idd, item.text(0))
|
||||
|
||||
if to_be_shown or to_be_hidden:
|
||||
if (to_be_shown or to_be_hidden) and emit:
|
||||
self.stateChanged.emit(list(to_be_shown), list(to_be_hidden))
|
||||
|
||||
return to_be_shown, to_be_hidden
|
||||
@ -229,7 +232,7 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
|
||||
def sort(self, graph_item: QtWidgets.QTreeWidgetItem, mode: str = 'value'):
|
||||
graph_id = graph_item.data(0, QtCore.Qt.UserRole)
|
||||
sets = self.management.get_attributes(graph_id, mode)
|
||||
sets = self.management.get_attributes(graph_id, mode)
|
||||
sets = [el[0] for el in sorted(sets.items(), key=lambda x: x[1])]
|
||||
|
||||
self.management.move_sets(sets, graph_id, graph_id, pos=-1)
|
||||
@ -247,11 +250,11 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
|
||||
self.blockSignals(False)
|
||||
|
||||
|
||||
def update_indexes(self):
|
||||
graph_cnt = -1
|
||||
set_cnt = 0
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
self.blockSignals(True)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
if item is not None:
|
||||
@ -266,6 +269,7 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
iterator += 1
|
||||
|
||||
self.resizeColumnToContents(1)
|
||||
self.blockSignals(False)
|
||||
|
||||
def set_name(self, sid, name):
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
@ -287,13 +291,20 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
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))
|
||||
# add sets inside graph to removal
|
||||
child_data = item.child(c_i).data(0, QtCore.Qt.UserRole)
|
||||
if child_data not in rm_sets:
|
||||
rm_sets.append(child_data)
|
||||
rm_graphs.append(item.data(0, QtCore.Qt.UserRole))
|
||||
|
||||
else:
|
||||
rm_sets.append(item.data(0, QtCore.Qt.UserRole))
|
||||
item_data = item.data(0, QtCore.Qt.UserRole)
|
||||
if item_data not in rm_sets:
|
||||
rm_sets.append(item_data)
|
||||
|
||||
# self.deleteItem.emit(rm_sets+rm_graphs)
|
||||
self.management.delete_sets(rm_sets+rm_graphs)
|
||||
@ -306,26 +317,24 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
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)
|
||||
sets.append(item)
|
||||
|
||||
to_be_hidden = set()
|
||||
to_be_shown = set()
|
||||
self.blockSignals(True)
|
||||
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)
|
||||
s1, s2 = self.data_change(it, emit=False)
|
||||
to_be_hidden |= s2
|
||||
to_be_shown |= s1
|
||||
self.blockSignals(False)
|
||||
self.stateChanged.emit(list(to_be_shown), list(to_be_hidden))
|
||||
else:
|
||||
super().keyPressEvent(evt)
|
||||
|
||||
@ -335,24 +344,47 @@ class DataTree(QtWidgets.QTreeWidget):
|
||||
self.setDragEnabled(idx.column() == 0)
|
||||
super().mousePressEvent(evt)
|
||||
|
||||
def remove_item(self, ids: list):
|
||||
def remove_item(self, ids: list[str]):
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self)
|
||||
|
||||
toberemoved = []
|
||||
graph_removal = []
|
||||
|
||||
# find all items that have to be removed
|
||||
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)
|
||||
item_parent = item.parent()
|
||||
if item_parent is None:
|
||||
raise AttributeError
|
||||
|
||||
idx = item_parent.indexOfChild(item)
|
||||
# item.parent().takeChild(idx)
|
||||
|
||||
toberemoved.append((item_parent, idx))
|
||||
|
||||
if _id in self._checked_sets:
|
||||
self._checked_sets.remove(_id)
|
||||
|
||||
except AttributeError:
|
||||
idx = self.invisibleRootItem().indexOfChild(item)
|
||||
self.invisibleRootItem().takeChild(idx)
|
||||
self._checked_graphs.remove(_id)
|
||||
# self.invisibleRootItem().takeChild(idx)
|
||||
|
||||
graph_removal.append(idx)
|
||||
|
||||
if _id in self._checked_graphs:
|
||||
self._checked_graphs.remove(_id)
|
||||
|
||||
iterator += 1
|
||||
|
||||
for (item, set_idx) in sorted(toberemoved, key=lambda x: x[1], reverse=True):
|
||||
item.takeChild(set_idx)
|
||||
|
||||
for graph_idx in sorted(graph_removal, reverse=True):
|
||||
self.invisibleRootItem().takeChild(graph_idx)
|
||||
|
||||
self.update_indexes()
|
||||
|
||||
def contextMenuEvent(self, evt):
|
||||
@ -516,6 +548,7 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
|
||||
self.setupUi(self)
|
||||
self.tree = DataTree(self)
|
||||
self.verticalLayout.addWidget(self.tree)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.tree.selectionModel().selectionChanged.connect(lambda x, y: self.show_property(x))
|
||||
|
||||
self.tree.keyChanged.connect(lambda x, y: self.keyChanged.emit(x, y))
|
||||
@ -533,9 +566,9 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
|
||||
self.tree.add_graph(idd, name)
|
||||
self.tree.blockSignals(False)
|
||||
|
||||
def add_item(self, idd: str, name: str, gid: str):
|
||||
def add_item(self, idd: str, name: str, value: str, gid: str, update: bool= True):
|
||||
self.tree.blockSignals(True)
|
||||
self.tree.add_item((idd, name), gid)
|
||||
self.tree.add_item((idd, name, value), gid, update=update)
|
||||
self.tree.blockSignals(False)
|
||||
|
||||
def add_item_list(self, loi: list, gid: str):
|
||||
@ -543,7 +576,7 @@ class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
|
||||
self.tree.add_item(loi, gid)
|
||||
self.tree.blockSignals(False)
|
||||
|
||||
def remove_item(self, key):
|
||||
def remove_item(self, key: list[str]):
|
||||
self.tree.remove_item(key)
|
||||
|
||||
def show_property(self, _: QtCore.QModelIndex = None):
|
||||
|
@ -42,6 +42,8 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
self.max_y = inf
|
||||
self.min_y = -inf
|
||||
|
||||
self.treeWidget.itemChanged.connect(self._update_by_tree)
|
||||
|
||||
def __call__(self, graph_name, items):
|
||||
self.label_2.setText(f'Connected to {graph_name}\nChanging tab will remove all integration limits.')
|
||||
|
||||
@ -77,7 +79,6 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
for idx, rnge in enumerate(self.ranges):
|
||||
self._update_values(idx, rnge)
|
||||
|
||||
|
||||
def add(self, pos):
|
||||
x = pos[0]
|
||||
self.ranges.append((x, x*1.1))
|
||||
@ -108,7 +109,10 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
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)
|
||||
if text.startswith('S'):
|
||||
child.setFlags(child.flags() | QtCore.Qt.ItemIsEditable)
|
||||
else:
|
||||
child.setFlags(QtCore.Qt.NoItemFlags)
|
||||
child.setText(0, f'{text}: {val:.5g}')
|
||||
child.setForeground(0, QtGui.QBrush(QtGui.QColor('black')))
|
||||
|
||||
@ -121,8 +125,27 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
|
||||
self._update_values(len(self.ranges) - 1, pts_i)
|
||||
|
||||
def _update_by_tree(self, item: QtWidgets.QTreeWidgetItem) -> None:
|
||||
parent_item = item.parent()
|
||||
idx = self.treeWidget.invisibleRootItem().indexOfChild(parent_item)
|
||||
|
||||
is_left_border = parent_item.indexOfChild(item) == 0
|
||||
current_region = self.lines[idx][0]
|
||||
current_limits = current_region.getRegion()
|
||||
|
||||
new_value = item.text(0)
|
||||
try:
|
||||
new_value = float(new_value)
|
||||
if is_left_border:
|
||||
current_region.setRegion((new_value, current_limits[1]))
|
||||
else:
|
||||
current_region.setRegion((current_limits[0], new_value))
|
||||
except ValueError:
|
||||
self._update_values(idx, current_limits)
|
||||
|
||||
def _update_integral(self):
|
||||
idx = None
|
||||
reg = None
|
||||
sender = self.sender()
|
||||
for i, (reg, _) in enumerate(self.lines):
|
||||
if sender == reg:
|
||||
@ -132,19 +155,20 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
if idx is None:
|
||||
return
|
||||
|
||||
self._update_values(idx, sender.getRegion())
|
||||
self._update_values(idx, reg.getRegion())
|
||||
|
||||
def _update_values(self, idx, new_range):
|
||||
self.ranges[idx] = new_range
|
||||
|
||||
area = self.make_integral(idx, *new_range)
|
||||
|
||||
self.treeWidget.blockSignals(True)
|
||||
|
||||
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)
|
||||
@ -157,9 +181,12 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
self._set_ratios(i, curr_max)
|
||||
self.max_area = curr_max
|
||||
|
||||
self.treeWidget.blockSignals(False)
|
||||
|
||||
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]
|
||||
@ -176,7 +203,9 @@ class IntegralWidget(QtWidgets.QWidget, Ui_Form):
|
||||
if integral.size != 0:
|
||||
area = integral[-1, 1]
|
||||
scale = (self.max_y-self.min_y) / area
|
||||
self.lines[idx][1].setData(x=integral[:, 0], y=integral[:, 1]*scale + self.min_y)
|
||||
self.lines[idx][1].setData(x=integral[:, 0], y=integral[:, 1]*scale + self.min_y)
|
||||
|
||||
self.areas[idx] = area
|
||||
|
||||
return area
|
||||
|
||||
|
@ -177,7 +177,7 @@ class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
|
||||
it.setText(f'{pos[0]:.5g} - {pos[1]:.5g}')
|
||||
self.peaktable.blockSignals(False)
|
||||
it_pts.blockSignals(True)
|
||||
it_pts.setRegion(pos)
|
||||
it_pts.setRegion(pos, use_log=True)
|
||||
it_pts.blockSignals(False)
|
||||
undo = False
|
||||
|
||||
@ -187,9 +187,15 @@ class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
|
||||
self.peaktable.blockSignals(False)
|
||||
|
||||
def set_graphs(self, graphs: list):
|
||||
last_graph = self.graph_combobox.currentData()
|
||||
self.graph_combobox.clear()
|
||||
for g in graphs:
|
||||
idx = 0
|
||||
for i, g in enumerate(graphs):
|
||||
self.graph_combobox.addItem(g[1], userData=g[0])
|
||||
if g[0] == last_graph:
|
||||
idx = i
|
||||
|
||||
self.graph_combobox.setCurrentIndex(idx)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged')
|
||||
def changed_state(self, checked):
|
||||
|
@ -1,7 +1,7 @@
|
||||
import numpy as np
|
||||
from itertools import cycle
|
||||
|
||||
from pyqtgraph import mkColor, mkPen
|
||||
from pyqtgraph import mkColor, mkPen, mkBrush
|
||||
|
||||
from nmreval.lib.colors import Tab10
|
||||
|
||||
@ -42,11 +42,17 @@ class QShift(QtWidgets.QDialog, Ui_shift_dialog):
|
||||
|
||||
def add_item(self, idx, name, x, y):
|
||||
color = mkColor(next(self._colors).rgb())
|
||||
if np.iscomplexobj(y):
|
||||
pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color)),
|
||||
PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color))]
|
||||
|
||||
if len(y) == 1:
|
||||
sym_kwds = {'symbol': 'o', 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
|
||||
else:
|
||||
pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color))]
|
||||
sym_kwds = {'symbol': None, 'symbolBrush': mkBrush(color=color), 'symbolPen': mkPen(color=color)}
|
||||
|
||||
if np.iscomplexobj(y):
|
||||
pl = [PlotItem(x=x, y=y.real, name=name, pen=mkPen(color=color), **sym_kwds),
|
||||
PlotItem(x=x, y=y.imag, name=name, pen=mkPen(color=color), **sym_kwds)]
|
||||
else:
|
||||
pl = [PlotItem(x=x, y=y, name=name, pen=mkPen(color=color), **sym_kwds)]
|
||||
|
||||
self.data[idx] = (pl, x, y)
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
from .phase_dialog import QApodDialog, QPhasedialog
|
||||
from .phase_dialog import QPreviewDialog
|
||||
from .baseline_dialog import QBaselineDialog
|
||||
|
@ -1,3 +1,4 @@
|
||||
from nmreval.lib.logger import logger
|
||||
from nmreval.math import apodization
|
||||
from nmreval.lib.importer import find_models
|
||||
from nmreval.utils.text import convert
|
||||
@ -46,7 +47,7 @@ class EditSignalWidget(QtWidgets.QWidget, Ui_Form):
|
||||
stype = 'pts'
|
||||
else:
|
||||
try:
|
||||
_nop = float(self.lineEdit.text())
|
||||
_nop = float(self.ls_lineEdit.text())
|
||||
except ValueError:
|
||||
_nop = 0.0
|
||||
stype = 'time'
|
||||
@ -67,7 +68,7 @@ class EditSignalWidget(QtWidgets.QWidget, Ui_Form):
|
||||
self.do_something.emit(sender, (ph0, ph1, pvt))
|
||||
|
||||
else:
|
||||
print('You should never reach this by accident.')
|
||||
logger.warning(f'You should never reach this by accident, invalid sender {sender!r}')
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
|
||||
def change_apodization(self, index):
|
||||
|
@ -1,119 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
from pyqtgraph import mkPen
|
||||
from numpy import inf, linspace
|
||||
from numpy.fft import fft, fftfreq, fftshift
|
||||
import numpy as np
|
||||
from numpy import pi
|
||||
from numpy.fft import fft, fftshift, fftfreq
|
||||
|
||||
from nmreval.data import FID, Spectrum
|
||||
from ...lib.pg_objects import PlotItem, LogInfiniteLine
|
||||
from nmreval.lib.importer import find_models
|
||||
from nmreval.math import apodization as apodization
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ...Qt import QtCore, QtWidgets
|
||||
from ...Qt import QtCore, QtWidgets, QtGui
|
||||
from ..._py.apod_dialog import Ui_ApodEdit
|
||||
from ..._py.phase_corr_dialog import Ui_SignalEdit
|
||||
from ...lib.forms import FormWidget
|
||||
|
||||
|
||||
class QPreviewDialogs(QtWidgets.QDialog):
|
||||
class QPreviewDialog(QtWidgets.QDialog, Ui_ApodEdit):
|
||||
finished = QtCore.pyqtSignal(str, tuple)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.data = []
|
||||
self.graphs = []
|
||||
|
||||
self.mode = ''
|
||||
|
||||
def setRange(self, xlim: list, ylim: list, logmode: list[bool]):
|
||||
self.graphicsView.getPlotItem().setLogMode(x=logmode[0], y=logmode[1])
|
||||
if logmode[0]:
|
||||
xlim = [np.log10(x) for x in xlim]
|
||||
if logmode[1]:
|
||||
ylim = [np.log10(y) for y in ylim]
|
||||
|
||||
self.graphicsView.setRange(xRange=xlim, yRange=ylim, padding=0, disableAutoRange=True)
|
||||
|
||||
def add_data(self, x, y):
|
||||
self.data.append((x, y))
|
||||
real_plt = PlotItem(x=x, y=y.real, pen=mkPen('b'), )
|
||||
imag_plt = PlotItem(x=x, y=y.imag, pen=mkPen('r'))
|
||||
self.graphs.append((real_plt, imag_plt))
|
||||
self.graphicsView.addItem(real_plt)
|
||||
self.graphicsView.addItem(imag_plt)
|
||||
|
||||
def done(self, val):
|
||||
self.cleanup()
|
||||
super().done(val)
|
||||
|
||||
def close(self):
|
||||
self.cleanup()
|
||||
super().close()
|
||||
|
||||
def accept(self):
|
||||
self.finished.emit(self.mode, self.get_value())
|
||||
super().accept()
|
||||
|
||||
def get_value(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def cleanup(self):
|
||||
self.blockSignals(True)
|
||||
|
||||
for line in self.graphs:
|
||||
for g in line:
|
||||
self.graphicsView.removeItem(g)
|
||||
del g
|
||||
|
||||
self.graphicsView.clear()
|
||||
|
||||
self.data = []
|
||||
self.graphs = []
|
||||
|
||||
self.blockSignals(False)
|
||||
|
||||
|
||||
class QPhasedialog(QPreviewDialogs, Ui_SignalEdit):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.mode = 'ph'
|
||||
self.data = []
|
||||
self.graphs = []
|
||||
self._tmp_data_bl = []
|
||||
self._tmp_data_zf = []
|
||||
self._tmp_data_ls = []
|
||||
self._tmp_data_ap = []
|
||||
self._tmp_data_ph = []
|
||||
|
||||
self.pvt_line = LogInfiniteLine(pos=0, movable=True)
|
||||
self.graphicsView.addItem(self.pvt_line)
|
||||
self.freq_graph.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)
|
||||
self.ls_lineedit.hide()
|
||||
|
||||
for i, (x, y) in enumerate(self.data):
|
||||
phasecorr = np.exp(-1j * (ph0 + ph1*(x-pvt)/np.max(x))*np.pi/180.)
|
||||
_y = y * phasecorr
|
||||
|
||||
self.graphs[i][0].setData(x=x, y=_y.real)
|
||||
self.graphs[i][1].setData(x=x, y=_y.imag)
|
||||
|
||||
def get_value(self):
|
||||
return float(self.ph0slider.text()), float(self.ph1slider.text()), float(self.pivot_lineedit.text())
|
||||
|
||||
def move_line(self, evt):
|
||||
self.pivot_lineedit.setText(f'{evt.value():.5g}')
|
||||
|
||||
|
||||
class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self._limits = (-inf, inf), -inf
|
||||
|
||||
self.apods = []
|
||||
self.apods = find_models(apodization)
|
||||
|
||||
self.apodcombobox.blockSignals(True)
|
||||
@ -122,72 +46,262 @@ class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
|
||||
self.apodcombobox.blockSignals(False)
|
||||
|
||||
self.apod_graph = PlotItem(x=[], y=[])
|
||||
self.graphicsView.addItem(self.apod_graph)
|
||||
self.time_graph.addItem(self.apod_graph)
|
||||
|
||||
self.mode = 'ap'
|
||||
for g in [self.freq_graph, self.time_graph]:
|
||||
pl = g.getPlotItem()
|
||||
pl.hideButtons()
|
||||
pl.setMenuEnabled(False)
|
||||
|
||||
self._all_time = None
|
||||
self._all_freq = None
|
||||
|
||||
self.change_apodization(0)
|
||||
|
||||
def add_data(self, x, y):
|
||||
real_plt = PlotItem(x=x, y=y.real, pen=mkPen('b'))
|
||||
# imag_plt = (x=x, y=y.imag, pen=pg.mkPen('r'))
|
||||
self.graphicsView.addItem(real_plt)
|
||||
# self.graphicsView.addItem(imag_plt)
|
||||
self.shift_box.clicked.connect(self._update_shift)
|
||||
self.ls_spinbox.valueChanged.connect(self._update_shift)
|
||||
self.ls_lineedit.setValidator(QtGui.QDoubleValidator())
|
||||
self.ls_lineedit.textChanged.connect(self._update_shift)
|
||||
|
||||
self.zerofill_box.clicked.connect(self._update_zf)
|
||||
self.zf_spinbox.valueChanged.connect(self._update_zf)
|
||||
|
||||
self.apod_box.clicked.connect(self._update_apod)
|
||||
|
||||
self.phase_box.clicked.connect(self._update_phase)
|
||||
self.ph0_spinbox.valueChanged.connect(self._update_phase)
|
||||
self.ph1_spinbox.valueChanged.connect(self._update_phase)
|
||||
self.pivot_lineedit.setValidator(QtGui.QDoubleValidator())
|
||||
self.pivot_lineedit.textChanged.connect(self._update_phase)
|
||||
self.pivot_lineedit.textEdited.connect(lambda x: self.pvt_line.setValue(float(x)))
|
||||
|
||||
def add_data(self: QPreviewDialog, data: FID | Spectrum) -> bool:
|
||||
|
||||
if isinstance(data, FID):
|
||||
if self._all_freq:
|
||||
msg = QtWidgets.QMessageBox.warning(self, 'Mixed types',
|
||||
'Timesignals and spectra cannot be edited at the same time.')
|
||||
return False
|
||||
else:
|
||||
self._all_time = True
|
||||
self._all_freq = False
|
||||
|
||||
elif isinstance(data, Spectrum):
|
||||
if self._all_time:
|
||||
msg = QtWidgets.QMessageBox.warning(self, 'Mixed types',
|
||||
'Timesignals and spectra cannot be edited at the same time.')
|
||||
return False
|
||||
else:
|
||||
self._all_time = False
|
||||
self._all_freq = True
|
||||
|
||||
fid = data.copy()
|
||||
spec = self._temp_fft_time(fid.x, fid.y, self.baseline_box.isChecked())
|
||||
|
||||
x_len = data.x.size
|
||||
self.zf_spinbox.setMaximum(min(2**17//x_len, 3))
|
||||
|
||||
real_plt = PlotItem(x=fid.x, y=fid.y.real, pen=mkPen('b'))
|
||||
imag_plt = PlotItem(x=fid.x, y=fid.y.imag, pen=mkPen('r'))
|
||||
self.time_graph.addItem(imag_plt)
|
||||
self.time_graph.addItem(real_plt)
|
||||
|
||||
real_plt_fft = PlotItem(x=spec[0], y=spec[1].real, pen=mkPen('b'))
|
||||
imag_plt_fft = PlotItem(x=spec[0], y=spec[1].imag, pen=mkPen('r'))
|
||||
self.freq_graph.addItem(imag_plt_fft)
|
||||
self.freq_graph.addItem(real_plt_fft)
|
||||
|
||||
self.data.append(data)
|
||||
for p in [self._tmp_data_bl, self._tmp_data_ls]:
|
||||
p.append(data.y.copy())
|
||||
|
||||
for p in [self._tmp_data_zf, self._tmp_data_ap]:
|
||||
p.append((data.x, data.y.copy()))
|
||||
|
||||
self._tmp_data_ph.append((data.x, data.y, spec[0], spec[1]))
|
||||
|
||||
self.graphs.append((real_plt, imag_plt, real_plt_fft, imag_plt_fft))
|
||||
|
||||
return True
|
||||
|
||||
@QtCore.pyqtSlot(name='on_baseline_box_clicked')
|
||||
def _update_bl(self):
|
||||
if self.baseline_box.isChecked():
|
||||
for y in self._tmp_data_bl:
|
||||
self._temp_baseline(y)
|
||||
else:
|
||||
for i, d in enumerate(self.data):
|
||||
self._tmp_data_bl[i] = d.y.copy()
|
||||
|
||||
self._update_shift()
|
||||
|
||||
def _update_shift(self):
|
||||
if self.shift_box.isChecked():
|
||||
if self.ls_combobox.currentIndex() == 0:
|
||||
num_points = self.ls_spinbox.value()
|
||||
is_time = False
|
||||
else:
|
||||
num_points = float(self.ls_lineedit.text())
|
||||
is_time = True
|
||||
|
||||
for i, y in enumerate(self._tmp_data_bl):
|
||||
self._tmp_data_ls[i] = self._temp_leftshift(self.data[i].dx, y, num_points, is_time)
|
||||
|
||||
else:
|
||||
for i, y in enumerate(self._tmp_data_bl):
|
||||
self._tmp_data_ls[i] = y
|
||||
|
||||
self._update_zf()
|
||||
|
||||
def _update_zf(self):
|
||||
zf_padding = self.zf_spinbox.value()
|
||||
|
||||
if self.zerofill_box.isChecked():
|
||||
for i, y in enumerate(self._tmp_data_ls):
|
||||
self._tmp_data_zf[i] = self._temp_zerofill(self.data[i].x, y, zf_padding)
|
||||
|
||||
else:
|
||||
for i, y in enumerate(self._tmp_data_ls):
|
||||
self._tmp_data_zf[i] = self.data[i].x, y
|
||||
|
||||
self._update_apod()
|
||||
|
||||
def _update_apod(self):
|
||||
if self.apod_box.isChecked():
|
||||
model = self.apods[self.apodcombobox.currentIndex()]
|
||||
p = self._get_parameter()
|
||||
|
||||
x_limit = np.inf, -np.inf
|
||||
y_limit = -np.inf
|
||||
|
||||
for i, (x, y) in enumerate(self._tmp_data_zf):
|
||||
self._tmp_data_ap[i] = x, y * model.apod(x, *p)
|
||||
y_limit = max(y.real.max(), y_limit)
|
||||
x_limit = min(x_limit[0], x.min()), max(x_limit[1], x.max())
|
||||
|
||||
_x_apod = np.linspace(*x_limit, num=150)
|
||||
_y_apod = model.apod(_x_apod, *p)
|
||||
self.apod_graph.setData(x=_x_apod, y=y_limit * _y_apod)
|
||||
self.apod_graph.show()
|
||||
|
||||
else:
|
||||
for i, (x, y) in enumerate(self._tmp_data_zf):
|
||||
self._tmp_data_ap[i] = x, y
|
||||
|
||||
self.apod_graph.hide()
|
||||
|
||||
self._update_phase()
|
||||
|
||||
def _update_phase(self):
|
||||
if self.phase_box.isChecked():
|
||||
pvt = float(self.pivot_lineedit.text())
|
||||
self.pvt_line.show()
|
||||
ph0 = self.ph0_spinbox.value()
|
||||
ph1 = self.ph1_spinbox.value()
|
||||
|
||||
for i, (x, y) in enumerate(self._tmp_data_ap):
|
||||
x_fft, y_fft = self._temp_fft_time(x, y, self.baseline_box.isChecked())
|
||||
|
||||
if ph0 != 0:
|
||||
y = self._temp_phase(x, y, ph0, 0, 0)
|
||||
y_fft = self._temp_phase(x, y_fft, ph0, ph1, pvt)
|
||||
elif ph1 != 0:
|
||||
y_fft = self._temp_phase(x, y_fft, ph0, ph1, pvt)
|
||||
|
||||
self._tmp_data_ph[i] = x, y, x_fft, y_fft
|
||||
|
||||
else:
|
||||
self.pvt_line.hide()
|
||||
for i, (x, y) in enumerate(self._tmp_data_ap):
|
||||
self._tmp_data_ph[i] = x, y, *self._temp_fft_time(x, y, self.baseline_box.isChecked())
|
||||
|
||||
self._update_plots()
|
||||
|
||||
def _update_plots(self):
|
||||
for i, (x, y, xf, yf) in enumerate(self._tmp_data_ph):
|
||||
self.graphs[i][0].setData(x=x, y=y.real)
|
||||
self.graphs[i][1].setData(x=x, y=y.imag)
|
||||
|
||||
self.graphs[i][2].setData(x=xf, y=yf.real)
|
||||
self.graphs[i][3].setData(x=xf, y=yf.imag)
|
||||
|
||||
@staticmethod
|
||||
def _temp_baseline_time(y):
|
||||
y -= y[int(-0.12 * y.size):].mean()
|
||||
|
||||
@staticmethod
|
||||
def _temp_baseline_freq(y):
|
||||
region = int(0.12 * y.size)
|
||||
y -= np.mean([y[-region:], y[:region]])
|
||||
|
||||
@staticmethod
|
||||
def _temp_phase(x: np.ndarray, y: np.ndarray, ph0: float, ph1: float, pvt: float) -> np.ndarray:
|
||||
phase_correction = np.exp(-1j * (ph0 + ph1 * (x - pvt) / x.max()) * pi / 180.)
|
||||
_y = y * phase_correction
|
||||
|
||||
return _y
|
||||
|
||||
@staticmethod
|
||||
def _temp_zerofill(x: np.ndarray, y: np.ndarray, num_padding: int) -> tuple[np.ndarray, np.ndarray]:
|
||||
length = x.size
|
||||
factor = 2**num_padding
|
||||
|
||||
_y = np.r_[y, np.zeros((factor-1) * length)]
|
||||
|
||||
_temp_x = np.arange(1, (factor-1) * length+1) * (x[1]-x[0]) + np.max(x)
|
||||
_x = np.r_[x, _temp_x]
|
||||
|
||||
return _x, _y
|
||||
|
||||
@staticmethod
|
||||
def _temp_leftshift(dx: np.ndarray, y: np.ndarray, points: float | int, is_time: bool) -> np.ndarray:
|
||||
if is_time:
|
||||
points = int(points//dx)
|
||||
_y = np.roll(y, -points)
|
||||
_y[-points-1:] = 0
|
||||
|
||||
return _y
|
||||
|
||||
@staticmethod
|
||||
def _temp_fft_time(x: np.ndarray, y: np.ndarray, baseline: bool = False) -> tuple[np.ndarray, np.ndarray]:
|
||||
y_fft = fftshift(fft(y))
|
||||
x_fft = fftshift(fftfreq(len(x), d=x[1]-x[0]))
|
||||
real_plt_fft = PlotItem(x=x_fft, y=y_fft.real, pen=mkPen('b'))
|
||||
# imag_plt_fft = pg.PlotDataItem(x=x_fft, y=y_fft.imag, pen=pg.mkPen('b'))
|
||||
self.graphicsView_2.addItem(real_plt_fft)
|
||||
# self.graphicsView_2.addItem(imag_plt_fft)
|
||||
|
||||
self.graphs.append((real_plt, real_plt_fft))
|
||||
self.data.append((x, y, x_fft))
|
||||
if baseline:
|
||||
QPreviewDialog._temp_baseline_freq(y_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
|
||||
return x_fft, y_fft
|
||||
|
||||
@staticmethod
|
||||
def _temp_fft_freq(x: np.ndarray, y: np.ndarray, _=None):
|
||||
return x, y
|
||||
|
||||
def move_line(self, evt):
|
||||
self.pivot_lineedit.setText(f'{evt.value():.5g}')
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
|
||||
def change_apodization(self, index):
|
||||
def change_apodization(self, index: int) -> None:
|
||||
# delete old widgets
|
||||
self.eqn_label.setText(convert(self.apods[index].equation))
|
||||
while self.widget_layout.count():
|
||||
item = self.widget_layout.takeAt(0)
|
||||
if isinstance(item, FormWidget):
|
||||
item.disconnect()
|
||||
|
||||
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)
|
||||
for k, v in enumerate(self.apods[index].params):
|
||||
widget = FormWidget(name=v)
|
||||
widget.value = 1
|
||||
widget.valueChanged.connect(self._update_apod)
|
||||
self.widget_layout.addWidget(widget)
|
||||
|
||||
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
|
||||
self._update_apod()
|
||||
|
||||
def _get_parameter(self):
|
||||
p = []
|
||||
@ -201,8 +315,113 @@ class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
|
||||
|
||||
return p
|
||||
|
||||
def get_value(self):
|
||||
apodmodel = self.apods[self.apodcombobox.currentIndex()]
|
||||
p = self._get_parameter()
|
||||
@QtCore.pyqtSlot(int, name='on_ls_combobox_currentIndexChanged')
|
||||
def change_ls(self, idx: int) -> None:
|
||||
self.ls_lineedit.setVisible(bool(idx))
|
||||
self.ls_spinbox.setVisible(not bool(idx))
|
||||
|
||||
@QtCore.pyqtSlot(bool, name='on_ft_checkbox_stateChanged')
|
||||
def change_ft(self, state: bool):
|
||||
self.ph1_spinbox.setEnabled(state)
|
||||
self.pivot_lineedit.setEnabled(state)
|
||||
|
||||
def cleanup(self):
|
||||
self.blockSignals(True)
|
||||
|
||||
for line in self.graphs:
|
||||
for g in line:
|
||||
self.time_graph.removeItem(g)
|
||||
self.freq_graph.removeItem(g)
|
||||
del g
|
||||
|
||||
self.time_graph.clear()
|
||||
self.freq_graph.clear()
|
||||
|
||||
self._tmp_data_ap = []
|
||||
self._tmp_data_bl = []
|
||||
self._tmp_data_ls = []
|
||||
self._tmp_data_ph = []
|
||||
self._tmp_data_zf = []
|
||||
|
||||
self.data = []
|
||||
self.graphs = []
|
||||
self.freq_graph.removeItem(self.pvt_line)
|
||||
self.time_graph.removeItem(self.pvt_line)
|
||||
|
||||
self.blockSignals(False)
|
||||
|
||||
def get_value(self):
|
||||
edits = [(None,), (None,), (None,), (None,), (None,), (None,)]
|
||||
|
||||
if self.baseline_box.isChecked():
|
||||
edits[0] = (True,)
|
||||
|
||||
if self.zerofill_box.isChecked():
|
||||
edits[2] = (self.zf_spinbox.value(),)
|
||||
|
||||
if self.shift_box.isChecked():
|
||||
if self.ls_combobox.currentIndex() == 0:
|
||||
edits[1] = (self.ls_spinbox.value(), 'pts')
|
||||
else:
|
||||
edits[1] = (float(self.ls_lineedit.text()), 'time')
|
||||
|
||||
if self.apod_box.isChecked():
|
||||
edits[3] = (self._get_parameter(), self.apods[self.apodcombobox.currentIndex()])
|
||||
|
||||
if self.phase_box.isChecked():
|
||||
edits[4] = (self.ph0_spinbox.value(), self.ph1_spinbox.value(), float(self.pivot_lineedit.text()))
|
||||
|
||||
if self.ft_box.isChecked():
|
||||
edits[5] = (self.phase_before_button.isChecked(),)
|
||||
|
||||
return edits
|
||||
|
||||
def exec(self):
|
||||
self._prepare_ui()
|
||||
return super().exec()
|
||||
|
||||
def _prepare_ui(self):
|
||||
"""Stuff we have to do before showing the window but after all the data was added"""
|
||||
|
||||
vb = self.freq_graph.getPlotItem().getViewBox()
|
||||
vb.disableAutoRange(axis=vb.YAxis)
|
||||
|
||||
vb = self.time_graph.getPlotItem().getViewBox()
|
||||
vb.disableAutoRange(axis=vb.YAxis)
|
||||
|
||||
self.zerofill_box.setVisible(self._all_time)
|
||||
self.apod_box.setVisible(self._all_time)
|
||||
self.shift_box.setVisible(self._all_time)
|
||||
self.time_graph.setVisible(self._all_time)
|
||||
self.logtime_widget.setVisible(self._all_time)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_logx_time_stateChanged')
|
||||
@QtCore.pyqtSlot(int, name='on_logy_time_stateChanged')
|
||||
@QtCore.pyqtSlot(int, name='on_logx_freq_stateChanged')
|
||||
@QtCore.pyqtSlot(int, name='on_logy_freq_stateChanged')
|
||||
def set_log(self, state: int):
|
||||
switch = {
|
||||
self.logx_time: lambda _x: self.time_graph.setLogMode(x=_x),
|
||||
self.logy_time: lambda _x: self.time_graph.setLogMode(y=_x),
|
||||
self.logx_freq: lambda _x: self.freq_graph.setLogMode(x=_x),
|
||||
self.logy_freq: lambda _x: self.freq_graph.setLogMode(y=_x),
|
||||
}[self.sender()]
|
||||
switch(state == QtCore.Qt.Checked)
|
||||
|
||||
vb = self.freq_graph.getPlotItem().getViewBox()
|
||||
vb.disableAutoRange(axis=vb.YAxis)
|
||||
|
||||
vb = self.time_graph.getPlotItem().getViewBox()
|
||||
vb.disableAutoRange(axis=vb.YAxis)
|
||||
|
||||
|
||||
self._temp_baseline = self._temp_baseline_time if self._all_time else self._temp_baseline_freq
|
||||
self._temp_fft = self._temp_fft_time if self._all_time else self._temp_fft_freq
|
||||
|
||||
self.freq_graph.setVisible(self._all_time)
|
||||
if self._all_freq:
|
||||
self.time_graph.addItem(self.pvt_line)
|
||||
else:
|
||||
self.freq_graph.addItem(self.pvt_line)
|
||||
|
||||
|
||||
return p, apodmodel
|
||||
|
@ -188,7 +188,15 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
|
||||
new_value = complex(val)
|
||||
new_value = new_value.real if new_value.imag == 0 else new_value
|
||||
|
||||
# table view loses focus when itemChanged is emitted
|
||||
# if edit of item is cause of change resume editing at next item
|
||||
prev_state = self.tableView.state()
|
||||
idx = self.tableView.currentIndex()
|
||||
idx = idx.sibling((col+1)//3+row, (col+1) % 3)
|
||||
self.itemChanged.emit(sid, (col, row), new_value)
|
||||
if prev_state == self.tableView.State.EditingState:
|
||||
self.tableView.setCurrentIndex(idx)
|
||||
self.tableView.edit(idx)
|
||||
|
||||
@QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection)
|
||||
def show_position(self, *_):
|
||||
@ -259,10 +267,7 @@ class ValueModel(QtCore.QAbstractTableModel):
|
||||
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}'
|
||||
return self.as_string(val)
|
||||
|
||||
elif role == QtCore.Qt.BackgroundRole:
|
||||
pal = QtGui.QGuiApplication.palette()
|
||||
@ -295,11 +300,16 @@ class ValueModel(QtCore.QAbstractTableModel):
|
||||
|
||||
if value:
|
||||
if role == QtCore.Qt.EditRole:
|
||||
if value == self.as_string(self._data[row][col]):
|
||||
return True
|
||||
|
||||
try:
|
||||
value = complex(value)
|
||||
except ValueError:
|
||||
# not a number
|
||||
return False
|
||||
|
||||
value = value.real if value.imag == 0 else value
|
||||
self._data[row][col] = value.real if value.imag == 0 else value
|
||||
self.itemChanged.emit(col, row, str(value))
|
||||
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role])
|
||||
@ -368,3 +378,10 @@ class ValueModel(QtCore.QAbstractTableModel):
|
||||
def unmask(self):
|
||||
self.mask = [True] * self.total_rows
|
||||
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole])
|
||||
|
||||
@staticmethod
|
||||
def as_string(value) -> str:
|
||||
if isinstance(value, complex):
|
||||
return f'{value.real:.8g}{value.imag:+.8g}j'
|
||||
else:
|
||||
return f'{value:.8g}'
|
||||
|
0
src/gui_qt/dsc/__init__.py
Normal file
0
src/gui_qt/dsc/__init__.py
Normal file
375
src/gui_qt/dsc/glass_dialog.py
Normal file
375
src/gui_qt/dsc/glass_dialog.py
Normal file
@ -0,0 +1,375 @@
|
||||
from itertools import cycle
|
||||
|
||||
from numpy import array, nan, isnan
|
||||
from pyqtgraph import mkPen, mkBrush
|
||||
|
||||
from nmreval.dsc.hodge import tau_hodge
|
||||
from nmreval.lib.colors import Tab10
|
||||
from ..Qt import QtWidgets, QtCore
|
||||
from .._py.tnmh_dialog import Ui_DSCEvalDialog
|
||||
from ..lib.pg_objects import PlotItem, RegionItem
|
||||
|
||||
from nmreval.data import DSC, Points
|
||||
|
||||
|
||||
class TgCalculator(QtWidgets.QWizard, Ui_DSCEvalDialog):
|
||||
newData = QtCore.pyqtSignal(dict, str)
|
||||
|
||||
def __init__(self, management, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
self._management = management
|
||||
self._colors = cycle(Tab10)
|
||||
|
||||
self._dsc = {}
|
||||
self._plots = {}
|
||||
self._tg_value = {}
|
||||
self._fit = {}
|
||||
self._hodge = {
|
||||
'onset': (
|
||||
PlotItem(x=[], y=[], pen=None, symbol='o', symbolBrush=Tab10.TabBlue.rgb(), name='Onset'),
|
||||
None,
|
||||
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabBlue.rgb()})),
|
||||
PlotItem(x=[], y=[], pen=None, symbol='o', symbolBrush=Tab10.TabBlue.rgb())),
|
||||
None,
|
||||
),
|
||||
'midpoint': (
|
||||
PlotItem(x=[], y=[], pen=None, symbol='s', symbolBrush=Tab10.TabOrange.rgb(), name='Midpoint'),
|
||||
None,
|
||||
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabOrange.rgb()})),
|
||||
PlotItem(x=[], y=[], pen=None, symbol='s', symbolBrush=Tab10.TabOrange.rgb())),
|
||||
None,
|
||||
),
|
||||
'end': (
|
||||
PlotItem(x=[], y=[], pen=None, symbol='t', symbolBrush=Tab10.TabGreen.rgb(), name='End'),
|
||||
None,
|
||||
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabGreen.rgb()})),
|
||||
PlotItem(x=[], y=[], pen=None, symbol='t', symbolBrush=Tab10.TabGreen.rgb())),
|
||||
None,
|
||||
),
|
||||
'inflection': (
|
||||
PlotItem(x=[], y=[], pen=None, symbol='d', symbolBrush=Tab10.TabRed.rgb(), name='Inflection'),
|
||||
None,
|
||||
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabRed.rgb()})),
|
||||
PlotItem(x=[], y=[], pen=None, symbol='d', symbolBrush=Tab10.TabRed.rgb())),
|
||||
None,
|
||||
),
|
||||
'fictive': (
|
||||
PlotItem(x=[], y=[], pen=None, symbol='t1', symbolBrush=Tab10.TabPurple.rgb(), name='Fictive'),
|
||||
None,
|
||||
(PlotItem(x=[], y=[], pen=mkPen({'color': Tab10.TabPurple.rgb()})),
|
||||
PlotItem(x=[], y=[], pen=None, symbol='t1', symbolBrush=Tab10.TabPurple.rgb())),
|
||||
None,
|
||||
),
|
||||
}
|
||||
self._lines = {}
|
||||
self.tau_plot.getPlotItem().addLegend()
|
||||
for plt, _, fitplt, _ in self._hodge.values():
|
||||
self.tau_plot.addItem(plt)
|
||||
self.tghodge_graph.addItem(fitplt[0])
|
||||
self.tghodge_graph.addItem(fitplt[1])
|
||||
self.tau_plot.setLogMode(y=True)
|
||||
self.tghodge_graph.setLogMode(y=True)
|
||||
|
||||
self.limits = RegionItem(), RegionItem()
|
||||
for lim in self.limits:
|
||||
self.dsc_plot.addItem(lim)
|
||||
self._limitless = True
|
||||
|
||||
self.add_sets()
|
||||
|
||||
self.tnmh_graph_check.stateChanged.connect(lambda state: self.tnmh_graph_combo.setEnabled(not bool(state)))
|
||||
self.hodge_graph_check.stateChanged.connect(lambda state: self.hodge_graph_combo.setEnabled(not bool(state)))
|
||||
|
||||
self.next_button.clicked.connect(lambda: self.stackedWidget.setCurrentIndex((self.stackedWidget.currentIndex() + 1) % 3))
|
||||
self.back_button.clicked.connect(lambda: self.stackedWidget.setCurrentIndex((self.stackedWidget.currentIndex() + 2) % 3))
|
||||
|
||||
self.listWidget.itemChanged.connect(self.change_visibility)
|
||||
|
||||
def __call__(self):
|
||||
self.clear()
|
||||
self._colors = cycle(Tab10)
|
||||
self.add_sets()
|
||||
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
self.listWidget.clear()
|
||||
self.tg_tree.clear()
|
||||
self.tnmh_tree.clear()
|
||||
|
||||
for plots in self._plots.values():
|
||||
for val in plots:
|
||||
self.dsc_plot.removeItem(val)
|
||||
self.tnmh_graphics.removeItem(val)
|
||||
|
||||
for key, plt in self._hodge.items():
|
||||
plt[0].setData(x=[], y=[])
|
||||
plt[2][0].setData(x=[], y=[])
|
||||
plt[2][1].setData(x=[], y=[])
|
||||
self._hodge[key] = (plt[0], None, plt[2], None)
|
||||
|
||||
self._dsc = {}
|
||||
self._plots = {}
|
||||
self._tg_value = {}
|
||||
self._lines = {}
|
||||
self._fit = {}
|
||||
|
||||
self.stackedWidget.setCurrentIndex(0)
|
||||
|
||||
def add_sets(self):
|
||||
for w in (self.tnmh_graph_combo, self.hodge_graph_combo):
|
||||
w.clear()
|
||||
for graphs in self._management.graphs.list():
|
||||
w.addItem(graphs[1], userData=graphs[0])
|
||||
|
||||
min_x = 10_000_000
|
||||
max_x = -10_000_000
|
||||
for (key, name), c in zip(self._management.active_sets, self._colors):
|
||||
data = self._management[key].data
|
||||
if not isinstance(data, DSC):
|
||||
continue
|
||||
|
||||
min_x = min(min_x, data.x.min())
|
||||
max_x = max(max_x, data.x.max())
|
||||
|
||||
item = QtWidgets.QListWidgetItem(name)
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
item.setData(QtCore.Qt.UserRole, key)
|
||||
item.setForeground(mkBrush(c.rgb()))
|
||||
self.listWidget.addItem(item)
|
||||
|
||||
self._dsc[key] = (data, None)
|
||||
|
||||
data_plot = PlotItem(x=data.x, y=data.y, pen=mkPen(c.rgb()))
|
||||
self.dsc_plot.addItem(data_plot)
|
||||
|
||||
glass = PlotItem()
|
||||
glass.set_line(style=2, color=c)
|
||||
self.dsc_plot.addItem(glass)
|
||||
|
||||
liquid = PlotItem()
|
||||
liquid.set_line(style=2, color=c)
|
||||
self.dsc_plot.addItem(liquid)
|
||||
|
||||
tangent = PlotItem()
|
||||
tangent.set_line(style=2, color=c)
|
||||
self.dsc_plot.addItem(tangent)
|
||||
|
||||
tg_plot = PlotItem(pen=None, symbolBrush=c.rgb(), symbol='o')
|
||||
self.dsc_plot.addItem(tg_plot)
|
||||
|
||||
fictive_cp = PlotItem(pen=mkPen(c.rgb()))
|
||||
self.tnmh_graphics.addItem(fictive_cp)
|
||||
|
||||
tnmh_fit = PlotItem()
|
||||
tnmh_fit.set_line(style=2, color=c)
|
||||
self.tnmh_graphics.addItem(tnmh_fit)
|
||||
|
||||
self._plots[key] = (data_plot, tg_plot, glass, liquid, tangent, fictive_cp, tnmh_fit)
|
||||
self._tg_value[key] = {
|
||||
'onset': (nan, nan),
|
||||
'midpoint': (nan, nan),
|
||||
'end': (nan, nan),
|
||||
'inflection': (nan, nan),
|
||||
# 'fictive': (nan, nan),
|
||||
}
|
||||
|
||||
if self._limitless and max_x != -10000000 and min_x != 10000000 :
|
||||
dist = max_x - min_x
|
||||
self.limits[0].setRegion((min_x, min_x+min(0.1*dist, 5)))
|
||||
self.limits[1].setRegion((max_x-min(5, 0.1*dist), max_x))
|
||||
self._limitless = False
|
||||
|
||||
@QtCore.pyqtSlot(name='on_calctg_button_clicked')
|
||||
def calc_tg(self):
|
||||
baselines = tuple(lim.getRegion() for lim in self.limits)
|
||||
if baselines[0][0] > baselines[1][0]:
|
||||
baselines = baselines[1], baselines[0]
|
||||
|
||||
for idx in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(idx)
|
||||
if item.checkState() == QtCore.Qt.Unchecked:
|
||||
continue
|
||||
|
||||
key = item.data(QtCore.Qt.UserRole)
|
||||
plot = self._plots[key]
|
||||
data, _ = self._dsc[key]
|
||||
|
||||
tg_results, glass, liquid, tangent = data.glass_transition(*baselines)
|
||||
self._lines[key] = (glass, liquid, tangent)
|
||||
|
||||
for i, line in enumerate((glass, liquid, tangent)):
|
||||
plot[i+2].setData(x=line.x, y=line.y)
|
||||
|
||||
self._tg_value[key].update(tg_results)
|
||||
|
||||
self._update_tg_plots()
|
||||
|
||||
def _update_tg_plots(self):
|
||||
self.tg_tree.clear()
|
||||
for idx in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(idx)
|
||||
|
||||
tree_item = QtWidgets.QTreeWidgetItem([item.text()])
|
||||
values = self._tg_value.get(item.data(QtCore.Qt.UserRole))
|
||||
|
||||
if values is not None:
|
||||
for name, pos in values.items():
|
||||
child_item = QtWidgets.QTreeWidgetItem([f'{name.capitalize()}: {pos[0]:.2f} K'])
|
||||
tree_item.addChild(child_item)
|
||||
|
||||
self.tg_tree.addTopLevelItem(tree_item)
|
||||
|
||||
key = item.data(QtCore.Qt.UserRole)
|
||||
plot = self._plots[key]
|
||||
data, _ = self._dsc[key]
|
||||
|
||||
plot[1].setData(array(list(self._tg_value[key].values())))
|
||||
|
||||
@QtCore.pyqtSlot(name='on_tg_export_button_clicked')
|
||||
def export_tg(self):
|
||||
ret_dic = {}
|
||||
for key, tg in self._tg_value.items():
|
||||
tgx = [x for x, y in tg.values()]
|
||||
tgy = [y for x, y in tg.values()]
|
||||
if self.tg_export_check.isChecked():
|
||||
tg_pts = Points(x=tgx, y=tgy, name=self._management[key].name + ' (Tg)', value=self._management[key].value)
|
||||
else:
|
||||
tg_pts = None
|
||||
|
||||
line = []
|
||||
if self.tglines_export_check.isChecked():
|
||||
line = self._lines.get(key, [])
|
||||
|
||||
ret_dic[key] = (tg_pts, line)
|
||||
|
||||
self.newData.emit(ret_dic, 'tg')
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
|
||||
def change_visibility(self, item: QtWidgets.QListWidgetItem):
|
||||
is_checked = bool(item.checkState())
|
||||
plot = self._plots[item.data(QtCore.Qt.UserRole)]
|
||||
for val in plot:
|
||||
val.setVisible(is_checked)
|
||||
|
||||
def get_fictive(self, key, baselines):
|
||||
plot = self._plots[key]
|
||||
data, _ = self._dsc[key]
|
||||
|
||||
cp, tg = data.get_fictive_cp(*baselines)
|
||||
|
||||
plot[5].setData(cp.x, cp.y)
|
||||
self._dsc[key] = (data, cp)
|
||||
|
||||
return cp
|
||||
|
||||
@QtCore.pyqtSlot(name='on_tnhm_fitbutton_clicked')
|
||||
def make_tnmh(self):
|
||||
baselines = tuple(lim.getRegion() for lim in self.limits)
|
||||
if baselines[0][0] > baselines[1][0]:
|
||||
baselines = baselines[1], baselines[0]
|
||||
|
||||
self.tnmh_tree.clear()
|
||||
for idx in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(idx)
|
||||
if item.checkState() == QtCore.Qt.Unchecked:
|
||||
continue
|
||||
|
||||
key = item.data(QtCore.Qt.UserRole)
|
||||
|
||||
data = self.get_fictive(key, baselines)
|
||||
|
||||
res = data.calculate_tnmh([60, 0.5, 1, 2e5], *baselines, return_fictive=False)
|
||||
self._fit[key] = res
|
||||
|
||||
plot = self._plots[key]
|
||||
plot[-1].setData(res.x, res.y)
|
||||
|
||||
for idx in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(idx)
|
||||
|
||||
tree_item = QtWidgets.QTreeWidgetItem([item.text()])
|
||||
values = self._fit.get(item.data(QtCore.Qt.UserRole))
|
||||
|
||||
if values is not None:
|
||||
child_item = QtWidgets.QTreeWidgetItem([values.parameter_string()])
|
||||
tree_item.addChild(child_item)
|
||||
|
||||
self.tnmh_tree.addTopLevelItem(tree_item)
|
||||
|
||||
@QtCore.pyqtSlot(name='on_tnmh_export_button_clicked')
|
||||
def export_tnmh(self):
|
||||
ret_dic = {}
|
||||
for idx in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(idx)
|
||||
if item.checkState() == QtCore.Qt.Unchecked:
|
||||
continue
|
||||
|
||||
key = item.data(QtCore.Qt.UserRole)
|
||||
|
||||
cp = None
|
||||
if self.fictive_export_check.isChecked():
|
||||
_, cp = self._dsc[key]
|
||||
|
||||
line = None
|
||||
|
||||
if self.tnmhfit_export_check.isChecked():
|
||||
line = self._fit.get(key)
|
||||
|
||||
ret_dic[key] = (cp, line)
|
||||
|
||||
ret_dic['graph'] = '' if self.tnmh_graph_check.isChecked() else self.tnmh_graph_combo.currentData()
|
||||
|
||||
self.newData.emit(ret_dic, 'tnmh')
|
||||
|
||||
@QtCore.pyqtSlot(name='on_hodge_button_clicked')
|
||||
def hodge(self):
|
||||
for tg_type, (plot, data, fitplots, fit) in self._hodge.items():
|
||||
|
||||
m = []
|
||||
for idx in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(idx)
|
||||
if item.checkState() == QtCore.Qt.Unchecked:
|
||||
continue
|
||||
|
||||
key = item.data(QtCore.Qt.UserRole)
|
||||
data, _ = self._dsc[key]
|
||||
try:
|
||||
tg_value = self._tg_value[key][tg_type][0]
|
||||
if isnan(tg_value):
|
||||
continue
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
m.append([tg_value, data.value])
|
||||
|
||||
if len(m) > 1:
|
||||
data, fit = tau_hodge(*array(m).T)
|
||||
data.name = f'{data.name} ({tg_type.capitalize()})'
|
||||
plot.setData(data.x, data.y)
|
||||
fitplots[0].setData(fit.x, fit.y)
|
||||
fitplots[1].setData(fit.x_data, fit.y_data)
|
||||
|
||||
self._hodge[tg_type] = (plot, data, fitplots, fit)
|
||||
|
||||
@QtCore.pyqtSlot(name='on_export_hodge_button_clicked')
|
||||
def export_hodge(self):
|
||||
ret_dic = {}
|
||||
|
||||
for cb in (self.onset_check, self.mid_check, self.end_check, self.inflection_check, self.fictive_check):
|
||||
if cb.isChecked():
|
||||
item = cb.text().lower()
|
||||
data = self._hodge.get(item)
|
||||
if data[1] is not None:
|
||||
ret_dic[item] = data[1]
|
||||
|
||||
ret_dic['graph'] = '' if self.hodge_graph_check.isChecked() else self.hodge_graph_combo.currentData()
|
||||
|
||||
self.newData.emit(ret_dic, 'hodge')
|
||||
|
||||
def close(self) -> bool:
|
||||
self.clear()
|
||||
return super().close()
|
@ -5,51 +5,48 @@ from nmreval.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
|
||||
from ..lib.iconloading import get_icon
|
||||
from ..lib.tables import TableWidget
|
||||
|
||||
|
||||
class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
|
||||
value_requested = QtCore.pyqtSignal(object)
|
||||
value_changed = QtCore.pyqtSignal(str)
|
||||
state_changed = QtCore.pyqtSignal()
|
||||
replace_single_value = QtCore.pyqtSignal(object)
|
||||
|
||||
def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.parametername.setText(label + ' ')
|
||||
self.name = label
|
||||
|
||||
self.parametername.setText(convert(label) + ' ')
|
||||
|
||||
validator = QtGui.QDoubleValidator()
|
||||
validator.setDecimals(9)
|
||||
self.parameter_line.setValidator(validator)
|
||||
self.parameter_line.setText('1')
|
||||
self.parameter_line.setMaximumWidth(60)
|
||||
self.lineEdit.setMaximumWidth(60)
|
||||
self.lineEdit_2.setMaximumWidth(60)
|
||||
self.parameter_line.setMaximumWidth(160)
|
||||
self.lineEdit.setMaximumWidth(100)
|
||||
self.lineEdit_2.setMaximumWidth(100)
|
||||
|
||||
self.label_3.setText(f'< {label} <')
|
||||
self.label_3.setText(f'< {convert(label)} <')
|
||||
|
||||
self.checkBox.stateChanged.connect(self.enableBounds)
|
||||
self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit())
|
||||
self.parameter_line.editingFinished.connect(self.update_parameter)
|
||||
self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self))
|
||||
self.parameter_line.replace_single_values.connect(lambda: self.replace_single_value.emit(None))
|
||||
self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text()))
|
||||
self.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')
|
||||
self.menu = QtWidgets.QMenu(self)
|
||||
|
||||
def set_parameter_string(self, p: str):
|
||||
self.parameter_line.setText(p)
|
||||
@ -69,123 +66,67 @@ class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
|
||||
|
||||
def set_parameter(self, p: float | None, bds: tuple[float, float, bool] = None,
|
||||
fixed: bool = None, glob: bool = None):
|
||||
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 = f'{p:.4g}'
|
||||
ptext = f'{p:.4g}'
|
||||
|
||||
self.set_parameter_string(ptext)
|
||||
self.set_parameter_string(ptext)
|
||||
|
||||
if bds is not None:
|
||||
self.set_bounds(*bds)
|
||||
if bds is not None:
|
||||
self.set_bounds(*bds)
|
||||
|
||||
if fixed is not None:
|
||||
self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked)
|
||||
if fixed is not None:
|
||||
self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked)
|
||||
|
||||
if glob is not None:
|
||||
self.global_checkbox.setCheckState(QtCore.Qt.Checked if glob else QtCore.Qt.Unchecked)
|
||||
if glob is not None:
|
||||
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
|
||||
try:
|
||||
p = float(self.parameter_line.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
p = self.parameter_line.text().replace(',', '.')
|
||||
|
||||
if self.checkBox.isChecked():
|
||||
try:
|
||||
lb = float(self.lineEdit.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
lb = None
|
||||
lb_text = self.lineEdit.text()
|
||||
lb = None
|
||||
if lb_text:
|
||||
try:
|
||||
lb = float(lb_text.replace(',', '.'))
|
||||
except ValueError:
|
||||
lb = lb_text
|
||||
|
||||
try:
|
||||
rb = float(self.lineEdit_2.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
rb = None
|
||||
ub_text = self.lineEdit_2.text()
|
||||
rb = None
|
||||
if ub_text:
|
||||
try:
|
||||
rb = float(ub_text.replace(',', '.'))
|
||||
except ValueError:
|
||||
rb = ub_text
|
||||
else:
|
||||
lb = rb = None
|
||||
|
||||
bounds = (lb, rb)
|
||||
|
||||
return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked(), self.is_linked
|
||||
return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked()
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
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
|
||||
def update_parameter(self):
|
||||
new_value = self.parameter_line.text()
|
||||
if not new_value:
|
||||
self.parameter_line.setText('1')
|
||||
|
||||
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()
|
||||
float(new_value)
|
||||
is_text = False
|
||||
except ValueError:
|
||||
is_text = True
|
||||
self.global_checkbox.setCheckState(False)
|
||||
|
||||
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()
|
||||
self.set_fixed(is_text)
|
||||
|
||||
|
||||
class QSaveModelDialog(QtWidgets.QDialog, Ui_SaveDialog):
|
||||
@ -280,8 +221,17 @@ class FitModelTree(QtWidgets.QTreeWidget):
|
||||
idx = item.data(0, self.counterRole)
|
||||
self.itemRemoved.emit(idx)
|
||||
|
||||
def add_function(self, idx: int, cnt: int, op: int, name: str, color: QtGui.QColor | str | tuple,
|
||||
parent: QtWidgets.QTreeWidgetItem = None, children: list = None, active: bool = True, **kwargs):
|
||||
def add_function(self,
|
||||
idx: int,
|
||||
cnt: int,
|
||||
op: int,
|
||||
name: str,
|
||||
color: QtGui.QColor | str | tuple,
|
||||
parent: QtWidgets.QTreeWidgetItem = None,
|
||||
children: list = None,
|
||||
active: bool = True,
|
||||
param_names: list[str] = None,
|
||||
**kwargs):
|
||||
"""
|
||||
Add function to tree and dictionary of functions.
|
||||
"""
|
||||
@ -296,6 +246,10 @@ class FitModelTree(QtWidgets.QTreeWidget):
|
||||
it.setData(0, self.counterRole, cnt)
|
||||
it.setData(0, self.operatorRole, op)
|
||||
it.setText(0, name)
|
||||
if param_names is not None:
|
||||
it.setToolTip(0,
|
||||
'Parameter names:\n' +
|
||||
'\n'.join(f'{pn}({cnt})' for pn in param_names))
|
||||
it.setForeground(0, QtGui.QBrush(color))
|
||||
|
||||
it.setIcon(0, get_icon(self.icons[op]))
|
||||
@ -365,7 +319,7 @@ class FitModelTree(QtWidgets.QTreeWidget):
|
||||
return funcs
|
||||
|
||||
|
||||
class FitTableWidget(QtWidgets.QTableWidget):
|
||||
class FitTableWidget(TableWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from nmreval.fit.parameter import Parameter
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..Qt import QtWidgets, QtCore, QtGui
|
||||
@ -62,8 +65,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
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 = FitModelWidget(label=v, parent=self.scrollwidget)
|
||||
widgt.parameter_pos = k
|
||||
widgt.func_idx = idx
|
||||
try:
|
||||
@ -78,11 +80,12 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
widgt.state_changed.connect(self.make_global)
|
||||
widgt.value_requested.connect(self.look_for_value)
|
||||
widgt.value_changed.connect(self.change_global_parameter)
|
||||
widgt.replace_single_value.connect(self.delete_single_parameter)
|
||||
|
||||
self.global_parameter.append(widgt)
|
||||
self.scrollwidget.layout().addWidget(widgt)
|
||||
|
||||
widgt2 = ParameterSingleWidget(name=name, parent=self.scrollwidget2)
|
||||
widgt2 = ParameterSingleWidget(name=v, parent=self.scrollwidget2)
|
||||
widgt2.valueChanged.connect(self.change_single_parameter)
|
||||
widgt2.removeSingleValue.connect(self.change_single_parameter)
|
||||
widgt2.installEventFilter(self)
|
||||
@ -114,20 +117,22 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
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)
|
||||
# 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: int = None):
|
||||
if idx is None:
|
||||
idx = self.global_parameter.index(self.sender())
|
||||
|
||||
self.glob_values[idx] = float(value)
|
||||
# self.glob_values[idx] = float(value)
|
||||
self.glob_values[idx] = value
|
||||
if self.data_values[self.comboBox.currentData()][idx] is None:
|
||||
self.data_parameter[idx].blockSignals(True)
|
||||
self.data_parameter[idx].value = float(value)
|
||||
# self.data_parameter[idx].value = float(value)
|
||||
self.data_parameter[idx].value = value
|
||||
self.data_parameter[idx].blockSignals(False)
|
||||
|
||||
@QtCore.pyqtSlot(str, object)
|
||||
@ -148,6 +153,13 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
if value is None:
|
||||
self.change_data(self.comboBox.currentIndex())
|
||||
|
||||
def delete_single_parameter(self):
|
||||
idx = self.global_parameter.index(self.sender())
|
||||
for i in range(self.comboBox.count()):
|
||||
set_id = self.comboBox.itemData(i)
|
||||
self.data_values[set_id][idx] = None
|
||||
self.change_data(self.comboBox.currentIndex())
|
||||
|
||||
def change_single_choice(self, _, value, sender=None):
|
||||
if sender is None:
|
||||
sender = self.sender()
|
||||
@ -163,7 +175,7 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
# disable single parameter if it is set global, enable if global is unset
|
||||
widget = self.sender()
|
||||
idx = self.global_parameter.index(widget)
|
||||
enable = (widget.global_checkbox.checkState() == QtCore.Qt.Unchecked) and (widget.is_linked is None)
|
||||
enable = (widget.global_checkbox.checkState() == QtCore.Qt.Unchecked)
|
||||
self.data_parameter[idx].setEnabled(enable)
|
||||
|
||||
def select_next_preview(self, direction):
|
||||
@ -181,6 +193,11 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
for i, value in enumerate(self.data_values[sid]):
|
||||
w = self.data_parameter[i]
|
||||
w.blockSignals(True)
|
||||
try:
|
||||
w.show_as_local_parameter(value is not None)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if value is None:
|
||||
w.value = self.glob_values[i]
|
||||
else:
|
||||
@ -191,64 +208,50 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
if sid not in self.data_values:
|
||||
self.data_values[sid] = [None] * len(self.data_parameter)
|
||||
|
||||
def get_parameter(self, use_func=None):
|
||||
def get_parameter(self, use_func=None) -> tuple[dict, list]:
|
||||
bds = []
|
||||
is_global = []
|
||||
is_fixed = []
|
||||
globs = []
|
||||
is_linked = []
|
||||
param_general = []
|
||||
|
||||
for g in self.global_parameter:
|
||||
if isinstance(g, FitModelWidget):
|
||||
p_i, bds_i, fixed_i, global_i, link_i = g.get_parameter()
|
||||
p_i, bds_i, fixed_i, global_i = g.get_parameter()
|
||||
parameter_i = Parameter(name=g.name, value=p_i, lb=bds_i[0], ub=bds_i[1], var=fixed_i)
|
||||
param_general.append(parameter_i)
|
||||
|
||||
globs.append(p_i)
|
||||
bds.append(bds_i)
|
||||
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])
|
||||
# set has no oen value
|
||||
p.append(param_general[i].copy())
|
||||
else:
|
||||
p.append(p_i)
|
||||
lb, ub = bds[i]
|
||||
try:
|
||||
if not ((lb < p_i < ub) or (not is_fixed[i])):
|
||||
raise ValueError(f'Parameter {g.name} is outside bounds ({lb}, {ub})')
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
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
|
||||
# create Parameter
|
||||
p.append(
|
||||
Parameter(name=g.name, value=p_i, lb=lb, ub=ub, var=is_fixed[i])
|
||||
)
|
||||
|
||||
else:
|
||||
if p_i is None:
|
||||
@ -260,20 +263,28 @@ class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
|
||||
data_parameter[sid] = (p, kw_p)
|
||||
|
||||
return data_parameter, lb, ub, is_fixed, global_p, is_linked
|
||||
global_parameter = []
|
||||
for param, global_flag in zip(param_general, is_global):
|
||||
if global_flag:
|
||||
param.is_global = True
|
||||
global_parameter.append(param)
|
||||
else:
|
||||
global_parameter.append(None)
|
||||
|
||||
return data_parameter, global_parameter
|
||||
|
||||
def set_parameter(self, set_id: str | None, parameter: list[float]) -> int:
|
||||
param_len = len(list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter)))
|
||||
|
||||
num_parameter = list(filter(lambda g: not isinstance(g, SelectionWidget), self.global_parameter))
|
||||
param_len = len(num_parameter)
|
||||
if set_id is None:
|
||||
for val, g in zip(parameter, self.global_parameter):
|
||||
if isinstance(g, SelectionWidget):
|
||||
continue
|
||||
for i, g in enumerate(num_parameter):
|
||||
val = parameter[i]
|
||||
g.set_parameter(val)
|
||||
self.glob_values[i] = val
|
||||
|
||||
else:
|
||||
new_param = self.data_values[set_id]
|
||||
min_len = min(param_len, len(new_param), len(new_param))
|
||||
min_len = min(param_len, len(new_param))
|
||||
for i in range(min_len):
|
||||
new_param[i] = parameter[i]
|
||||
|
||||
@ -291,10 +302,12 @@ class ParameterSingleWidget(QtWidgets.QWidget):
|
||||
|
||||
self._init_ui()
|
||||
|
||||
self._name = name
|
||||
self.name = name
|
||||
self.label.setText(convert(name))
|
||||
self.label.setToolTip('If this is bold then this parameter is only for this data. '
|
||||
'Otherwise, the general parameter is used and displayed')
|
||||
|
||||
self.value_line.setValidator(QtGui.QDoubleValidator())
|
||||
# self.value_line.setValidator(QtGui.QDoubleValidator())
|
||||
self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0)
|
||||
self.reset_button.clicked.connect(lambda x: self.removeSingleValue.emit())
|
||||
|
||||
@ -309,10 +322,12 @@ class ParameterSingleWidget(QtWidgets.QWidget):
|
||||
layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))
|
||||
|
||||
self.value_line = QtWidgets.QLineEdit(self)
|
||||
self.value_line.textEdited.connect(lambda x: self.show_as_local_parameter(True))
|
||||
layout.addWidget(self.value_line)
|
||||
|
||||
self.reset_button = QtWidgets.QToolButton(self)
|
||||
self.reset_button.setText('Use global')
|
||||
self.reset_button.clicked.connect(lambda: self.show_as_local_parameter(False))
|
||||
layout.addWidget(self.reset_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
@ -326,4 +341,11 @@ class ParameterSingleWidget(QtWidgets.QWidget):
|
||||
|
||||
@value.setter
|
||||
def value(self, val):
|
||||
self.value_line.setText(f'{float(val):.5g}')
|
||||
# self.value_line.setText(f'{float(val):.5g}')
|
||||
self.value_line.setText(f'{val}')
|
||||
|
||||
def show_as_local_parameter(self, is_local: bool):
|
||||
if is_local:
|
||||
self.label.setStyleSheet('font-weight: bold;')
|
||||
else:
|
||||
self.label.setStyleSheet('')
|
||||
|
@ -8,10 +8,11 @@ from nmreval.lib.importer import find_models
|
||||
from nmreval.lib.colors import BaseColor, Tab10
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..lib import get_icon
|
||||
from ..lib.iconloading import get_icon
|
||||
from .._py.fitfunctionwidget import Ui_Form
|
||||
from ..Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
||||
class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
|
||||
func_cnt = count()
|
||||
func_colors = cycle(Tab10)
|
||||
@ -127,7 +128,7 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
|
||||
|
||||
self.newFunction.emit(idx, cnt)
|
||||
|
||||
self.add_function(idx, cnt, op, name, col)
|
||||
self.add_function(idx, cnt, op, name, col, param_names=self.functions[idx].params)
|
||||
|
||||
def add_function(self, idx: int, cnt: int, op: int,
|
||||
name: str, color: str | tuple[float, float, float] | BaseColor, **kwargs):
|
||||
@ -140,6 +141,7 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
|
||||
qcolor = QtGui.QColor.fromRgbF(*color)
|
||||
else:
|
||||
qcolor = QtGui.QColor(color)
|
||||
|
||||
self.functree.add_function(idx, cnt, op, name, qcolor, **kwargs)
|
||||
|
||||
f = self.functions[idx]
|
||||
|
@ -9,10 +9,14 @@ import numpy as np
|
||||
from pyqtgraph import mkPen
|
||||
|
||||
from nmreval.fit._meta import MultiModel, ModelFactory
|
||||
from nmreval.fit.data import Data
|
||||
from nmreval.fit.model import Model
|
||||
from nmreval.fit.parameter import Parameters
|
||||
from nmreval.fit.result import FitResult
|
||||
|
||||
from .fit_forms import FitTableWidget
|
||||
from .fit_parameter import QFitParameterWidget
|
||||
from ..lib import Relations
|
||||
from ..lib.pg_objects import PlotItem
|
||||
from ..Qt import QtGui, QtCore, QtWidgets
|
||||
from .._py.fitdialog import Ui_FitDialog
|
||||
@ -49,7 +53,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
self._func_list = {}
|
||||
self._complex = {}
|
||||
|
||||
self.connected_figure = ''
|
||||
self.connected_figure = None
|
||||
|
||||
self.model_frame.hide()
|
||||
self.preview_button.hide()
|
||||
@ -77,16 +81,11 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
w.deleteLater()
|
||||
del self.param_widgets[idx]
|
||||
|
||||
if len(self.functionwidget) == 0:
|
||||
self._current_function = None
|
||||
if len(self.param_widgets) == 0:
|
||||
# empty model
|
||||
self.newmodel_button.setEnabled(False)
|
||||
self.deletemodel_button.setEnabled(False)
|
||||
self._current_function = None
|
||||
|
||||
else:
|
||||
f_tree = self.functionwidget.functree
|
||||
func_idx = f_tree.currentItem().data(0, f_tree.counterRole)
|
||||
self._current_function = self.functionwidget.functions[func_idx]
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def show_function_parameter(self, function_id: int, function_idx: int = None):
|
||||
@ -120,7 +119,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
|
||||
# collect parameter names etc. to allow linkage
|
||||
self._func_list[self._current_model] = self.functionwidget.get_parameter_list()
|
||||
dialog.set_links(self._func_list)
|
||||
# dialog.set_links(self._func_list)
|
||||
|
||||
# show same tab (general parameter/Data parameter)
|
||||
tab_idx = 0
|
||||
@ -148,6 +147,13 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
Add name and id of dataset to list.
|
||||
"""
|
||||
self.data_table.load(ids)
|
||||
|
||||
# deselect all fit sets
|
||||
for i in range(self.data_table.rowCount()):
|
||||
data_id = self.data_table.item(i, 0).data(QtCore.Qt.UserRole+1)
|
||||
if self._management[data_id].mode == 'fit' or self._management[data_id].has_relation(Relations.isFitPartOf):
|
||||
self.data_table.item(i, 0).setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
if self.models:
|
||||
for m in self.models.keys():
|
||||
self.data_table.add_model(m)
|
||||
@ -216,57 +222,49 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
|
||||
def _prepare(self, model: list, function_use: list = None,
|
||||
parameter: dict = None, add_idx: bool = False, cnt: int = 0) -> tuple[dict, int]:
|
||||
|
||||
if parameter is None:
|
||||
parameter = {'parameter': {}, 'lb': (), 'ub': (), 'var': [],
|
||||
'glob': {'idx': [], 'p': [], 'var': [], 'lb': [], 'ub': []},
|
||||
'links': [], 'color': []}
|
||||
parameter = {
|
||||
'data_parameter': {},
|
||||
'global_parameter': [],
|
||||
'links': [],
|
||||
'color': [],
|
||||
}
|
||||
|
||||
for i, f in enumerate(model):
|
||||
if not f['active']:
|
||||
continue
|
||||
|
||||
try:
|
||||
p, lb, ub, var, glob, links = self.param_widgets[f['cnt']].get_parameter(function_use)
|
||||
p, glob = self.param_widgets[f['cnt']].get_parameter(function_use)
|
||||
except ValueError as e:
|
||||
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value', str(e),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return {}, -1
|
||||
|
||||
p_len = len(parameter['lb'])
|
||||
|
||||
parameter['lb'] += lb
|
||||
parameter['ub'] += ub
|
||||
parameter['var'] += var
|
||||
parameter['links'] += links
|
||||
parameter['color'] += [f['color']]
|
||||
parameter['color'].append(f['color'])
|
||||
parameter['global_parameter'].extend(glob)
|
||||
|
||||
cnt = f['cnt']
|
||||
|
||||
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]
|
||||
if p_k in parameter['data_parameter']:
|
||||
params, kw = parameter['data_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]
|
||||
parameter['data_parameter'][p_k] = (v_k[0], kw_k)
|
||||
|
||||
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)
|
||||
_, cnt = self._prepare(f['children'], parameter=parameter, add_idx=add_idx, cnt=cnt)
|
||||
|
||||
return parameter, cnt
|
||||
|
||||
@ -277,30 +275,43 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
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)
|
||||
for model_name, model_parameter in self.models.items():
|
||||
func, order, param_len = ModelFactory.create_from_list(model_parameter)
|
||||
|
||||
if func is None:
|
||||
continue
|
||||
|
||||
if k in data:
|
||||
parameter, _ = self._prepare(mod, function_use=data[k], add_idx=isinstance(func, MultiModel))
|
||||
func = Model(func)
|
||||
|
||||
if model_name in data:
|
||||
parameter, _ = self._prepare(model_parameter, function_use=data[model_name], add_idx=isinstance(func, MultiModel))
|
||||
|
||||
if parameter is None:
|
||||
return
|
||||
|
||||
for (data_parameter, _) in parameter['data_parameter'].values():
|
||||
for pname, param in zip(func.params, data_parameter):
|
||||
param.name = pname
|
||||
|
||||
if self._complex[model_name] is not None:
|
||||
for p_k, p_v in parameter['data_parameter'].items():
|
||||
p_v[1].update({'complex_mode': self._complex[model_name]})
|
||||
parameter['data_parameter'][p_k] = p_v[0], p_v[1]
|
||||
|
||||
for pname, param_value in zip(func.params, parameter['global_parameter']):
|
||||
if param_value is not None:
|
||||
param_value.name = pname
|
||||
func.set_global_parameter(param_value)
|
||||
|
||||
parameter['func'] = func
|
||||
parameter['order'] = order
|
||||
parameter['len'] = param_len
|
||||
parameter['complex'] = self._complex[k]
|
||||
if self._complex[k] is not None:
|
||||
for p_k, p_v in parameter['parameter'].items():
|
||||
p_v[1].update({'complex_mode': self._complex[k]})
|
||||
parameter['parameter'][p_k] = p_v[0], p_v[1]
|
||||
parameter['complex'] = self._complex[model_name]
|
||||
|
||||
func_dict[k] = parameter
|
||||
func_dict[model_name] = parameter
|
||||
|
||||
replaceable = []
|
||||
for k, v in func_dict.items():
|
||||
for model_name, v in func_dict.items():
|
||||
for i, link_i in enumerate(v['links']):
|
||||
if link_i is None:
|
||||
continue
|
||||
@ -331,7 +342,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
|
||||
replaceable.append((k, i, rep_model, repl_idx))
|
||||
replaceable.append((model_name, i, rep_model, repl_idx))
|
||||
|
||||
replace_value = None
|
||||
for p_k in f['parameter'].values():
|
||||
@ -409,31 +420,37 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
def make_previews(self, x, models_parameters: dict):
|
||||
self.preview_lines = []
|
||||
|
||||
# needed to create namespace
|
||||
param_dict = Parameters()
|
||||
|
||||
cnt = 0
|
||||
for model in models_parameters.values():
|
||||
f = model['func']
|
||||
for parameter_list in model['data_parameter'].values():
|
||||
for i, p_value in enumerate(parameter_list[0]):
|
||||
p_value.name = f.params[i]
|
||||
param_dict.add_parameter(f'a{cnt}', p_value)
|
||||
cnt += 1
|
||||
|
||||
for k, model in models_parameters.items():
|
||||
f = model['func']
|
||||
is_complex = self._complex[k]
|
||||
|
||||
parameters = model['parameter']
|
||||
parameters = model['data_parameter']
|
||||
color = model['color']
|
||||
|
||||
seen_parameter = []
|
||||
|
||||
for p, kwargs in parameters.values():
|
||||
if (p, kwargs) in seen_parameter:
|
||||
# plot only previews with different parameter
|
||||
continue
|
||||
|
||||
seen_parameter.append((p, kwargs))
|
||||
p_value = [pp.value for pp in p]
|
||||
|
||||
if is_complex is not None:
|
||||
y = f.func(x, *p, complex_mode=is_complex, **kwargs)
|
||||
y = f.func(x, *p_value, complex_mode=is_complex, **kwargs)
|
||||
if np.iscomplexobj(y):
|
||||
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)))
|
||||
else:
|
||||
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
|
||||
else:
|
||||
y = f.func(x, *p, **kwargs)
|
||||
y = f.func(x, *p_value, **kwargs)
|
||||
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
|
||||
|
||||
if isinstance(f, MultiModel):
|
||||
@ -441,7 +458,7 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
if is_complex is not None:
|
||||
sub_kwargs.update({'complex_mode': is_complex})
|
||||
|
||||
for i, s in enumerate(f.subs(x, *p, **sub_kwargs)):
|
||||
for i, s in enumerate(f.subs(x, *p_value, **sub_kwargs)):
|
||||
pen_i = mkPen(QtGui.QColor.fromRgbF(*color[i]))
|
||||
if np.iscomplexobj(s):
|
||||
self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i))
|
||||
@ -449,15 +466,17 @@ class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
else:
|
||||
self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i))
|
||||
|
||||
param_dict.clear()
|
||||
|
||||
return self.preview_lines
|
||||
|
||||
def set_parameter(self, parameter: dict[str, FitResult]):
|
||||
# which data uses which model
|
||||
data = self.data_table.collect_data(default=self.default_combobox.currentData())
|
||||
|
||||
glob_fit_parameter = []
|
||||
|
||||
for fitted_model, fitted_data in data.items():
|
||||
glob_fit_parameter = []
|
||||
|
||||
for fit_id, fit_curve in parameter.items():
|
||||
if fit_id in fitted_data:
|
||||
fit_parameter = list(fit_curve.parameter.values())
|
||||
|
@ -14,7 +14,7 @@ from gui_qt.lib.namespace import QNamespaceWidget
|
||||
|
||||
__all__ = ['QUserFitCreator']
|
||||
|
||||
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[A-Za-z]\S*'))
|
||||
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[_A-Za-z][_A-Za-z0-9]*'))
|
||||
pattern = re.compile(r'def func\(.*\):', flags=re.MULTILINE)
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.classCreated.emit()
|
||||
super().accept()
|
||||
|
||||
|
||||
class KwargsWidget(QtWidgets.QWidget):
|
||||
Changed = QtCore.pyqtSignal()
|
||||
|
||||
@ -209,7 +210,7 @@ class KwargsWidget(QtWidgets.QWidget):
|
||||
def get_strings(self) -> str:
|
||||
kwargs = []
|
||||
if self.use_nuclei.isChecked():
|
||||
kwargs.append("(r'\gamma', 'nucleus', gamma)")
|
||||
kwargs.append(r"(r'\gamma', 'nucleus', gamma)")
|
||||
|
||||
for i in range(self.choices.count()):
|
||||
kwargs.append(self.choices.widget(i).get_strings())
|
||||
@ -300,7 +301,7 @@ class ChoiceWidget(QtWidgets.QWidget):
|
||||
def get_strings(self) -> str:
|
||||
opts = []
|
||||
for i in range(self.table.rowCount()):
|
||||
name = self.table.item(i, 0).text()
|
||||
name = self.table.cellWidget(i, 0).text()
|
||||
val = self._make_value(i)
|
||||
opts.append(f'{name!r}: {val!r}')
|
||||
|
||||
@ -471,7 +472,7 @@ class DescWidget(QtWidgets.QWidget):
|
||||
stringi = f'class {self.klass_lineedit.text()}:\n' \
|
||||
f' name = {self.name_lineedit.text()!r}\n' \
|
||||
f' type = {self.group_lineedit.text()!r}\n' \
|
||||
f' equation = {self.eq_lineedit.text()!r}\n'
|
||||
f" equation = r'{self.eq_lineedit.text()}'\n"
|
||||
|
||||
return stringi
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
from math import isnan
|
||||
|
||||
from pyqtgraph import mkBrush
|
||||
from pyqtgraph import mkBrush, mkPen
|
||||
|
||||
from nmreval.utils.text import convert
|
||||
from ..lib.graph_items import logTickValues
|
||||
|
||||
from ..lib.utils import RdBuCMap
|
||||
from ..Qt import QtWidgets, QtGui, QtCore
|
||||
@ -27,70 +28,82 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.extrapolate_box.stateChanged.connect(lambda x: self.minx_line.setEnabled(x))
|
||||
self.extrapolate_box.stateChanged.connect(lambda x: self.numx_line.setEnabled(x))
|
||||
|
||||
self._prevs = {}
|
||||
self._models = {}
|
||||
self._previous_fits = {}
|
||||
self._opts = []
|
||||
self._results = {}
|
||||
self.graph_opts = {}
|
||||
self.last_idx = None
|
||||
|
||||
for res in results:
|
||||
idx = res.idx
|
||||
data_k = management.data[idx]
|
||||
self.fit_plot = self.graphicsView.addPlot(row=1, col=0, title='Fit')
|
||||
self.resid_plot = self.graphicsView.addPlot(row=0, col=0, title='Residual')
|
||||
|
||||
if res.name not in self._models:
|
||||
self._models[res.name] = []
|
||||
for orient in ['top', 'bottom', 'left', 'right']:
|
||||
self.fit_plot.getAxis(orient).logTickValues = logTickValues
|
||||
self.resid_plot.getAxis(orient).logTickValues = logTickValues
|
||||
|
||||
self._models[res.name].append(idx)
|
||||
self.graphicsView.ci.layout.setRowStretchFactor(0, 1)
|
||||
self.graphicsView.ci.layout.setRowStretchFactor(1, 2)
|
||||
|
||||
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._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=(31, 119, 180)),
|
||||
pen=None)
|
||||
self.resid_graph_imag = PlotItem(x=[], y=[],
|
||||
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
|
||||
pen=None)
|
||||
self.residplot.addItem(self.resid_graph)
|
||||
self.residplot.addItem(self.resid_graph_imag)
|
||||
self.residplot.setLabel('left', 'Residual')
|
||||
self.resid_plot.addItem(self.resid_graph)
|
||||
self.resid_plot.addItem(self.resid_graph_imag)
|
||||
|
||||
self.fitplot = self.graphicsView.addPlot(row=1, col=0)
|
||||
self.data_graph = PlotItem(x=[], y=[],
|
||||
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
|
||||
pen=None)
|
||||
self.data_graph_imag = PlotItem(x=[], y=[],
|
||||
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
|
||||
pen=None)
|
||||
self.fitplot.addItem(self.data_graph)
|
||||
self.fitplot.addItem(self.data_graph_imag)
|
||||
self.fitplot.setLabel('left', 'Function')
|
||||
self.fit_plot.addItem(self.data_graph)
|
||||
self.fit_plot.addItem(self.data_graph_imag)
|
||||
|
||||
self.fit_graph = PlotItem(x=[], y=[])
|
||||
self.fit_graph_imag = PlotItem(x=[], y=[])
|
||||
self.fitplot.addItem(self.fit_graph)
|
||||
self.fitplot.addItem(self.fit_graph_imag)
|
||||
self.fit_plot.addItem(self.fit_graph)
|
||||
self.fit_plot.addItem(self.fit_graph_imag)
|
||||
|
||||
self.cmap = RdBuCMap(vmin=-1, vmax=1)
|
||||
|
||||
self.sets_comboBox.blockSignals(True)
|
||||
for n in self._models.keys():
|
||||
self.sets_comboBox.addItem(n)
|
||||
self.sets_comboBox.blockSignals(False)
|
||||
|
||||
self.set_parameter(0)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
|
||||
self.param_tableWidget.itemClicked.connect(self.show_results)
|
||||
self.param_tableWidget.horizontalHeader().sectionClicked.connect(lambda i: self.show_results(None, idx=i))
|
||||
|
||||
self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked))
|
||||
|
||||
self.logy_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(y=bool(x)))
|
||||
self.logx_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(x=bool(x)))
|
||||
self.residplot.setXLink(self.fitplot)
|
||||
self.logy_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(y=bool(x)))
|
||||
self.logx_box.stateChanged.connect(lambda x: self.fit_plot.setLogMode(x=bool(x)))
|
||||
self.resid_plot.setXLink(self.fit_plot)
|
||||
|
||||
self.set_results(results)
|
||||
|
||||
def __call__(self, results: list):
|
||||
self._previous_fits = {}
|
||||
self.sets_comboBox.blockSignals(True)
|
||||
self.sets_comboBox.clear()
|
||||
self.sets_comboBox.blockSignals(False)
|
||||
self._results = {}
|
||||
self._opts = {}
|
||||
|
||||
self.set_results(results)
|
||||
|
||||
def set_results(self, results: list):
|
||||
self.sets_comboBox.blockSignals(True)
|
||||
for res in results:
|
||||
idx = res.idx
|
||||
data_k = self._management.data[idx]
|
||||
|
||||
self._previous_fits[idx] = []
|
||||
for fit in data_k.get_fits():
|
||||
self._previous_fits[idx].append((fit.name, fit.statistics, fit.nobs - fit.nvar))
|
||||
|
||||
self.sets_comboBox.addItem(data_k.name, userData=idx)
|
||||
self.sets_comboBox.blockSignals(False)
|
||||
|
||||
self._results = {res.idx: res for res in results}
|
||||
self._opts = [(False, False) for _ in range(len(self._results))]
|
||||
|
||||
self.set_parameter(0)
|
||||
|
||||
def add_graphs(self, graphs: list):
|
||||
self.graph_comboBox.clear()
|
||||
@ -99,56 +112,40 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
@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))
|
||||
set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole)
|
||||
|
||||
r = self._results[sets[0]]
|
||||
self.param_tableWidget.setRowCount(len(r.parameter))
|
||||
res = self._results[set_id]
|
||||
self.param_tableWidget.setRowCount(len(res.parameter))
|
||||
for j, (pkey, pvalue) in enumerate(res.parameter.items()):
|
||||
name = pkey
|
||||
p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'str', brackets=True))
|
||||
self.param_tableWidget.setVerticalHeaderItem(j, p_header)
|
||||
|
||||
for i, pval in enumerate(r.parameter.values()):
|
||||
name = pval.full_name
|
||||
p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'html', brackets=False))
|
||||
self.param_tableWidget.setVerticalHeaderItem(i, p_header)
|
||||
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, 0, QtWidgets.QTableWidgetItem('-'))
|
||||
else:
|
||||
self.param_tableWidget.setItem(2*j+1, 0, QtWidgets.QTableWidgetItem())
|
||||
item = QtWidgets.QTableWidgetItem(item_text)
|
||||
self.param_tableWidget.setItem(j, 0, item)
|
||||
|
||||
for i, set_id in enumerate(sets):
|
||||
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(None, idx=0)
|
||||
self.param_tableWidget.resizeColumnToContents(0)
|
||||
self.show_results(idx)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged')
|
||||
@QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged')
|
||||
def change_opts(self, _):
|
||||
idx = self.param_tableWidget.currentIndex().column()
|
||||
idx = self.sets_comboBox.currentIndex()
|
||||
|
||||
self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked,
|
||||
self.del_prev_checkBox.checkState() == QtCore.Qt.Checked)
|
||||
|
||||
def show_results(self, item, idx=None):
|
||||
if item is not None:
|
||||
idx = self.param_tableWidget.indexFromItem(item).column()
|
||||
|
||||
set_id = self.param_tableWidget.horizontalHeaderItem(idx).data(QtCore.Qt.UserRole)
|
||||
def show_results(self, idx):
|
||||
set_id = self.sets_comboBox.itemData(idx, QtCore.Qt.UserRole)
|
||||
self.set_plot(set_id)
|
||||
self.set_correlation(set_id)
|
||||
self.set_statistics(set_id)
|
||||
|
||||
self.reject_fit_checkBox.blockSignals(True)
|
||||
self.reject_fit_checkBox.setChecked(self._opts[idx][0])
|
||||
self.reject_fit_checkBox.blockSignals(False)
|
||||
@ -157,9 +154,22 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.del_prev_checkBox.blockSignals(False)
|
||||
|
||||
def set_plot(self, idx: str):
|
||||
if self.last_idx is not None:
|
||||
self.graph_opts[self.last_idx] = (
|
||||
self.fit_plot.viewRange(),
|
||||
self.logx_box.isChecked(),
|
||||
self.logy_box.isChecked(),
|
||||
)
|
||||
|
||||
self.last_idx = idx
|
||||
res = self._results[idx]
|
||||
iscomplex = res.iscomplex
|
||||
|
||||
sub_funcs = res.sub(res.x)
|
||||
for item in self.fit_plot.items[::-1]:
|
||||
if item not in [self.data_graph, self.data_graph_imag, self.fit_graph, self.fit_graph_imag]:
|
||||
self.fit_plot.removeItem(item)
|
||||
|
||||
if iscomplex:
|
||||
self.data_graph.setData(x=res.x_data, y=res.y_data.real)
|
||||
self.data_graph_imag.setData(x=res.x_data, y=res.y_data.imag)
|
||||
@ -167,6 +177,13 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.fit_graph_imag.setData(x=res.x, y=res.y.imag)
|
||||
self.resid_graph.setData(x=res.x_data, y=res.residual.real)
|
||||
self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag)
|
||||
|
||||
for i, f in enumerate(sub_funcs):
|
||||
item = PlotItem(x=f.x, y=f.y.real, pen=mkPen({'color': i, 'style': 2}))
|
||||
self.fit_plot.addItem(item)
|
||||
item = PlotItem(x=f.x, y=f.y.imag, pen=mkPen({'color': i, 'style': 2}))
|
||||
self.fit_plot.addItem(item)
|
||||
|
||||
else:
|
||||
self.resid_graph.setData(x=res.x_data, y=res.residual)
|
||||
self.resid_graph_imag.setData(x=[], y=[])
|
||||
@ -175,8 +192,27 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.fit_graph.setData(x=res.x, y=res.y)
|
||||
self.fit_graph_imag.setData(x=[], y=[])
|
||||
|
||||
self.fitplot.setLogMode(x=res.islog)
|
||||
self.residplot.setLogMode(x=res.islog)
|
||||
for i, f in enumerate(sub_funcs):
|
||||
item = PlotItem(x=f.x, y=f.y, pen=mkPen({'color': i, 'style': 2}))
|
||||
self.fit_plot.addItem(item)
|
||||
|
||||
self.logx_box.blockSignals(True)
|
||||
self.logx_box.setChecked(res.islog)
|
||||
self.logx_box.blockSignals(False)
|
||||
|
||||
self.fit_plot.setLogMode(x=res.islog)
|
||||
self.resid_plot.setLogMode(x=res.islog)
|
||||
|
||||
if idx in self.graph_opts:
|
||||
view_range, logx, logy = self.graph_opts[idx]
|
||||
self.fit_plot.setRange(xRange=view_range[0], yRange=view_range[1], padding=0)
|
||||
self.fit_plot.setLogMode(x=logx, y=logy)
|
||||
self.logx_box.blockSignals(True)
|
||||
self.logx_box.setChecked(logx)
|
||||
self.logx_box.blockSignals(False)
|
||||
self.logy_box.blockSignals(True)
|
||||
self.logy_box.setChecked(logy)
|
||||
self.logy_box.blockSignals(False)
|
||||
|
||||
def set_correlation(self, idx: str):
|
||||
while self.corr_tableWidget.rowCount():
|
||||
@ -207,7 +243,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
res = self._results[idx]
|
||||
|
||||
self.stats_tableWidget.setColumnCount(1 + len(self._prevs[idx]))
|
||||
self.stats_tableWidget.setColumnCount(1 + len(self._previous_fits[idx]))
|
||||
self.stats_tableWidget.setRowCount(len(res.statistics)+3)
|
||||
|
||||
it = QtWidgets.QTableWidgetItem(f'{res.dof}')
|
||||
@ -215,7 +251,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
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):
|
||||
for col, (name, _, dof) in enumerate(self._previous_fits[idx], start=1):
|
||||
self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name))
|
||||
it = QtWidgets.QTableWidgetItem(f'{dof}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
@ -229,7 +265,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
best_idx = -1
|
||||
best_val = v
|
||||
for col, (_, stats, _) in enumerate(self._prevs[idx], start=1):
|
||||
for col, (_, stats, _) in enumerate(self._previous_fits[idx], start=1):
|
||||
if k in ['adj. R^2', 'R^2']:
|
||||
best_idx = col if best_val < stats[k] else max(0, best_idx)
|
||||
else:
|
||||
@ -249,7 +285,7 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
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):
|
||||
for col, (_, stats, dof) in enumerate(self._previous_fits[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)
|
||||
@ -279,27 +315,30 @@ class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
plot_fits = self.curve_checkbox.isChecked()
|
||||
|
||||
|
||||
parts = self.partial_checkBox.checkState() == QtCore.Qt.Checked
|
||||
|
||||
extrapolate = [None, None, None]
|
||||
error = []
|
||||
if self.extrapolate_box.isChecked():
|
||||
try:
|
||||
extrapolate[0] = float(self.minx_line.text())
|
||||
except TypeError:
|
||||
pass
|
||||
except (TypeError, ValueError):
|
||||
error.append('Start value is missing')
|
||||
try:
|
||||
extrapolate[1] = float(self.maxx_line.text())
|
||||
except TypeError:
|
||||
pass
|
||||
except (TypeError, ValueError):
|
||||
error.append('End value is missing')
|
||||
try:
|
||||
extrapolate[2] = int(self.numx_line.text())
|
||||
except TypeError:
|
||||
pass
|
||||
except (TypeError, ValueError):
|
||||
error.append('Number of points is missing')
|
||||
|
||||
|
||||
self.closed.emit(self._results, self._opts, graph, plot_fits, parts, extrapolate)
|
||||
self.accept()
|
||||
if error:
|
||||
msg = QtWidgets.QMessageBox.warning(self, 'Error', 'Extrapolation failed because:\n' + '\n'.join(error))
|
||||
return
|
||||
else:
|
||||
self.closed.emit(self._results, self._opts, graph, plot_fits, parts, extrapolate)
|
||||
self.accept()
|
||||
|
||||
else:
|
||||
self.reject()
|
||||
@ -333,7 +372,7 @@ class FitExtension(QtWidgets.QDialog):
|
||||
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox()
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
|
||||
gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
|
||||
|
||||
self.setLayout(gridLayout)
|
||||
@ -350,4 +389,4 @@ class FitExtension(QtWidgets.QDialog):
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
return xmin, xmax, nums
|
||||
return xmin, xmax, nums
|
||||
|
@ -138,9 +138,7 @@ class DrawingsWidget(QtWidgets.QWidget, Ui_Form):
|
||||
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)
|
||||
|
||||
|
@ -4,19 +4,21 @@ import itertools
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from math import isnan
|
||||
from math import isfinite
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from numpy import errstate, floor, log10
|
||||
from pyqtgraph import GraphicsObject, getConfigOption, mkColor
|
||||
|
||||
from nmreval.lib.logger import logger
|
||||
from nmreval.utils.text import convert
|
||||
from ..io.filedialog import FileDialog
|
||||
|
||||
from ..lib.pg_objects import LegendItemBlock, RegionItem
|
||||
from ..Qt import QtCore, QtWidgets, QtGui
|
||||
from .._py.graph import Ui_GraphWindow
|
||||
from ..lib import make_action_icons
|
||||
from ..lib.iconloading import make_action_icons
|
||||
from ..lib.configurations import GraceMsgBox
|
||||
|
||||
|
||||
@ -24,7 +26,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
mousePositionChanged = QtCore.pyqtSignal(float, float)
|
||||
mouseDoubleClicked = QtCore.pyqtSignal()
|
||||
positionClicked = QtCore.pyqtSignal(tuple, bool)
|
||||
aboutToClose = QtCore.pyqtSignal(str)
|
||||
aboutToClose = QtCore.pyqtSignal(list)
|
||||
|
||||
counter = itertools.count()
|
||||
|
||||
@ -43,7 +45,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
self.id = str(uuid.uuid4())
|
||||
|
||||
self.sets = []
|
||||
self.active = []
|
||||
self._active = []
|
||||
|
||||
self.real_plots = {}
|
||||
self.imag_plots = {}
|
||||
@ -53,6 +55,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
self._external_items = []
|
||||
self.closable = True
|
||||
|
||||
self._block = False
|
||||
|
||||
self.log = [False, False]
|
||||
|
||||
self.scene = self.plotItem.scene()
|
||||
@ -71,6 +75,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
self.scene.contextMenu[0].disconnect()
|
||||
self.scene.contextMenu[0].triggered.connect(self.export_dialog)
|
||||
|
||||
self.bwbutton.toggled.connect(self.change_background)
|
||||
|
||||
def _init_gui(self):
|
||||
self.setWindowTitle('Graph ' + str(next(QGraphWindow.counter)))
|
||||
|
||||
@ -110,11 +116,11 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
return iter(self.active)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.active)
|
||||
return len(self._active)
|
||||
|
||||
def curves(self) -> tuple:
|
||||
for set_id in self.sets:
|
||||
if set_id in self.active:
|
||||
if set_id in self._active:
|
||||
if self.real_button.isChecked():
|
||||
if self.error_plots[set_id] is not None:
|
||||
yield self.real_plots[set_id], self.error_plots[set_id]
|
||||
@ -137,19 +143,43 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
r = self.plotItem.getViewBox().viewRange()
|
||||
for i in [0, 1]:
|
||||
if self.log[i]:
|
||||
r[i] = tuple([10**x for x in r[i]])
|
||||
tmp = [np.nan, np.nan]
|
||||
for j, x in enumerate(r[i]):
|
||||
try:
|
||||
tmp[j] = 10**min(x, 199)
|
||||
except OverflowError:
|
||||
pass
|
||||
r[i] = tuple(tmp)
|
||||
else:
|
||||
r[i] = tuple(r[i])
|
||||
|
||||
return tuple(r)
|
||||
|
||||
@property
|
||||
def active(self) -> list:
|
||||
return [set_id for set_id in self.sets if set_id in self._active]
|
||||
|
||||
@active.setter
|
||||
def active(self, value: list):
|
||||
self._active = value
|
||||
|
||||
def block(self, state: bool):
|
||||
self._block = state
|
||||
|
||||
if not self._block:
|
||||
self.graphic.enableAutoRange()
|
||||
self._update_zorder()
|
||||
self.show_legend()
|
||||
else:
|
||||
self.graphic.disableAutoRange()
|
||||
|
||||
def add(self, name: str | list, plots: list):
|
||||
if isinstance(name, str):
|
||||
name = [name]
|
||||
plots = [plots]
|
||||
|
||||
toplevel = len(self.sets)
|
||||
self.listWidget.blockSignals(True)
|
||||
for (real_plot, imag_plot, err_plot), n in zip(plots, name):
|
||||
toplevel = len(self.sets)
|
||||
self.sets.append(n)
|
||||
|
||||
if real_plot:
|
||||
@ -169,7 +199,16 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
list_item.setCheckState(QtCore.Qt.Checked)
|
||||
self.listWidget.addItem(list_item)
|
||||
|
||||
self.show_item(name)
|
||||
toplevel += 1
|
||||
self.listWidget.blockSignals(False)
|
||||
|
||||
if len(name) < 200:
|
||||
self.show_item(name)
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self, 'Display disabled',
|
||||
'If more than 200 sets are added at once, they are not displayed to avoid major performance issues.\n'
|
||||
'The checkmark in the data tree is invalid.\n'
|
||||
'Please display them manually in smaller batches, thank you!')
|
||||
|
||||
def remove(self, name: str | list):
|
||||
if isinstance(name, str):
|
||||
@ -181,8 +220,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
for plot in [self.real_plots, self.imag_plots, self.error_plots]:
|
||||
self.graphic.removeItem(plot[n])
|
||||
|
||||
if n in self.active:
|
||||
self.active.remove(n)
|
||||
if n in self._active:
|
||||
self._active.remove(n)
|
||||
|
||||
# remove from label list
|
||||
self.listWidget.blockSignals(True)
|
||||
@ -194,8 +233,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
|
||||
self.listWidget.blockSignals(False)
|
||||
|
||||
self._update_zorder()
|
||||
self.show_legend()
|
||||
if not self._block:
|
||||
self._update_zorder()
|
||||
self.show_legend()
|
||||
|
||||
def move_sets(self, sets: list, position: int):
|
||||
move_plots = []
|
||||
@ -225,8 +265,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
return
|
||||
|
||||
for a in idlist:
|
||||
if a not in self.active:
|
||||
self.active.append(a)
|
||||
if a not in self._active:
|
||||
self._active.append(a)
|
||||
|
||||
for (bttn, plot_dic) in [
|
||||
(self.real_button, self.real_plots),
|
||||
@ -245,8 +285,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
return
|
||||
|
||||
for r in idlist:
|
||||
if r in self.active:
|
||||
self.active.remove(r)
|
||||
if r in self._active:
|
||||
self._active.remove(r)
|
||||
|
||||
for plt in [self.real_plots, self.imag_plots, self.error_plots]:
|
||||
item = plt[r]
|
||||
@ -268,7 +308,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
else:
|
||||
func = self.graphic.removeItem
|
||||
|
||||
for a in self.active:
|
||||
for a in self._active:
|
||||
item = plots[a]
|
||||
if item is not None:
|
||||
func(item)
|
||||
@ -285,12 +325,12 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
return
|
||||
|
||||
if visible:
|
||||
for a in self.active:
|
||||
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:
|
||||
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)
|
||||
@ -354,7 +394,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||
|
||||
if res == QtWidgets.QMessageBox.Yes:
|
||||
self.aboutToClose.emit(self.id)
|
||||
self.aboutToClose.emit([self.id])
|
||||
evt.accept()
|
||||
else:
|
||||
evt.ignore()
|
||||
@ -402,6 +442,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
def set_logmode(self, xmode: bool = None, ymode: bool = None):
|
||||
r = self.ranges
|
||||
|
||||
self.plotItem.setXRange(*r[0])
|
||||
self.plotItem.setYRange(*r[1])
|
||||
|
||||
if xmode is None:
|
||||
xmode = self.plotItem.ctrl.logXCheck.isChecked()
|
||||
else:
|
||||
@ -418,6 +461,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
item.logmode[0] = self.log[:]
|
||||
|
||||
self.plotItem.updateLogMode()
|
||||
self.set_range(x=r[0], y=r[1])
|
||||
|
||||
self.plotItem.enableAutoRange()
|
||||
|
||||
@ -460,9 +504,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
with errstate(all='ignore'):
|
||||
xy = [log10(val) for val in xy]
|
||||
|
||||
if isnan(xy[1]):
|
||||
if not isfinite(xy[1]):
|
||||
xy = [-1, 1]
|
||||
elif isnan(xy[0]):
|
||||
elif not isfinite(xy[0]):
|
||||
xy[0] = xy[1]-4
|
||||
|
||||
func(xy[0], xy[1], padding=0)
|
||||
@ -494,6 +538,9 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
self.checkBox.setVisible(visible)
|
||||
|
||||
def update_legend(self, sid, name):
|
||||
if self._block:
|
||||
return
|
||||
|
||||
self.listWidget.blockSignals(True)
|
||||
|
||||
for i in range(self.listWidget.count()):
|
||||
@ -520,16 +567,14 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
elif other_item in self.graphic.items():
|
||||
self.legend.addItem(other_item, convert(other_item.opts.get('name', ''), old='tex', new='html'))
|
||||
|
||||
def export_dialog(self, path=None):
|
||||
def export_dialog(self, _=None):
|
||||
filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)'
|
||||
for imgformat in QtGui.QImageWriter.supportedImageFormats():
|
||||
str_format = imgformat.data().decode('utf-8')
|
||||
filters += ';;' + str_format.upper() + ' (*.' + str_format + ')'
|
||||
|
||||
if path is None:
|
||||
path = ''
|
||||
outfile = None
|
||||
f = FileDialog(caption='Export graphic', directory=str(path), filter=filters, mode='save')
|
||||
f = FileDialog(caption='Export graphic', filter=filters, mode='save')
|
||||
f.setOption(FileDialog.DontConfirmOverwrite)
|
||||
mode = f.exec()
|
||||
if mode == QtWidgets.QDialog.Accepted:
|
||||
@ -608,7 +653,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
try:
|
||||
item_dic = plot_item.get_data_opts()
|
||||
except Exception as e:
|
||||
print(f'{item} could not exported because {e.args}')
|
||||
logger.exception(f'{item} could not exported because {e.args}')
|
||||
continue
|
||||
|
||||
if len(item) == 2:
|
||||
@ -622,7 +667,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
try:
|
||||
dic['items'].append(item.get_data_opts())
|
||||
except Exception as e:
|
||||
print(f'{item} could not be exported because {e.args}')
|
||||
logger.exception(f'{item} could not be exported because {e.args}')
|
||||
continue
|
||||
|
||||
in_legend.append(False)
|
||||
@ -645,7 +690,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
'legend': self.legend.isVisible(),
|
||||
'plots': (self.real_button.isChecked(), self.imag_button.isChecked(), self.error_button.isChecked()),
|
||||
'children': self.sets,
|
||||
'active': self.active,
|
||||
'active': self._active,
|
||||
}
|
||||
|
||||
in_legend = []
|
||||
@ -708,7 +753,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
if background is not None:
|
||||
self._bgcolor = mkColor(background)
|
||||
self.graphic.setBackground(self._bgcolor)
|
||||
self.legend.setBrush(self._bgcolor)
|
||||
# self.legend.setBrush(self._bgcolor)
|
||||
|
||||
if foreground is not None:
|
||||
self._fgcolor = mkColor(foreground)
|
||||
@ -725,7 +770,7 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
self.show_legend()
|
||||
|
||||
title = self.plotItem.titleLabel.text
|
||||
if title is not None:
|
||||
if title is not None and title != '':
|
||||
self.plotItem.setTitle(title, **{'size': '10pt', 'color': self._fgcolor})
|
||||
|
||||
x = self.plotItem.getAxis('bottom').labelText
|
||||
@ -736,8 +781,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
|
||||
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
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from nmreval.io.asciireader import AsciiReader
|
||||
from nmreval.utils import NUMBER_RE
|
||||
|
||||
from ..Qt import QtGui, QtCore, QtWidgets
|
||||
from .._py.asciidialog import Ui_ascii_reader
|
||||
@ -13,6 +18,9 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
self.setupUi(self)
|
||||
|
||||
self.reader = None
|
||||
self._matches = []
|
||||
self.regex_input.setText(NUMBER_RE.pattern)
|
||||
self.custom_input.setValidator(QtGui.QDoubleValidator())
|
||||
|
||||
self.comment_textfield = QtWidgets.QPlainTextEdit(self.header_widget)
|
||||
self.comment_textfield.setReadOnly(True)
|
||||
@ -41,6 +49,8 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
def __call__(self, fname, *args, **kwargs):
|
||||
self.reader = AsciiReader(fname)
|
||||
|
||||
self.check_filename(self.regex_input.text())
|
||||
|
||||
if self.skip:
|
||||
self.accept()
|
||||
return
|
||||
@ -52,6 +62,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
self.log_checkBox.setChecked(False)
|
||||
|
||||
self.set_gui()
|
||||
self.set_column_names(1)
|
||||
|
||||
self.skippy_checkbox.blockSignals(True)
|
||||
self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
||||
@ -63,12 +74,14 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
for text in self.reader.header:
|
||||
self.comment_textfield.appendPlainText(text)
|
||||
|
||||
tmp = self.line_spinBox.value()
|
||||
if self.reader.header:
|
||||
self.line_spinBox.setMaximum(len(self.reader.header))
|
||||
else:
|
||||
self.line_spinBox.setValue(0)
|
||||
self.line_spinBox.setEnabled(False)
|
||||
self.show_preview(10)
|
||||
self.line_spinBox.setValue(tmp)
|
||||
|
||||
if self.reader.delays is not None:
|
||||
set_string = ''.join(str(d) + '\n' for d in self.reader.delays)
|
||||
@ -98,15 +111,19 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_preview_spinBox_valueChanged')
|
||||
def show_preview(self, line_no: int):
|
||||
preview, width = self.reader.make_preview(line_no)
|
||||
preview, width, comments = self.reader.make_preview(line_no)
|
||||
self.ascii_table.setRowCount(min(line_no, len(preview)))
|
||||
self.ascii_table.setColumnCount(width)
|
||||
self.ascii_table.setColumnCount(width + 1)
|
||||
|
||||
for i, line in enumerate(preview):
|
||||
comment_line = comments[i]
|
||||
for j, field in enumerate(line):
|
||||
it = QtWidgets.QTableWidgetItem(field)
|
||||
self.ascii_table.setItem(i, j, it)
|
||||
|
||||
it = QtWidgets.QTableWidgetItem(comment_line)
|
||||
self.ascii_table.setItem(i, len(line), it)
|
||||
|
||||
self.ascii_table.resizeColumnsToContents()
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_column_checkBox_stateChanged')
|
||||
@ -134,7 +151,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
staggered_range = 0
|
||||
except ValueError:
|
||||
_ = QtWidgets.QMessageBox.information(self, 'No delays',
|
||||
'Delays cannot be calculated: Not enough or wrong arguments.')
|
||||
'Delays cannot be calculated: Not enough or wrong arguments.')
|
||||
return
|
||||
|
||||
self.reader.calc_delays(start, stop, num_delays, log=self.log_checkBox.isChecked(),
|
||||
@ -165,10 +182,13 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
|
||||
def apply(self):
|
||||
# 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
|
||||
print(x)
|
||||
except ValueError:
|
||||
x = self.x_lineedit.text()
|
||||
if x:
|
||||
try:
|
||||
x = int(x)-1
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
x = None
|
||||
|
||||
try:
|
||||
@ -194,12 +214,17 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
col_header = [col_header[i] for i in range(len(col_header)) if i in y]
|
||||
|
||||
try:
|
||||
ret_dic = self.reader.export(x=x, y=y, yerr=y_err,
|
||||
mode=self.buttonGroup.checkedButton().text(),
|
||||
col_names=col_header)
|
||||
ret_dic = self.reader.export(
|
||||
x=x,
|
||||
y=y,
|
||||
yerr=y_err,
|
||||
mode=self.buttonGroup.checkedButton().text(),
|
||||
col_names=col_header,
|
||||
num_value=self.get_numerical_value(),
|
||||
)
|
||||
self.data_read.emit(ret_dic)
|
||||
|
||||
except Exception as e:
|
||||
except ImportError as e:
|
||||
_ = QtWidgets.QMessageBox.information(self, 'Reading failed',
|
||||
f'Import data failed with {e.args}')
|
||||
|
||||
@ -211,5 +236,45 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_skippy_checkbox_stateChanged')
|
||||
def skip_next_dial(self, _: int):
|
||||
print('skippy das buschkänguru', _)
|
||||
self.skip = self.skippy_checkbox.isChecked()
|
||||
|
||||
@QtCore.pyqtSlot(str, name='on_regex_input_textChanged')
|
||||
def check_filename(self, pattern: str = NUMBER_RE.pattern):
|
||||
if self.reader is None:
|
||||
return
|
||||
|
||||
try:
|
||||
pattern = re.compile(pattern)
|
||||
self.regex_input.setStyleSheet('color: rgb(0, 0, 0)')
|
||||
self._matches = [m for m in pattern.finditer(str(self.reader.fname.stem))]
|
||||
except re.error:
|
||||
self._matches = []
|
||||
|
||||
if self._matches:
|
||||
self.re_match_index.blockSignals(True)
|
||||
self.re_match_index.setMaximum(len(self._matches))
|
||||
self.re_match_index.blockSignals(False)
|
||||
else:
|
||||
self.regex_input.setStyleSheet('color: rgb(255, 0, 0)')
|
||||
|
||||
self.show_match(self.re_match_index.value())
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_re_match_index_valueChanged')
|
||||
def show_match(self, idx: int = 0):
|
||||
fname = str(self.reader.fname.stem)
|
||||
|
||||
if self._matches:
|
||||
m = self._matches[idx-1]
|
||||
self.label_8.setText(f'{fname[:m.start()]}<b>{fname[m.start():m.end()]}</b>{fname[m.end():]}')
|
||||
else:
|
||||
self.label_8.setText(fname)
|
||||
|
||||
def get_numerical_value(self):
|
||||
val = 0
|
||||
if self.re_button.isChecked() and self._matches:
|
||||
m = self._matches[self.re_match_index.value()-1]
|
||||
val = float(NUMBER_RE.search(m.group()).group().replace('p', '.'))
|
||||
elif self.custom_button.isChecked():
|
||||
val = float(self.custom_input.text())
|
||||
|
||||
return val
|
||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||
import numpy as np
|
||||
from pyqtgraph import PlotDataItem
|
||||
|
||||
from nmreval.data.points import Points
|
||||
from nmreval.data import DSC
|
||||
from nmreval.io.dsc import Cyclohexane, DSCCalibrator, DSCSample
|
||||
|
||||
from ..Qt import QtWidgets, QtCore
|
||||
@ -83,7 +83,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
if opts[0] == 'i':
|
||||
item.setFlags(QtCore.Qt.NoItemFlags)
|
||||
item.setText(f'{opts[1]:.2f} K for {opts[1] / 60:.0f} min')
|
||||
item.setText(f'{opts[1]:.2f} K for {opts[2] / 60:.0f} min')
|
||||
else:
|
||||
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')
|
||||
@ -97,7 +97,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
if empty:
|
||||
self.empty = self.calibrator.set_measurement(empty, mode='empty')
|
||||
self.empty_label.setText(str(self.empty.fname.name))
|
||||
self.empty_label.setText('~/' + str(self.empty.fname.relative_to(Path.home())))
|
||||
|
||||
self.update_plots()
|
||||
|
||||
@ -158,20 +158,28 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.sample_idx = None
|
||||
self.clear_plots()
|
||||
|
||||
def get_data(self, ):
|
||||
def get_data(self):
|
||||
if self.sample_idx is None:
|
||||
return
|
||||
|
||||
rate = self.current_run[0]
|
||||
slope_type = {self.none_radioButton: None,
|
||||
self.isotherm_radioButton: 'iso',
|
||||
self.slope_radioButton: 'curve'}[self.buttonGroup.checkedButton()]
|
||||
slope_type = {
|
||||
self.none_radioButton: None,
|
||||
self.isotherm_radioButton: 'iso',
|
||||
self.slope_radioButton: 'curve',
|
||||
}[self.buttonGroup.checkedButton()]
|
||||
|
||||
limit = None
|
||||
if slope_type == 'curve':
|
||||
try:
|
||||
limit = float(self.limit1_lineedit.text())*60, float(self.limit2_lineedit.text())*60
|
||||
except ValueError:
|
||||
limit = None
|
||||
|
||||
try:
|
||||
raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx,
|
||||
slope=slope_type)
|
||||
raw_sample, drift_value, sample_data, empty_data, slope = self.calibrator.get_data(self.sample_idx, slope=slope_type, limits=limit)
|
||||
except ValueError as e:
|
||||
_msg = QtWidgets.QMessageBox.warning(self, 'No rate found', e.args[0])
|
||||
_msg = QtWidgets.QMessageBox.warning(self, f'Data collection with error', e.args[0])
|
||||
return
|
||||
|
||||
self.calibrator.ref_list = []
|
||||
@ -194,8 +202,14 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonGroup_buttonClicked')
|
||||
@QtCore.pyqtSlot(int, name='on_cp_checkBox_stateChanged')
|
||||
@QtCore.pyqtSlot(str, name='on_limit1_lineedit_textChanged')
|
||||
@QtCore.pyqtSlot(str, name='on_limit2_lineedit_textChanged')
|
||||
def update_plots(self, _=None):
|
||||
sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions = self.get_data()
|
||||
res = self.get_data()
|
||||
if res is None:
|
||||
return
|
||||
|
||||
sample_data, raw_sample, empty_data, drift_value, slope, calib_x, calib_y, regions = res
|
||||
|
||||
self.raw_sample.setData(x=raw_sample[0], y=raw_sample[1])
|
||||
self.drift_sample.setData(x=drift_value[0], y=drift_value[1])
|
||||
@ -236,10 +250,11 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
return
|
||||
|
||||
rate, mode = self.current_run
|
||||
new_val = Points(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})')
|
||||
new_val = DSC(sample_data[0], sample_data[1], value=rate, name=f'{self.fname.stem} {rate} ({mode})')
|
||||
|
||||
if filesave:
|
||||
new_val.savetxt(self.fname.with_name(f'{self.fname.stem} {rate}K-min {mode}.dat'.replace(' ', '_')))
|
||||
close_after = False
|
||||
else:
|
||||
self.data_read.emit([new_val])
|
||||
|
||||
@ -250,7 +265,7 @@ class QDSCReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
def button_clicked(self, bttn: QtWidgets.QAbstractButton):
|
||||
bttn_value = self.buttonBox.standardButton(bttn)
|
||||
if bttn_value in (self.buttonBox.Ok, self.buttonBox.Apply, self.buttonBox.Save):
|
||||
self.export_data(filesave=bttn_value==self.buttonBox.Save, close_after=bttn_value==self.buttonBox.Ok)
|
||||
self.export_data(filesave=bttn_value == self.buttonBox.Save, close_after=bttn_value == self.buttonBox.Ok)
|
||||
else:
|
||||
super().close()
|
||||
|
||||
|
@ -55,9 +55,9 @@ class GraceExporter:
|
||||
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))
|
||||
c_num = max(colors.keys())+1
|
||||
colors[c_num] = (f'color{c_num}', sc)
|
||||
new_colors.append((c_num, f'color{c_num}', sc))
|
||||
|
||||
new_s.set_symbol(**{'symbol': item['symbol'].value, 'size': item['symbolsize'] / 10., 'color': c_num,
|
||||
'fill color': c_num, 'fill pattern': 1})
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
|
||||
from nmreval.io.fcbatchreader import FCReader
|
||||
@ -31,6 +33,10 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog):
|
||||
|
||||
return super().eventFilter(src, evt)
|
||||
|
||||
def add_graphs(self, graphs: list[tuple[str, str]]):
|
||||
for gid, graph_name in graphs:
|
||||
self.graph_comboBox.addItem(graph_name, gid)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_region_checkBox_stateChanged')
|
||||
def use_region(self, state: int):
|
||||
self.start_lineedit.setEnabled(state == QtCore.Qt.Checked)
|
||||
@ -43,17 +49,17 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog):
|
||||
infiles, _ = QtWidgets.QFileDialog.getOpenFileNames(caption='Select HDF files',
|
||||
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)
|
||||
infiles = [infiles] if infiles else infiles
|
||||
|
||||
if infiles:
|
||||
self.listWidget.addItem(infiles)
|
||||
self.label.setText(str(pathlib.Path(infiles).parent))
|
||||
if infiles:
|
||||
self.listWidget.addItems(infiles)
|
||||
self.path = pathlib.Path(infiles[-1]).parent
|
||||
self.label.setText(str(self.path))
|
||||
|
||||
@QtCore.pyqtSlot(name='on_savebutton_clicked')
|
||||
def save_path(self):
|
||||
@ -72,7 +78,7 @@ class QFCReader(QtWidgets.QDialog, Ui_FCEval_dialog):
|
||||
self.close()
|
||||
|
||||
def read(self, items):
|
||||
region = (None, None)
|
||||
region = None
|
||||
if self.region_box.isChecked():
|
||||
start = None
|
||||
if self.start_lineedit.text():
|
||||
|
@ -4,6 +4,7 @@ from nmreval.lib.lines import LineStyle
|
||||
from nmreval.lib.symbols import SymbolStyle
|
||||
from nmreval.data.points import Points
|
||||
from nmreval.io.graceeditor import GraceEditor
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..Qt import QtCore, QtWidgets, QtGui
|
||||
from .._py.gracereader import Ui_Dialog
|
||||
@ -55,8 +56,12 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
if ds is None:
|
||||
continue
|
||||
|
||||
item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {gset.get_property("legend")}, '
|
||||
legend = gset.get_property('legend')
|
||||
legend_str = convert(legend, old='agr', new='str') if legend is not None else ''
|
||||
|
||||
item_2 = QtWidgets.QTreeWidgetItem([f'Set {gset.idx} (Label: {legend_str}, '
|
||||
f'shape: {ds.shape})'])
|
||||
|
||||
item_2.setCheckState(0, QtCore.Qt.Checked)
|
||||
item_2.setData(0, QtCore.Qt.UserRole, (graphs.idx, gset.idx))
|
||||
item.addChild(item_2)
|
||||
@ -89,8 +94,12 @@ class QGraceReader(QtWidgets.QDialog, Ui_Dialog):
|
||||
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('"', '')
|
||||
label = self._reader.get_property(*key, 'legend')
|
||||
if label is None:
|
||||
label = ''
|
||||
else:
|
||||
label = label.replace('"', '')
|
||||
label = convert(label, old='agr', new='str')
|
||||
sd = s.data
|
||||
sd = np.atleast_2d(sd)
|
||||
if s.type == 'xydy':
|
||||
|
@ -1,80 +1 @@
|
||||
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
|
||||
|
||||
if icon_name != 'logo':
|
||||
dirname = f'resources.icons.{icon_type}_light'
|
||||
else:
|
||||
dirname = 'resources.icons'
|
||||
|
||||
if HAS_IMPORTLIB_RESOURCE:
|
||||
with path(dirname, icon_name+'.png') as imgpath:
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
|
||||
return icon
|
||||
else:
|
||||
imgpath = resource_filename(dirname, icon_name+'.png')
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(imgpath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
|
||||
return icon
|
||||
from .enums import Relations
|
||||
|
@ -237,7 +237,7 @@ class CodeEditor(QtWidgets.QPlainTextEdit):
|
||||
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,
|
||||
painter.drawText(0, int(top), self.current_linenumber.width() - 3, height,
|
||||
QtCore.Qt.AlignRight, number)
|
||||
|
||||
block = block.next()
|
||||
|
@ -30,7 +30,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
rect = options.rect
|
||||
rect.adjust(5, 0, -5, 0)
|
||||
mid = (rect.bottom()+rect.top()) / 2
|
||||
mid = int((rect.bottom()+rect.top()) / 2)
|
||||
painter.drawLine(rect.left(), mid, rect.right(), mid)
|
||||
painter.restore()
|
||||
|
||||
@ -42,7 +42,7 @@ class PropertyDelegate(QtWidgets.QStyledItemDelegate):
|
||||
painter.setPen(pen)
|
||||
|
||||
pm = make_symbol_pixmap(r)
|
||||
painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, (options.rect.height()-pm.height())/2), pm)
|
||||
painter.drawPixmap(options.rect.topLeft()+QtCore.QPoint(3, int((options.rect.height()-pm.height())/2)), pm)
|
||||
|
||||
style = QtWidgets.QApplication.style()
|
||||
text_rect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None)
|
||||
@ -171,7 +171,7 @@ class LineStyleEditor(QtWidgets.QComboBox):
|
||||
rect = painter.style().subControlRect(QtWidgets.QStyle.CC_ComboBox,
|
||||
opt, QtWidgets.QStyle.SC_ComboBoxEditField, None)
|
||||
rect.adjust(+10, 0, -10, 0)
|
||||
mid = (rect.bottom() + rect.top()) / 2
|
||||
mid = int((rect.bottom() + rect.top()) / 2)
|
||||
painter.drawLine(rect.left(), mid, rect.right(), mid)
|
||||
painter.end()
|
||||
else:
|
||||
@ -193,7 +193,7 @@ class LineStyleDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
rect = option.rect
|
||||
rect.adjust(+10, 0, -10, 0)
|
||||
mid = (rect.bottom()+rect.top()) / 2
|
||||
mid = int((rect.bottom()+rect.top()) / 2)
|
||||
painter.drawLine(rect.left(), mid, rect.right(), mid)
|
||||
else:
|
||||
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
|
||||
|
8
src/gui_qt/lib/enums.py
Normal file
8
src/gui_qt/lib/enums.py
Normal file
@ -0,0 +1,8 @@
|
||||
from enum import IntEnum, auto
|
||||
|
||||
|
||||
class Relations(IntEnum):
|
||||
isFitOf = auto()
|
||||
hasFit = auto()
|
||||
isFitPartOf = auto()
|
||||
hasFitPart = auto()
|
@ -1,3 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
from numpy import inf
|
||||
|
||||
from nmreval.utils.text import convert
|
||||
@ -50,19 +52,24 @@ class QDelayWidget(QtWidgets.QWidget):
|
||||
|
||||
class LineEdit(QtWidgets.QLineEdit):
|
||||
values_requested = QtCore.pyqtSignal()
|
||||
replace_single_values = 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 sets')
|
||||
request_action = menu.addAction('Use numeric value of sets')
|
||||
set_value_action = menu.addAction('Replace single set values')
|
||||
|
||||
action = menu.exec(evt.globalPos())
|
||||
|
||||
if action == request_action:
|
||||
self.values_requested.emit()
|
||||
|
||||
elif action == set_value_action:
|
||||
self.replace_single_values.emit()
|
||||
|
||||
|
||||
class LineEditPost(QtWidgets.QLineEdit):
|
||||
values_requested = QtCore.pyqtSignal()
|
||||
@ -404,3 +411,21 @@ class ElideComboBox(QtWidgets.QComboBox):
|
||||
|
||||
opt.currentText = painter.fontMetrics().elidedText(opt.currentText, QtCore.Qt.ElideRight, rect.width())
|
||||
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, opt)
|
||||
|
||||
|
||||
class CheckCombobox(QtWidgets.QComboBox):
|
||||
|
||||
def addItem(self, text: str, userData: Any=None) -> None:
|
||||
super().addItem(text, userData=userData)
|
||||
|
||||
item = self.model().item(self.count()-1)
|
||||
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
|
||||
def addItems(self, text):
|
||||
for text_i in text:
|
||||
self.addItem(text_i)
|
||||
|
||||
def isChecked(self, idx: int) -> bool:
|
||||
return bool(self.model().item(idx).checkState())
|
||||
|
||||
|
53
src/gui_qt/lib/graph_items.py
Normal file
53
src/gui_qt/lib/graph_items.py
Normal file
@ -0,0 +1,53 @@
|
||||
from numpy import log10, arange, floor, ceil
|
||||
from pyqtgraph import PlotWidget, PlotItem
|
||||
|
||||
|
||||
__all__ = ['NMRPlotWidget', 'logTickValues']
|
||||
|
||||
|
||||
class NMRPlotWidget(PlotWidget):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
for orient in ['top', 'bottom', 'left', 'right']:
|
||||
# BAD HACK!!! but seems to work, see function for explanation
|
||||
self.plotItem.getAxis(orient).logTickValues = logTickValues
|
||||
|
||||
|
||||
class NMRPlotItem(PlotItem):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
for orient in ['top', 'bottom', 'left', 'right']:
|
||||
self.plotItem.getAxis(orient).logTickValues = logTickValues
|
||||
|
||||
|
||||
def logTickValues(minVal, maxVal, size, stdTicks):
|
||||
# TODO FIND A BETTER SOLUTION!!!
|
||||
# Sometimes minVal and maxVal are not log-scaled values and the loop from v1 to v2 is humongous,
|
||||
# The minor list then fills the RAM completely and freezes everything
|
||||
# Until there is a better solution, we overwrite this function for every AxesItem
|
||||
# and do not draw minor ticks at all if there are too many
|
||||
|
||||
# start with the tick spacing given by tickValues().
|
||||
# Any level whose spacing is < 1 needs to be converted to log scale
|
||||
ticks = []
|
||||
for (spacing, t) in stdTicks:
|
||||
if spacing >= 1.0:
|
||||
ticks.append((spacing, t))
|
||||
|
||||
if len(ticks) < 3:
|
||||
v1 = int(floor(minVal))
|
||||
v2 = int(ceil(maxVal))
|
||||
# major = list(range(v1+1, v2))
|
||||
minor = []
|
||||
|
||||
if v2 - v1 < 400:
|
||||
for v in range(v1, v2):
|
||||
minor.extend(v + log10(arange(1, 10)))
|
||||
minor = [x for x in minor if minVal < x < maxVal]
|
||||
ticks.append((None, minor))
|
||||
return ticks
|
||||
|
66
src/gui_qt/lib/iconloading.py
Normal file
66
src/gui_qt/lib/iconloading.py
Normal file
@ -0,0 +1,66 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
HAS_IMPORTLIB_RESOURCE = False
|
||||
from pkg_resources import resource_filename
|
||||
else:
|
||||
HAS_IMPORTLIB_RESOURCE = True
|
||||
from importlib.resources import path
|
||||
|
||||
from ..Qt import QtGui, QtWidgets
|
||||
|
||||
|
||||
def make_action_icons(widget):
|
||||
global HAS_IMPORTLIB_RESOURCE
|
||||
icon_type = QtWidgets.QApplication.instance().theme
|
||||
from json import loads
|
||||
|
||||
if HAS_IMPORTLIB_RESOURCE:
|
||||
with path('resources.icons', 'icons.json') as fp:
|
||||
with fp.open('r') as f:
|
||||
icon_list = loads(f.read())
|
||||
|
||||
for ac, img in icon_list[widget.objectName()].items():
|
||||
dirname = 'resources.icons.%s_light' % icon_type
|
||||
with path(dirname, img+'.png') as imgpath:
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
getattr(widget, ac).setIcon(icon)
|
||||
|
||||
else:
|
||||
with open(resource_filename('resources.icons', 'icons.json'), 'r') as f:
|
||||
icon_list = loads(f.read())
|
||||
|
||||
for ac, img in icon_list[widget.objectName()].items():
|
||||
dirname = 'resources.icons.%s_light' % icon_type
|
||||
imgpath = resource_filename(dirname, img+'.png')
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
getattr(widget, ac).setIcon(icon)
|
||||
|
||||
|
||||
def get_icon(icon_name):
|
||||
try:
|
||||
icon_type = QtWidgets.QApplication.instance().theme
|
||||
except AttributeError:
|
||||
icon_type = 'normal'
|
||||
|
||||
global HAS_IMPORTLIB_RESOURCE
|
||||
|
||||
if icon_name != 'logo':
|
||||
dirname = f'resources.icons.{icon_type}_light'
|
||||
else:
|
||||
dirname = 'resources.icons'
|
||||
|
||||
if HAS_IMPORTLIB_RESOURCE:
|
||||
with path(dirname, icon_name+'.png') as imgpath:
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(str(imgpath)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
|
||||
return icon
|
||||
else:
|
||||
imgpath = resource_filename(dirname, icon_name+'.png')
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(imgpath), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
|
||||
return icon
|
20
src/gui_qt/lib/listwidget.py
Normal file
20
src/gui_qt/lib/listwidget.py
Normal file
@ -0,0 +1,20 @@
|
||||
from ..Qt import QtWidgets, QtGui, QtCore
|
||||
|
||||
|
||||
class QListWidgetSelect(QtWidgets.QListWidget):
|
||||
"""
|
||||
Extension of QListWidget to change the check state of all selected QListWidgetItems with space key
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
def keyPressEvent(self, evt: QtGui.QKeyEvent):
|
||||
if evt.key() == QtCore.Qt.Key.Key_Space:
|
||||
for idx in self.selectedIndexes():
|
||||
item = self.itemFromIndex(idx)
|
||||
cs = item.checkState()
|
||||
item.setCheckState(QtCore.Qt.CheckState.Unchecked if cs == QtCore.Qt.CheckState.Checked
|
||||
else QtCore.Qt.CheckState.Checked)
|
||||
else:
|
||||
super().keyPressEvent(evt)
|
49
src/gui_qt/lib/mdiarea.py
Normal file
49
src/gui_qt/lib/mdiarea.py
Normal file
@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..Qt import QtWidgets, QtCore
|
||||
|
||||
from nmreval.lib.logger import logger
|
||||
from ..graphs.graphwindow import QGraphWindow
|
||||
|
||||
|
||||
class MdiAreaTile(QtWidgets.QMdiArea):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
def tileSubWindowsVertically(self):
|
||||
window_list = self.subWindowList()
|
||||
rect = QtCore.QRect(0, 0, self.width(), int(self.height() / len(window_list)))
|
||||
pos = QtCore.QPoint(0, 0)
|
||||
|
||||
for win in window_list:
|
||||
win.setGeometry(rect)
|
||||
win.move(pos)
|
||||
|
||||
pos.setY(pos.y() + win.height())
|
||||
|
||||
def tileSubWindowsHorizontally(self):
|
||||
window_list = self.subWindowList()
|
||||
rect = QtCore.QRect(0, 0, int(self.width() / len(window_list)), self.height())
|
||||
pos = QtCore.QPoint(0, 0)
|
||||
|
||||
for win in window_list:
|
||||
win.setGeometry(rect)
|
||||
win.move(pos)
|
||||
|
||||
pos.setX(pos.x() + win.width())
|
||||
|
||||
def addSubWindow(self, widget: QtWidgets.QWidget, flags: QtCore.Qt.WindowFlags = QtCore.Qt.WindowFlags()) -> QtWidgets.QMdiSubWindow | None:
|
||||
subwindow = super().addSubWindow(widget)
|
||||
subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandMove, True)
|
||||
subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandResize, True)
|
||||
subwindow.setMinimumHeight(240)
|
||||
subwindow.setMinimumWidth(360)
|
||||
|
||||
return subwindow
|
||||
|
||||
def setActiveSubWidget(self, key: str):
|
||||
for win in self.subWindowList():
|
||||
wdgt = win.widget()
|
||||
if isinstance(wdgt, QGraphWindow) and wdgt.id == key:
|
||||
self.setActiveSubWindow(win)
|
||||
break
|
@ -21,36 +21,53 @@ class Namespace:
|
||||
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(
|
||||
{'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'))
|
||||
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(
|
||||
{'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))'))
|
||||
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)
|
||||
@ -104,9 +121,18 @@ class Namespace:
|
||||
graph = namedtuple('graphs', ['s'])
|
||||
sets = namedtuple('sets', ['x', 'y', 'y_err', 'value', 'fit'], defaults=(None,))
|
||||
|
||||
gamma = {}
|
||||
|
||||
gs = re.compile(r'g\[(\d+)].s\[(\d+)].(x|y(?:_err)*|value|fit)')
|
||||
gamma_re = re.compile(r'gamma\["(\w+)"\]')
|
||||
|
||||
for k, v in self.namespace.items():
|
||||
m = gamma_re.match(k)
|
||||
if m:
|
||||
gamma[m.group(1)] = v[0]
|
||||
|
||||
continue
|
||||
|
||||
m = gs.match(k)
|
||||
if m:
|
||||
if 'g' not in ret_dic:
|
||||
@ -118,7 +144,7 @@ class Namespace:
|
||||
|
||||
gg = ret_dic['g'][int(g_num)]
|
||||
if int(s_num) not in gg.s:
|
||||
gg.s[int(s_num)] = sets('', '', '', '')
|
||||
gg.s[int(s_num)] = sets('', '', '', '', {})
|
||||
|
||||
ss = gg.s[int(s_num)]
|
||||
if pos == 'fit':
|
||||
@ -130,8 +156,11 @@ class Namespace:
|
||||
else:
|
||||
ret_dic['g'][int(g_num)].s[int(s_num)] = ss._replace(**{pos: v[0]})
|
||||
|
||||
else:
|
||||
ret_dic[k] = v[0]
|
||||
continue
|
||||
|
||||
ret_dic[k] = v[0]
|
||||
|
||||
ret_dic['gamma'] = gamma
|
||||
|
||||
return ret_dic
|
||||
|
||||
|
@ -174,6 +174,7 @@ class PlotItem(PlotDataItem):
|
||||
pen = self.opts['pen']
|
||||
if isinstance(pen, tuple):
|
||||
self.opts['linecolor'] = pen
|
||||
self.opts['pen'] = mkPen(color=pen)
|
||||
else:
|
||||
c = pen.color()
|
||||
self.opts['linecolor'] = c.red(), c.green(), c.blue()
|
||||
@ -279,7 +280,7 @@ class PlotItem(PlotDataItem):
|
||||
else:
|
||||
self.scatter.hide()
|
||||
|
||||
def set_symbol(self, symbol=None, size=None, color=None):
|
||||
def set_symbol(self, *, symbol=None, size=None, color=None):
|
||||
if symbol is not None:
|
||||
if isinstance(symbol, int):
|
||||
self.setSymbol(SymbolStyle(symbol).to_str())
|
||||
@ -313,14 +314,13 @@ class PlotItem(PlotDataItem):
|
||||
self.opts['pen'] = pen
|
||||
self.updateItems()
|
||||
|
||||
def set_line(self, style=None, width=None, color=None):
|
||||
def set_line(self, *, style=None, width=None, color=None):
|
||||
pen = self.opts['pen']
|
||||
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
|
||||
@ -375,7 +375,6 @@ class PlotItem(PlotDataItem):
|
||||
class RegionItem(LinearRegionItem):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.mode = kwargs.pop('mode', 'half')
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.logmode = False
|
||||
@ -383,6 +382,10 @@ class RegionItem(LinearRegionItem):
|
||||
if not hasattr(self, '_bounds') and hasattr(self, '_boundingRectCache'):
|
||||
self._bounds = self._boundingRectCache
|
||||
|
||||
for l in self.lines:
|
||||
# higher z for borderlines improves chances that you can move it when multiple regions overlap
|
||||
l.setZValue(self.zValue() + 1)
|
||||
|
||||
def setLogMode(self, xmode, _):
|
||||
if self.logmode == xmode:
|
||||
return
|
||||
@ -418,6 +421,15 @@ class RegionItem(LinearRegionItem):
|
||||
else:
|
||||
return region
|
||||
|
||||
def setRegion(self, region, use_log=False):
|
||||
if self.logmode and use_log:
|
||||
region = np.log10(region[0]), np.log10(region[1])
|
||||
|
||||
if not np.all(np.isfinite(region)):
|
||||
raise ValueError(f'Invalid region limits ({region[0]}, {region[1]})')
|
||||
else:
|
||||
super().setRegion(region)
|
||||
|
||||
def boundingRect(self):
|
||||
# overwrite to draw correct rect in logmode
|
||||
|
||||
@ -460,11 +472,18 @@ class LegendItemBlock(LegendItem):
|
||||
def mouseDragEvent(self, ev):
|
||||
if ev.button() == QtCore.Qt.LeftButton:
|
||||
ev.accept()
|
||||
|
||||
dpos = ev.pos() - ev.lastPos()
|
||||
|
||||
upper_left = self.pos()
|
||||
lower_right = self.pos()
|
||||
lower_right.setX(lower_right.x() + self.width())
|
||||
lower_right.setY(lower_right.y() + self.height())
|
||||
|
||||
vb_rect = self.parentItem().rect()
|
||||
pos = self.pos()
|
||||
# upper left corner and a point a little more to the bottom right must be inside
|
||||
if vb_rect.contains(pos+dpos) and vb_rect.contains(pos+dpos+QtCore.QPointF(20., 20.)):
|
||||
self.autoAnchor(pos + dpos)
|
||||
|
||||
# upper left and lower right corner must be inside viewbox
|
||||
if vb_rect.contains(upper_left + dpos) and vb_rect.contains(lower_right + dpos):
|
||||
self.autoAnchor(upper_left + dpos)
|
||||
else:
|
||||
self.autoAnchor(pos)
|
||||
self.autoAnchor(upper_left)
|
||||
|
@ -1,110 +0,0 @@
|
||||
import os.path
|
||||
import json
|
||||
import urllib.request
|
||||
import webbrowser
|
||||
import random
|
||||
|
||||
from ..Qt import QtGui, QtCore, QtWidgets
|
||||
from .._py.pokemon import Ui_Dialog
|
||||
|
||||
random.seed()
|
||||
|
||||
|
||||
class QPokemon(QtWidgets.QDialog, Ui_Dialog):
|
||||
def __init__(self, number=None, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self._js = json.load(open(os.path.join(path_to_module, 'utils', 'pokemon.json'), 'r'), encoding='UTF-8')
|
||||
self._id = 0
|
||||
|
||||
if number is not None and number in range(1, len(self._js)+1):
|
||||
poke_nr = f'{number:03d}'
|
||||
self._id = number
|
||||
else:
|
||||
poke_nr = f'{random.randint(1, len(self._js)):03d}'
|
||||
self._id = int(poke_nr)
|
||||
|
||||
self._pokemon = None
|
||||
self.show_pokemon(poke_nr)
|
||||
self.label_15.linkActivated.connect(lambda x: webbrowser.open(x))
|
||||
|
||||
self.buttonBox.clicked.connect(self.randomize)
|
||||
self.next_button.clicked.connect(self.show_next)
|
||||
self.prev_button.clicked.connect(self.show_prev)
|
||||
|
||||
def show_pokemon(self, nr):
|
||||
self._pokemon = self._js[nr]
|
||||
self.setWindowTitle('Pokémon: ' + self._pokemon['Deutsch'])
|
||||
|
||||
for i in range(self.tabWidget.count(), -1, -1):
|
||||
print('i', self.tabWidget.count(), i)
|
||||
try:
|
||||
self.tabWidget.widget(i).deleteLater()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for n, img in self._pokemon['Bilder']:
|
||||
w = QtWidgets.QWidget()
|
||||
vl = QtWidgets.QVBoxLayout()
|
||||
l = QtWidgets.QLabel(self)
|
||||
l.setAlignment(QtCore.Qt.AlignHCenter)
|
||||
pixmap = QtGui.QPixmap()
|
||||
|
||||
try:
|
||||
pixmap.loadFromData(urllib.request.urlopen(img, timeout=0.5).read())
|
||||
except IOError:
|
||||
l.setText(n)
|
||||
else:
|
||||
sc_pixmap = pixmap.scaled(256, 256, QtCore.Qt.KeepAspectRatio)
|
||||
l.setPixmap(sc_pixmap)
|
||||
|
||||
vl.addWidget(l)
|
||||
w.setLayout(vl)
|
||||
self.tabWidget.addTab(w, n)
|
||||
|
||||
if len(self._pokemon['Bilder']) <= 1:
|
||||
self.tabWidget.tabBar().setVisible(False)
|
||||
else:
|
||||
self.tabWidget.tabBar().setVisible(True)
|
||||
self.tabWidget.adjustSize()
|
||||
|
||||
self.name.clear()
|
||||
keys = ['National-Dex', 'Kategorie', 'Typ', 'Größe', 'Gewicht', 'Farbe', 'Link']
|
||||
label_list = [self.pokedex_nr, self.category, self.poketype, self.weight, self.height, self.color, self.info]
|
||||
for (k, label) in zip(keys, label_list):
|
||||
v = self._pokemon[k]
|
||||
if isinstance(v, list):
|
||||
v = os.path.join('', *v)
|
||||
|
||||
if k == 'Link':
|
||||
v = '<a href={}>{}</a>'.format(v, v)
|
||||
|
||||
label.setText(v)
|
||||
|
||||
for k in ['Deutsch', 'Japanisch', 'Englisch', 'Französisch']:
|
||||
v = self._pokemon[k]
|
||||
self.name.addItem(k + ': ' + v)
|
||||
|
||||
self.adjustSize()
|
||||
|
||||
def randomize(self, idd):
|
||||
if idd.text() == 'Retry':
|
||||
new_number = f'{random.randint(1, len(self._js)):03d}'
|
||||
self._id = int(new_number)
|
||||
self.show_pokemon(new_number)
|
||||
else:
|
||||
self.close()
|
||||
|
||||
def show_next(self):
|
||||
new_number = self._id + 1
|
||||
if new_number > len(self._js):
|
||||
new_number = 1
|
||||
self._id = new_number
|
||||
self.show_pokemon(f'{new_number:03d}')
|
||||
|
||||
def show_prev(self):
|
||||
new_number = self._id - 1
|
||||
if new_number == 0:
|
||||
new_number = len(self._js)
|
||||
self._id = new_number
|
||||
self.show_pokemon(f'{new_number:03d}')
|
100
src/gui_qt/lib/starter.py
Normal file
100
src/gui_qt/lib/starter.py
Normal file
@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from shutil import copyfile
|
||||
from pathlib import Path
|
||||
import os
|
||||
from configparser import ConfigParser
|
||||
from importlib.resources import path as resource_path
|
||||
|
||||
from nmreval.configs import config_paths
|
||||
from nmreval.lib.logger import logger
|
||||
|
||||
|
||||
def make_starter(app_file: str | None):
|
||||
if app_file is not None:
|
||||
make_starter_appimage(Path(app_file))
|
||||
else:
|
||||
make_starter_src()
|
||||
|
||||
|
||||
def make_starter_appimage(app_file: Path):
|
||||
new_path = Path.home() / '.local' / 'bin'
|
||||
|
||||
if not new_path.exists():
|
||||
new_path.mkdir(parents=True)
|
||||
|
||||
new_path /= app_file.name
|
||||
|
||||
if app_file != new_path:
|
||||
app_file.rename(new_path)
|
||||
|
||||
create_desktop_file(new_path)
|
||||
|
||||
|
||||
def make_starter_src():
|
||||
home = Path.home()
|
||||
p = Path.home()
|
||||
for p in Path(__file__).parents:
|
||||
if p.stem == 'src':
|
||||
break
|
||||
elif p == home:
|
||||
break
|
||||
|
||||
success = p != Path.home()
|
||||
if success:
|
||||
bin_path = p.with_name('bin') / 'evaluate.py'
|
||||
success = bin_path.exists()
|
||||
|
||||
if not success:
|
||||
logger.warning('Location of evaluate.py could not be determined')
|
||||
return False
|
||||
|
||||
create_desktop_file(bin_path)
|
||||
|
||||
|
||||
def create_desktop_file(new_path: Path):
|
||||
logo_path = config_paths() / 'logo.png'
|
||||
if not logo_path.exists():
|
||||
with resource_path('resources', 'logo.png') as fp:
|
||||
copyfile(fp, logo_path)
|
||||
desktop_entry = f"""\
|
||||
[Desktop Entry]
|
||||
Name=NMReval
|
||||
Comment=Best program ever (maybe)
|
||||
Exec={new_path}
|
||||
Icon={logo_path}
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Categories=Science;NumericalAnalysis;Physics;DataVisualization;Other;
|
||||
NoDisplay=false
|
||||
"""
|
||||
file_name = 'pkm.vogel.nmreval.desktop'
|
||||
with Path('~/.local/share/applications/', file_name).expanduser().open('w') as f:
|
||||
f.write(desktop_entry)
|
||||
|
||||
desktop_dir = get_xkg_user_dirs('desktop')
|
||||
if desktop_dir is not None:
|
||||
desk_file = Path(desktop_dir, file_name)
|
||||
with desk_file.open('w') as f:
|
||||
f.write(desktop_entry)
|
||||
|
||||
desk_file.chmod(0o755)
|
||||
|
||||
|
||||
def get_xkg_user_dirs(dir_type: str) -> str | None:
|
||||
xdg_conf_home = os.getenv('XDG_CONFIG_HOME') or str(Path.home() / '.config')
|
||||
|
||||
with Path(xdg_conf_home, 'user-dirs.dirs').open('r') as f:
|
||||
conf_string = '[XDG_USER_DIRS]\n' + f.read()
|
||||
conf_string = re.sub(r'\$HOME', str(Path.home()), conf_string)
|
||||
conf_string = re.sub('"', '', conf_string)
|
||||
|
||||
config = ConfigParser()
|
||||
config.read_string(conf_string)
|
||||
|
||||
return config['XDG_USER_DIRS'].get(f'xdg_{dir_type}_dir')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_starter()
|
@ -6,7 +6,7 @@ import numpy as np
|
||||
from ..Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
||||
__all__ = ['Game']
|
||||
__all__ = ['Game', 'QMines']
|
||||
|
||||
|
||||
class Game(QtWidgets.QDialog):
|
||||
|
@ -1,4 +1,4 @@
|
||||
from . import HAS_IMPORTLIB_RESOURCE
|
||||
from .iconloading import HAS_IMPORTLIB_RESOURCE
|
||||
from ..Qt import QtGui, QtWidgets, QtCore
|
||||
|
||||
|
||||
|
@ -28,4 +28,17 @@ class TreeWidget(QtWidgets.QTreeWidget):
|
||||
continue
|
||||
it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
|
||||
else:
|
||||
super().keyPressEvent(evt)
|
||||
super().keyPressEvent(evt)
|
||||
|
||||
|
||||
class TableWidget(QtWidgets.QTableWidget):
|
||||
def keyPressEvent(self, evt: QtGui.QKeyEvent):
|
||||
if evt.key() == QtCore.Qt.Key.Key_Space:
|
||||
for idx in self.selectedIndexes():
|
||||
item = self.itemFromIndex(idx)
|
||||
cs = item.checkState()
|
||||
item.setCheckState(QtCore.Qt.CheckState.Unchecked if cs == QtCore.Qt.CheckState.Checked
|
||||
else QtCore.Qt.CheckState.Checked)
|
||||
else:
|
||||
super().keyPressEvent(evt)
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
||||
from numpy import argsort
|
||||
|
||||
from . import Relations
|
||||
from ..Qt import QtWidgets, QtCore
|
||||
from ..data.container import FitContainer
|
||||
from ..graphs.graphwindow import QGraphWindow
|
||||
@ -84,6 +87,21 @@ class ShiftCommand(QtWidgets.QUndoCommand):
|
||||
self.__data.apply('ls', self.__args)
|
||||
|
||||
|
||||
class EditCommand(QtWidgets.QUndoCommand):
|
||||
def __init__(self, data, *args):
|
||||
super().__init__('Edit signal')
|
||||
|
||||
self.__data = data
|
||||
self.__arguments = args
|
||||
self.__original = copy.deepcopy(self.__data.data)
|
||||
|
||||
def undo(self):
|
||||
self.__data.data = copy.deepcopy(self.__original)
|
||||
|
||||
def redo(self):
|
||||
self.__data.edit_signal(*self.__arguments)
|
||||
|
||||
|
||||
class NormCommand(QtWidgets.QUndoCommand):
|
||||
def __init__(self, data, mode):
|
||||
super().__init__('Normalize')
|
||||
@ -216,33 +234,69 @@ class DeleteGraphCommand(QtWidgets.QUndoCommand):
|
||||
|
||||
|
||||
class DeleteCommand(QtWidgets.QUndoCommand):
|
||||
def __init__(self, container, key, signal1, signal2):
|
||||
def __init__(self, container: dict, keys: list[str], graphs: dict, graphid: str,
|
||||
signal1: QtCore.pyqtSignal, signal2: QtCore.pyqtSignal):
|
||||
super().__init__('Delete data')
|
||||
|
||||
self.__container = container
|
||||
self.__value = self.__container[key]
|
||||
self.__key = key
|
||||
self.__graph_container = graphs
|
||||
self.__graph_key = graphid
|
||||
self.__value = {}
|
||||
for k in keys:
|
||||
self.__value[k] = self.__container[k]
|
||||
self.__keys = tuple(keys)
|
||||
self.__signal_add = signal1
|
||||
self.__signal_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]
|
||||
# stop graph from rescaling and updating legend
|
||||
self.__graph_container[self.__graph_key].block(True)
|
||||
|
||||
self.__signal_remove.emit(list(self.__keys[::-1]))
|
||||
for sid in self.__keys[::-1]:
|
||||
val = self.__value[sid]
|
||||
|
||||
if isinstance(val, FitContainer):
|
||||
try:
|
||||
self.__container[val.fitted_key]._fits.remove(sid)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for (flag1, flag2) in ((Relations.isFitPartOf, Relations.hasFitPart), (Relations.hasFitPart, Relations.isFitPartOf)):
|
||||
for related_item in val.get_relation(flag1):
|
||||
try:
|
||||
self.__container[related_item].remove_relation(flag2, sid)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
del self.__container[sid]
|
||||
|
||||
self.__graph_container[self.__graph_key].block(False)
|
||||
|
||||
def undo(self):
|
||||
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
|
||||
# stop graph from rescaling and updating legend
|
||||
self.__graph_container[self.__graph_key].block(True)
|
||||
|
||||
self.__signal_add.emit([self.__key], self.__value.graph)
|
||||
for sid in self.__keys:
|
||||
val = self.__value[sid]
|
||||
self.__container[sid] = val
|
||||
|
||||
if isinstance(val, FitContainer):
|
||||
try:
|
||||
self.__container[val.fitted_key]._fits.append(sid)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for (flag1, flag2) in (('isFitPartOf', 'hasFitPartOf'), ('hasFitPartOf', 'isFitPartOf')):
|
||||
for related_item in val.get_relation(flag1):
|
||||
try:
|
||||
self.__container[related_item].add_relation(flag2, sid)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.__signal_add.emit(list(self.__keys), self.__graph_key)
|
||||
|
||||
self.__graph_container[self.__graph_key].block(False)
|
||||
|
||||
|
||||
class EvalCommand(QtWidgets.QUndoCommand):
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
import os
|
||||
import urllib.request
|
||||
from os import getenv, stat
|
||||
from os.path import exists
|
||||
import hashlib
|
||||
@ -9,8 +9,7 @@ import subprocess
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from urllib.error import HTTPError
|
||||
from numpy import linspace
|
||||
from scipy.interpolate import interp1d
|
||||
|
||||
@ -59,13 +58,13 @@ class RdBuCMap:
|
||||
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)))
|
||||
col = QtGui.QColor.fromRgb(*(int(self.spline[i](val)) for i in range(3)))
|
||||
|
||||
return col
|
||||
|
||||
|
||||
class UpdateDialog(QtWidgets.QDialog):
|
||||
startDownload = QtCore.pyqtSignal(list)
|
||||
startDownload = QtCore.pyqtSignal(tuple)
|
||||
|
||||
def __init__(self, filename: str = None, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
@ -75,7 +74,6 @@ class UpdateDialog(QtWidgets.QDialog):
|
||||
filename = getenv('APPIMAGE')
|
||||
self._appfile = filename
|
||||
|
||||
self.success = False
|
||||
self.updater = Updater()
|
||||
|
||||
self.thread = QtCore.QThread(self)
|
||||
@ -116,28 +114,28 @@ class UpdateDialog(QtWidgets.QDialog):
|
||||
# Download zsync file of latest Appimage, look for SHA-1 hash and compare with hash of AppImage
|
||||
is_updateble, m_time_file, m_time_zsync = self.updater.get_update_information(filename)
|
||||
|
||||
if m_time_zsync is None:
|
||||
label_text = '<p>Retrieval of version information failed.</p>' \
|
||||
'<p>Please try later (or complain to people that it does not work).</p>'
|
||||
label_text = ''
|
||||
|
||||
if is_updateble is None:
|
||||
label_text += '<p>Could not determine if this version is newer, please update manually (if necessary).</p>'
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Close
|
||||
elif is_updateble:
|
||||
label_text += '<p>Newer version available. Press Ok to download new version, Cancel to ignore.</p>'
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||
else:
|
||||
label_text = f'<p>Date of most recent AppImage: {m_time_zsync.strftime("%d %B %Y %H:%M")}</p>'
|
||||
label_text += '<p>Version may be already up-to-date.</p>'
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Close
|
||||
|
||||
if m_time_file is None:
|
||||
label_text += 'No AppImage file found, press Ok to download latest version.'
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Close
|
||||
else:
|
||||
label_text += f'<p>Date of used AppImage: {m_time_file.strftime("%d %B %Y %H:%M")}</p>'
|
||||
if m_time_zsync is None:
|
||||
label_text += '<p>Creation date of remote version is unknown.</p>'
|
||||
else:
|
||||
label_text += f'<p>Date of most recent AppImage: {m_time_zsync.strftime("%d %B %Y %H:%M")}</p>'
|
||||
|
||||
if is_updateble is None:
|
||||
self.status.setText('Could not determine if this version is newer, please update manually (if necessary).')
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Close
|
||||
elif is_updateble:
|
||||
self.status.setText(f'<p>Newer version available. Press Ok to download new version, Cancel to ignore.')
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel
|
||||
else:
|
||||
self.status.setText(f'Version may be already up-to-date.')
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Close
|
||||
if m_time_file is None:
|
||||
label_text += 'No AppImage file found, press Ok to download latest version.'
|
||||
dialog_bttns = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Close
|
||||
else:
|
||||
label_text += f'<p>Date of used AppImage: {m_time_file.strftime("%d %B %Y %H:%M")}</p>'
|
||||
|
||||
self.label.setText(label_text)
|
||||
self.dialog_button.setStandardButtons(dialog_bttns)
|
||||
@ -145,24 +143,20 @@ class UpdateDialog(QtWidgets.QDialog):
|
||||
@QtCore.pyqtSlot()
|
||||
def update_appimage(self):
|
||||
if self._appfile is None:
|
||||
args = [self.updater.zsync_url]
|
||||
args = (self.updater.zsync_url,)
|
||||
else:
|
||||
# this breaks the download for some reason
|
||||
args = ['-i', self._appfile, self.updater.zsync_url]
|
||||
|
||||
args = [self.updater.zsync_url]
|
||||
args = (self.updater.zsync_url, self._appfile)
|
||||
|
||||
self.dialog_button.setEnabled(False)
|
||||
|
||||
self.startDownload.emit(args)
|
||||
self.status.show()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def finish_update(self, retcode: int):
|
||||
# print('finished with', retcode)
|
||||
self.success = retcode == 0
|
||||
@QtCore.pyqtSlot(int, str)
|
||||
def finish_update(self, retcode: int, file_loc: str):
|
||||
if retcode == 0:
|
||||
self.status.setText('Download complete.')
|
||||
self.status.setText(f'Download complete.New AppImage lies in <p><em>{file_loc}</em>.</p>')
|
||||
else:
|
||||
self.status.setText(f'Download failed :( with return code {retcode}.')
|
||||
self.dialog_button.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
|
||||
@ -172,78 +166,85 @@ class UpdateDialog(QtWidgets.QDialog):
|
||||
self.thread.quit()
|
||||
self.thread.wait()
|
||||
|
||||
if self.success:
|
||||
appname = self.updater.get_zsync()[2]
|
||||
if self._appfile is not None:
|
||||
appimage_path = appname
|
||||
old_version = Path(self._appfile).rename(self._appfile+'.old')
|
||||
appimage_path = Path(appimage_path).replace(self._appfile)
|
||||
else:
|
||||
appimage_path = Path().cwd() / appname
|
||||
# rename to version-agnostic name
|
||||
appimage_path = appimage_path.rename('NMReval.AppImage')
|
||||
appimage_path.chmod(appimage_path.stat().st_mode | 73) # 73 = 0o111 = a+x
|
||||
|
||||
_ = QtWidgets.QMessageBox.information(self, 'Complete',
|
||||
f'New AppImage available at<br>{appimage_path}')
|
||||
|
||||
super().closeEvent(evt)
|
||||
|
||||
|
||||
class Downloader(QtCore.QObject):
|
||||
started = QtCore.pyqtSignal()
|
||||
finished = QtCore.pyqtSignal(int)
|
||||
finished = QtCore.pyqtSignal(int, str)
|
||||
progressChanged = QtCore.pyqtSignal(str)
|
||||
|
||||
@QtCore.pyqtSlot(list)
|
||||
def run_download(self, args: list[str]):
|
||||
logger.info(f'Download with args {args}')
|
||||
process = subprocess.Popen(['zsync'] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True)
|
||||
while True:
|
||||
nextline = process.stdout.readline().strip()
|
||||
if nextline:
|
||||
self.progressChanged.emit(nextline)
|
||||
@QtCore.pyqtSlot(tuple)
|
||||
def run_download(self, args: tuple[str]):
|
||||
status = 0
|
||||
appimage_location = args[0][:-6]
|
||||
logger.info(f'Download {appimage_location}')
|
||||
if len(args) == 2:
|
||||
new_file = Path(args[1])
|
||||
else:
|
||||
new_file = Path.home() / 'Downloads' / 'NMReval-latest-x86_64.AppImage'
|
||||
|
||||
# line = process.stderr.readline().strip()
|
||||
if new_file.exists():
|
||||
os.rename(new_file, new_file.with_suffix('.AppImage.old'))
|
||||
|
||||
if process.poll() is not None:
|
||||
break
|
||||
try:
|
||||
with urllib.request.urlopen(appimage_location) as response:
|
||||
with new_file.open('wb') as f:
|
||||
f.write(response.read())
|
||||
|
||||
self.finished.emit(process.returncode)
|
||||
new_file.chmod(0o755)
|
||||
except HTTPError as e:
|
||||
logger.exception(f'Download failed with {e}')
|
||||
status = 3
|
||||
except Exception as e:
|
||||
logger.exception(f'Download failed with {e.args}')
|
||||
status = 1
|
||||
|
||||
if status != 0:
|
||||
logger.warning('Download failed, restore previous AppImage')
|
||||
try:
|
||||
os.rename(new_file.with_suffix('.AppImage.old'), new_file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
# zsync does not support https
|
||||
|
||||
self.finished.emit(status, str(new_file))
|
||||
|
||||
|
||||
class Updater:
|
||||
host = 'mirror.infra.pkm'
|
||||
bucket = 'nmreval'
|
||||
host = 'gitea.pkm.physik.tu-darmstadt.de/api/packages/IPKM/generic/NMReval/latest/'
|
||||
version = 'NMReval-latest-x86_64'
|
||||
|
||||
@property
|
||||
def zsync_url(self):
|
||||
return f'http://{Updater.host}/{Updater.bucket}/{Updater.version}.AppImage.zsync'
|
||||
return f'https://{Updater.host}/{Updater.version}.AppImage.zsync'
|
||||
|
||||
@staticmethod
|
||||
@lru_cache(3)
|
||||
def get_zsync():
|
||||
url_zsync = f'http://{Updater.host}/{Updater.bucket}/{Updater.version}.AppImage.zsync'
|
||||
url_zsync = f'https://{Updater.host}/{Updater.version}.AppImage.zsync'
|
||||
m_time_zsync = None
|
||||
checksum_zsync = None
|
||||
zsync_file = None
|
||||
filename = None
|
||||
try:
|
||||
response = requests.get(url_zsync)
|
||||
if response.status_code == requests.codes['\o/']:
|
||||
zsync_file = response.content
|
||||
else:
|
||||
logger.error(f'Request for zsync returned code {response.statuse_code}')
|
||||
except Exception:
|
||||
logger.exception('Download of zsync failed')
|
||||
with urllib.request.urlopen(url_zsync) as response:
|
||||
zsync_file = response.read()
|
||||
except HTTPError as e:
|
||||
logger.error(f'Request for zsync returned code {e}')
|
||||
except Exception as e:
|
||||
logger.exception(f'Download of zsync failed with exception {e.args}')
|
||||
|
||||
if zsync_file is not None:
|
||||
for line in zsync_file.split(b'\n'):
|
||||
try:
|
||||
kw, val = line.split(b': ')
|
||||
time_string = str(val, encoding='utf-8')
|
||||
time_format = '%a, %d %b %Y %H:%M:%S %z'
|
||||
if kw == b'MTime':
|
||||
m_time_zsync = datetime.strptime(str(val, encoding='utf-8'), '%a, %d %b %Y %H:%M:%S %z').astimezone(None)
|
||||
try:
|
||||
m_time_zsync = datetime.strptime(time_string, time_format).astimezone(None)
|
||||
except ValueError:
|
||||
logger.warning(f'zsync time "{time_string}" does not match "{time_format}"')
|
||||
elif kw == b'SHA-1':
|
||||
checksum_zsync = str(val, encoding='utf-8')
|
||||
elif kw == b'Filename':
|
||||
@ -267,6 +268,8 @@ class Updater:
|
||||
m_time_file = datetime.fromtimestamp(stat_mtime).replace(microsecond=0)
|
||||
with open(filename, 'rb') as f:
|
||||
checksum_file = hashlib.sha1(f.read()).hexdigest()
|
||||
if checksum_file is None:
|
||||
logger.warning('No checksum for AppImage calculated')
|
||||
|
||||
return m_time_file, checksum_file
|
||||
|
||||
@ -275,30 +278,10 @@ class Updater:
|
||||
m_time_zsync, checksum_zsync, appname = Updater.get_zsync()
|
||||
m_time_file, checksum_file = Updater.get_appimage_info(filename)
|
||||
|
||||
logger.info(f'zsync information {m_time_zsync}, {checksum_zsync}, {appname}')
|
||||
logger.info(f'file information {m_time_file}, {checksum_zsync}')
|
||||
logger.debug(f'zsync information {m_time_zsync}, {checksum_zsync}, {appname}')
|
||||
logger.debug(f'file information {m_time_file}, {checksum_file}')
|
||||
|
||||
if not ((checksum_file is not None) and (checksum_zsync is not None)):
|
||||
return None, m_time_file, m_time_zsync
|
||||
else:
|
||||
return checksum_file != checksum_zsync, m_time_file, m_time_zsync
|
||||
|
||||
|
||||
def open_bug_report():
|
||||
form_entries = {
|
||||
'description': 'Please state the nature of the medical emergency.',
|
||||
'title': 'Everything is awesome?',
|
||||
'assign[0]': 'dominik',
|
||||
'subscribers[0]': 'dominik',
|
||||
'tag': 'nmreval',
|
||||
'priority': 'normal',
|
||||
'status': 'open',
|
||||
}
|
||||
full_url = 'https://chaos3.fkp.physik.tu-darmstadt.de/maniphest/task/edit/?'
|
||||
|
||||
for k, v in form_entries.items():
|
||||
full_url += f'{k}={v}&'
|
||||
full_url.replace(' ', '+')
|
||||
|
||||
import webbrowser
|
||||
webbrowser.open(full_url)
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
@ -9,18 +9,23 @@ from numpy import geomspace, linspace
|
||||
from pyqtgraph import ViewBox
|
||||
|
||||
from nmreval.configs import *
|
||||
from nmreval.lib.logger import logger
|
||||
from nmreval.io.sessionwriter import NMRWriter
|
||||
|
||||
from .management import UpperManagement
|
||||
from ..Qt import QtGui, QtPrintSupport
|
||||
from ..data.shift_graphs import QShift
|
||||
from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog
|
||||
from ..data.signaledit import QPreviewDialog, QBaselineDialog
|
||||
from ..dsc.glass_dialog import TgCalculator
|
||||
from ..fit.result import FitExtension, QFitResult
|
||||
from ..graphs.graphwindow import QGraphWindow
|
||||
from ..graphs.movedialog import QMover
|
||||
from ..io.fcbatchreader import QFCReader
|
||||
from ..io.filedialog import *
|
||||
from ..lib import get_icon, make_action_icons
|
||||
from ..lib.iconloading import make_action_icons, get_icon
|
||||
from ..lib.pg_objects import RegionItem
|
||||
from ..lib.starter import make_starter
|
||||
from ..math.binning import BinningWindow
|
||||
from ..math.evaluation import QEvalDialog
|
||||
from ..math.interpol import InterpolDialog
|
||||
from ..math.mean_dialog import QMeanTimes
|
||||
@ -28,7 +33,7 @@ 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 ..lib.utils import UpdateDialog, open_bug_report, Updater
|
||||
from ..lib.utils import UpdateDialog, Updater
|
||||
|
||||
|
||||
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@ -53,6 +58,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.fitpreview = []
|
||||
self._fit_plot_id = None
|
||||
self.savefitdialog = None
|
||||
self._tg_dialog = None
|
||||
self.fitresult_dialog = None
|
||||
self.eval = None
|
||||
self.editor = None
|
||||
|
||||
@ -63,7 +70,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self._block_window_change = False
|
||||
|
||||
self.fname = None
|
||||
self.tim = QtCore.QTimer()
|
||||
|
||||
self.settings = QtCore.QSettings('NMREVal', 'settings')
|
||||
self._init_gui()
|
||||
@ -73,6 +79,18 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
if Updater.get_update_information(os.getenv('APPIMAGE'))[0]:
|
||||
self.look_for_update()
|
||||
|
||||
self.check_for_backup()
|
||||
|
||||
self.__timer = QtCore.QTimer()
|
||||
self.__backup_path = config_paths() / f'{datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr'
|
||||
self.__timer.start(3*60*1000) # every three minutes
|
||||
self.__timer.timeout.connect(self._autosave)
|
||||
|
||||
self.fit_timer = QtCore.QTimer()
|
||||
self.fit_timer.setInterval(500)
|
||||
self.fit_timer.timeout.connect(
|
||||
lambda: self.status.setText(f'Fit running... ({self.management.fitter.step} evaluations)'))
|
||||
|
||||
def _init_gui(self):
|
||||
self.setupUi(self)
|
||||
make_action_icons(self)
|
||||
@ -103,7 +121,10 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
self.mousepos = QtWidgets.QLabel('')
|
||||
self.status = QtWidgets.QLabel('')
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.statusBar.addWidget(self.status)
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.statusBar.addWidget(self.mousepos)
|
||||
|
||||
self.fitregion = RegionItem()
|
||||
@ -114,7 +135,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
self.datawidget.management = self.management
|
||||
self.integralwidget.management = self.management
|
||||
self.drawingswidget.graphs = self.management.graphs
|
||||
# self.drawingswidget.graphs = self.management.graphs
|
||||
|
||||
self.ac_group = QtWidgets.QActionGroup(self)
|
||||
self.ac_group.addAction(self.action_lm_fit)
|
||||
@ -139,20 +160,23 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
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.action_edit.triggered.connect(self.do_preview)
|
||||
self.actionPick_position.triggered.connect(lambda: self._show_tab('pick'))
|
||||
self.actionIntegration.triggered.connect(lambda: self._show_tab('integrate'))
|
||||
self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit'))
|
||||
self.action_draw_object.triggered.connect(lambda: self._show_tab('drawing'))
|
||||
|
||||
self.action_new_set.triggered.connect(self.management.create_empty)
|
||||
|
||||
self.actionDelete_window.triggered.connect(self.management.delete_sets)
|
||||
self.actionCascade_windows.triggered.connect(self.area.cascadeSubWindows)
|
||||
self.actionTile.triggered.connect(self.area.tileSubWindows)
|
||||
self.actionTileHorizontal.triggered.connect(self.area.tileSubWindowsHorizontally)
|
||||
self.actionTileVertical.triggered.connect(self.area.tileSubWindowsVertically)
|
||||
|
||||
self.datawidget.keyChanged.connect(self.management.change_keys)
|
||||
self.datawidget.tree.deleteItem.connect(self.management.delete_sets)
|
||||
self.datawidget.tree.moveItem.connect(self.management.move_sets)
|
||||
@ -166,7 +190,8 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.datawidget.tree.saveFits.connect(self.save_fit_parameter)
|
||||
self.datawidget.tree.extendFits.connect(self.extend_fit)
|
||||
|
||||
self.management.newData.connect(self.show_new_data)
|
||||
self.management.newData[list, str].connect(self.show_new_data)
|
||||
self.management.newData[list, str, bool].connect(self.show_new_data)
|
||||
self.management.newGraph.connect(self.new_graph)
|
||||
self.management.dataChanged.connect(self.update_data)
|
||||
self.management.deleteData.connect(self.delete_data)
|
||||
@ -179,7 +204,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
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.fit_dialog.abortFit.connect(lambda: self.management.stopFit.emit())
|
||||
|
||||
self.movedialog.moveData.connect(self.move_sets)
|
||||
self.movedialog.copyData.connect(self.management.copy_sets)
|
||||
@ -189,7 +214,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
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.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)
|
||||
@ -217,7 +242,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
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())
|
||||
self.actionConcatenate_sets.triggered.connect(lambda: self.management.cat())
|
||||
|
||||
self.management.graphs.valueChanged.connect(self.update_graph_list)
|
||||
|
||||
@QtCore.pyqtSlot(name='on_action_open_triggered')
|
||||
def open(self):
|
||||
@ -242,6 +269,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@QtCore.pyqtSlot(name='on_actionOpen_FC_triggered')
|
||||
def read_fc(self):
|
||||
reader = QFCReader(path=self.path, parent=self)
|
||||
reader.add_graphs(self.management.graphs.list())
|
||||
reader.data_read.connect(self.management.add_new_data)
|
||||
reader.exec()
|
||||
|
||||
@ -262,10 +290,11 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
selected_filter = save_dialog.selectedNameFilter()
|
||||
|
||||
if savefile is not None:
|
||||
self.path = savefile.parent
|
||||
use_underscore = save_dialog.checkBox.isChecked()
|
||||
self.management.save(savefile, selected_filter, strip_spaces=use_underscore)
|
||||
|
||||
param_outfile = re.sub('[_\s-]?<label>[_\s-]?', '', savefile.stem)
|
||||
param_outfile = re.sub(r'[_\s-]?<label>[_\s-]?', '', savefile.stem)
|
||||
|
||||
bad_character = r'/*<>\|:"'
|
||||
for c in bad_character:
|
||||
@ -307,10 +336,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
return w.id
|
||||
|
||||
@QtCore.pyqtSlot(list, str)
|
||||
def show_new_data(self, sets: list, graph: str):
|
||||
@QtCore.pyqtSlot(list, str, bool)
|
||||
def show_new_data(self, sets: list, graph: str, skip_change: bool = False):
|
||||
if len(sets) == 0:
|
||||
return
|
||||
|
||||
prev_graph = ''
|
||||
if skip_change:
|
||||
prev_graph = self.management.current_graph
|
||||
|
||||
if graph == '':
|
||||
graph = self.new_graph()
|
||||
|
||||
@ -319,22 +353,21 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
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.add_item(new_item.id, new_item.name, new_item.value, graph, update=False)
|
||||
self.datawidget.blockSignals(False)
|
||||
self.datawidget.tree.update_indexes()
|
||||
|
||||
if graph == self.fit_dialog.connected_figure:
|
||||
self.fit_dialog.load(self.management.graphs.active(graph))
|
||||
# if graph == self.fit_dialog.connected_figure:
|
||||
# self.fit_dialog.load(self.management.graphs.active(graph))
|
||||
if skip_change:
|
||||
self.area.setActiveSubWidget(prev_graph)
|
||||
|
||||
if self.valuewidget.isVisible():
|
||||
self.valuewidget(self.management.graphs.tree())
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionDelete_window_triggered')
|
||||
def delete_windows(self):
|
||||
self.management.delete_sets()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def remove_graph(self, gid: str):
|
||||
self.datawidget.remove_item(gid)
|
||||
self.datawidget.remove_item([gid])
|
||||
val_figure = self.valuewidget.connected_figure
|
||||
self.valuewidget.remove_graph()
|
||||
|
||||
@ -347,10 +380,15 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
if wdgt == self.current_graph_widget:
|
||||
if self.ptsselectwidget.connected_figure == gid:
|
||||
self.ptsselectwidget.connected_figure = None
|
||||
for line in self.ptsselectwidget.pts_lines:
|
||||
self.current_graph_widget.remove_external(line)
|
||||
|
||||
self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget))
|
||||
|
||||
if self.t1tauwidget.connected_figure == gid:
|
||||
self.t1tauwidget.connected_figure = None
|
||||
self.current_graph_widget.add_external(self.t1tauwidget.min_pos)
|
||||
self.current_graph_widget.add_external(self.t1tauwidget.parabola)
|
||||
self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget))
|
||||
|
||||
if self.fit_dialog.connected_figure == gid:
|
||||
@ -359,12 +397,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.current_graph_widget.remove_external(item)
|
||||
|
||||
if val_figure == gid:
|
||||
self.current_graph_widget.remove_external(self.valuewidget.selection_real)
|
||||
self.current_graph_widget.remove_external(self.valuewidget.selection_imag)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
|
||||
self.current_graph_widget.enable_picking(False)
|
||||
|
||||
self.current_graph_widget = None
|
||||
self.management.current_graph = ''
|
||||
self.management.current_graph = None
|
||||
self.current_plotitem = None
|
||||
|
||||
wdgt.setParent(None)
|
||||
@ -375,7 +415,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
break
|
||||
|
||||
|
||||
if w is not None:
|
||||
self.area.removeSubWindow(w)
|
||||
w.close()
|
||||
@ -388,6 +427,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
if self.area.subWindowList():
|
||||
self.area.activateNextSubWindow()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def update_graph_list(self):
|
||||
graph_list = self.management.graphs.list()
|
||||
self.t1tauwidget.set_graphs(graph_list)
|
||||
self.ptsselectwidget.set_graphs(graph_list)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def set_graph(self, key: str):
|
||||
w = self.management.graphs[key]
|
||||
@ -405,7 +450,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.datawidget.blockSignals(False)
|
||||
|
||||
w.mousePositionChanged.connect(self.mousemoved)
|
||||
w.aboutToClose.connect(self.delete_windows)
|
||||
w.aboutToClose.connect(self.management.delete_sets)
|
||||
w.positionClicked.connect(self.point_selected)
|
||||
w.show()
|
||||
|
||||
@ -416,42 +461,36 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@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 wd is None:
|
||||
return
|
||||
|
||||
if self.ptsselectwidget.isVisible():
|
||||
self._select_ptswidget(False, False, False)
|
||||
if self.current_graph_widget is not None:
|
||||
self.current_graph_widget.closable = True
|
||||
|
||||
if self.fit_dialog.isVisible():
|
||||
self._select_fitwidget(False, False)
|
||||
|
||||
self.current_graph_widget = wd.widget()
|
||||
self.management.current_graph = wd.widget().id
|
||||
self.current_plotitem = self.current_graph_widget.graphic
|
||||
|
||||
self.change_mouse_mode(self.actionMouse_behaviour.isChecked())
|
||||
|
||||
pick = False
|
||||
block = False
|
||||
if self.ptsselectwidget.isVisible():
|
||||
pick, block = self._select_ptswidget(True, pick, block)
|
||||
self._select_ptswidget(False, False, False)
|
||||
|
||||
if self.fit_dialog.isVisible():
|
||||
block = self._select_fitwidget(True, block)
|
||||
self._select_fitwidget(False, False)
|
||||
|
||||
self._set_pick_block(pick, block)
|
||||
self.current_graph_widget = wd.widget()
|
||||
self.management.current_graph = wd.widget().id
|
||||
self.current_plotitem = self.current_graph_widget.graphic
|
||||
|
||||
self.datawidget.tree.blockSignals(True)
|
||||
self.datawidget.tree.highlight(self.management.current_graph)
|
||||
self.datawidget.tree.blockSignals(False)
|
||||
self.change_mouse_mode(self.actionMouse_behaviour.isChecked())
|
||||
|
||||
@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()
|
||||
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_actionChange_datatypes_triggered')
|
||||
def type_change_dialog(self):
|
||||
@ -475,7 +514,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
'signal': (self.editsignalwidget, 'Signals'),
|
||||
'pick': (self.ptsselectwidget, 'Pick points'),
|
||||
'fit': (self.fit_dialog, 'Fit'),
|
||||
'drawing': (self.drawingswidget, 'Draw'),
|
||||
'integrate': (self.integralwidget, 'Integrate'),
|
||||
}[mode]
|
||||
|
||||
@ -511,7 +549,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
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._select_drawingswidget(widget == self.drawingswidget)
|
||||
pick_required = self._select_integralwidget(widget == self.integralwidget, pick_required, block_window)
|
||||
|
||||
self._set_pick_block(pick_required, block_window)
|
||||
@ -544,8 +581,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.management.graphs[current_graph].add_external(self.valuewidget.selection_imag)
|
||||
else:
|
||||
if self.valuewidget.connected_figure is not None:
|
||||
self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_real)
|
||||
self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_imag)
|
||||
conn_fig = self.valuewidget.connected_figure
|
||||
self.management.graphs[conn_fig].remove_external(self.valuewidget.selection_real)
|
||||
self.management.graphs[conn_fig].remove_external(self.valuewidget.selection_imag)
|
||||
|
||||
def _select_integralwidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
|
||||
if self.current_graph_widget is None:
|
||||
@ -599,17 +637,10 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
return pick_required, block_window
|
||||
|
||||
def _select_drawingswidget(self, onoff):
|
||||
if onoff:
|
||||
if self.drawingswidget.graphs is None:
|
||||
self.drawingswidget.graphs = self.management.graphs
|
||||
self.drawingswidget.update_tree()
|
||||
else:
|
||||
self.drawingswidget.clear()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def get_data(self, key: str):
|
||||
self.sender().set_data(self.management[key])
|
||||
if hasattr(self.sender(), 'set_data'):
|
||||
self.sender().set_data(self.management[key])
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionCalculateT1_triggered')
|
||||
def show_t1calc_dialog(self):
|
||||
@ -683,6 +714,14 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
self.eval.exec()
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionBinning_triggered')
|
||||
def open_binning(self):
|
||||
dialog = BinningWindow(self)
|
||||
res = dialog.exec()
|
||||
if res:
|
||||
digits = float(dialog.spinbox.text())
|
||||
self.management.binning(digits)
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionDerivation_triggered')
|
||||
# @QtCore.pyqtSlot(name='on_actionIntegration_triggered')
|
||||
@QtCore.pyqtSlot(name='on_actionFilon_triggered')
|
||||
@ -719,10 +758,11 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
self.datawidget.set_name(sid, self.management[sid].name)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def delete_data(self, sid):
|
||||
if self.valuewidget.shown_set == sid:
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
@QtCore.pyqtSlot(list)
|
||||
def delete_data(self, sid: list[str]):
|
||||
for key in sid:
|
||||
if self.valuewidget.shown_set == key:
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
|
||||
self.datawidget.remove_item(sid)
|
||||
|
||||
@ -744,29 +784,22 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
editor.finished.connect(self.management.apply)
|
||||
editor.exec()
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def do_preview(self, mode):
|
||||
@QtCore.pyqtSlot()
|
||||
def do_preview(self):
|
||||
dialog = QPreviewDialog(self)
|
||||
|
||||
if mode == 'ap':
|
||||
dialog = QApodDialog(parent=self)
|
||||
elif mode == 'ph':
|
||||
dialog = QPhasedialog(parent=self)
|
||||
else:
|
||||
raise ValueError('Unknown preview mode %s' % str(mode))
|
||||
|
||||
dialog.setRange(*self.current_graph_widget.ranges, self.current_graph_widget.log)
|
||||
success = True
|
||||
|
||||
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 data_mode in ('fid', 'spectrum'):
|
||||
success = dialog.add_data(self.management[sid].data)
|
||||
|
||||
if tobeadded:
|
||||
dialog.add_data(*self.management.get_data(sid, xy_only=True))
|
||||
if not success:
|
||||
break
|
||||
|
||||
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
||||
self.management.apply(mode, dialog.get_value())
|
||||
if success and dialog.exec() == QtWidgets.QDialog.Accepted:
|
||||
self.management.edit_signals(dialog.get_value())
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered')
|
||||
def move_sets_dialog(self):
|
||||
@ -817,6 +850,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
except KeyError:
|
||||
ret_val = None
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
self.sender().receive_data(ret_val)
|
||||
|
||||
return ret_val
|
||||
@ -832,7 +866,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.t1tauwidget.t1min_picked(pos)
|
||||
|
||||
elif w == self.integralwidget:
|
||||
region, integral_plot = self.integralwidget.add(pos)
|
||||
region, integral_plot = self.integralwidget.add(pos)
|
||||
self.current_graph_widget.add_external(region)
|
||||
self.current_graph_widget.add_external(integral_plot)
|
||||
|
||||
@ -876,12 +910,18 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
self.action_odr_fit: 'odr'
|
||||
}[self.ac_group.checkedAction()]
|
||||
|
||||
self.fit_dialog.fit_button.setEnabled(False)
|
||||
self.management.start_fit(parameter, links, fit_options)
|
||||
fit_is_ready = self.management.prepare_fit(parameter, links, fit_options)
|
||||
if fit_is_ready:
|
||||
self.management.start_fit()
|
||||
self.fit_dialog.fit_button.setEnabled(False)
|
||||
self.status.setText('Fit running...'.format(self.management.fitter.step))
|
||||
self.fit_timer.start(500)
|
||||
|
||||
@QtCore.pyqtSlot(dict, int, bool)
|
||||
def show_fit_preview(self, funcs: dict, num: int, show: bool):
|
||||
if self.fit_dialog.connected_figure is None:
|
||||
if not self.fit_dialog.connected_figure:
|
||||
logger.warning(f'Fit dialog is not connected graph: Fit {self.fit_dialog.connected_figure}, '
|
||||
f'current graph: {self.management.current_graph} ({self.management.current_graph in self.management.graphs})')
|
||||
return
|
||||
|
||||
g = self.management.graphs[self.fit_dialog.connected_figure]
|
||||
@ -901,15 +941,22 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
@QtCore.pyqtSlot(list)
|
||||
def show_fit_results(self, results: list):
|
||||
self.fit_dialog.fit_button.setEnabled(True)
|
||||
self.fit_timer.stop()
|
||||
self.status.setText('')
|
||||
if results:
|
||||
res_dialog = QFitResult(results, self.management, parent=self)
|
||||
res_dialog.add_graphs(self.management.graphs.list())
|
||||
res_dialog.closed.connect(self.accepts_fit)
|
||||
res_dialog.redoFit.connect(self.management.redo_fits)
|
||||
res_dialog.show()
|
||||
if self.fitresult_dialog is None:
|
||||
self.fitresult_dialog = QFitResult(results, self.management, parent=self)
|
||||
self.fitresult_dialog.add_graphs(self.management.graphs.list())
|
||||
self.fitresult_dialog.closed.connect(self.accepts_fit)
|
||||
self.fitresult_dialog.redoFit.connect(self.management.redo_fits)
|
||||
else:
|
||||
self.fitresult_dialog(results)
|
||||
self.fitresult_dialog.add_graphs(self.management.graphs.list())
|
||||
self.fitresult_dialog.show()
|
||||
|
||||
@QtCore.pyqtSlot(dict, list, str, bool, bool, list)
|
||||
def accepts_fit(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None:
|
||||
def accepts_fit(self, res: dict, opts: list, param_graph: str,
|
||||
show_fit: bool, parts: bool, extrapolate: list) -> None:
|
||||
self.fit_dialog.set_parameter(res)
|
||||
self.management.make_fits(res, opts, param_graph, show_fit, parts, extrapolate)
|
||||
|
||||
@ -927,7 +974,6 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
def extend_fit(self, sets: list):
|
||||
w = FitExtension(self)
|
||||
res = w.exec()
|
||||
print(res)
|
||||
if res:
|
||||
p = w.values
|
||||
x = linspace(p[0], p[1], num=p[2])
|
||||
@ -1020,6 +1066,9 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
def close(self):
|
||||
write_state({'History': {'recent path': str(self.path)}})
|
||||
|
||||
# remove backup file when closing
|
||||
self.__backup_path.unlink(missing_ok=True)
|
||||
|
||||
super().close()
|
||||
|
||||
def read_state(self):
|
||||
@ -1043,3 +1092,56 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||
|
||||
QLog(parent=self).show()
|
||||
|
||||
def _autosave(self):
|
||||
# TODO better separate thread may it takes some time to save
|
||||
self.status.setText('Autosave...')
|
||||
success = NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path.with_suffix('.nmr.0'))
|
||||
if success:
|
||||
self.__backup_path.with_suffix('.nmr.0').rename(self.__backup_path)
|
||||
|
||||
self.status.setText('')
|
||||
|
||||
def check_for_backup(self):
|
||||
backups = []
|
||||
for filename in config_paths().glob('*.nmr'):
|
||||
try:
|
||||
backups.append((filename, datetime.datetime.strptime(filename.stem, "%Y-%m-%d_%H%M%S")))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if backups:
|
||||
backup_by_date = sorted(backups, key=lambda x: x[1])
|
||||
msg = QtWidgets.QMessageBox.information(
|
||||
self, 'Backup found',
|
||||
f'{len(backups)} backup files in directory {backup_by_date[-1][0].parent} found.\n\n'
|
||||
f'Latest backup date: {backup_by_date[-1][1]}\n\n'
|
||||
f'Press Ok to load, Cancel to delete backup, Close to do nothing.',
|
||||
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Close
|
||||
)
|
||||
|
||||
if msg == QtWidgets.QMessageBox.Ok:
|
||||
self.management.load_files([str(backup_by_date[-1][0])])
|
||||
backup_by_date[-1][0].unlink()
|
||||
elif msg == QtWidgets.QMessageBox.Cancel:
|
||||
backup_by_date[-1][0].unlink()
|
||||
else:
|
||||
pass
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionCreate_starter_triggered')
|
||||
def create_starter(self):
|
||||
make_starter(os.getenv('APPIMAGE'))
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionAbout_triggered')
|
||||
def show_version(self):
|
||||
from nmreval.version import __version__
|
||||
QtWidgets.QMessageBox.about(self, 'Version', f'Build date of AppImage: {__version__}')
|
||||
|
||||
@QtCore.pyqtSlot(name='on_actionTNMH_model_triggered')
|
||||
@QtCore.pyqtSlot(name='on_actionTNMH_triggered')
|
||||
def show_tg_dialog(self):
|
||||
if self._tg_dialog is None:
|
||||
self._tg_dialog = TgCalculator(self.management, parent=self)
|
||||
self._tg_dialog.newData.connect(self.management.addTg)
|
||||
else:
|
||||
self._tg_dialog()
|
||||
self._tg_dialog.show()
|
||||
|
@ -18,6 +18,7 @@ from nmreval.math.smooth import smooth
|
||||
from nmreval.nmr.relaxation import Relaxation
|
||||
|
||||
from ..Qt import QtCore, QtWidgets
|
||||
from ..lib import Relations
|
||||
from ..lib.undos import *
|
||||
from ..data.container import *
|
||||
from ..io.filereaders import QFileReader
|
||||
@ -57,11 +58,18 @@ class GraphDict(OrderedDict):
|
||||
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:
|
||||
def active(self, key: str, return_val: str = 'both'):
|
||||
if not key:
|
||||
return []
|
||||
else:
|
||||
if return_val == 'both':
|
||||
return [(self._data[i].id, self._data[i].name) for i in self[key]]
|
||||
elif return_val == 'id':
|
||||
return [self._data[i].id for i in self[key]]
|
||||
elif return_val == 'name':
|
||||
return [self._data[i].name for i in self[key]]
|
||||
else:
|
||||
raise ValueError(f'return_val got wrong value {return_val!r}')
|
||||
|
||||
def current_sets(self, key: str):
|
||||
if key:
|
||||
@ -74,8 +82,8 @@ class UpperManagement(QtCore.QObject):
|
||||
newGraph = QtCore.pyqtSignal()
|
||||
restoreGraph = QtCore.pyqtSignal(str)
|
||||
deleteGraph = QtCore.pyqtSignal(str)
|
||||
newData = QtCore.pyqtSignal(list, str)
|
||||
deleteData = QtCore.pyqtSignal(str)
|
||||
newData = QtCore.pyqtSignal([list, str], [list, str, bool])
|
||||
deleteData = QtCore.pyqtSignal(list)
|
||||
dataChanged = QtCore.pyqtSignal(str)
|
||||
fitFinished = QtCore.pyqtSignal(list)
|
||||
stopFit = QtCore.pyqtSignal()
|
||||
@ -110,7 +118,7 @@ class UpperManagement(QtCore.QObject):
|
||||
self.counter = 0
|
||||
self.data = OrderedDict()
|
||||
self.window = window
|
||||
self.current_graph = ''
|
||||
self.current_graph = None
|
||||
self.graphs = GraphDict(self.data)
|
||||
self.namespace = None
|
||||
self.undostack = QtWidgets.QUndoStack()
|
||||
@ -147,6 +155,10 @@ class UpperManagement(QtCore.QObject):
|
||||
def active_sets(self):
|
||||
return self.graphs.active(self.current_graph)
|
||||
|
||||
@property
|
||||
def active_id(self):
|
||||
return self.graphs.active(self.current_graph, return_val='id')
|
||||
|
||||
def get_attributes(self, graph_id: str, attr: str) -> dict[str, Any]:
|
||||
return {self.data[i].id: getattr(self.data[i], attr) for i in self.graphs[graph_id].sets}
|
||||
|
||||
@ -180,7 +192,10 @@ class UpperManagement(QtCore.QObject):
|
||||
graph.active = active
|
||||
graph.listWidget.blockSignals(True)
|
||||
for i, l in enumerate(g['in_legend']):
|
||||
graph.listWidget.item(i).setCheckState(l)
|
||||
try:
|
||||
graph.listWidget.item(i).setCheckState(QtCore.Qt.Checked if l else QtCore.Qt.Unchecked)
|
||||
except AttributeError:
|
||||
pass
|
||||
graph.listWidget.blockSignals(False)
|
||||
|
||||
# set unchecked in tree and hide/show in plot
|
||||
@ -231,9 +246,17 @@ class UpperManagement(QtCore.QObject):
|
||||
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)
|
||||
def plot_from_graph(self, key: list[str]):
|
||||
sort_graph = {}
|
||||
for sid in key:
|
||||
v = self.data[sid].graph
|
||||
if v not in sort_graph:
|
||||
sort_graph[v] = []
|
||||
sort_graph[v].append(sid)
|
||||
|
||||
for gid, sets in sort_graph.items():
|
||||
self.graphs[gid].remove(sets)
|
||||
|
||||
@QtCore.pyqtSlot(list, str, str)
|
||||
def move_sets(self, sets: list, dest: str, src: (str|list), pos: int = -1):
|
||||
@ -276,6 +299,7 @@ class UpperManagement(QtCore.QObject):
|
||||
|
||||
@QtCore.pyqtSlot(list)
|
||||
@QtCore.pyqtSlot(str)
|
||||
@QtCore.pyqtSlot()
|
||||
def delete_sets(self, rm_sets: list = None):
|
||||
rm_graphs = []
|
||||
|
||||
@ -284,13 +308,26 @@ class UpperManagement(QtCore.QObject):
|
||||
|
||||
self.undostack.beginMacro('Delete')
|
||||
|
||||
rm_set_by_graph = {}
|
||||
|
||||
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:
|
||||
parent_graph = self.data[k].graph
|
||||
if parent_graph not in rm_set_by_graph:
|
||||
rm_set_by_graph[parent_graph] = []
|
||||
|
||||
rm_set_by_graph[parent_graph].append(k)
|
||||
|
||||
elif k in self.graphs:
|
||||
rm_graphs.append(k)
|
||||
|
||||
else:
|
||||
logger.warning(f'delete_sets: {k} is not in data or graph found')
|
||||
|
||||
for gid, sid_list in rm_set_by_graph.items():
|
||||
cmd = DeleteCommand(self.data, sid_list, self.graphs, gid, self.newData, self.deleteData)
|
||||
self.undostack.push(cmd)
|
||||
|
||||
for k in rm_graphs:
|
||||
cmd = DeleteGraphCommand(self.graphs, k, self.restoreGraph, self.deleteGraph)
|
||||
self.undostack.push(cmd)
|
||||
@ -318,7 +355,7 @@ class UpperManagement(QtCore.QObject):
|
||||
if joined is None:
|
||||
joined = data_i.copy()
|
||||
else:
|
||||
joined.append(data_i.x, data_i.y, data_i.y_err)
|
||||
joined.append(data_i.data.x, data_i.data.y, y_err=data_i.data.y_err, mask=data_i.data.mask)
|
||||
|
||||
name_set.add(data_i.name)
|
||||
group_set.add(data_i.group)
|
||||
@ -348,12 +385,16 @@ class UpperManagement(QtCore.QObject):
|
||||
|
||||
def change_visibility(self, selected: list, deselected: list):
|
||||
"""Change status of list of ids after status change in datawidget"""
|
||||
for item_list, func in [(selected, 'show_item'), (deselected, 'hide_item')]:
|
||||
grouping = {}
|
||||
for s in item_list:
|
||||
g = self.data[s].graph
|
||||
if g not in grouping:
|
||||
grouping[g] = []
|
||||
grouping[g].append(s)
|
||||
|
||||
for s in selected:
|
||||
self.graphs[self.data[s].graph].show_item([s])
|
||||
|
||||
for d in deselected:
|
||||
self.graphs[self.data[d].graph].hide_item([d])
|
||||
for k, v in grouping.items():
|
||||
getattr(self.graphs[k], func)(v)
|
||||
|
||||
@QtCore.pyqtSlot(str, str)
|
||||
def change_keys(self, identifier: str, name: str):
|
||||
@ -377,6 +418,13 @@ class UpperManagement(QtCore.QObject):
|
||||
self.undostack.push(single_undo)
|
||||
self.undostack.endMacro()
|
||||
|
||||
def edit_signals(self: UpperManagement, args: list[tuple]) -> None:
|
||||
self.undostack.beginMacro('Edit signals')
|
||||
for sid in self.graphs[self.current_graph]:
|
||||
single_undo = EditCommand(self.data[sid], *args)
|
||||
self.undostack.push(single_undo)
|
||||
self.undostack.endMacro()
|
||||
|
||||
def cut(self):
|
||||
if self.current_graph:
|
||||
xlim, _ = self.graphs[self.current_graph].ranges
|
||||
@ -387,68 +435,105 @@ class UpperManagement(QtCore.QObject):
|
||||
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):
|
||||
def prepare_fit(self, parameter: dict, links: list, fit_options: dict) -> bool:
|
||||
if self._fit_active:
|
||||
return
|
||||
return False
|
||||
|
||||
self.__fit_options = (parameter, links, fit_options)
|
||||
|
||||
fitter = FitRoutine()
|
||||
self.fitter = FitRoutine()
|
||||
models = {}
|
||||
fit_limits = fit_options['limits']
|
||||
fit_mode = fit_options['fit_mode']
|
||||
we = fit_options['we']
|
||||
we_option = fit_options['we']
|
||||
|
||||
for model_id, model_p in parameter.items():
|
||||
m = Model(model_p['func'])
|
||||
models[model_id] = m
|
||||
self.fitter.fitmethod = fit_mode
|
||||
|
||||
m_complex = model_p['complex']
|
||||
# all-encompassing error catch
|
||||
try:
|
||||
for model_id, model_p in parameter.items():
|
||||
m = model_p['func']
|
||||
models[model_id] = m
|
||||
|
||||
for set_id, set_params in model_p['parameter'].items():
|
||||
data_i = self.data[set_id]
|
||||
if we.lower() == 'deltay':
|
||||
we = data_i.y_err**2
|
||||
m_complex = model_p['complex']
|
||||
|
||||
if m_complex is None or m_complex == 1:
|
||||
_y = data_i.y.real
|
||||
elif m_complex == 2 and np.iscomplexobj(data_i.y):
|
||||
_y = data_i.y.imag
|
||||
else:
|
||||
_y = data_i.y
|
||||
# sets are not in active order but in order they first appeared in fit dialog
|
||||
# iterate over order of set id in active order and access parameter inside loop
|
||||
# instead of directly looping
|
||||
try:
|
||||
list_ids = list(self.active_id)
|
||||
set_order = [self.active_id.index(i) for i in model_p['data_parameter'].keys()]
|
||||
except ValueError as e:
|
||||
raise Exception('Getting order failed') from e
|
||||
|
||||
_x = data_i.x
|
||||
for pos in set_order:
|
||||
set_id = list_ids[pos]
|
||||
|
||||
if fit_limits == 'none':
|
||||
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]))
|
||||
try:
|
||||
data_i = self.data[set_id]
|
||||
except KeyError as e:
|
||||
raise KeyError(f'{set_id} not found') from e
|
||||
|
||||
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)
|
||||
try:
|
||||
set_params = model_p['data_parameter'][set_id]
|
||||
except KeyError as e:
|
||||
raise KeyError(f'No parameter found for {set_id}') from e
|
||||
|
||||
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])
|
||||
if we_option.lower() == 'deltay':
|
||||
we = data_i.y_err**2
|
||||
else:
|
||||
we = we_option
|
||||
|
||||
fitter.add_data(d)
|
||||
if m_complex is None or m_complex == 1:
|
||||
_y = data_i.y.real
|
||||
elif m_complex == 2 and np.iscomplexobj(data_i.y):
|
||||
_y = data_i.y.imag
|
||||
else:
|
||||
_y = data_i.y
|
||||
|
||||
model_globs = model_p['glob']
|
||||
if model_globs:
|
||||
m.set_global_parameter(**model_p['glob'])
|
||||
_x = data_i.x
|
||||
|
||||
for links_i in links:
|
||||
fitter.set_link_parameter((models[links_i[0]], links_i[1]),
|
||||
(models[links_i[2]], links_i[3]))
|
||||
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]))
|
||||
|
||||
try:
|
||||
if isinstance(we, str):
|
||||
d = fit_d.Data(_x[inside], _y[inside], we=we, idx=set_id)
|
||||
else:
|
||||
d = fit_d.Data(_x[inside], _y[inside], we=we[inside], idx=set_id)
|
||||
except Exception as e:
|
||||
raise Exception(f'Setting data failed for {set_id}')
|
||||
|
||||
d.set_model(m)
|
||||
try:
|
||||
d.set_parameter(set_params[0], fun_kwargs=set_params[1])
|
||||
except Exception as e:
|
||||
raise Exception('Setting parameter failed') from e
|
||||
|
||||
self.fitter.add_data(d)
|
||||
|
||||
for links_i in links:
|
||||
self.fitter.set_link_parameter((models[links_i[0]], links_i[1]),
|
||||
(models[links_i[2]], links_i[3]))
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error('Fit preparation failed', *e.args)
|
||||
QtWidgets.QMessageBox.warning(QtWidgets.QWidget(),
|
||||
'Fit prep failed',
|
||||
f'Fit preparation failed:\n'
|
||||
'Message:\n'
|
||||
f'{e.args}:\n')
|
||||
return False
|
||||
|
||||
def start_fit(self):
|
||||
with busy_cursor():
|
||||
self.fit_worker = FitWorker(fitter, fit_mode)
|
||||
self.fit_worker = FitWorker(self.fitter)
|
||||
self.fit_thread = QtCore.QThread()
|
||||
self.fit_worker.moveToThread(self.fit_thread)
|
||||
|
||||
@ -464,12 +549,14 @@ class UpperManagement(QtCore.QObject):
|
||||
|
||||
@QtCore.pyqtSlot(list, bool)
|
||||
def end_fit(self, result: list, success: bool):
|
||||
print('FIT FINISHED')
|
||||
if success:
|
||||
logger.info('Successful fit')
|
||||
self.fitFinished.emit(result)
|
||||
else:
|
||||
e = result[0]
|
||||
logger.exception(e, exc_info=True)
|
||||
QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), 'Fit failed',
|
||||
'Fit kaput with exception: \n' + "\n".join(result[0]))
|
||||
f'Fit kaput with exception: \n\n{e!r}')
|
||||
self.fitFinished.emit([])
|
||||
self._fit_active = False
|
||||
|
||||
@ -482,7 +569,8 @@ class UpperManagement(QtCore.QObject):
|
||||
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)
|
||||
if self.prepare_fit(*self.__fit_options):
|
||||
self.start_fit()
|
||||
|
||||
def make_fits(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: bool, extrapolate: list) -> None:
|
||||
"""
|
||||
@ -558,6 +646,9 @@ class UpperManagement(QtCore.QObject):
|
||||
subfunc.name += data_name
|
||||
sub_f_id = self.add(subfunc, color=col, linestyle=LineStyle.Dashed, symbol=SymbolStyle.No)
|
||||
|
||||
self[sub_f_id].add_relation(Relations.isFitPartOf, f_id)
|
||||
self[f_id].add_relation(Relations.hasFitPart, sub_f_id)
|
||||
|
||||
f_id_list.append(sub_f_id)
|
||||
gid = data_k.graph
|
||||
self.delete_sets(tobedeleted)
|
||||
@ -600,11 +691,11 @@ class UpperManagement(QtCore.QObject):
|
||||
if not graph_id:
|
||||
graph_id = ''
|
||||
|
||||
self.newData.emit(p_id_list, graph_id)
|
||||
self.newData[list, str, bool].emit(p_id_list, graph_id, True)
|
||||
|
||||
def save_fit_parameter(self, fname: str | pathlib.Path, fit_sets: list[str] = None):
|
||||
if fit_sets is None:
|
||||
fit_sets = [s for (s, _) in self.active_sets]
|
||||
fit_sets = [s for s in self.active_id]
|
||||
|
||||
for set_id in fit_sets:
|
||||
data = self.data[set_id]
|
||||
@ -626,12 +717,10 @@ class UpperManagement(QtCore.QObject):
|
||||
else:
|
||||
continue
|
||||
|
||||
for key, pvalue in data.parameter.items():
|
||||
name = pvalue.full_name
|
||||
fit_key = key + data.model_name
|
||||
for fit_key, pvalue in data.parameter.items():
|
||||
|
||||
if fit_key not in fit_dict:
|
||||
fit_dict[fit_key] = [[], name]
|
||||
fit_dict[fit_key] = [[], fit_key]
|
||||
|
||||
err = 0 if pvalue.error is None else pvalue.error
|
||||
|
||||
@ -668,7 +757,7 @@ class UpperManagement(QtCore.QObject):
|
||||
key = self.add(Points(x=new_x_axis, y=_temp[:, i, 1], y_err=_temp[:, i, 2], name=label))
|
||||
key_list.append(key)
|
||||
|
||||
self.newData.emit(key_list, gid)
|
||||
self.newData[list, str, bool].emit(key_list, gid, True)
|
||||
|
||||
@QtCore.pyqtSlot(list)
|
||||
def get_properties(self, sid: list) -> dict:
|
||||
@ -739,6 +828,47 @@ class UpperManagement(QtCore.QObject):
|
||||
|
||||
self.newData.emit(new_key, dest_graph)
|
||||
|
||||
def binning(self, digits: float):
|
||||
_active = self.graphs[self.current_graph].active
|
||||
new_data = []
|
||||
for sid in _active:
|
||||
key = self.add(self.data[sid].binning(digits=digits))
|
||||
new_data.append(key)
|
||||
|
||||
self.newData.emit(new_data, self.current_graph)
|
||||
|
||||
@QtCore.pyqtSlot(dict, str)
|
||||
def addTg(self, dic: dict, dtype: str):
|
||||
graph_id = self.current_graph if dtype == 'tg' else dic.pop('graph')
|
||||
|
||||
set_id_list = []
|
||||
|
||||
if dtype == 'hodge':
|
||||
for v in dic.values():
|
||||
set_id_list.append(self.add(v))
|
||||
|
||||
else:
|
||||
for k, (data, lines) in dic.items():
|
||||
p: ExperimentContainer = self[k]
|
||||
col = p.plot_real.linecolor
|
||||
|
||||
if data is not None:
|
||||
set_id_list.append(self.add(data, color=col))
|
||||
|
||||
if dtype == 'tnmh':
|
||||
if lines is not None:
|
||||
lines = [lines]
|
||||
else:
|
||||
lines = []
|
||||
|
||||
for line in lines:
|
||||
set_id = self.add(line, color=col)
|
||||
self[set_id].setLine(style=LineStyle.Dashed)
|
||||
self[set_id].setSymbol(symbol=SymbolStyle.No)
|
||||
set_id_list.append(set_id)
|
||||
|
||||
self.newData.emit(set_id_list, graph_id)
|
||||
|
||||
@QtCore.pyqtSlot(int, dict)
|
||||
def smooth_data(self, npoints, param_kwargs):
|
||||
_active = self.graphs[self.current_graph].active
|
||||
@ -771,13 +901,10 @@ class UpperManagement(QtCore.QObject):
|
||||
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]
|
||||
d_k.shift_scale(v[0], v[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]
|
||||
new_data.shift_scale(v[0], v[1])
|
||||
|
||||
sid = self.add(new_data)
|
||||
sid_list.append(sid)
|
||||
@ -873,21 +1000,26 @@ class UpperManagement(QtCore.QObject):
|
||||
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
|
||||
for i, g in enumerate(self.graphs.values()):
|
||||
for j, sid in enumerate(g.sets):
|
||||
|
||||
if sid not in set_ids:
|
||||
continue
|
||||
|
||||
data_i = self.data[sid]
|
||||
try:
|
||||
# use a copy of original namespace
|
||||
new_data = data_i.eval_expression(cmds, dict(ns), i=i, j=j)
|
||||
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))
|
||||
logger.warning(str(data_i) + ' failed with Exception: ' + ''.join(e.args))
|
||||
continue
|
||||
|
||||
if overwrite:
|
||||
self.undostack.endMacro()
|
||||
@ -921,9 +1053,9 @@ class UpperManagement(QtCore.QObject):
|
||||
self.newData.emit([s_id], graph)
|
||||
|
||||
except Exception as err:
|
||||
print('Creation failed with error: ' + ', '.join(err.args))
|
||||
logger.exception('Creation failed with error: ' + ', '.join(err.args))
|
||||
err_msg = QtWidgets.QMessageBox(parent=self.sender())
|
||||
err_msg.setText('One or more errors occured during evaluation.')
|
||||
err_msg.setText('One or more errors occurred during evaluation.')
|
||||
err_msg.setDetailedText('Creation failed with error: ' + ', '.join(err.args))
|
||||
err_msg.exec()
|
||||
|
||||
@ -932,7 +1064,7 @@ class UpperManagement(QtCore.QObject):
|
||||
def show_statistics(self, mode):
|
||||
x, y, = [], []
|
||||
|
||||
for i, _ in self.active_sets:
|
||||
for i in self.active_id:
|
||||
_temp = self.data[i]
|
||||
try:
|
||||
x.append(float(_temp.name))
|
||||
@ -943,7 +1075,7 @@ class UpperManagement(QtCore.QObject):
|
||||
@QtCore.pyqtSlot()
|
||||
def calc_magn(self):
|
||||
new_id = []
|
||||
for k, _ in self.active_sets:
|
||||
for k in self.active_id:
|
||||
dataset = self.data[k]
|
||||
if isinstance(dataset, SignalContainer):
|
||||
new_value = dataset.copy(full=True)
|
||||
@ -955,7 +1087,7 @@ class UpperManagement(QtCore.QObject):
|
||||
@QtCore.pyqtSlot()
|
||||
def center(self):
|
||||
new_id = []
|
||||
for k, _ in self.active_sets:
|
||||
for k in self.active_id:
|
||||
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))
|
||||
@ -994,7 +1126,7 @@ class UpperManagement(QtCore.QObject):
|
||||
def bds_deriv(self):
|
||||
new_sets = []
|
||||
|
||||
for (set_id, _) in self.active_sets:
|
||||
for set_id in self.active_id:
|
||||
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)
|
||||
@ -1007,13 +1139,14 @@ class UpperManagement(QtCore.QObject):
|
||||
def logft(self, **kwargs):
|
||||
new_sets = []
|
||||
ft_mode = kwargs['ft_mode']
|
||||
return_f = kwargs['return_f']
|
||||
|
||||
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))
|
||||
new_data = Points(*logft(data_i.x, data_i.y, mode=ft_mode, return_f=return_f))
|
||||
else:
|
||||
new_data = Signal(*logft(data_i.x, data_i.y, mode=ft_mode))
|
||||
new_data = Signal(*logft(data_i.x, data_i.y, mode=ft_mode, return_f=return_f))
|
||||
|
||||
new_sets.append(self.add(new_data, color=data_i['color'], symbol=data_i['symbol'], line=data_i['line']))
|
||||
self.data[new_sets[-1]].update(data_i.data.meta)
|
||||
@ -1021,7 +1154,7 @@ class UpperManagement(QtCore.QObject):
|
||||
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:
|
||||
for k in self.active_id:
|
||||
src = self.data[k]
|
||||
if invert:
|
||||
mask = np.mod(np.arange(offset, src.x.size+offset), step) != 0
|
||||
@ -1047,9 +1180,11 @@ class UpperManagement(QtCore.QObject):
|
||||
params = opts['pts']
|
||||
if len(params) == 4:
|
||||
if params[3]:
|
||||
_x = x1 = np.geomspace(params[0], params[1], num=params[2])
|
||||
_x = np.geomspace(params[0], params[1], num=params[2])
|
||||
x1 = np.geomspace(params[0], params[1], num=params[2])
|
||||
else:
|
||||
_x = x1 = np.linspace(params[0], params[1], num=params[2])
|
||||
_x = np.linspace(params[0], params[1], num=params[2])
|
||||
x1 = np.linspace(params[0], params[1], num=params[2])
|
||||
|
||||
if opts['axis1'] in ['t', 'invt1000']:
|
||||
t_p = opts['t_param']
|
||||
@ -1068,21 +1203,23 @@ class UpperManagement(QtCore.QObject):
|
||||
_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')
|
||||
sd_param = list(zip(*[self.data[p].y.real if isinstance(p, str) else [p]*len(_x) for p in opts['sd_param'][0]]))
|
||||
|
||||
relax = Relaxation()
|
||||
relax.set_distribution(sd, parameter=sd_param, keywords=opts['sd_param'][1])
|
||||
relax.set_distribution(sd, keywords=opts['sd_param'][1])
|
||||
|
||||
cp_param = [self.data[p].y.real if isinstance(p, str) else p for p in opts['cp_param'][0]]
|
||||
relax.set_coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1])
|
||||
cp_param = list(zip(*[self.data[p].y.real if isinstance(p, str) else [p]*len(_x) for p in opts['cp_param'][0]]))
|
||||
# relax.set_coupling(opts['coup'], parameter=cp_param, keywords=opts['cp_param'][1])
|
||||
|
||||
if opts['out'] == 't1':
|
||||
y = relax.t1(x2, _x)
|
||||
else:
|
||||
y = relax.t2(x2, _x)
|
||||
relax_func = relax.t1 if opts['out'] == 't1' else relax.t2
|
||||
y = np.zeros(_x.size)
|
||||
|
||||
for i in range(_x.size):
|
||||
_x_i = sd.convert(_x[i], *sd_param[i], from_=opts['tau_type'], to_='raw')
|
||||
relax.dist_parameter = sd_param[i]
|
||||
relax.set_coupling(opts['coup'], parameter=cp_param[i], keywords=opts['cp_param'][1])
|
||||
y[i] = relax_func(x2, _x_i)
|
||||
|
||||
pts = Points(x1, y, name=sd.name)
|
||||
pts.meta.update(opts)
|
||||
@ -1176,18 +1313,17 @@ class UpperManagement(QtCore.QObject):
|
||||
class FitWorker(QtCore.QObject):
|
||||
finished = QtCore.pyqtSignal(list, bool)
|
||||
|
||||
def __init__(self, fitter, mode):
|
||||
def __init__(self, fitter):
|
||||
super().__init__()
|
||||
|
||||
self.fitter = fitter
|
||||
self.mode = mode
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def run(self):
|
||||
try:
|
||||
res = self.fitter.run(mode=self.mode)
|
||||
res = self.fitter.run()
|
||||
success = True
|
||||
except Exception as e:
|
||||
res = [e.args]
|
||||
res = [e]
|
||||
success = False
|
||||
self.finished.emit(res, success)
|
||||
|
21
src/gui_qt/math/binning.py
Normal file
21
src/gui_qt/math/binning.py
Normal file
@ -0,0 +1,21 @@
|
||||
from ..Qt import QtWidgets, QtGui
|
||||
|
||||
|
||||
class BinningWindow(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QFormLayout()
|
||||
|
||||
self.label = QtWidgets.QLabel('Digits (negative values position left of decimal point)')
|
||||
self.spinbox = QtWidgets.QLineEdit()
|
||||
self.spinbox.setValidator(QtGui.QDoubleValidator())
|
||||
self.spinbox.setText('1')
|
||||
layout.addRow(self.label, self.spinbox)
|
||||
|
||||
self.dialogbox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
|
||||
self.dialogbox.accepted.connect(self.accept)
|
||||
self.dialogbox.rejected.connect(self.reject)
|
||||
layout.addWidget(self.dialogbox)
|
||||
|
||||
self.setLayout(layout)
|
@ -31,13 +31,35 @@ class QEvalDialog(QtWidgets.QDialog, Ui_CalcDialog):
|
||||
self.namespace_widget.set_namespace(self.namespace)
|
||||
|
||||
def add_data(self, data):
|
||||
self.listWidget.clear()
|
||||
# self.listWidget.clear()
|
||||
tmp = []
|
||||
while self.listWidget.count():
|
||||
tmp.append(self.listWidget.takeItem(0))
|
||||
|
||||
for set_id, name in data:
|
||||
item = QtWidgets.QListWidgetItem(name)
|
||||
item.setData(QtCore.Qt.UserRole, set_id)
|
||||
item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.listWidget.addItem(item)
|
||||
# search if set was used before
|
||||
new_one = True
|
||||
for i in range(len(tmp)):
|
||||
w = tmp[i]
|
||||
if w.data(QtCore.Qt.UserRole) == set_id:
|
||||
w.setText(name)
|
||||
self.listWidget.addItem(w)
|
||||
tmp.pop(i)
|
||||
new_one = False
|
||||
break
|
||||
|
||||
# new set, create item
|
||||
if new_one:
|
||||
item = QtWidgets.QListWidgetItem(name)
|
||||
item.setData(QtCore.Qt.UserRole, set_id)
|
||||
item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.listWidget.addItem(item)
|
||||
|
||||
while len(tmp):
|
||||
# delete remaining ListWidgetItems
|
||||
w = tmp.pop()
|
||||
del w
|
||||
|
||||
def set_graphs(self, graphs: list):
|
||||
self.graph_comboBox.clear()
|
||||
|
@ -16,16 +16,19 @@ class QDeriveIntegrate(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.start_lineedit.setValidator(QtGui.QDoubleValidator())
|
||||
self.stop_lineedit.setValidator(QtGui.QDoubleValidator())
|
||||
self.ft_comboBox.hide()
|
||||
self.freq_box.hide()
|
||||
|
||||
elif self.mode == 'd':
|
||||
self.setWindowTitle('Differentiation dialog')
|
||||
self.widget.hide()
|
||||
self.ft_comboBox.hide()
|
||||
self.freq_box.hide()
|
||||
|
||||
elif self.mode == 'l':
|
||||
self.setWindowTitle('Logarithmic FT dialog')
|
||||
self.log_checkbox.hide()
|
||||
self.widget.hide()
|
||||
self.freq_box.show()
|
||||
|
||||
else:
|
||||
raise ValueError(f'Unknown mode {mode}, use "d", "i", or "l".')
|
||||
@ -54,8 +57,10 @@ class QDeriveIntegrate(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.stop_lineedit.setEnabled(full_range != QtCore.Qt.Checked)
|
||||
|
||||
def get_options(self):
|
||||
opts = {'graph': '' if self.newgraph_checkbox.isChecked() else self.graph_combobox.currentData(),
|
||||
'mode': self.mode, 'sets': []}
|
||||
opts = {
|
||||
'graph': '' if self.newgraph_checkbox.isChecked() else self.graph_combobox.currentData(),
|
||||
'mode': self.mode, 'sets': []
|
||||
}
|
||||
|
||||
if self.mode == 'i':
|
||||
start = None
|
||||
@ -75,6 +80,7 @@ class QDeriveIntegrate(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
if self.mode == 'l':
|
||||
opts['ft_mode'] = ['cos', 'sin', 'complex'][self.ft_comboBox.currentIndex()]
|
||||
opts['return_f'] = self.freq_box.isChecked()
|
||||
else:
|
||||
opts['log'] = self.log_checkbox.isChecked()
|
||||
|
||||
|
@ -19,7 +19,7 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
|
||||
|
||||
self.graphs = {}
|
||||
|
||||
self.specdens = [ColeCole, ColeDavidson, HavriliakNegami, KWW]
|
||||
self.specdens = [ColeCole, ColeDavidson, HavriliakNegami, KWW, LogGaussian]
|
||||
self.coupling = [Quadrupolar, HomoDipolar, Czjzek]
|
||||
self.tau_parameter = []
|
||||
|
||||
@ -131,7 +131,7 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
|
||||
self.verticalLayout_3.addWidget(_temp)
|
||||
|
||||
def get_taus(self, dic: dict):
|
||||
dic['tau_type'] = {0: 'raw', 1: 'mean', 2: 'peak', 3: 'logmean'}[self.xtype_combobox.currentIndex()]
|
||||
dic['tau_type'] = {0: 'raw', 1: 'peak', 2: 'mean', 3: 'logmean'}[self.xtype_combobox.currentIndex()]
|
||||
dic['axis1'] = {self.radioButton: 'tau', self.radioButton_2: 'omega',
|
||||
self.radioButton_3: 't', self.radioButton_4: 'invt1000'}[self.buttonGroup.checkedButton()]
|
||||
|
||||
@ -199,3 +199,9 @@ class QRelaxCalc(QtWidgets.QDialog, Ui_Dialog):
|
||||
def accept(self):
|
||||
self.calc_relaxation()
|
||||
super().accept()
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
|
||||
def on_buttonBox_clicked(self, button: QtWidgets.QAbstractButton):
|
||||
role = self.buttonBox.buttonRole(button)
|
||||
if role == self.buttonBox.ApplyRole:
|
||||
self.calc_relaxation()
|
||||
|
@ -61,6 +61,8 @@ class QT1Widget(QtWidgets.QDialog, Ui_t1dialog):
|
||||
self.freq_combox.currentIndexChanged.connect(lambda x: self.update_model())
|
||||
self.freq_spinbox.valueChanged.connect(lambda x: self.update_model())
|
||||
|
||||
self.checkBox_interpol.setVisible(False)
|
||||
|
||||
self.update_specdens(0)
|
||||
self.update_coupling(0)
|
||||
|
||||
|
158
src/nmreval/clib/integrate.c
Normal file
158
src/nmreval/clib/integrate.c
Normal file
@ -0,0 +1,158 @@
|
||||
/* integrands used in quadrature integration with scipy's LowLevelCallables */
|
||||
#include <math.h>
|
||||
|
||||
const double KB = 8.617333262145179e-05;
|
||||
|
||||
/* FFHS functions */
|
||||
double ffhsSD(double x, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau = c[1];
|
||||
|
||||
double u = x*x;
|
||||
double res = u*u * tau;
|
||||
|
||||
res /= 81. + 9.*u - 2.*u*u + u*u*u;
|
||||
res /= u*u + omega*omega * tau*tau;
|
||||
return res;
|
||||
}
|
||||
|
||||
/* log-gaussian functions */
|
||||
double logNormalDist(double tau, double tau0, double sigma) {
|
||||
return exp(- pow((log(tau/tau0) / sigma), 2) / 2.) / sqrt(2*M_PI)/sigma;
|
||||
}
|
||||
|
||||
double logGaussian_imag_high(double u, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau = c[1];
|
||||
double sigma = c[2];
|
||||
|
||||
double uu = exp(-u);
|
||||
double dist = logNormalDist(1./uu, tau, sigma);
|
||||
|
||||
return dist * omega * uu / (pow(uu, 2) + pow(omega, 2));
|
||||
}
|
||||
|
||||
double logGaussian_imag_low(double u, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau = c[1];
|
||||
double sigma = c[2];
|
||||
|
||||
double uu = exp(u);
|
||||
|
||||
double dist = logNormalDist(uu, tau, sigma);
|
||||
|
||||
return dist * omega * uu / (1. + pow(omega*uu, 2));
|
||||
}
|
||||
|
||||
double logGaussian_real_high(double u, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau = c[1];
|
||||
double sigma = c[2];
|
||||
|
||||
double uu = exp(-2.*u);
|
||||
double dist = logNormalDist(exp(uu), tau, sigma);
|
||||
|
||||
return dist * uu / (uu + pow(omega, 2));
|
||||
}
|
||||
|
||||
double logGaussian_real_low(double u, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau = c[1];
|
||||
double sigma = c[2];
|
||||
|
||||
double uu = exp(u);
|
||||
|
||||
double dist = logNormalDist(uu, tau, sigma);
|
||||
|
||||
return dist / (1. + pow(omega*uu, 2));
|
||||
}
|
||||
|
||||
double logGaussianCorrelation(double x, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double t = c[0];
|
||||
double tau = c[1];
|
||||
double sigma = c[2];
|
||||
|
||||
double uu = exp(x);
|
||||
|
||||
double dist = logNormalDist(uu, tau, sigma);
|
||||
|
||||
return dist * exp(-t/uu);
|
||||
}
|
||||
|
||||
// functions for distribution of energy
|
||||
double normalDist(double x, double x0, double sigma) {
|
||||
return exp(- pow((x-x0) / sigma, 2) / 2.) / sqrt(2 * M_PI) / sigma;
|
||||
}
|
||||
|
||||
double rate(double tau0, double ea, double t) {
|
||||
return exp(-ea / t / KB) / tau0;
|
||||
}
|
||||
|
||||
double energyDist_SD(double x, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau0 = c[1];
|
||||
double e_m = c[2];
|
||||
double e_b = c[3];
|
||||
double temp = c[4];
|
||||
|
||||
double r = rate(tau0, x, temp);
|
||||
|
||||
return r/(pow(r, 2) + pow(omega, 2)) * normalDist(x, e_m, e_b);
|
||||
}
|
||||
|
||||
double energyDistSuscReal(double x, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau0 = c[1];
|
||||
double e_m = c[2];
|
||||
double e_b = c[3];
|
||||
double temp = c[4];
|
||||
|
||||
double r = rate(tau0, x, temp);
|
||||
|
||||
return 1 / (pow(r, 2) + pow(omega, 2)) * normalDist(x, e_m, e_b);
|
||||
}
|
||||
|
||||
double energyDistSuscImag(double x, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double omega = c[0];
|
||||
double tau0 = c[1];
|
||||
double e_m = c[2];
|
||||
double e_b = c[3];
|
||||
double temp = c[4];
|
||||
|
||||
double r = rate(tau0, x, temp);
|
||||
|
||||
return omega * r / (pow(r, 2) + pow(omega, 2)) * normalDist(x, e_m, e_b);
|
||||
}
|
||||
|
||||
double energyDistCorrelation(double x, void *user_data) {
|
||||
double *c = (double *)user_data;
|
||||
|
||||
double t = c[0];
|
||||
double tau0 = c[1];
|
||||
double e_m = c[2];
|
||||
double e_b = c[3];
|
||||
double temp = c[4];
|
||||
|
||||
double r = rate(tau0, x, temp);
|
||||
|
||||
return normalDist(x, e_m, e_b) * exp(-t * r);
|
||||
}
|
||||
|
BIN
src/nmreval/clib/integrate.so
Executable file
BIN
src/nmreval/clib/integrate.so
Executable file
Binary file not shown.
@ -10,19 +10,25 @@ __all__ = ['config_paths', 'check_for_config', 'read_configuration', 'write_conf
|
||||
|
||||
def check_for_config(make=True):
|
||||
try:
|
||||
config_paths()
|
||||
conf_path = config_paths()
|
||||
except FileNotFoundError as e:
|
||||
if make:
|
||||
conf_path = pathlib.Path('~/.auswerten').expanduser()
|
||||
conf_path.mkdir(parents=True)
|
||||
cwd = pathlib.Path(__file__).parent
|
||||
copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
|
||||
with resource_path('resources', 'Default.agr') as fp:
|
||||
copyfile(fp, conf_path / 'Default.agr')
|
||||
|
||||
else:
|
||||
raise e
|
||||
|
||||
for filename in ('Default.agr', 'logo.png'):
|
||||
_file = conf_path / filename
|
||||
if not _file.exists():
|
||||
with resource_path('resources', filename) as fp:
|
||||
copyfile(fp, _file)
|
||||
|
||||
if not (conf_path / 'usermodels.py').exists():
|
||||
cwd = pathlib.Path(__file__).parent
|
||||
copyfile(cwd / 'models' / 'usermodels.py', conf_path / 'usermodels.py')
|
||||
|
||||
|
||||
def config_paths() -> pathlib.Path:
|
||||
searchpaths = ['~/.config/nmreval', '~/.auswerten', '/usr/share/nmreval']
|
||||
|
@ -1,6 +1,138 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from scipy.optimize import fsolve
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
try:
|
||||
from scipy.integrate import cumulative_trapezoid
|
||||
except ImportError:
|
||||
from scipy.integrate import cumtrapz as cumulative_trapezoid
|
||||
from scipy.stats import linregress
|
||||
|
||||
from .points import Points
|
||||
|
||||
from ..dsc.tnmh_model import TNMH
|
||||
|
||||
|
||||
class DSC(Points):
|
||||
def __init__(self, x, y, **kwargs):
|
||||
super().__init__(x, y, **kwargs)
|
||||
|
||||
y = np.asarray(y).reshape(np.asarray(x).shape)
|
||||
|
||||
x, unique = np.unique(x, return_index=True)
|
||||
if kwargs.get('y_err', None) is not None:
|
||||
_yerr = np.asarray(kwargs['y_err']).reshape(np.asarray(x).shape)
|
||||
kwargs['y_err'] = _yerr[unique]
|
||||
|
||||
self.tg = {'onset': np.nan, 'mid': np.nan, 'end': np.nan, 'inflection': np.nan, 'fictive': np.nan}
|
||||
|
||||
super().__init__(x, y[unique], **kwargs)
|
||||
|
||||
def get_fictive_cp(self, glass: tuple[float, float], liquid: tuple[float, float]) -> ('DSC', float):
|
||||
min_glass, max_glass = min(glass), max(glass)
|
||||
min_liquid, max_liquid = min(liquid), max(liquid)
|
||||
|
||||
region = self.copy()
|
||||
region.cut(min_glass, max_liquid)
|
||||
|
||||
glass_regime = (min_glass < region.x) & (region.x < max_glass)
|
||||
regress = linregress(region.x[glass_regime], region.y[glass_regime])
|
||||
glass_extrapolation = regress.slope * region.x + regress.intercept
|
||||
|
||||
liquid_regime = (min_liquid < region.x) & (region.x < max_liquid)
|
||||
regress2 = linregress(region.x[liquid_regime], region.y[liquid_regime])
|
||||
|
||||
region.y -= glass_extrapolation
|
||||
real_area = cumulative_trapezoid(region.y, region.x, initial=0)
|
||||
real_area -= real_area[-1]
|
||||
|
||||
t = regress2.intercept - regress.intercept
|
||||
m = regress2.slope - regress.slope
|
||||
c0 = 0.5 * m * region.x.max() ** 2 + t * region.x.max()
|
||||
|
||||
def equiv(_x, _i):
|
||||
return (0.5 * m * _x ** 2 + t * _x - c0) - real_area[_i]
|
||||
|
||||
def equiv_prime(_x, _i):
|
||||
return m * _x + t
|
||||
|
||||
fictive_temperature = np.array(
|
||||
[fsolve(equiv, region.x[i], fprime=equiv_prime, args=(i,))[0] for i in range(len(region.x))])
|
||||
|
||||
t_g_fictive = fictive_temperature[:20].mean()
|
||||
region.y = np.gradient(fictive_temperature, region.x)
|
||||
|
||||
return region, t_g_fictive
|
||||
|
||||
def calculate_tnmh(self, p0: list, glass: tuple[float, float], liquid: tuple[float, float],
|
||||
tg: float = None, num_points: int = 200, return_fictive: bool = True) \
|
||||
-> ('FitResult', Optional[float], Optional[DSC]):
|
||||
|
||||
dtf_dt, fictive_tg = self.get_fictive_cp(glass, liquid)
|
||||
if tg is None:
|
||||
tg = fictive_tg
|
||||
|
||||
temp_equidist = np.linspace(dtf_dt.x[0], dtf_dt.x[-1], num_points)
|
||||
dtf_dt_equidist = np.interp(temp_equidist, dtf_dt.x, dtf_dt.y)
|
||||
|
||||
from ..fit.minimizer import FitRoutine
|
||||
fitter = FitRoutine()
|
||||
fitter.set_model(TNMH)
|
||||
data = fitter.add_data(temp_equidist, dtf_dt_equidist)
|
||||
data.set_parameter(p0 + [tg, self.value], var=[True] * 4 + [False] * 2, default_bounds=True)
|
||||
|
||||
res = fitter.run()[0]
|
||||
|
||||
if return_fictive:
|
||||
return res, tg, dtf_dt
|
||||
else:
|
||||
return res
|
||||
|
||||
def glass_transition(self, glass, liquid):
|
||||
low_idx = tuple(np.argmin(np.abs(self.x - g)) for g in glass)
|
||||
high_idx = tuple(np.argmin(np.abs(self.x - l)) for l in liquid)
|
||||
|
||||
x = self.x[low_idx[0]:high_idx[1]]
|
||||
y = self.y[low_idx[0]:high_idx[1]]
|
||||
|
||||
win_len = min(len(x) // 20, 51)
|
||||
if win_len % 2 == 0:
|
||||
win_len += 1
|
||||
yy = savgol_filter(y, window_length=win_len, polyorder=1, deriv=1) / np.mean(np.diff(x))
|
||||
|
||||
high_idx = (high_idx[0] - low_idx[0], high_idx[1] - low_idx[0])
|
||||
low_idx = (0, low_idx[1] - low_idx[0])
|
||||
|
||||
inflection = np.argmax(yy)
|
||||
|
||||
p1 = linregress(x[low_idx[0]:low_idx[1]], y[low_idx[0]:low_idx[1]])
|
||||
glass_baseline = p1.slope * x + p1.intercept
|
||||
|
||||
p2 = linregress(x[high_idx[0]:high_idx[1]], y[high_idx[0]:high_idx[1]])
|
||||
liquid_baseline = p2.slope * x + p2.intercept
|
||||
|
||||
tangent_line = yy[inflection] * (x - x[inflection]) + y[inflection]
|
||||
|
||||
onset = np.argmin(np.abs(tangent_line - glass_baseline))
|
||||
end = np.argmin(np.abs(tangent_line - liquid_baseline))
|
||||
|
||||
midpoint = np.argmin(np.abs(y - 0.5 * (liquid_baseline[end] - glass_baseline[onset])))
|
||||
cut_tangent = np.where((tangent_line > y.min() - 1) & (tangent_line < y.max() + 1))
|
||||
|
||||
glass = Points(x, glass_baseline, name=f'Glass baseline ({self.name})', value=self.value)
|
||||
tangent = Points(x[cut_tangent], tangent_line[cut_tangent], name=f'Tangent ({self.name})', value=self.value)
|
||||
liquid = Points(x, liquid_baseline, name=f'Liquid baseline ({self.name})', value=self.value)
|
||||
|
||||
ret_dic = {
|
||||
'onset': (x[onset], glass_baseline[onset]),
|
||||
'midpoint': (x[midpoint], y[midpoint]),
|
||||
'end': (x[end], liquid_baseline[end]),
|
||||
'inflection': (x[inflection], y[inflection]),
|
||||
}
|
||||
|
||||
self.tg.update(ret_dic)
|
||||
|
||||
return ret_dic, glass, liquid, tangent
|
||||
|
@ -62,6 +62,10 @@ class FID(Signal):
|
||||
|
||||
return self
|
||||
|
||||
def manual_phase(self, ph0: float = 0., ph1: float = 0., pvt: float = 0):
|
||||
"""FID knows only how to phase correct in zeroth order"""
|
||||
super().manual_phase(ph0=ph0)
|
||||
|
||||
def fourier(self) -> 'Spectrum':
|
||||
ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx
|
||||
freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx))
|
||||
|
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from math import log10
|
||||
from numbers import Number, Real
|
||||
from pathlib import Path
|
||||
from typing import Any, TypeVar
|
||||
@ -318,20 +319,21 @@ class Points:
|
||||
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 idx is not None:
|
||||
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 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']:
|
||||
@ -483,20 +485,25 @@ class Points:
|
||||
|
||||
return self
|
||||
|
||||
def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray = None) -> PointLike:
|
||||
def set_data(self, x: np.ndarray = None, y: np.ndarray = None, y_err: np.ndarray | float = None, replace_mask: bool = True) -> PointLike:
|
||||
if x is None:
|
||||
x = self._x
|
||||
if y is None:
|
||||
y = self._y
|
||||
if y_err is not None:
|
||||
if y_err is None:
|
||||
y_err = self._y_err
|
||||
|
||||
self._x, self._y, self._y_err, self.mask = self._prepare_xy(x, y, y_err)
|
||||
self._x, self._y, self._y_err, mask = self._prepare_xy(x, y, y_err)
|
||||
if replace_mask:
|
||||
self.mask = mask
|
||||
|
||||
return self
|
||||
|
||||
def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None):
|
||||
x, y, y_err, mask = self._prepare_xy(x, y, y_err)
|
||||
def append(self, x: ArrayLike, y: ArrayLike, y_err: ArrayLike = None, mask: ArrayLike = None):
|
||||
if mask is None:
|
||||
x, y, y_err, mask = self._prepare_xy(x, y, y_err)
|
||||
else:
|
||||
x, y, y_err, _ = self._prepare_xy(x, y, y_err)
|
||||
|
||||
self._x = np.r_[self._x, x]
|
||||
self._y = np.r_[self._y, y]
|
||||
@ -541,6 +548,34 @@ class Points:
|
||||
|
||||
return self
|
||||
|
||||
def binning(self, value: float):
|
||||
if value <= 0:
|
||||
raise ValueError('value must be a positive number')
|
||||
|
||||
copy = self.copy()
|
||||
|
||||
upper_lim = (self.x[-1]//value + 1) * value
|
||||
lower_lim = (self.x[0]//value) * value
|
||||
|
||||
offset = value / 2
|
||||
|
||||
xbins = np.linspace(lower_lim - offset, upper_lim + offset, num=int((upper_lim-lower_lim)/value + 2))
|
||||
|
||||
n, _ = np.histogram(copy.x, bins=xbins)
|
||||
sum_y, _ = np.histogram(copy.x, bins=xbins, weights=copy.y)
|
||||
sum_yerr_2, _ = np.histogram(copy.x, bins=xbins, weights=copy.y_err**2)
|
||||
|
||||
isnan = n != 0
|
||||
|
||||
n = n[isnan]
|
||||
sum_y = sum_y[isnan]
|
||||
sum_yerr_2 = sum_yerr_2[isnan]
|
||||
xaxis = (xbins[:-1] + offset)[isnan]
|
||||
|
||||
copy.set_data(xaxis, sum_y/n, y_err=np.sqrt(sum_yerr_2/n))
|
||||
|
||||
return copy
|
||||
|
||||
def shift(self, points: int) -> PointLike:
|
||||
"""
|
||||
Shift indexes of y values.
|
||||
|
@ -29,7 +29,7 @@ class Distribution(abc.ABC):
|
||||
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def susceptibility(omega, tau, *args):
|
||||
def susceptibility(omega: ArrayLike, tau: ArrayLike, *args: Any):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
|
@ -54,6 +54,9 @@ class ColeCole(Distribution):
|
||||
tau (array_like):
|
||||
alpha (float):
|
||||
"""
|
||||
if alpha == 1:
|
||||
return tau / (1 + omega**2 * tau**2)
|
||||
|
||||
omtau = (omega*tau)**alpha
|
||||
return np.sin(alpha*np.pi/2) * omtau / (1 + omtau**2 + 2*np.cos(alpha*np.pi/2)*omtau) / omega
|
||||
|
||||
|
@ -1,13 +1,18 @@
|
||||
from itertools import product
|
||||
from ctypes import c_double, cast, pointer, c_void_p
|
||||
|
||||
import numpy as np
|
||||
from scipy import LowLevelCallable
|
||||
from scipy.integrate import quad, simps as simpson
|
||||
|
||||
from .base import Distribution
|
||||
from ..lib.utils import ArrayLike
|
||||
from ..utils.constants import kB
|
||||
|
||||
from .helper import HAS_C_FUNCS, lib
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
class EnergyBarriers(Distribution):
|
||||
name = 'Energy barriers'
|
||||
parameter = [r'\tau_{0}', r'E_{m}', r'\Delta E']
|
||||
@ -23,24 +28,30 @@ class EnergyBarriers(Distribution):
|
||||
return np.exp(-e_a / (kB * te)) / t0
|
||||
|
||||
@staticmethod
|
||||
def energydistribution(e_a, mu, sigma):
|
||||
def energy_distribution(e_a, mu, sigma):
|
||||
return np.exp(-0.5 * ((mu-e_a) / sigma) ** 2) / (np.sqrt(2 * np.pi) * sigma)
|
||||
|
||||
@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)
|
||||
|
||||
def correlation(t: ArrayLike, temperature: ArrayLike, tau0: float, e_m: float, e_b: float) -> ArrayLike:
|
||||
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)
|
||||
if HAS_C_FUNCS:
|
||||
ret_val = _integrate_c(lib.energyDistCorrelation, t, temperature, tau0, e_m, e_b)
|
||||
else:
|
||||
ret_val = _integrate_py(_integrand_time, t, temperature, tau0, e_m, e_b)
|
||||
|
||||
ret_val = np.array([simpson(integrand(e_axis, o, tau0, e_m, e_b, tt), e_axis)
|
||||
for o in t for tt in temperature])
|
||||
return ret_val
|
||||
|
||||
@staticmethod
|
||||
def specdens(omega: ArrayLike, temperature: ArrayLike, tau0: float, e_m: float, e_b: float) -> ArrayLike:
|
||||
omega = np.atleast_1d(omega)
|
||||
temperature = np.atleast_1d(temperature)
|
||||
|
||||
if HAS_C_FUNCS:
|
||||
ret_val = _integrate_c(lib.energyDist_SD, omega, temperature, tau0, e_m, e_b)
|
||||
else:
|
||||
ret_val = _integrate_py(_integrand_sd, omega, temperature, tau0, e_m, e_b)
|
||||
|
||||
return ret_val
|
||||
|
||||
@ -51,70 +62,73 @@ class EnergyBarriers(Distribution):
|
||||
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_freq_real(e_axis, o, tau0, e_m, e_b, tt), e_axis) -
|
||||
1j * simpson(_integrand_freq_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])
|
||||
if HAS_C_FUNCS:
|
||||
ret_val = _integrate_c(lib.energyDistSuscReal, omega, temperature, tau0, e_m, e_b) + \
|
||||
1j * _integrate_c(lib.energyDistSuscImag, omega, temperature, tau0, e_m, e_b)
|
||||
else:
|
||||
ret_val = _integrate_py(_integrand_susc_real, omega, temperature, tau0, e_m, e_b) + \
|
||||
1j * _integrate_py(_integrand_susc_imag, omega, temperature, tau0, e_m, e_b)
|
||||
|
||||
return ret_val
|
||||
|
||||
@staticmethod
|
||||
def mean(*args):
|
||||
return args[1]*np.exp(args[2]/(kB*args[0]))
|
||||
def mean(temperature, tau0, ea):
|
||||
return tau0*np.exp(ea/(kB*temperature))
|
||||
|
||||
@staticmethod
|
||||
def logmean(*args):
|
||||
return args[1] + args[2] / (kB * args[0])
|
||||
def logmean(temperature, tau0, ea):
|
||||
return tau0 + ea / (kB * temperature)
|
||||
|
||||
@staticmethod
|
||||
def max(*args):
|
||||
return args[1] * np.exp(args[2] / (kB * args[0]))
|
||||
|
||||
|
||||
def _integrate_process_imag(args):
|
||||
pass
|
||||
# helper functions
|
||||
def _integrate_c(func, omega: np.ndarray, temperature: np.ndarray, tau0: float, e_m: float, e_b: float) -> np.ndarray:
|
||||
res = []
|
||||
for o, t in product(omega, temperature):
|
||||
c = (c_double * 5)(o, tau0, e_m, e_b, t)
|
||||
user_data = cast(pointer(c), c_void_p)
|
||||
area = quad(LowLevelCallable(func, user_data), 0, np.infty, epsabs=1e-13)[0]
|
||||
|
||||
res.append(area)
|
||||
|
||||
ret_val = np.array(res).reshape(omega.shape[0], temperature.shape[0])
|
||||
|
||||
return ret_val.squeeze()
|
||||
|
||||
|
||||
def _integrate_process_real(args):
|
||||
omega_i, t, tau0, mu, sigma, temp_j = args
|
||||
return quad(_integrand_freq_real(), 0, 10, args=(omega_i, t, tau0, mu, sigma, temp_j))[0]
|
||||
def _integrate_py(func, axis, temp, tau0, e_m, e_b):
|
||||
x = np.atleast_1d(axis)
|
||||
temperature = np.atleast_1d(temp)
|
||||
|
||||
e_axis = np.linspace(max(0., e_m - 50*e_b), e_m + 50*e_b, num=5001)
|
||||
ret_val = []
|
||||
for o, tt in product(x, temperature):
|
||||
ret_val.append(simpson(func(e_axis, o, tau0, e_m, e_b, tt), e_axis))
|
||||
|
||||
ret_val = np.array(ret_val).reshape(x.shape[0], temperature.shape[0])
|
||||
|
||||
return ret_val.squeeze()
|
||||
|
||||
|
||||
def _integrate_process_time(args):
|
||||
omega_i, t, tau0, mu, sigma, temp_j = args
|
||||
return quad(_integrand_time, 0, 10, args=(omega_i, t, tau0, mu, sigma, temp_j))[0]
|
||||
|
||||
|
||||
def _integrand_freq_real(u, omega, tau0, mu, sigma, temp):
|
||||
# python integrands
|
||||
def _integrand_sd(u, omega, tau0, mu, sigma, temp):
|
||||
r = EnergyBarriers.rate(tau0, u, temp)
|
||||
return 1 / (r**2 + omega**2) * EnergyBarriers.energydistribution(u, mu, sigma)
|
||||
return r / (r**2 + omega**2) * EnergyBarriers.energy_distribution(u, mu, sigma)
|
||||
|
||||
|
||||
def _integrand_freq_imag(u, omega, tau0, mu, sigma, temp):
|
||||
def _integrand_susc_real(u, omega, tau0, mu, sigma, temp):
|
||||
r = EnergyBarriers.rate(tau0, u, temp)
|
||||
return 1 / (r**2 + omega**2) * EnergyBarriers.energy_distribution(u, mu, sigma)
|
||||
|
||||
|
||||
def _integrand_susc_imag(u, omega, tau0, mu, sigma, temp):
|
||||
rate = EnergyBarriers.rate(tau0, u, temp)
|
||||
return omega * rate / (rate**2 + omega**2) * EnergyBarriers.energydistribution(u, mu, sigma)
|
||||
return omega * rate / (rate**2 + omega**2) * EnergyBarriers.energy_distribution(u, mu, sigma)
|
||||
|
||||
|
||||
def _integrand_time(u, t, tau0, mu, sigma, temp):
|
||||
rate = EnergyBarriers.rate(tau0, u, temp)
|
||||
return EnergyBarriers.energydistribution(u, mu, sigma) * np.exp(-t*rate)
|
||||
return EnergyBarriers.energy_distribution(u, mu, sigma) * np.exp(-t*rate)
|
||||
|
48
src/nmreval/distributions/helper.py
Normal file
48
src/nmreval/distributions/helper.py
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
from pathlib import Path
|
||||
from ctypes import CDLL, c_double, c_void_p
|
||||
|
||||
from ..lib.logger import logger
|
||||
|
||||
|
||||
lib = None
|
||||
try:
|
||||
lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so'))
|
||||
|
||||
# FFHS integrand
|
||||
lib.ffhsSD.restype = c_double
|
||||
lib.ffhsSD.argtypes = (c_double, c_void_p)
|
||||
|
||||
# Log-Gaussian integrands
|
||||
lib.logGaussian_imag_high.restype = c_double
|
||||
lib.logGaussian_imag_high.argtypes = (c_double, c_void_p)
|
||||
lib.logGaussian_imag_low.restype = c_double
|
||||
lib.logGaussian_imag_low.argtypes = (c_double, c_void_p)
|
||||
|
||||
lib.logGaussian_real_high.restype = c_double
|
||||
lib.logGaussian_real_high.argtypes = (c_double, c_void_p)
|
||||
lib.logGaussian_real_low.restype = c_double
|
||||
lib.logGaussian_real_low.argtypes = (c_double, c_void_p)
|
||||
|
||||
lib.logGaussianCorrelation.restype = c_double
|
||||
lib.logGaussianCorrelation.argtypes = (c_double, c_void_p)
|
||||
|
||||
# integrands for distribution of energies
|
||||
lib.energyDist_SD.restype = c_double
|
||||
lib.energyDist_SD.argtypes = (c_double, c_void_p)
|
||||
|
||||
lib.energyDistCorrelation.restype = c_double
|
||||
lib.energyDistCorrelation.argtypes = (c_double, c_void_p)
|
||||
|
||||
lib.energyDistSuscReal.restype = c_double
|
||||
lib.energyDistSuscReal.argtypes = (c_double, c_void_p)
|
||||
lib.energyDistSuscImag.restype = c_double
|
||||
lib.energyDistSuscImag.argtypes = (c_double, c_void_p)
|
||||
|
||||
|
||||
HAS_C_FUNCS = True
|
||||
logger.info('Use C functions')
|
||||
except OSError:
|
||||
HAS_C_FUNCS = False
|
||||
logger.info('Use python functions')
|
||||
|
@ -1,9 +1,16 @@
|
||||
import ctypes
|
||||
|
||||
import numpy as np
|
||||
from scipy import LowLevelCallable
|
||||
from scipy.integrate import quad
|
||||
|
||||
from .helper import HAS_C_FUNCS, lib
|
||||
from .base import Distribution
|
||||
|
||||
|
||||
# Everything except spectral density is implemented in Python only because the only use case of FFHS is NMR
|
||||
# field cycling measurements with T1 results
|
||||
|
||||
class FFHS(Distribution):
|
||||
name = 'Intermolecular (FFHS)'
|
||||
parameter = None
|
||||
@ -19,19 +26,30 @@ class FFHS(Distribution):
|
||||
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])
|
||||
ret_val = np.array([quad(integrand, 0, np.infty, args=(tt, tau0))[0] for tt in t])
|
||||
|
||||
return ret_val
|
||||
|
||||
@staticmethod
|
||||
def specdens(omega, tau0, *args):
|
||||
def specdens_py(omega, tau0):
|
||||
def integrand(u, o, tau0):
|
||||
return u**4 * tau0 / (81 + 9*u**2 - 2*u**4 + u**6) / (u**4 + (o*tau0)**2)
|
||||
# return FFHS.distribution(u, tau0) * u / (1+o**2 * u**2)
|
||||
|
||||
ret_val = np.array([quad(integrand, 0, np.infty, args=(o, tau0), epsabs=1e-12, epsrel=1e-12)[0] for o in omega])
|
||||
ret_val = np.array([quad(integrand, 0, np.infty, args=(o, tau0))[0] for o in omega])
|
||||
|
||||
return ret_val
|
||||
return ret_val * 54 / np.pi
|
||||
|
||||
@staticmethod
|
||||
def specdens_c(omega, tau0):
|
||||
res = []
|
||||
for o in omega:
|
||||
c = (ctypes.c_double * 2)(o, tau0)
|
||||
user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p)
|
||||
func = LowLevelCallable(lib.ffhsSD, user_data)
|
||||
res.append(quad(func, 0, np.infty)[0])
|
||||
|
||||
return np.array(res) * 54 / np.pi
|
||||
|
||||
@staticmethod
|
||||
def susceptibility(omega, tau0, *args):
|
||||
@ -41,15 +59,16 @@ class FFHS(Distribution):
|
||||
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 = np.array([quad(integrand_real, 0, np.infty, args=(o, tau0))[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])
|
||||
ret_val.imag += np.array([quad(integrand_imag, 0, np.infty, args=(o, tau0))[0] for o in omega])
|
||||
|
||||
return ret_val
|
||||
|
||||
|
||||
FFHS.specdens = FFHS.specdens_c if HAS_C_FUNCS else FFHS.specdens_py
|
||||
|
||||
|
||||
# class Bessel(Distribution):
|
||||
# name = 'Intermolecular (Bessel)'
|
||||
# parameter = None
|
||||
|
@ -1,7 +1,13 @@
|
||||
import ctypes
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from itertools import product
|
||||
from typing import Callable
|
||||
|
||||
import numpy as np
|
||||
from scipy import LowLevelCallable
|
||||
from scipy.special import erf
|
||||
|
||||
from nmreval.lib.utils import ArrayLike
|
||||
|
||||
try:
|
||||
from scipy.integrate import simpson
|
||||
@ -9,89 +15,146 @@ except ImportError:
|
||||
from scipy.integrate import simps as simpson
|
||||
from scipy.integrate import quad
|
||||
|
||||
from .base import Distribution
|
||||
from nmreval.distributions.helper import HAS_C_FUNCS, lib
|
||||
from nmreval.distributions.base import Distribution
|
||||
|
||||
|
||||
__all__ = ['LogGaussian']
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
class LogGaussian(Distribution):
|
||||
name = 'Log-Gaussian'
|
||||
parameter = [r'\sigma']
|
||||
bounds = [(0, 10)]
|
||||
|
||||
@staticmethod
|
||||
def distribution(tau, tau0, sigma: float):
|
||||
def distribution(tau: ArrayLike, tau0: ArrayLike, sigma: float) -> ArrayLike:
|
||||
return np.exp(-0.5*(np.log(tau/tau0)/sigma)**2)/np.sqrt(2*np.pi)/sigma
|
||||
|
||||
@staticmethod
|
||||
def correlation(t, tau0, sigma: float):
|
||||
def correlation(t: ArrayLike, tau0: ArrayLike, sigma: float):
|
||||
_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)]
|
||||
if HAS_C_FUNCS:
|
||||
res = _integrate_correlation_c(_t, _tau, sigma)
|
||||
else:
|
||||
res = _integration_parallel(_t, _tau, sigma, _integrate_process_time)
|
||||
|
||||
with np.errstate(divide='ignore'):
|
||||
res = np.array(pool.map(_integrate_process_time, integration_ranges))
|
||||
ret_val = res.reshape((_t.shape[0], _tau.shape[0]))
|
||||
|
||||
return ret_val.squeeze()
|
||||
return res.squeeze()
|
||||
|
||||
@staticmethod
|
||||
def susceptibility(omega, tau0, sigma: float):
|
||||
def susceptibility(omega: ArrayLike, tau0: ArrayLike, sigma: float):
|
||||
_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)]
|
||||
if HAS_C_FUNCS:
|
||||
res_real = _integrate_susc_real_c(_omega, _tau, sigma)
|
||||
res_imag = _integrate_susc_imag_c(_omega, _tau, sigma)
|
||||
else:
|
||||
res_real = _integration_parallel(_omega, _tau, sigma, _integrate_process_imag)
|
||||
res_imag = _integration_parallel(_omega, _tau, sigma, _integrate_process_real)
|
||||
|
||||
with np.errstate(divide='ignore'):
|
||||
res_real = np.array(pool.map(_integrate_process_imag, integration_ranges))
|
||||
res_imag = np.array(pool.map(_integrate_process_real, integration_ranges))
|
||||
ret_val = (res_real+1j*res_imag).reshape((_omega.shape[0], _tau.shape[0]))
|
||||
|
||||
return ret_val.squeeze()
|
||||
return (res_real + 1j * res_imag).squeeze()
|
||||
|
||||
@staticmethod
|
||||
def specdens(omega, tau0, sigma):
|
||||
def specdens(omega: ArrayLike, tau: ArrayLike, sigma: float) -> np.ndarray:
|
||||
_omega = np.atleast_1d(omega)
|
||||
_tau = np.atleast_1d(tau0)
|
||||
_tau = np.atleast_1d(tau)
|
||||
|
||||
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_imag, integration_ranges))
|
||||
ret_val = res.reshape((_omega.shape[0], _tau.shape[0]))
|
||||
if HAS_C_FUNCS:
|
||||
ret_val = _integrate_susc_imag_c(_omega, _tau, sigma)
|
||||
else:
|
||||
ret_val = _integration_parallel(_omega, _tau, sigma, _integrate_process_imag)
|
||||
|
||||
ret_val /= _omega[:, None]
|
||||
ret_val[_omega == 0, :] = _tau[None, :] * np.exp(sigma**2 / 2)
|
||||
|
||||
return ret_val.squeeze()
|
||||
|
||||
def mean(*args):
|
||||
return args[0]*np.exp(args[1]**2 / 2)
|
||||
@staticmethod
|
||||
def mean(tau, sigma):
|
||||
return tau*np.exp(sigma**2 / 2)
|
||||
|
||||
|
||||
def _integrate_process_imag(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]
|
||||
def _integration_parallel(x: np.ndarray, tau: np.ndarray, sigma: float, func: Callable) -> np.ndarray:
|
||||
pool = Pool(processes=min(cpu_count(), 4))
|
||||
integration_ranges = [(x_i, tau_j, sigma) for (x_i, tau_j) in product(x, tau)]
|
||||
|
||||
with np.errstate(divide='ignore'):
|
||||
res = pool.map(func, integration_ranges)
|
||||
|
||||
res = np.array(res).reshape((x.shape[0], tau.shape[0]))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _integrate_susc_imag_c(omega: np.ndarray, tau: np.ndarray, sigma: float) -> np.ndarray:
|
||||
return _integrate_susc_c(lib.logGaussian_imag_low, lib.logGaussian_imag_high, omega, tau, sigma)
|
||||
|
||||
|
||||
def _integrate_susc_real_c(omega: np.ndarray, tau: np.ndarray, sigma: float) -> np.ndarray:
|
||||
return _integrate_susc_c(lib.logGaussian_real_low, lib.logGaussian_real_high, omega, tau, sigma)
|
||||
|
||||
|
||||
def _integrate_susc_c(lowfunc, highfunc, omega, tau, sigma):
|
||||
res = []
|
||||
|
||||
for o, t in product(omega, tau):
|
||||
c = (ctypes.c_double * 3)(o, t, sigma)
|
||||
user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p)
|
||||
|
||||
area = 0
|
||||
for (func, limits) in [(highfunc, (0, np.inf)), (lowfunc, (-np.infty, 0))]:
|
||||
epsabs = 1e-12
|
||||
while epsabs > 1e-25:
|
||||
a = quad(LowLevelCallable(func, user_data), *limits, epsabs=epsabs, epsrel=1e-12, full_output=1)
|
||||
if a[2]['last'] > 2 or a[0] < 1e-48:
|
||||
break
|
||||
epsabs /= 10.
|
||||
|
||||
area += a[0]
|
||||
|
||||
res.append(area)
|
||||
|
||||
res = np.asanyarray(res).reshape((omega.shape[0], tau.shape[0]))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _integrate_process_imag(omega, tau, sigma):
|
||||
area = quad(_integrand_freq_imag_high, 0, 50, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
|
||||
area += quad(_integrand_freq_imag_low, -50, 0, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
|
||||
|
||||
return area
|
||||
|
||||
|
||||
def _integrate_process_real(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]
|
||||
def _integrate_process_real(omega: float, tau: float, sigma: float):
|
||||
area = quad(_integrand_freq_real_high, 0, 50, args=(omega, tau, sigma))[0]
|
||||
area += quad(_integrand_freq_real_low, -50, 0, args=(omega, tau, sigma))[0]
|
||||
|
||||
return area
|
||||
|
||||
|
||||
def _integrate_process_time(args):
|
||||
omega_i, tau_j, sigma = args
|
||||
return quad(_integrand_time, -50, 50, args=(omega_i, tau_j, sigma))[0]
|
||||
def _integrate_correlation_c(t, tau, sigma):
|
||||
res = []
|
||||
|
||||
for t_i, tau_i in product(t, tau):
|
||||
c = (ctypes.c_double * 3)(t_i, tau_i, sigma)
|
||||
user_data = ctypes.cast(ctypes.pointer(c), ctypes.c_void_p)
|
||||
|
||||
area = quad(LowLevelCallable(lib.logGaussianCorrelation, user_data), -np.infty, np.infty)[0]
|
||||
|
||||
res.append(area)
|
||||
|
||||
res = np.asanyarray(res).reshape((t.shape[0], tau.shape[0]))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _integrate_process_time(omega, tau, sigma):
|
||||
return quad(_integrand_time, -50, 50, args=(omega, tau, sigma), epsabs=1e-12, epsrel=1e-12)[0]
|
||||
|
||||
|
||||
def _integrand_time(u, t, tau, sigma):
|
||||
|
@ -1,289 +0,0 @@
|
||||
import os
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import scipy.interpolate
|
||||
from scipy.integrate import simps
|
||||
|
||||
from ..io.dsc import DSCSample, Cyclohexane
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Calibrate DSC data')
|
||||
parser.add_argument('sample', type=str, help='filename of DSC sample')
|
||||
parser.add_argument('empty', type=str, help='filename of empty pan')
|
||||
parser.add_argument('reference', help='filename of reference', type=str)
|
||||
parser.add_argument('--cooling', help='Show figure of found cooling rates', action='store_true')
|
||||
|
||||
|
||||
def evaluate(sample, empty, reference, ref_points=Cyclohexane, show_cooling=False):
|
||||
sample = DSCSample(sample)
|
||||
empty = DSCSample(empty)
|
||||
reference = DSCSample(reference)
|
||||
|
||||
if show_cooling:
|
||||
fig, ax = plt.subplots()
|
||||
print('\n')
|
||||
for k, v in sample.cooling.items():
|
||||
print('Plot run {} with cooling rate {} K/min'.format(k, v))
|
||||
c = sample.flow_data(v, mode='c')
|
||||
ax.plot(c[0], c[1], label=str(v)+' K/min')
|
||||
ax.set_xlabel('T / K')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
return
|
||||
|
||||
run_list = []
|
||||
if len(sample.heating) > 1:
|
||||
run = None
|
||||
print('\nMultiple heat rates found:')
|
||||
for k, v in sample.heating.items():
|
||||
print(' run {}: {} K/min'.format(k, v))
|
||||
while run not in sample.heating:
|
||||
# choose your own adventure!!!
|
||||
value = input('\nPlease select a run (press Enter for all heat rates): ')
|
||||
if value == '':
|
||||
run_list = list(sample.heating.keys())
|
||||
break
|
||||
else:
|
||||
run = int(value)
|
||||
run_list = [run]
|
||||
else:
|
||||
run_list = list(sample.heating.keys())
|
||||
|
||||
for run in run_list:
|
||||
rate = sample.heating[run]
|
||||
|
||||
print('\nProcessing heat rate {} K/min'.format(rate))
|
||||
|
||||
print('Load data of heating data')
|
||||
len_sample = sample.length(run)
|
||||
|
||||
# sanity checks
|
||||
try:
|
||||
reference_data = reference.flow_data(rate)
|
||||
except IndexError:
|
||||
print('ERROR: Reference measurement has no heat rate {} K/min'.format(rate))
|
||||
print('Stop evaluation')
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
run_baseline = empty.get_run(rate)
|
||||
except ValueError:
|
||||
print('ERROR: Empty measurement has no heat rate {} K/min'.format(rate))
|
||||
print('Stop evaluation')
|
||||
sys.exit()
|
||||
|
||||
len_baseline = empty.length(run_baseline)
|
||||
if len_baseline != len_sample:
|
||||
print('WARNING: measurements differ by {} points'.format(abs(len_baseline - len_sample)))
|
||||
# max_length = min(len_baseline, len_sample)
|
||||
|
||||
sample_data = sample.flow_data(rate, length=None)
|
||||
empty_data = empty.flow_data(rate, length=None)
|
||||
|
||||
# plot input data
|
||||
fig1, ax1 = plt.subplots(2, 3, **{'figsize': (10, 6)})
|
||||
ax1[0, 0].set_title('raw data')
|
||||
ax1[0, 0].set_xlabel('T / K')
|
||||
|
||||
ax1[0, 0].plot(sample_data[0], sample_data[1], 'k-', label='Sample')
|
||||
ax1[0, 0].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
|
||||
ax1[0, 0].plot(reference_data[0], reference_data[1], 'r-', label='Reference')
|
||||
ax1[0, 0].legend()
|
||||
|
||||
print('Substract empty data\n')
|
||||
sample_baseline = sample_data.copy()
|
||||
empty_y = empty_data[1]
|
||||
if len_sample != len_baseline:
|
||||
with np.errstate(all='ignore'):
|
||||
empty_y = scipy.interpolate.interp1d(empty_data[0], empty_data[1],
|
||||
fill_value='extrapolate')(sample_data[0])
|
||||
sample_baseline[1] = sample_data[1] - empty_y
|
||||
|
||||
# plot baseline correction
|
||||
ax1[0, 1].set_title('baseline correction')
|
||||
ax1[0, 1].set_xlabel('T / K')
|
||||
|
||||
ax1[0, 1].plot(sample_data[0], sample_data[1], 'k--', label='Raw')
|
||||
ax1[0, 1].plot(sample_baseline[0], sample_baseline[1], 'k-', label='Baseline corrected')
|
||||
ax1[0, 1].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
|
||||
ax1[0, 1].legend()
|
||||
|
||||
print('Load isothermal data around heat rate')
|
||||
mean_isotherms = []
|
||||
for offset, where, ls in [(-1, 'low', '-'), (1, 'high', '--')]:
|
||||
# read isotherms and baseline correct
|
||||
len_baseline = empty.length(run_baseline+offset)
|
||||
len_sample = sample.length(run+offset)
|
||||
if len_baseline != len_sample:
|
||||
print('WARNING: {} T isotherms differ by {} points'.format(where, abs(len_baseline-len_sample)))
|
||||
|
||||
max_length = min(len_baseline, len_sample)
|
||||
isotherm_sample = sample.isotherm_data(run_baseline+offset, length=max_length)
|
||||
isotherm_empty = empty.isotherm_data(run+offset, length=max_length)
|
||||
|
||||
isotherm_sample[1] -= isotherm_empty[1]
|
||||
|
||||
# get mean isotherm value
|
||||
m = np.polyfit(isotherm_sample[0, 200:-200], isotherm_sample[1, 200:-200], 0)[0]
|
||||
mean_isotherms.append(m)
|
||||
print('Calculated {} heat flow: {:.4} mW'.format(where, m))
|
||||
|
||||
ax1[0, 2].plot(isotherm_sample[0], isotherm_sample[1], 'k--')
|
||||
|
||||
# calculate slope from difference between isotherms
|
||||
slope = (mean_isotherms[1]-mean_isotherms[0]) / (sample_data[2, -1] - empty_data[2, 0])
|
||||
print('Heat flow slope from isotherms: {:.4} per minute'.format(slope*60))
|
||||
|
||||
# calculate mean slope of heat flow at points in the beginning
|
||||
slope_baseline = np.gradient(sample_baseline[1, int(4000/rate):int(9000/rate)],
|
||||
sample_baseline[2, 300]-sample_baseline[2, 299]).mean()
|
||||
print('Heat flow slope from initial heating: {:.4f} per minute\n'.format(slope_baseline*60))
|
||||
|
||||
drift_corrected = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope
|
||||
drift_from_slope = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope_baseline
|
||||
|
||||
# plot
|
||||
ax1[0, 2].axhline(mean_isotherms[0], linestyle=':')
|
||||
ax1[0, 2].axhline(mean_isotherms[1], linestyle=':')
|
||||
|
||||
ax1[0, 2].plot(sample_baseline[2], sample_baseline[1], 'k-', label='Baseline corrected')
|
||||
ax1[0, 2].plot(sample_baseline[2], drift_corrected, 'g-', label='Corrected (isotherm)')
|
||||
ax1[0, 2].plot(sample_baseline[2], drift_from_slope, 'b-', label='Corrected (heating)')
|
||||
|
||||
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + (sample_baseline[2]-empty_data[2, 0])*slope, 'g--')
|
||||
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + slope_baseline*(sample_baseline[2]-empty_data[2, 0]),
|
||||
'b--')
|
||||
|
||||
ax1[0, 2].plot(sample_baseline[2, int(4000/rate):int(9000/rate)],
|
||||
sample_baseline[1, int(4000/rate):int(9000/rate)], 'r--')
|
||||
|
||||
ax1[0, 2].set_title('time dependence')
|
||||
ax1[0, 2].set_xlabel('t / s')
|
||||
ax1[0, 2].legend()
|
||||
|
||||
melts = []
|
||||
for i, (ref_temp, enthalpy) in enumerate(ref_points.transitions):
|
||||
# region around reference peaks
|
||||
t_low_lim = ref_temp - 15
|
||||
t_high_lim = ref_temp + 15
|
||||
low_border = np.argmin(np.abs(reference_data[0]-t_low_lim))
|
||||
high_border = np.argmin(np.abs(reference_data[0]-t_high_lim))
|
||||
ref_zoom = reference_data[:, low_border:high_border]
|
||||
x_val = np.array([[ref_zoom[0, 0], 1],
|
||||
[ref_zoom[0, -1], 1]])
|
||||
y_val = np.array([ref_zoom[1, 0],
|
||||
ref_zoom[1, -1]])
|
||||
print('Baseline correct reference of %.2f transition' % ref_temp)
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
|
||||
peak_max = ref_zoom[:, np.argmax(ref_zoom[1])]
|
||||
integration_limit = np.argmin(abs(ref_zoom[0]-peak_max[0]+3)), np.argmin(abs(ref_zoom[0]-peak_max[0]-3))
|
||||
|
||||
# substract baseline around reference peaks
|
||||
x_val = np.array([[ref_zoom[0, integration_limit[0]], 1],
|
||||
[ref_zoom[0, integration_limit[1]], 1]])
|
||||
y_val = np.array([ref_zoom[1, integration_limit[0]],
|
||||
ref_zoom[1, integration_limit[1]]])
|
||||
|
||||
print('Baseline correct reference of %.2f transition' % ref_temp)
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
|
||||
|
||||
# calculate onset slope (use points at position of maximum gradient +/- 100/rate)
|
||||
ref_grad = np.gradient(ref_zoom[1])
|
||||
max_grad = np.argmax(ref_grad)
|
||||
|
||||
x_val = np.array([[ref_zoom[0, max_grad-int(100/rate)], 1],
|
||||
[ref_zoom[0, max_grad+int(100/rate)+1], 1]])
|
||||
y_val = np.array([ref_zoom[1, max_grad-int(100/rate)],
|
||||
ref_zoom[1, max_grad+int(100/rate)+1]])
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
onset = sol[0]*ref_zoom[0] + sol[1]
|
||||
|
||||
melts.append(-sol[1]/sol[0])
|
||||
|
||||
# plot
|
||||
ax1[1, i].set_title(f'reference: {ref_temp:.2f}')
|
||||
ax1[1, i].set_xlabel('T / K')
|
||||
|
||||
ax1[1, i].plot(reference_data[0], reference_data[1], 'r-')
|
||||
ax1[1, i].plot(ref_zoom[0, max_grad], ref_zoom[0, max_grad], 'kx')
|
||||
ax1[1, i].plot(ref_zoom[0], onset, 'k--')
|
||||
ax1[1, i].axhline(0, color='k', linestyle='--')
|
||||
|
||||
ax1[1, i].set_xlim(ref_zoom[0, integration_limit[0]], ref_zoom[0, integration_limit[1]])
|
||||
ax1[1, i].set_ylim(-max(ref_zoom[1])/10, max(ref_zoom[1])*1.1)
|
||||
|
||||
print('Onset of transition: %.2f K found at %.2f' % (ref_temp, melts[-1]))
|
||||
if enthalpy is not None:
|
||||
# integrate over low temperature peak to calibrate y axis
|
||||
# delta H in J/g: Integrate Peak over time and divide by weight
|
||||
area = 1e-3 * simps(ref_zoom[1, integration_limit[0]:integration_limit[1]],
|
||||
ref_zoom[2, integration_limit[0]:integration_limit[1]],
|
||||
even='avg')
|
||||
calib_y_axis = enthalpy / (area / reference.weight)
|
||||
print("Calibration factor of peak: %f\n" % calib_y_axis)
|
||||
|
||||
sample_baseline[1] *= calib_y_axis
|
||||
|
||||
fig1.delaxes(ax1[1, 2])
|
||||
fig1.tight_layout()
|
||||
|
||||
plt.show()
|
||||
|
||||
# give a choice how to compensate for long-time drift
|
||||
mode = None
|
||||
while mode not in ['i', 'h']:
|
||||
mode = input('\nUse [i]sotherms or initial [h]eating for long-time correction? (Default: i) ')
|
||||
if mode == '':
|
||||
mode = 'i'
|
||||
|
||||
if mode == 'h':
|
||||
print('\nCorrect slope from initial heating')
|
||||
sample_baseline[1] = drift_from_slope
|
||||
else:
|
||||
print('\nCorrect slope from isotherm')
|
||||
sample_baseline[1] = drift_corrected
|
||||
|
||||
# calibrate T axis
|
||||
print('\nCalibrate temperature')
|
||||
real_trans = np.array([temp for (temp, _) in ref_points.transitions])
|
||||
t_vals = np.array([[melts[0], 1],
|
||||
[melts[1], 1]])
|
||||
calibration_temp = np.linalg.solve(t_vals, real_trans)
|
||||
print('T_real = {:.4f} * T_meas {:+.4f}'.format(*calibration_temp))
|
||||
|
||||
sample_baseline[0] = calibration_temp[0] * sample_baseline[0] + calibration_temp[1]
|
||||
|
||||
print('Convert to capacity')
|
||||
cp = sample_baseline[1] * 60. / rate / sample.weight / 1000.
|
||||
if sample.weight is None:
|
||||
raise ValueError('No sample weight given')
|
||||
|
||||
# plot final results in separate figure
|
||||
fig2, ax2 = plt.subplots()
|
||||
ax2.set_title('{} K/min: Heat flow vs. heat capacity (close to cont.)'.format(rate))
|
||||
ax2.set_xlabel('Temperature / K')
|
||||
|
||||
ax2.plot(sample_baseline[0], sample_baseline[1], label='heat flow')
|
||||
ax2.plot(sample_baseline[0], cp, label='heat capacity')
|
||||
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
outname = os.path.splitext(sample.name)[0] + '_' + str(rate) + 'K-min.dat'
|
||||
header = 'Made with version: {}\n'.format(__version__)
|
||||
header += 'T/K\tCp/J/(gK)\theat flow/mW'
|
||||
|
||||
print()
|
||||
print('Save to', outname)
|
||||
np.savetxt(outname, np.c_[sample_baseline[0], cp, sample_baseline[1]], header=header)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
evaluate(args.sample, args.empty, args.reference, show_cooling=args.cooling)
|
@ -1,292 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = '0.1.2'
|
||||
|
||||
import os
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.integrate import simps
|
||||
|
||||
from nmreval.io.dsc import DSCReader, Cyclohexane, ReferenceValue
|
||||
|
||||
parser = ArgumentParser(description='Calibrate DSC data')
|
||||
parser.add_argument('sample', type=str, help='filename of DSC sample')
|
||||
parser.add_argument('empty', type=str, help='filename of empty pan')
|
||||
parser.add_argument('reference', help='filename of reference', type=str)
|
||||
parser.add_argument('--cooling', help='Plot found cooling rates', action='store_true')
|
||||
|
||||
|
||||
def evaluate(sample: str|Path, empty: str|Path, reference: str|Path,
|
||||
ref_points: ReferenceValue = Cyclohexane, show_cooling: bool = False):
|
||||
|
||||
sample = DSCReader(sample)
|
||||
empty = DSCReader(empty)
|
||||
reference = DSCReader(reference)
|
||||
print(sample)
|
||||
|
||||
if show_cooling:
|
||||
fig, ax = plt.subplots()
|
||||
print('\n')
|
||||
for k, v in sample.cooling.items():
|
||||
print('Plot run {} with cooling rate {} K/min'.format(k, v))
|
||||
c = sample.flow_data(v, mode='c')
|
||||
ax.plot(c[0], c[1], label=str(v)+' K/min')
|
||||
ax.set_xlabel('T / K')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
return
|
||||
|
||||
run_list = []
|
||||
if len(sample.heating) > 1:
|
||||
run = None
|
||||
print('\nMultiple heat rates found:')
|
||||
for k, v in sample.heating.items():
|
||||
print(' run {}: {} K/min'.format(k, v))
|
||||
while run not in sample.heating:
|
||||
# choose your own adventure!!!
|
||||
value = input('\nPlease select a run (press Enter for all heat rates): ')
|
||||
if value == '':
|
||||
run_list = list(sample.heating.keys())
|
||||
break
|
||||
else:
|
||||
run = int(value)
|
||||
run_list = [run]
|
||||
else:
|
||||
run_list = list(sample.heating.keys())
|
||||
|
||||
for run in run_list:
|
||||
rate = sample.heating[run]
|
||||
|
||||
print('\nProcessing heat rate {} K/min'.format(rate))
|
||||
|
||||
print('Load data of heating data')
|
||||
len_sample = sample.length(run)
|
||||
|
||||
# sanity checks
|
||||
try:
|
||||
reference_data = reference.flow_data(rate)
|
||||
except IndexError:
|
||||
print('ERROR: Reference measurement has no heat rate {} K/min'.format(rate))
|
||||
print('Stop evaluation')
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
run_baseline = empty.get_run(rate)
|
||||
except ValueError:
|
||||
print('ERROR: Empty measurement has no heat rate {} K/min'.format(rate))
|
||||
print('Stop evaluation')
|
||||
sys.exit()
|
||||
|
||||
len_baseline = empty.length(run_baseline)
|
||||
max_length = None
|
||||
if len_baseline != len_sample:
|
||||
print('WARNING: measurements differ by {} points'.format(abs(len_baseline - len_sample)))
|
||||
max_length = min(len_baseline, len_sample)
|
||||
|
||||
sample_data = sample.flow_data(rate, length=max_length)
|
||||
empty_data = empty.flow_data(rate, length=max_length)
|
||||
|
||||
# plot input data
|
||||
fig1, ax1 = plt.subplots(2, 3, **{'figsize': (10, 6)})
|
||||
ax1[0, 0].set_title('raw data')
|
||||
ax1[0, 0].set_xlabel('T / K')
|
||||
|
||||
ax1[0, 0].plot(sample_data[0], sample_data[1], 'k-', label='Sample')
|
||||
ax1[0, 0].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
|
||||
ax1[0, 0].plot(reference_data[0], reference_data[1], 'r-', label='Reference')
|
||||
ax1[0, 0].legend()
|
||||
|
||||
print('Substract empty data')
|
||||
sample_baseline = sample_data.copy()
|
||||
sample_baseline[1] = sample_data[1] - empty_data[1]
|
||||
|
||||
# plot baseline correction
|
||||
ax1[0, 1].set_title('baseline correction')
|
||||
ax1[0, 1].set_xlabel('T / K')
|
||||
|
||||
ax1[0, 1].plot(sample_data[0], sample_data[1], 'k--', label='Raw')
|
||||
ax1[0, 1].plot(sample_baseline[0], sample_baseline[1], 'k-', label='Baseline corrected')
|
||||
ax1[0, 1].plot(empty_data[0], empty_data[1], 'b-', label='Empty')
|
||||
ax1[0, 1].legend()
|
||||
|
||||
print('Load isothermal data around heat rate')
|
||||
mean_isotherms = []
|
||||
for offset, where, ls in [(-1, 'low', '-'), (1, 'high', '--')]:
|
||||
# read isotherms and baseline correct
|
||||
len_baseline = empty.length(run_baseline+offset)
|
||||
len_sample = sample.length(run+offset)
|
||||
if len_baseline != len_sample:
|
||||
print('WARNING: {} T isotherms differ by {} points'.format(where, abs(len_baseline-len_sample)))
|
||||
|
||||
max_length = min(len_baseline, len_sample)
|
||||
isotherm_sample = sample.isotherm_data(run_baseline+offset, length=max_length)
|
||||
isotherm_empty = empty.isotherm_data(run+offset, length=max_length)
|
||||
|
||||
isotherm_sample[1] -= isotherm_empty[1]
|
||||
|
||||
# get mean isotherm value
|
||||
m = np.polyfit(isotherm_sample[0, 200:-200], isotherm_sample[1, 200:-200], 0)[0]
|
||||
mean_isotherms.append(m)
|
||||
print('Calculated {} heat flow: {} mW'.format(where, m))
|
||||
|
||||
ax1[0, 2].plot(isotherm_sample[0], isotherm_sample[1], 'k--')
|
||||
|
||||
# calculate slope from difference between isotherms
|
||||
slope = (mean_isotherms[1]-mean_isotherms[0]) / (sample_data[2, -1] - empty_data[2, 0])
|
||||
print('Heat flow slope from isotherms: {} per minute'.format(slope*60))
|
||||
|
||||
# calculate mean slope of heat flow at points in the beginning
|
||||
slope_baseline = np.gradient(sample_baseline[1, int(4000/rate):int(9000/rate)],
|
||||
sample_baseline[2, 300]-sample_baseline[2, 299]).mean()
|
||||
print('Heat flow slope from initial heating: {} per minute'.format(slope_baseline*60))
|
||||
|
||||
drift_corrected = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope
|
||||
drift_from_slope = sample_baseline[1] - mean_isotherms[0] - (sample_baseline[2]-empty_data[2, 0])*slope_baseline
|
||||
|
||||
# plot
|
||||
ax1[0, 2].axhline(mean_isotherms[0], linestyle=':')
|
||||
ax1[0, 2].axhline(mean_isotherms[1], linestyle=':')
|
||||
|
||||
ax1[0, 2].plot(sample_baseline[2], sample_baseline[1], 'k-', label='Baseline corrected')
|
||||
ax1[0, 2].plot(sample_baseline[2], drift_corrected, 'g-', label='Corrected (isotherm)')
|
||||
ax1[0, 2].plot(sample_baseline[2], drift_from_slope, 'b-', label='Corrected (heating)')
|
||||
|
||||
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + (sample_baseline[2]-empty_data[2, 0])*slope, 'g--')
|
||||
ax1[0, 2].plot(sample_baseline[2], mean_isotherms[0] + slope_baseline*(sample_baseline[2]-empty_data[2, 0]),
|
||||
'b--')
|
||||
|
||||
ax1[0, 2].plot(sample_baseline[2, int(4000/rate):int(9000/rate)],
|
||||
sample_baseline[1, int(4000/rate):int(9000/rate)], 'r--')
|
||||
|
||||
ax1[0, 2].set_title('time dependence')
|
||||
ax1[0, 2].set_xlabel('t / s')
|
||||
ax1[0, 2].legend()
|
||||
|
||||
melts = []
|
||||
for i, (trans_temp, enthalpy) in enumerate(ref_points.transitions):
|
||||
print(trans_temp, enthalpy)
|
||||
|
||||
# region around reference peaks
|
||||
# NOTE: limits are hard coded for cyclohexane, other references need other limits
|
||||
low_border = np.argmin(abs(reference_data[0]-(trans_temp-15)))
|
||||
high_border = np.argmin(abs(reference_data[0]-(trans_temp+15)))
|
||||
ref_zoom = reference_data[:, low_border:high_border]
|
||||
x_val = np.array([[ref_zoom[0, 0], 1],
|
||||
[ref_zoom[0, -1], 1]])
|
||||
y_val = np.array([ref_zoom[1, 0],
|
||||
ref_zoom[1, -1]])
|
||||
print('Baseline correct reference of {} transition'.format(trans_temp))
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
|
||||
peak_max = ref_zoom[:, np.argmax(ref_zoom[1])]
|
||||
integration_limit = np.argmin(abs(ref_zoom[0]-peak_max[0]+3)), np.argmin(abs(ref_zoom[0]-peak_max[0]-3))
|
||||
|
||||
# substract baseline around reference peaks
|
||||
x_val = np.array([[ref_zoom[0, integration_limit[0]], 1],
|
||||
[ref_zoom[0, integration_limit[1]], 1]])
|
||||
y_val = np.array([ref_zoom[1, integration_limit[0]],
|
||||
ref_zoom[1, integration_limit[1]]])
|
||||
|
||||
print('Baseline correct reference of {} transition'.format(trans_temp))
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
|
||||
|
||||
# calculate onset slope (use points at position of maximum gradient +/- 100/rate)
|
||||
ref_grad = np.gradient(ref_zoom[1])
|
||||
max_grad = np.argmax(ref_grad)
|
||||
|
||||
x_val = np.array([[ref_zoom[0, max_grad-int(100/rate)], 1],
|
||||
[ref_zoom[0, max_grad+int(100/rate)+1], 1]])
|
||||
y_val = np.array([ref_zoom[1, max_grad-int(100/rate)],
|
||||
ref_zoom[1, max_grad+int(100/rate)+1]])
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
onset = sol[0]*ref_zoom[0] + sol[1]
|
||||
|
||||
melts.append(-sol[1]/sol[0])
|
||||
|
||||
# plot
|
||||
ax1[1, i].set_title('reference: {:.2f} K'.format(trans_temp))
|
||||
ax1[1, i].set_xlabel('T / K')
|
||||
|
||||
ax1[1, i].plot(reference_data[0], reference_data[1], 'r-')
|
||||
ax1[1, i].plot(ref_zoom[0, max_grad], ref_zoom[0, max_grad], 'kx')
|
||||
ax1[1, i].plot(ref_zoom[0], onset, 'k--')
|
||||
ax1[1, i].axhline(0, color='k', linestyle='--')
|
||||
|
||||
ax1[1, i].set_xlim(ref_zoom[0, integration_limit[0]], ref_zoom[0, integration_limit[1]])
|
||||
ax1[1, i].set_ylim(-max(ref_zoom[1])/10, max(ref_zoom[1])*1.1)
|
||||
|
||||
print('Onset of transition: {:.2f} K, should be at {:.2f}'.format(melts[-1], trans_temp))
|
||||
if enthalpy is not None:
|
||||
# integrate over low temperature peak to calibrate y axis
|
||||
# NOTE: again, this is only valid for cyclohexane
|
||||
# delta H in J/g: Integrate Peak over time and divide by weight
|
||||
area = 1e-3 * simps(ref_zoom[1, integration_limit[0]:integration_limit[1]],
|
||||
ref_zoom[2, integration_limit[0]:integration_limit[1]],
|
||||
even='avg')
|
||||
calib_y_axis = enthalpy / (area / reference.weight)
|
||||
print("Calibration factor of peak: {}".format(calib_y_axis))
|
||||
|
||||
sample_baseline[1] *= calib_y_axis
|
||||
|
||||
fig1.delaxes(ax1[1, 2])
|
||||
fig1.tight_layout()
|
||||
|
||||
plt.show()
|
||||
|
||||
# give a choice how to compensate for long-time drift
|
||||
mode = None
|
||||
while mode not in ['i', 'h']:
|
||||
mode = input('\nUse [i]sotherms or initial [h]eating for long-time correction? (Default: i) ')
|
||||
if mode == '':
|
||||
mode = 'i'
|
||||
|
||||
if mode == 'h':
|
||||
print('\nCorrect slope from initial heating')
|
||||
sample_baseline[1] = drift_from_slope
|
||||
else:
|
||||
print('\nCorrect slope from isotherm')
|
||||
sample_baseline[1] = drift_corrected
|
||||
|
||||
# calibrate T axis
|
||||
print('\nCalibrate temperature')
|
||||
real_trans = np.array([ref_points.transition1, ref_points.transition2])
|
||||
t_vals = np.array([[melts[0], 1],
|
||||
[melts[1], 1]])
|
||||
calibration_temp = np.linalg.solve(t_vals, real_trans)
|
||||
print('T_real = {:.4f} * T_meas {:+.4f}'.format(*calibration_temp))
|
||||
|
||||
sample_baseline[0] = calibration_temp[0] * sample_baseline[0] + calibration_temp[1]
|
||||
|
||||
print('Convert to capacity')
|
||||
cp = sample_baseline[1] * 60. / rate / sample.weight / 1000.
|
||||
if sample.weight is None:
|
||||
raise ValueError('No sample weight given')
|
||||
|
||||
# plot final results in separate figure
|
||||
fig2, ax2 = plt.subplots()
|
||||
ax2.set_title('{} K/min: Heat flow vs. heat capacity (close to cont.)'.format(rate))
|
||||
ax2.set_xlabel('Temperature / K')
|
||||
|
||||
ax2.plot(sample_baseline[0], sample_baseline[1], label='heat flow')
|
||||
ax2.plot(sample_baseline[0], cp, label='heat capacity')
|
||||
|
||||
plt.legend()
|
||||
plt.show()
|
||||
|
||||
outname = os.path.splitext(sample.name)[0] + '_' + str(rate) + 'K-min.dat'
|
||||
header = 'Made with version: {}\n'.format(__version__)
|
||||
header += 'T/K\tCp/J/(gK)\theat flow/mW'
|
||||
|
||||
print()
|
||||
print('Save to', outname)
|
||||
np.savetxt(outname, np.c_[sample_baseline[0], cp, sample_baseline[1]], header=header)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
evaluate(args.sample, args.empty, args.reference, show_cooling=args.cooling)
|
27
src/nmreval/dsc/hodge.py
Normal file
27
src/nmreval/dsc/hodge.py
Normal file
@ -0,0 +1,27 @@
|
||||
import numpy as np
|
||||
from scipy.stats import linregress
|
||||
|
||||
from nmreval.data import Points
|
||||
from nmreval.fit.minimizer import FitRoutine
|
||||
from nmreval.fit.result import FitResult
|
||||
from nmreval.lib.utils import ArrayLike
|
||||
from nmreval.models import Arrhenius
|
||||
from nmreval.utils import kB
|
||||
|
||||
|
||||
def tau_hodge(tg: ArrayLike, rate: ArrayLike) -> (Points, FitResult):
|
||||
rate = np.asanyarray(rate)
|
||||
tg = np.asanyarray(tg)
|
||||
fitter = FitRoutine()
|
||||
fitter.set_model(Arrhenius)
|
||||
d = fitter.add_data(1000/tg, rate, we='y')
|
||||
|
||||
init = linregress(1000/tg, np.log(rate))
|
||||
|
||||
d.set_parameter([np.exp(init.intercept), 1000*init.slope*kB], fun_kwargs={'invt': 'invt1000'})
|
||||
|
||||
res = fitter.run()[0]
|
||||
de = res.parameter['E_{A}']
|
||||
tau = kB*tg**2/np.abs(de.value)/rate*60
|
||||
|
||||
return Points(x=1000/tg, y=tau, y_err=tau*de.error/np.abs(de.value), name='Hodge'), res
|
46
src/nmreval/dsc/tnmh_model.py
Normal file
46
src/nmreval/dsc/tnmh_model.py
Normal file
@ -0,0 +1,46 @@
|
||||
import numpy as np
|
||||
|
||||
from nmreval.utils.constants import R_joule as R
|
||||
|
||||
|
||||
class TNMH:
|
||||
type = 'DSC'
|
||||
name = 'TNMH model'
|
||||
equation = r''
|
||||
params = [r'\tau_{g}', 'x', r'\beta', r'\Delta H', 'T_{g}', 'rate']
|
||||
bounds = [(0, None), (0, 1), (0, 1), (0, None), (0, None), (0, None)]
|
||||
|
||||
@staticmethod
|
||||
def func(x: np.ndarray, tau_g: float, xx: float, beta: float, energy: float, tg: float, rate: float) -> np.ndarray:
|
||||
model_temp = np.r_[x[::-1], x[1:]]
|
||||
|
||||
curve = TNMH.tnm_function(model_temp, tau_g, xx, beta, energy, tg, rate)[x.size - 1:]
|
||||
res = np.gradient(curve, x)
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def tnm_function(x: np.ndarray, tau_g: float, xx: float, beta: float, energy: float, tg: float, rate: float) -> np.ndarray:
|
||||
step = x.size
|
||||
|
||||
Tf = np.empty(step)
|
||||
Tf[0] = x[0]
|
||||
|
||||
delta_temp = np.diff(x)
|
||||
delta_time = np.abs(delta_temp) * 60 / rate
|
||||
|
||||
tau = np.empty(step)
|
||||
dt_by_tau = np.zeros(step)
|
||||
temp_0 = x[0]
|
||||
|
||||
for i in range(0, step - 1):
|
||||
tau[i] = TNMH.relax(x[i+1], Tf[i], tau_g, tg, energy, xx)
|
||||
dt_by_tau[:i] += delta_time[i] / tau[i]
|
||||
Tf[i + 1] = np.sum(delta_temp[:i] * (1 - np.exp(-dt_by_tau[:i] ** beta))) + temp_0
|
||||
|
||||
return Tf
|
||||
|
||||
@staticmethod
|
||||
def relax(t, tf, tau_g, t_glass, ea, x):
|
||||
h = ea/R
|
||||
return tau_g * np.exp((x*h / t) + ((1 - x) * h / tf) - h / t_glass)
|
@ -24,9 +24,10 @@ class ModelFactory:
|
||||
param_len.append(len(func['func'].params))
|
||||
|
||||
if func['children']:
|
||||
right, _, _ = ModelFactory.create_from_list(func['children'], left=func['func'], left_cnt=func['pos'],
|
||||
right, _, _ = ModelFactory.create_from_list(func['children'], left_cnt=func['pos'],
|
||||
func_order=func_order, param_len=param_len)
|
||||
right_cnt = None
|
||||
right = MultiModel(func['func'], right, func['children'][0]['op'], left_idx=func['cnt'], right_idx=None)
|
||||
else:
|
||||
right = func['func']
|
||||
right_cnt = func['cnt']
|
||||
@ -46,7 +47,13 @@ class MultiModel:
|
||||
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: str | Callable | int = '+', left_idx=0, right_idx=1):
|
||||
def __init__(self,
|
||||
left: Any,
|
||||
right: Any,
|
||||
op: str | Callable | int = '+',
|
||||
left_idx: int | None = 0,
|
||||
right_idx: int | None = 1,
|
||||
):
|
||||
self._left = left
|
||||
self._right = right
|
||||
|
||||
@ -68,6 +75,7 @@ class MultiModel:
|
||||
self._kwargs_right = {}
|
||||
self._kwargs_left = {}
|
||||
self.fun_kwargs = {}
|
||||
self.idx = (left_idx, right_idx)
|
||||
|
||||
# mapping kwargs to kwargs of underlying functions
|
||||
self._ext_int_kw = {}
|
||||
@ -178,13 +186,13 @@ class MultiModel:
|
||||
if isinstance(self._left, MultiModel):
|
||||
yield from self._left.sub_name()
|
||||
elif hasattr(self._left, 'name'):
|
||||
yield self._left.name
|
||||
yield f'{self._left.name}({self.idx[0]})'
|
||||
else:
|
||||
yield self.name + '(lhs)'
|
||||
|
||||
if isinstance(self._right, MultiModel):
|
||||
yield from self._right.sub_name()
|
||||
elif hasattr(self._right, 'name'):
|
||||
yield self._right.name
|
||||
yield f'{self._right.name}({self.idx[1]})'
|
||||
else:
|
||||
yield self.name + '(rhs)'
|
||||
|
@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .model import Model
|
||||
from .parameter import Parameters
|
||||
from .parameter import Parameters, Parameter
|
||||
|
||||
|
||||
class Data(object):
|
||||
@ -11,27 +13,27 @@ class Data(object):
|
||||
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.we, self.we_string = self._calc_weights(we)
|
||||
self.idx = idx
|
||||
self.model = None
|
||||
self.minimizer = None
|
||||
self.parameter = Parameters()
|
||||
self.para_keys = None
|
||||
self.para_keys: list = []
|
||||
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':
|
||||
def _calc_weights(self, we: str | np.ndarray | None) -> tuple[np.ndarray, str]:
|
||||
if isinstance(we, str) or we is None:
|
||||
if we is None or we.lower() == 'none':
|
||||
we_string = 'None'
|
||||
we_func = lambda yy: np.ones_like(len(yy))
|
||||
elif we == 'y2':
|
||||
we_string = we
|
||||
we_func = lambda yy: 1. / yy**2
|
||||
else:
|
||||
we_string = we
|
||||
we_func = lambda yy: 1. / np.abs(yy)
|
||||
|
||||
if np.iscomplexobj(self.y):
|
||||
@ -40,6 +42,7 @@ class Data(object):
|
||||
weights = we_func(self.y)
|
||||
|
||||
else:
|
||||
we_string = 'yerr' # This is pure speculation that array equals error
|
||||
we = 1. / np.asarray(we)
|
||||
if np.iscomplexobj(self.y):
|
||||
if np.iscomplexobj(we):
|
||||
@ -51,7 +54,7 @@ class Data(object):
|
||||
|
||||
weights[weights == np.inf] = np.finfo(float).max
|
||||
|
||||
return weights
|
||||
return weights, we_string
|
||||
|
||||
def set_model(self, func, *args, **kwargs):
|
||||
if isinstance(func, Model):
|
||||
@ -68,12 +71,19 @@ class Data(object):
|
||||
def get_model(self):
|
||||
return self.model
|
||||
|
||||
def set_parameter(self, parameter, var=None, ub=None, lb=None,
|
||||
default_bounds=False, fun_kwargs=None):
|
||||
def set_parameter(self,
|
||||
values: list[float | Parameter],
|
||||
*,
|
||||
var: list[bool] = None,
|
||||
ub: list[float] = None,
|
||||
lb: list[float] = None,
|
||||
default_bounds: bool = False,
|
||||
fun_kwargs: dict = None
|
||||
):
|
||||
"""
|
||||
Creates parameter for this data.
|
||||
If no Model is available, it falls back to the model
|
||||
:param parameter: list of parameters
|
||||
:param values: list of parameters
|
||||
:param var: list of boolean or boolean; False fixes parameter at given list index.
|
||||
Single value is broadcast to all parameter
|
||||
:param ub: list of upper boundaries or float; Single value is broadcast to all parameter.
|
||||
@ -87,23 +97,46 @@ class Data(object):
|
||||
model = self.model
|
||||
if model is None:
|
||||
# Data has no unique
|
||||
if self.minimizer is None:
|
||||
model = None
|
||||
else:
|
||||
if self.minimizer is not None:
|
||||
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 len(values) != len(model.params):
|
||||
raise ValueError('Number of given parameter does not match number of model parameters')
|
||||
|
||||
is_parameter = [isinstance(v, Parameter) for v in values]
|
||||
if all(is_parameter):
|
||||
for p_i in values:
|
||||
key = f"p{next(Parameters.parameter_counter)}"
|
||||
self.parameter.add_parameter(key, p_i)
|
||||
elif any(is_parameter):
|
||||
raise ValueError('list of parameter are not all float of Parameter')
|
||||
|
||||
else:
|
||||
if var is None:
|
||||
var = [True] * len(values)
|
||||
|
||||
if lb is None:
|
||||
lb = model.lb
|
||||
if default_bounds:
|
||||
lb = model.lb
|
||||
else:
|
||||
lb = [None] * len(values)
|
||||
|
||||
if ub is None:
|
||||
ub = model.ub
|
||||
if default_bounds:
|
||||
ub = model.ub
|
||||
else:
|
||||
ub = [None] * len(values)
|
||||
|
||||
self.para_keys = self.parameter.add_parameter(parameter, var=var, lb=lb, ub=ub)
|
||||
arg_names = ['name', 'value', 'var', 'lb', 'ub']
|
||||
for parameter_arg in zip(model.params, values, var, lb, ub):
|
||||
self.parameter.add(**{arg_name: arg_value for arg_name, arg_value in zip(arg_names, parameter_arg)})
|
||||
|
||||
self.para_keys = list(self.parameter.keys())
|
||||
|
||||
self.fun_kwargs.update(model.fun_kwargs)
|
||||
if fun_kwargs is not None:
|
||||
self.fun_kwargs.update(fun_kwargs)
|
||||
|
||||
@ -123,6 +156,18 @@ class Data(object):
|
||||
else:
|
||||
return [p.value for p in self.minimizer.parameters[self.parameter]]
|
||||
|
||||
def replace_parameter(self, key: str, parameter: Parameter) -> None:
|
||||
tobereplaced = None
|
||||
for k, v in self.parameter.items():
|
||||
if v.name == parameter.name:
|
||||
tobereplaced = k
|
||||
break
|
||||
|
||||
if tobereplaced is None:
|
||||
raise KeyError(f'Global parameter {key} not found in list of parameters')
|
||||
self.para_keys[self.para_keys.index(tobereplaced)] = key
|
||||
self.parameter.replace_parameter(tobereplaced, key, parameter)
|
||||
|
||||
def cost(self, p):
|
||||
"""
|
||||
Cost function :math:`y-f(p, x)`
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from itertools import product
|
||||
|
||||
@ -14,21 +16,81 @@ from .result import FitResultCreator
|
||||
|
||||
__all__ = ['FitRoutine', 'FitAbortException']
|
||||
|
||||
from ..lib.logger import logger
|
||||
|
||||
|
||||
class FitAbortException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# COST FUNCTIONS: f(x) - y (least_square, minimize), and f(x) (ODR)
|
||||
def _cost_scipy_glob(p: list[float], data: list[Data], varpars: list[str], used_pars: list[list[str]]):
|
||||
# replace values
|
||||
for keys, values in zip(varpars, p):
|
||||
for data_i in data:
|
||||
if keys in data_i.parameter.keys():
|
||||
# TODO move this to scaled_value setter
|
||||
data_i.parameter[keys].scaled_value = values
|
||||
data_i.parameter[keys].namespace[keys] = data_i.parameter[keys].value
|
||||
r = []
|
||||
# unpack parameter and calculate y values and concatenate all
|
||||
for values, p_idx in zip(data, used_pars):
|
||||
actual_parameters = [values.parameter[keys].value for keys in p_idx]
|
||||
r = np.r_[r, values.cost(actual_parameters)]
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def _cost_scipy(p, data, varpars, used_pars):
|
||||
for keys, values in zip(varpars, p):
|
||||
data.parameter[keys].scaled_value = values
|
||||
data.parameter[keys].namespace[keys] = data.parameter[keys].value
|
||||
|
||||
actual_parameters = [data.parameter[keys].value for keys in used_pars]
|
||||
return data.cost(actual_parameters)
|
||||
|
||||
|
||||
def _cost_odr(p: list[float], data: Data, varpars: list[str], used_pars: list[str], fitmode: int=0):
|
||||
for keys, values in zip(varpars, p):
|
||||
data.parameter[keys].scaled_value = values
|
||||
data.parameter[keys].namespace[keys] = data.parameter[keys].value
|
||||
|
||||
actual_parameters = [data.parameter[keys].value for keys in used_pars]
|
||||
|
||||
return data.func(actual_parameters, data.x)
|
||||
|
||||
|
||||
def _cost_odr_glob(p: list[float], data: list[Data], var_pars: list[str], used_pars: list[str]):
|
||||
# replace values
|
||||
for data_i in data:
|
||||
_update_parameter(data_i, var_pars, p)
|
||||
|
||||
r = []
|
||||
# unpack parameter and calculate y values and concatenate all
|
||||
for values, p_idx in zip(data, used_pars):
|
||||
actual_parameters = [values.parameter[keys].value for keys in p_idx]
|
||||
r = np.r_[r, values.func(actual_parameters, values.x)]
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def _update_parameter(data: Data, varied_keys: list[str], parameter: list[float]):
|
||||
for keys, values in zip(varied_keys, parameter):
|
||||
if keys in data.parameter.keys():
|
||||
data.parameter[keys].scaled_value = values
|
||||
data.parameter[keys].namespace[keys] = data.parameter[keys].value
|
||||
|
||||
|
||||
class FitRoutine(object):
|
||||
def __init__(self, mode='lsq'):
|
||||
self._fitmethod = mode
|
||||
self.fitmethod = mode
|
||||
self.data = []
|
||||
self.fit_model = None
|
||||
self._no_own_model = []
|
||||
self.parameter = Parameters()
|
||||
self.result = []
|
||||
self.linked = []
|
||||
self._abort = False
|
||||
self.step = 0
|
||||
|
||||
def add_data(self, x, y=None, we=None, idx=None):
|
||||
if isinstance(x, Data):
|
||||
@ -78,29 +140,27 @@ class FitRoutine(object):
|
||||
|
||||
return self.fit_model
|
||||
|
||||
def set_link_parameter(self, parameter: tuple, replacement: tuple):
|
||||
def set_link_parameter(self, dismissed_param: tuple[Model | Data, str], replacement: tuple[Model, str]):
|
||||
if isinstance(replacement[0], Model):
|
||||
if 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 replacement[1] not in replacement[0].parameter:
|
||||
raise KeyError(f'Parameter {replacement[1]} of '
|
||||
f'model {replacement[0]} is not global')
|
||||
|
||||
if isinstance(parameter[0], Model):
|
||||
warnings.warn(f'Replaced parameter at pos {parameter[1]} in {str(parameter[0])} '
|
||||
if isinstance(dismissed_param[0], Model):
|
||||
warnings.warn(f'Replaced parameter {dismissed_param[1]} in {dismissed_param[0]} '
|
||||
f'becomes global with linkage.')
|
||||
|
||||
self.linked.append((*parameter, *replacement))
|
||||
self.linked.append((*dismissed_param, *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
|
||||
# set temporary model
|
||||
if v.model is None:
|
||||
v.model = self.fit_model
|
||||
self._no_own_model.append(v)
|
||||
@ -108,8 +168,6 @@ class FitRoutine(object):
|
||||
# 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)
|
||||
|
||||
@ -117,24 +175,21 @@ class FitRoutine(object):
|
||||
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]
|
||||
for dismiss_model, dismiss_param, replace_model, replace_param in self.linked:
|
||||
linked_sender[replace_model].add(dismiss_model)
|
||||
linked_sender[replace_model].add(replace_model)
|
||||
|
||||
replace_key = replace_model.parameter.get_key(replace_param)
|
||||
dismiss_key = dismiss_model.parameter.get_key(dismiss_param)
|
||||
|
||||
if isinstance(replace_model, Data):
|
||||
linked_parameter[dismiss_key] = replace_key
|
||||
else:
|
||||
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)
|
||||
p = dismiss_model.set_global_parameter(dismiss_param, replace_key)
|
||||
p._expr_disp = replace_param
|
||||
|
||||
for mm, m_data in _found_models.items():
|
||||
if mm.global_parameter:
|
||||
if mm.parameter:
|
||||
for dd in m_data:
|
||||
linked_sender[mm].add(dd)
|
||||
linked_sender[dd].add(mm)
|
||||
@ -163,15 +218,16 @@ class FitRoutine(object):
|
||||
self.find_paths(neighbor, graph, coupled_nodes, visited_nodes)
|
||||
|
||||
def abort(self):
|
||||
print('ABORT ???')
|
||||
logger.info('Fit aborted by user')
|
||||
self._abort = True
|
||||
|
||||
def run(self, mode='lsq'):
|
||||
def run(self, mode: str = None):
|
||||
self._abort = False
|
||||
self.parameter = Parameters()
|
||||
|
||||
if mode is None:
|
||||
mode = self.fitmethod
|
||||
|
||||
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]
|
||||
@ -202,17 +258,32 @@ class FitRoutine(object):
|
||||
|
||||
self.unprep_run()
|
||||
|
||||
for r in self.result:
|
||||
r.pprint()
|
||||
|
||||
return self.result
|
||||
|
||||
def make_preview(self, x: np.ndarray) -> list[np.ndarray]:
|
||||
y_pred = []
|
||||
fit_groups, linked_parameter = self.prepare_links()
|
||||
for data_groups in fit_groups:
|
||||
data = data_groups[0]
|
||||
actual_parameters = [p.value for p in data.parameter.values()]
|
||||
y_pred.append(data.func(actual_parameters, x))
|
||||
|
||||
return y_pred
|
||||
|
||||
def _prep_data(self, data):
|
||||
if data.get_model() is None:
|
||||
data._model = self.fit_model
|
||||
self._no_own_model.append(data)
|
||||
|
||||
# data.parameter.prepare_bounds()
|
||||
|
||||
return self._prep_parameter(data.parameter)
|
||||
|
||||
@staticmethod
|
||||
def _prep_parameter(parameter):
|
||||
def _prep_parameter(parameter: Parameters):
|
||||
vals = []
|
||||
var_pars = []
|
||||
for p_k, v_k in parameter.items():
|
||||
@ -224,29 +295,26 @@ class FitRoutine(object):
|
||||
|
||||
return pp, lb, ub, var_pars
|
||||
|
||||
def _prep_global(self, data_group, linked):
|
||||
@staticmethod
|
||||
def _prep_global(data_group: list[Data], linked):
|
||||
p0 = []
|
||||
lb = []
|
||||
ub = []
|
||||
var = []
|
||||
data_pars = []
|
||||
|
||||
# loopyloop over data that belong to one fit (linked or global)
|
||||
# loopy-loop over data that belong to one fit (linked or global)
|
||||
for data in data_group:
|
||||
# is parameter replaced by global parameter?
|
||||
for k, v in data.model.parameter.items():
|
||||
data.replace_parameter(k, v)
|
||||
|
||||
# data.parameter.prepare_bounds()
|
||||
|
||||
actual_pars = []
|
||||
for i, (p_k, v_k) in enumerate(data.parameter.items()):
|
||||
for i, p_k in enumerate(data.para_keys):
|
||||
p_k_used = p_k
|
||||
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]
|
||||
v_k_used = data.parameter[p_k]
|
||||
|
||||
actual_pars.append(p_k_used)
|
||||
# parameter is variable and was not found before as shared parameter
|
||||
@ -265,58 +333,20 @@ class FitRoutine(object):
|
||||
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
|
||||
Parameters.reset()
|
||||
|
||||
def _least_squares_single(self, data, p0, lb, ub, var):
|
||||
self.step = 0
|
||||
|
||||
def cost(p):
|
||||
self.step += 1
|
||||
if self._abort:
|
||||
raise FitAbortException(f'Fit aborted by user')
|
||||
|
||||
return self.__cost_scipy(p, data, var, data.para_keys)
|
||||
return _cost_scipy(p, data, var, data.para_keys)
|
||||
|
||||
with np.errstate(all='ignore'):
|
||||
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=1000 * len(p0))
|
||||
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=500 * len(p0))
|
||||
|
||||
err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
|
||||
self.make_results(data, res.x, var, data.para_keys, res.jac.shape,
|
||||
@ -324,12 +354,13 @@ class FitRoutine(object):
|
||||
|
||||
def _least_squares_global(self, data, p0, lb, ub, var, data_pars):
|
||||
def cost(p):
|
||||
self.step += 1
|
||||
if self._abort:
|
||||
raise FitAbortException(f'Fit aborted by user')
|
||||
return self.__cost_scipy_glob(p, data, var, data_pars)
|
||||
return _cost_scipy_glob(p, data, var, data_pars)
|
||||
|
||||
with np.errstate(all='ignore'):
|
||||
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=1000 * len(p0))
|
||||
res = optimize.least_squares(cost, p0, bounds=(lb, ub), max_nfev=500 * len(p0))
|
||||
|
||||
err, corr, partial_corr = self._calc_error(res.jac, np.sum(res.fun**2), *res.jac.shape)
|
||||
for v, var_pars_k in zip(data, data_pars):
|
||||
@ -338,25 +369,27 @@ class FitRoutine(object):
|
||||
|
||||
def _nm_single(self, data, p0, lb, ub, var):
|
||||
def cost(p):
|
||||
self.step += 1
|
||||
if self._abort:
|
||||
raise FitAbortException(f'Fit aborted by user')
|
||||
return (self.__cost_scipy(p, data, var, data.para_keys)**2).sum()
|
||||
return (_cost_scipy(p, data, var, data.para_keys) ** 2).sum()
|
||||
|
||||
with np.errstate(all='ignore'):
|
||||
res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)],
|
||||
method='Nelder-Mead', options={'maxiter': 1000 * len(p0)})
|
||||
method='Nelder-Mead', options={'maxiter': 500 * len(p0)})
|
||||
|
||||
self.make_results(data, res.x, var, data.para_keys, (len(data), len(p0)))
|
||||
|
||||
def _nm_global(self, data, p0, lb, ub, var, data_pars):
|
||||
def cost(p):
|
||||
self.step += 1
|
||||
if self._abort:
|
||||
raise FitAbortException(f'Fit aborted by user')
|
||||
return (self.__cost_scipy_glob(p, data, var, data_pars)**2).sum()
|
||||
return (_cost_scipy_glob(p, data, var, data_pars) ** 2).sum()
|
||||
|
||||
with np.errstate(all='ignore'):
|
||||
res = optimize.minimize(cost, p0, bounds=[(b1, b2) for (b1, b2) in zip(lb, ub)],
|
||||
method='Nelder-Mead', options={'maxiter': 1000 * len(p0)})
|
||||
method='Nelder-Mead', options={'maxiter': 500 * len(p0)})
|
||||
|
||||
for v, var_pars_k in zip(data, data_pars):
|
||||
self.make_results(v, res.x, var, var_pars_k, (sum(len(d) for d in data), len(p0)))
|
||||
@ -365,15 +398,21 @@ class FitRoutine(object):
|
||||
odr_data = odr.Data(data.x, data.y)
|
||||
|
||||
def func(p, _):
|
||||
self.step += 1
|
||||
if self._abort:
|
||||
raise FitAbortException(f'Fit aborted by user')
|
||||
return self.__cost_odr(p, data, var_pars, data.para_keys)
|
||||
return _cost_odr(p, data, var_pars, data.para_keys)
|
||||
|
||||
odr_model = odr.Model(func)
|
||||
|
||||
corr, partial_corr, res = self._odr_fit(odr_data, odr_model, p0)
|
||||
|
||||
self.make_results(data, res.beta, var_pars, data.para_keys, (len(data), len(p0)),
|
||||
err=res.sd_beta, corr=corr, partial_corr=partial_corr)
|
||||
|
||||
def _odr_fit(self, odr_data, odr_model, p0):
|
||||
o = odr.ODR(odr_data, odr_model, beta0=p0)
|
||||
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)
|
||||
@ -382,15 +421,14 @@ class FitRoutine(object):
|
||||
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)
|
||||
return corr, partial_corr, res
|
||||
|
||||
def _odr_global(self, data, p0, var, data_pars):
|
||||
def func(p, _):
|
||||
self.step += 1
|
||||
if self._abort:
|
||||
raise FitAbortException(f'Fit aborted by user')
|
||||
return self.__cost_odr_glob(p, data, var, data_pars)
|
||||
return _cost_odr_glob(p, data, var, data_pars)
|
||||
|
||||
x = []
|
||||
y = []
|
||||
@ -401,17 +439,7 @@ class FitRoutine(object):
|
||||
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
|
||||
corr, partial_corr, res = self._odr_fit(odr_data, odr_model, p0)
|
||||
|
||||
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)),
|
||||
@ -425,15 +453,17 @@ class FitRoutine(object):
|
||||
|
||||
# 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
|
||||
if keys in data.parameter.keys():
|
||||
data.parameter[keys].scaled_value = p_value
|
||||
data.parameter[keys].scaled_error = err_value
|
||||
data.parameter[keys].namespace[keys] = data.parameter[keys].value
|
||||
|
||||
combinations = list(product(var_pars, var_pars))
|
||||
actual_parameters = []
|
||||
corr_idx = []
|
||||
|
||||
for i, p_i in enumerate(used_pars):
|
||||
actual_parameters.append(self.parameter[p_i])
|
||||
actual_parameters.append(data.parameter[p_i])
|
||||
for j, p_j in enumerate(used_pars):
|
||||
try:
|
||||
# find the position of the parameter combinations
|
||||
@ -454,9 +484,18 @@ class FitRoutine(object):
|
||||
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)
|
||||
self.result[idx] = FitResultCreator.make_with_model(
|
||||
model,
|
||||
data.x,
|
||||
data.y,
|
||||
actual_parameters,
|
||||
data.fun_kwargs,
|
||||
data.we_string,
|
||||
data.idx,
|
||||
*shape,
|
||||
corr=actual_corr,
|
||||
pcorr=actual_pcorr,
|
||||
)
|
||||
|
||||
return self.result
|
||||
|
||||
@ -464,11 +503,18 @@ class FitRoutine(object):
|
||||
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)
|
||||
try:
|
||||
_, s, vt = la.svd(jac, full_matrices=False)
|
||||
except ValueError as e:
|
||||
# this may be issue #39: On entry to DGESSD parameter had an illegal value
|
||||
# catch this exception and ignore error calculation
|
||||
logger.error(f'Error calculation failed with {e.args}')
|
||||
pcov = None
|
||||
else:
|
||||
threshold = EPS * max(jac.shape) * s[0]
|
||||
s = s[s > threshold]
|
||||
vt = vt[:s.size]
|
||||
pcov = np.dot(vt.T / s**2, vt) * chi / (nobs - nvars)
|
||||
|
||||
if pcov is None:
|
||||
_err = np.zeros(nvars)
|
||||
@ -487,3 +533,4 @@ class FitRoutine(object):
|
||||
partial_corr = corr
|
||||
|
||||
return _err, corr, partial_corr
|
||||
|
||||
|
@ -6,7 +6,7 @@ from typing import Sized
|
||||
from numpy import inf
|
||||
|
||||
from ._meta import MultiModel
|
||||
from .parameter import Parameters
|
||||
from .parameter import Parameters, Parameter
|
||||
|
||||
|
||||
class Model(object):
|
||||
@ -25,7 +25,6 @@ class Model(object):
|
||||
self.ub = [i if i is not None else inf for i in self.ub]
|
||||
|
||||
self.parameter = Parameters()
|
||||
self.global_parameter = {}
|
||||
self.is_complex = None
|
||||
self._complex_part = False
|
||||
|
||||
@ -80,23 +79,33 @@ class Model(object):
|
||||
self.fun_kwargs = {k: v.default for k, v in inspect.signature(model.func).parameters.items()
|
||||
if v.default is not inspect.Parameter.empty}
|
||||
|
||||
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
|
||||
def set_global_parameter(self,
|
||||
key: str | Parameter,
|
||||
value: float | str = None,
|
||||
*,
|
||||
var: bool = None,
|
||||
lb: float = None,
|
||||
ub: float = None,
|
||||
default_bounds: bool = False,
|
||||
) -> Parameter:
|
||||
|
||||
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]
|
||||
if isinstance(key, Parameter):
|
||||
p = key
|
||||
key = f'p{next(Parameters.parameter_counter)}'
|
||||
self.parameter.add_parameter(key, p)
|
||||
|
||||
gp = self.parameter.add_parameter(p, var=var, lb=lb, ub=ub)
|
||||
for k, v in zip(idx, gp):
|
||||
self.global_parameter[k] = v
|
||||
else:
|
||||
idx = [self.params.index(key)]
|
||||
if default_bounds:
|
||||
if lb is None:
|
||||
lb = [self.lb[i] for i in idx]
|
||||
if ub is None:
|
||||
ub = [self.lb[i] for i in idx]
|
||||
|
||||
return gp
|
||||
p = self.parameter.add(key, value, var=var, lb=lb, ub=ub)
|
||||
p.is_global = True
|
||||
|
||||
return p
|
||||
|
||||
@staticmethod
|
||||
def _prep(param_len, val):
|
||||
|
@ -1,123 +1,268 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from numbers import Number
|
||||
import ast
|
||||
import re
|
||||
from itertools import count
|
||||
|
||||
from io import StringIO
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Parameters(dict):
|
||||
count = count()
|
||||
parameter_counter = count()
|
||||
# is one global namespace a good idea?
|
||||
namespace: dict = {}
|
||||
|
||||
def __str__(self):
|
||||
return 'Parameters:\n' + '\n'.join([str(k)+': '+str(v) for k, v in self.items()])
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._mapping: dict = {}
|
||||
|
||||
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
|
||||
def __str__(self) -> str:
|
||||
return 'Parameters:\n' + '\n'.join([f'{k}: {v}' for k, v in self.items()])
|
||||
|
||||
def __getitem__(self, item) -> Parameter:
|
||||
if item in self._mapping:
|
||||
return super().__getitem__(self._mapping[item])
|
||||
else:
|
||||
return super().__getitem__(item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.add_parameter(key, value)
|
||||
|
||||
def __contains__(self, item):
|
||||
for v in self.values():
|
||||
if item == v.name:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def add(self,
|
||||
name: str,
|
||||
value: str | float | int = None,
|
||||
*,
|
||||
var: bool = True,
|
||||
lb: str | float = -np.inf,
|
||||
ub: str | float = np.inf) -> Parameter:
|
||||
|
||||
par = Parameter(name=name, value=value, var=var, lb=lb, ub=ub)
|
||||
key = f'_p{next(Parameters.parameter_counter)}'
|
||||
|
||||
self.add_parameter(key, par)
|
||||
|
||||
return par
|
||||
|
||||
def add_parameter(self, key: str, parameter: Parameter):
|
||||
self._mapping[parameter.name] = key
|
||||
super().__setitem__(key, parameter)
|
||||
|
||||
parameter.eval_allowed = False
|
||||
self.namespace[key] = parameter.value
|
||||
parameter.namespace = self.namespace
|
||||
parameter.eval_allowed = True
|
||||
|
||||
self.update_namespace()
|
||||
|
||||
def replace_parameter(self, key_out: str, key_in: str, parameter: Parameter):
|
||||
self.add_parameter(key_in, parameter)
|
||||
for k, v in self._mapping.items():
|
||||
if v == key_out:
|
||||
self._mapping[k] = key_in
|
||||
break
|
||||
|
||||
if key_out in self.namespace:
|
||||
del self.namespace[key_out]
|
||||
|
||||
def fix(self):
|
||||
for v in self.keys():
|
||||
v._value = v.value
|
||||
v.namespace = {}
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
def reset():
|
||||
Parameters.namespace = {}
|
||||
|
||||
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()
|
||||
def get_key(self, name: str) -> str | None:
|
||||
for k, v in self.items():
|
||||
p[k] = Parameter(v.value, var=v.var, lb=v.lb, ub=v.ub)
|
||||
if name == v.name:
|
||||
return k
|
||||
|
||||
if len(p) == 0:
|
||||
return p
|
||||
|
||||
max_k = max(p.keys())
|
||||
c = next(p.count)
|
||||
while c < max_k:
|
||||
c = next(p.count)
|
||||
|
||||
return p
|
||||
return
|
||||
|
||||
def get_state(self):
|
||||
return {k: v.get_state() for k, v in self.items()}
|
||||
|
||||
def update_namespace(self):
|
||||
for p in self.values():
|
||||
try:
|
||||
p.value
|
||||
except NameError:
|
||||
expression = p._expr_disp
|
||||
for n, k in self._mapping.items():
|
||||
expression, num_replaced = re.subn(re.escape(n), k, expression)
|
||||
|
||||
p._expr = expression
|
||||
|
||||
def prepare_bounds(self):
|
||||
print('prepare_bounds')
|
||||
original_values = list(self.values())
|
||||
for param in original_values:
|
||||
already_with_expression = False
|
||||
for mode, value in (('lower', param.lb), ('upper', param.ub)):
|
||||
print(mode, value)
|
||||
if already_with_expression:
|
||||
raise ValueError('Only one boundary can be an expression')
|
||||
already_with_expression = self.parse(param, value, bnd=mode)
|
||||
|
||||
def parse(self, parameter, expression: str, bnd: str = 'lower') -> bool:
|
||||
try:
|
||||
float(expression)
|
||||
return False
|
||||
|
||||
except ValueError:
|
||||
pp = ast.parse(expression)
|
||||
expr_syms = pp.body[0].value
|
||||
|
||||
if isinstance(expr_syms, ast.BinOp):
|
||||
left, op, right = expr_syms.left, expr_syms.op, expr_syms.right
|
||||
|
||||
# check for sign in numerator
|
||||
sign = 1
|
||||
if isinstance(left, ast.UnaryOp):
|
||||
if isinstance(left.op, (ast.Not, ast.Invert)):
|
||||
raise ValueError('Only `+` and `-` are supported for signs')
|
||||
if isinstance(left.op, ast.USub):
|
||||
sign = -1
|
||||
left = left.operand
|
||||
|
||||
# is expression number / parameter?
|
||||
if not (isinstance(left, ast.Constant) and isinstance(op, ast.Div) and isinstance(right, ast.Name)):
|
||||
raise ValueError('Only simple division `const/parameter` are possible')
|
||||
|
||||
result = self.make_proxy_div(parameter, left.value*sign, right.id, bnd=bnd)
|
||||
|
||||
return result
|
||||
|
||||
else:
|
||||
raise ValueError('I cannot work under these conditions')
|
||||
|
||||
def make_proxy_div(self, param, num: str | float, denom: str | float, bnd: str = 'upper') -> bool:
|
||||
other_param = self[denom]
|
||||
other_lb, other_ub = other_param.lb, other_param.ub
|
||||
|
||||
proxy = {'name': f'{param.name}*{other_param.name}', 'value': other_param.value*param.value, 'lb': None, 'ub': None}
|
||||
|
||||
# switching signs is bad for inequalities
|
||||
if other_lb < 0 < other_ub:
|
||||
raise ValueError('Parameter in expression changes sign')
|
||||
|
||||
if bnd == 'upper':
|
||||
# this whole schlamassel is only working for some values as lower bound
|
||||
if param.lb not in [-np.inf, 0]:
|
||||
raise ValueError('Invalid lower bounds')
|
||||
|
||||
if other_ub < 0 or num < 0:
|
||||
# upper bound is always negative, switch boundaries
|
||||
proxy['lb'] = num
|
||||
proxy['ub'] = param.lb if param.lb == 0 else np.inf
|
||||
else:
|
||||
# upper bound is always positive
|
||||
proxy['lb'] = param.lb
|
||||
proxy['ub'] = num
|
||||
|
||||
elif bnd == 'lower':
|
||||
if param.ub not in [np.inf, 0]:
|
||||
raise ValueError('Invalid upper bound')
|
||||
|
||||
if other_ub <= 0 or num < 0:
|
||||
proxy['lb'] = param.lb if param.lb == 0 else np.inf
|
||||
proxy['ub'] = num
|
||||
else:
|
||||
proxy['lb'] = num
|
||||
proxy['ub'] = param.ub
|
||||
|
||||
else:
|
||||
raise ValueError(f'unknown bound {bnd!r}, use `upper`or `lower`')
|
||||
|
||||
param.set_expression(f'{num}/{denom}')
|
||||
self.add(**proxy)
|
||||
self.update_namespace()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Parameter:
|
||||
"""
|
||||
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
|
||||
# TODO Parameter should know its own key
|
||||
def __init__(self,
|
||||
name: str,
|
||||
value: float | str,
|
||||
var: bool = True,
|
||||
lb: str | float = -np.inf,
|
||||
ub: str | float = np.inf,
|
||||
):
|
||||
self._value: float | None = None
|
||||
self.var: bool = bool(var) if var is not None else True
|
||||
self.error: None | float = None if self.var is False else 0.0
|
||||
self.name: str = name
|
||||
self.function: str = ""
|
||||
|
||||
if self.lb <= value <= self.ub:
|
||||
self.value = value
|
||||
self.lb: str | None | float = lb if lb is not None else -np.inf
|
||||
self.ub: str | float | None = ub if ub is not None else np.inf
|
||||
self.namespace: dict = {}
|
||||
self.eval_allowed: bool = True
|
||||
self._expr: None | str = None
|
||||
self._expr_disp: None | str = None
|
||||
self.is_global = False
|
||||
|
||||
if isinstance(value, str):
|
||||
self._expr_disp = value
|
||||
self._expr = value
|
||||
self.var = False
|
||||
else:
|
||||
raise ValueError('Value of parameter is outside bounds')
|
||||
if isinstance(self.lb, (int, float)) and isinstance(self.ub, (int, float)):
|
||||
if (self.lb <= value <= self.ub) or (not self.var):
|
||||
self._value = value
|
||||
else:
|
||||
raise ValueError('Value of parameter is outside bounds')
|
||||
else:
|
||||
self._value = value
|
||||
|
||||
self.init_val = value
|
||||
self.init_val = value
|
||||
|
||||
with np.errstate(divide='ignore'):
|
||||
# throws RuntimeWarning for zeros
|
||||
self.scale = 10**(np.floor(np.log10(np.abs(self.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.
|
||||
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 = ''
|
||||
def __str__(self) -> str:
|
||||
start = StringIO()
|
||||
if self.name:
|
||||
if self.function:
|
||||
start = f'{self.name} ({self.function}): '
|
||||
start.write(f"{self.name} ({self.function})")
|
||||
else:
|
||||
start = self.name + ': '
|
||||
start.write(self.name)
|
||||
|
||||
if self.is_global:
|
||||
start.write("*")
|
||||
|
||||
start.write(": ")
|
||||
|
||||
if self.var:
|
||||
return start + f'{self.value:.4g} +/- {self.error:.4g}, init={self.init_val}'
|
||||
start.write(f"{self.value:.4g} +/- {self.error:.4g}, init={self.init_val}")
|
||||
else:
|
||||
return start + f'{self.value:} (fixed)'
|
||||
start.write(f"{self.value:.4g}")
|
||||
if self._expr is None:
|
||||
start.write(" (fixed)")
|
||||
else:
|
||||
start.write(f" (calc: {self._expr_disp})")
|
||||
|
||||
def __add__(self, other: Parameter | float) -> float:
|
||||
return start.getvalue()
|
||||
|
||||
def __add__(self, other: Parameter | float | int) -> float:
|
||||
if isinstance(other, (float, int)):
|
||||
return self.value + other
|
||||
elif isinstance(other, Parameter):
|
||||
@ -127,40 +272,88 @@ class Parameter:
|
||||
return self.__add__(other)
|
||||
|
||||
@property
|
||||
def scaled_value(self):
|
||||
def scaled_value(self) -> float:
|
||||
return self.value / self.scale
|
||||
|
||||
@scaled_value.setter
|
||||
def scaled_value(self, value):
|
||||
self.value = value * self.scale
|
||||
def scaled_value(self, value: float) -> None:
|
||||
self._value = value * self.scale
|
||||
|
||||
@property
|
||||
def scaled_error(self):
|
||||
if self.error is None:
|
||||
return self.error
|
||||
else:
|
||||
def value(self) -> float | None:
|
||||
if self._value is not None:
|
||||
return self._value
|
||||
|
||||
if self._expr is not None and self.eval_allowed:
|
||||
return eval(self._expr, {}, self.namespace)
|
||||
|
||||
return
|
||||
|
||||
def set_expression(self, expr: str):
|
||||
self._value = None
|
||||
self._expr_disp = expr
|
||||
self._expr = expr
|
||||
self.var = False
|
||||
|
||||
@property
|
||||
def scaled_error(self) -> None | float:
|
||||
if self.error is not None:
|
||||
return self.error / self.scale
|
||||
|
||||
return
|
||||
|
||||
@scaled_error.setter
|
||||
def scaled_error(self, value):
|
||||
def scaled_error(self, value) -> None:
|
||||
self.error = value * self.scale
|
||||
|
||||
def get_state(self):
|
||||
|
||||
return {slot: getattr(self, slot) for slot in self.__slots__}
|
||||
def get_state(self) -> dict:
|
||||
state_dict = {
|
||||
'name': self.name,
|
||||
'value': self._value,
|
||||
'error': self.error,
|
||||
'init_val': self.init_val,
|
||||
'var': self.var,
|
||||
'lb': self.lb,
|
||||
'ub': self.ub,
|
||||
'scale': self.scale,
|
||||
'function': self.function,
|
||||
'_expr': self._expr,
|
||||
'_expr_disp': self._expr_disp,
|
||||
'is_global': self.is_global,
|
||||
}
|
||||
return state_dict
|
||||
|
||||
@staticmethod
|
||||
def set_state(state: dict):
|
||||
par = Parameter(state.pop('value'))
|
||||
def set_state(state: dict) -> Parameter:
|
||||
par = Parameter(state.get('name'), state.pop('value'))
|
||||
for k, v in state.items():
|
||||
setattr(par, k, v)
|
||||
|
||||
return par
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
def full_name(self) -> str:
|
||||
name = self.name
|
||||
if self.function:
|
||||
name += ' (' + self.function + ')'
|
||||
name += f" ({self.function})"
|
||||
|
||||
return name
|
||||
|
||||
def copy(self) -> Parameter:
|
||||
if self._expr:
|
||||
val = self._expr_disp
|
||||
else:
|
||||
val = self._value
|
||||
para_copy = Parameter(name=self.name, value=val, var=self.var, lb=self.lb, ub=self.ub)
|
||||
para_copy._expr = self._expr
|
||||
para_copy.namespace = self.namespace
|
||||
para_copy.is_global = self.is_global
|
||||
para_copy.error = self.error
|
||||
para_copy.function = self.function
|
||||
|
||||
return para_copy
|
||||
|
||||
def fix(self):
|
||||
self._value = self.value
|
||||
self.namespace = {}
|
||||
|
||||
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@ -41,7 +42,19 @@ class FitResultCreator:
|
||||
kwargs['name'], stats, idx)
|
||||
|
||||
@staticmethod
|
||||
def make_with_model(model, x_orig, y_orig, p, fun_kwargs, idx, nobs, nvar, corr, pcorr) -> FitResult:
|
||||
def make_with_model(
|
||||
model: 'Model',
|
||||
x_orig: np.ndarray,
|
||||
y_orig: np.ndarray,
|
||||
p: 'Parameters',
|
||||
fun_kwargs: dict,
|
||||
we: str,
|
||||
idx: str | None,
|
||||
nobs: int,
|
||||
nvar: int,
|
||||
corr: np.ndarray,
|
||||
pcorr: np.ndarray,
|
||||
) -> FitResult:
|
||||
if np.all(x_orig > 0) and (np.max(x_orig) > 100 * np.min(x_orig)):
|
||||
islog = True
|
||||
else:
|
||||
@ -63,9 +76,23 @@ class FitResultCreator:
|
||||
parameters = OrderedDict([(k, v) for k, v in zip(pnames, p)])
|
||||
p_final = [p.value for p in parameters.values()]
|
||||
|
||||
_y = model.func(p_final, _x, **fun_kwargs)
|
||||
resid = model.func(p_final, x_orig, **fun_kwargs) - y_orig
|
||||
|
||||
actual_mode = -1
|
||||
if 'complex_mode' in fun_kwargs:
|
||||
actual_mode = fun_kwargs['complex_mode']
|
||||
fun_kwargs['complex_mode'] = 0
|
||||
|
||||
_y = model.func(p_final, _x, **fun_kwargs)
|
||||
|
||||
if not actual_mode < 0:
|
||||
if actual_mode == 1:
|
||||
_y.imag = 0
|
||||
elif actual_mode == 2:
|
||||
_y.real = 0
|
||||
|
||||
fun_kwargs['complex_mode'] = actual_mode
|
||||
|
||||
stats = FitResultCreator.calc_statistics(_y, resid, nobs, nvar)
|
||||
varied = [p.var for p in parameters.values()]
|
||||
|
||||
@ -89,10 +116,25 @@ class FitResultCreator:
|
||||
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, func=model)
|
||||
return FitResult(
|
||||
x=_x,
|
||||
y=_y,
|
||||
x_data=x_orig,
|
||||
y_data=y_orig,
|
||||
params=parameters,
|
||||
fun_kwargs=fun_kwargs,
|
||||
resid=resid,
|
||||
nobs=nobs,
|
||||
nvar=nvar,
|
||||
we=we,
|
||||
name=model.name,
|
||||
stats=stats,
|
||||
idx=idx,
|
||||
corr=correlation,
|
||||
pcorr=partial_correlation,
|
||||
islog=islog,
|
||||
func=model,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def calc_statistics(y, residual, nobs=None, nvar=None):
|
||||
@ -130,22 +172,38 @@ class FitResultCreator:
|
||||
|
||||
class FitResult(Points):
|
||||
|
||||
def __init__(self, x: np.ndarray, y: np.ndarray,
|
||||
x_data: np.ndarray, y_data: np.ndarray,
|
||||
params: dict, fun_kwargs: dict,
|
||||
resid: np.ndarray, nobs: int, nvar: int, name: str, stats: dict,
|
||||
idx=None, corr=None, pcorr=None, islog=False, func=None,
|
||||
**kwargs):
|
||||
def __init__(
|
||||
self: FitResult,
|
||||
x: np.ndarray,
|
||||
y: np.ndarray,
|
||||
x_data: np.ndarray,
|
||||
y_data: np.ndarray,
|
||||
params: dict,
|
||||
fun_kwargs: dict,
|
||||
resid: np.ndarray,
|
||||
nobs: int,
|
||||
nvar: int,
|
||||
we: str,
|
||||
name: str,
|
||||
stats: dict,
|
||||
idx: int = None,
|
||||
corr: np.ndarray = None,
|
||||
pcorr: np.ndarray = None,
|
||||
islog: bool = False,
|
||||
func=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
self.parameter, name = self._prepare_names(params, name)
|
||||
|
||||
super().__init__(x=x, y=y, name=name, **kwargs)
|
||||
label = kwargs.get('label', name)
|
||||
super().__init__(x=x, y=y, name=label, **kwargs)
|
||||
|
||||
self.residual = resid
|
||||
self.idx = idx
|
||||
self.statistics = stats
|
||||
self.nobs = nobs
|
||||
self.nvar = nvar
|
||||
self.we = we
|
||||
self.fun_kwargs = fun_kwargs
|
||||
self.correlation = corr
|
||||
self.partial_correlation = pcorr
|
||||
@ -172,7 +230,7 @@ class FitResult(Points):
|
||||
nice_name = m.group(1)
|
||||
if func_number in split_funcs:
|
||||
nice_func = split_funcs[func_number]
|
||||
|
||||
pvalue.fix()
|
||||
pvalue.name = nice_name
|
||||
pvalue.function = nice_func
|
||||
parameter_dic[pname] = pvalue
|
||||
@ -209,27 +267,31 @@ class FitResult(Points):
|
||||
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())
|
||||
s = StringIO()
|
||||
s.write('Fit result:\n')
|
||||
s.write(f' model : {self.name}\n')
|
||||
s.write(f' #data : {self.nobs}\n')
|
||||
s.write(f' #var : {self.nvar}\n')
|
||||
s.write(f' #weight: {self.we}\n')
|
||||
s.write('\nParameter\n')
|
||||
s.write(self.parameter_string())
|
||||
|
||||
if statistics:
|
||||
print('Statistics')
|
||||
s.write('\nStatistics\n')
|
||||
for k, v in self.statistics.items():
|
||||
print(f' {k} : {v:.4f}')
|
||||
s.write(f' {k} : {v:.4f}\n')
|
||||
|
||||
if correlations and self.correlation is not None:
|
||||
print('\nCorrelation (partial corr.)')
|
||||
print(self._correlation_string())
|
||||
print()
|
||||
s.write('\nCorrelation (partial corr.)\n')
|
||||
s.write(self._correlation_string())
|
||||
s.write('\n')
|
||||
|
||||
def _parameter_string(self):
|
||||
print(s.getvalue())
|
||||
|
||||
def parameter_string(self):
|
||||
ret_val = ''
|
||||
|
||||
for pval in self.parameter.values():
|
||||
for pkey, pval in self.parameter.items():
|
||||
ret_val += convert(str(pval), old='tex', new='str') + '\n'
|
||||
|
||||
if self.fun_kwargs:
|
||||
@ -241,9 +303,7 @@ class FitResult(Points):
|
||||
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)
|
||||
ret_val += f" {convert(p_i, old='tex', new='str')} / {convert(p_j, old='tex', new='str')} : {corr_ij:.4f} ({pcorr_ij:.4f})\n"
|
||||
return ret_val
|
||||
|
||||
def correlation_list(self, limit=0.1):
|
||||
@ -292,7 +352,7 @@ class FitResult(Points):
|
||||
|
||||
with path.open(writemode) as f:
|
||||
if overwrite or not path.exists():
|
||||
f.write('# label(1)\t')
|
||||
f.write('# xvalue(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')
|
||||
@ -306,10 +366,15 @@ class FitResult(Points):
|
||||
err = 0.
|
||||
f.write(f'{p.value:.8e}\t{err:.8e}\t')
|
||||
|
||||
f.write('# ')
|
||||
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(f'weight: {self.we}\t')
|
||||
|
||||
f.write('\n')
|
||||
f.write(f'# line above from: {self.name} (model: {self.model_name})')
|
||||
f.write('\n')
|
||||
|
||||
def f_test(self, chi2: float, dof: float):
|
||||
@ -317,7 +382,13 @@ class FitResult(Points):
|
||||
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)
|
||||
|
||||
try:
|
||||
prob_f = 1-fdist.cdf(f_value, dof-self.dof, self.dof)
|
||||
except:
|
||||
prob_f = 0
|
||||
|
||||
return f_value, prob_f
|
||||
|
||||
def get_state(self):
|
||||
state = super().get_state()
|
||||
@ -327,6 +398,7 @@ class FitResult(Points):
|
||||
state[attr] = getattr(self, attr)
|
||||
|
||||
state['name'] = self._model_name
|
||||
state['label'] = self.name
|
||||
state['corr'] = self.correlation
|
||||
state['pcorr'] = self.partial_correlation
|
||||
state['stats'] = self.statistics
|
||||
@ -346,21 +418,47 @@ class FitResult(Points):
|
||||
|
||||
def with_new_x(self, x_values):
|
||||
if self.func is None:
|
||||
raise ValueError('no fit function available to calcualate new y values')
|
||||
raise ValueError('no fit function available to calculate new y values')
|
||||
|
||||
actual_mode = -1
|
||||
if 'complex_mode' in self.fun_kwargs:
|
||||
actual_mode = self.fun_kwargs['complex_mode']
|
||||
self.fun_kwargs['complex_mode'] = 0
|
||||
|
||||
new_fit = self.copy()
|
||||
y_values = self.func.func(self.p_final, x_values, **self.fun_kwargs)
|
||||
new_fit.set_data(x_values, y_values)
|
||||
if not actual_mode < 0:
|
||||
if actual_mode == 1:
|
||||
y_values.imag = 0
|
||||
elif actual_mode == 2:
|
||||
y_values.real = 0
|
||||
|
||||
self.fun_kwargs['complex_mode'] = actual_mode
|
||||
|
||||
new_fit.set_data(x_values, y_values, y_err=0.0)
|
||||
|
||||
return new_fit
|
||||
|
||||
|
||||
def sub(self, x_values):
|
||||
part_functions = []
|
||||
actual_mode = -1
|
||||
if 'complex_mode' in self.fun_kwargs:
|
||||
actual_mode = self.fun_kwargs['complex_mode']
|
||||
self.fun_kwargs['complex_mode'] = 0
|
||||
|
||||
for sub_name, sub_y in zip(self.func.sub_name(), self.func.sub(self.p_final, x_values, **self.fun_kwargs)):
|
||||
if np.iscomplexobj(sub_y):
|
||||
if not actual_mode < 0:
|
||||
if actual_mode == 1:
|
||||
sub_y.imag = 0
|
||||
elif actual_mode == 2:
|
||||
sub_y.real = 0
|
||||
|
||||
part_functions.append(Signal(x_values, sub_y, name=sub_name))
|
||||
|
||||
else:
|
||||
part_functions.append(Points(x_values, sub_y, name=sub_name))
|
||||
|
||||
return part_functions
|
||||
if actual_mode > 0:
|
||||
self.fun_kwargs['complex_mode'] = actual_mode
|
||||
|
||||
return part_functions
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
from io import BytesIO
|
||||
@ -7,18 +9,18 @@ import numpy as np
|
||||
|
||||
from ..data.points import Points
|
||||
from ..data.nmr import FID, Spectrum
|
||||
from ..data.bds import BDS
|
||||
from ..data.dsc import DSC
|
||||
from ..utils.utils import staggered_range
|
||||
|
||||
NUMBERRE = re.compile(r'[0-9]\.*[0-9]*[Ee]*[+-]*[0-9]*')
|
||||
|
||||
|
||||
class AsciiReader:
|
||||
delimiters = ['\t', ' ', ',']
|
||||
# delimiters = ['\t', ' ', ',']
|
||||
|
||||
def __init__(self, fname):
|
||||
self.fname = None
|
||||
self.header = []
|
||||
self.data = []
|
||||
self.lines = []
|
||||
self.delays = None
|
||||
self.width = []
|
||||
self.line_comment = []
|
||||
@ -40,15 +42,16 @@ class AsciiReader:
|
||||
break
|
||||
|
||||
def make_preview(self, num_lines: int):
|
||||
if num_lines <= len(self.data):
|
||||
return self.data[:num_lines], max(self.width[:num_lines])
|
||||
if num_lines <= len(self.lines):
|
||||
return self.lines[:num_lines], max(self.width[:num_lines]), self.line_comment
|
||||
|
||||
num_lines += len(self.header)
|
||||
with self.fname.open('r') as f:
|
||||
for i, line in enumerate(islice(f, len(self.header)+len(self.data), num_lines)):
|
||||
line = line.rstrip('\n\t\r, ')
|
||||
for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)):
|
||||
line = line.strip('\n\t\r, ')
|
||||
line = re.sub(r'[\t ;,] *', ';', line)
|
||||
line = line.split(';')
|
||||
|
||||
line = re.split('[\s,;]', line)
|
||||
try:
|
||||
comment_start = line.index('#')
|
||||
self.line_comment.append(' '.join(line[comment_start:]))
|
||||
@ -56,10 +59,15 @@ class AsciiReader:
|
||||
except ValueError:
|
||||
self.line_comment.append('')
|
||||
|
||||
self.width.append(len(line))
|
||||
self.data.append(line)
|
||||
is_empty = len(line) == 0
|
||||
|
||||
return self.data, max(self.width)
|
||||
if not is_empty:
|
||||
self.lines.append(line)
|
||||
self.width.append(len(line))
|
||||
else:
|
||||
self.lines.append('')
|
||||
|
||||
return self.lines, max(self.width), self.line_comment
|
||||
|
||||
def look_for_delay(self, fname=None):
|
||||
if fname is None:
|
||||
@ -79,34 +87,49 @@ class AsciiReader:
|
||||
if stagg:
|
||||
self.delays = staggered_range(self.delays, stepsize=stag_size)
|
||||
|
||||
def export(self, x: int = None, y: list = None, yerr: list = None,
|
||||
mode: str = 'Points', col_names=None) -> list:
|
||||
def export(
|
||||
self: 'AsciiReader',
|
||||
x: int | str = None,
|
||||
y: list = None,
|
||||
yerr: list = None,
|
||||
mode: str = 'points',
|
||||
col_names=None,
|
||||
num_value: float = None,
|
||||
) -> list:
|
||||
|
||||
mode = mode.lower()
|
||||
if mode not in ['points', 'fid', 'spectrum', 'dsc', 'bds']:
|
||||
raise ValueError(f'Unknown mode {mode!r} as selected class')
|
||||
|
||||
if yerr is None:
|
||||
yerr = []
|
||||
elif y is None:
|
||||
raise ValueError('y is None and yerr is not None')
|
||||
|
||||
if (x is None) ^ (y is None):
|
||||
raise ValueError(f'x is {type(x)} and y is {type(y)}, should be both None or both defined')
|
||||
# if (x is None) ^ (y is None):
|
||||
# raise ValueError(f'x is {type(x)} and y is {type(y)}, should be both None or both defined')
|
||||
|
||||
if x is None:
|
||||
x = [0]
|
||||
elif isinstance(x, int):
|
||||
x = [x]
|
||||
elif isinstance(x, str) and x == 'index':
|
||||
x = []
|
||||
else:
|
||||
raise ValueError(f'x is {type(x)} not int')
|
||||
raise ValueError(f'type of x is {type(x)} not `int` or `str`')
|
||||
|
||||
if y is None:
|
||||
y = list(range(1, max(self.width)))
|
||||
y = list(range(int(len(x) != 0), max(self.width)))
|
||||
|
||||
cols = x + y + yerr
|
||||
with self.fname.open('rb') as fh:
|
||||
tmp_ = re.sub(b'[;,]', b' ', fh.read())
|
||||
raw_data = np.genfromtxt(BytesIO(tmp_), usecols=cols, missing_values='--')
|
||||
del tmp_
|
||||
|
||||
if raw_data.ndim == 1:
|
||||
# only one row or column
|
||||
if len(self.data) == 1:
|
||||
if len(self.lines) == 1:
|
||||
# one row
|
||||
raw_data = raw_data.reshape(1, -1)
|
||||
else:
|
||||
@ -119,33 +142,45 @@ class AsciiReader:
|
||||
else:
|
||||
raw_data = raw_data.reshape((1, *raw_data.shape))
|
||||
|
||||
if len(x) == 0 or raw_data.shape[2] == 1:
|
||||
_temp = np.zeros((raw_data.shape[0], raw_data.shape[1], raw_data.shape[2]+1))
|
||||
_temp[:, :, 0] = np.arange(raw_data.shape[1])
|
||||
_temp[:, :, 1:] = raw_data
|
||||
raw_data = _temp
|
||||
|
||||
if y:
|
||||
y = [i+1 for i in y]
|
||||
else:
|
||||
y = [1]
|
||||
|
||||
filename = self.fname.stem
|
||||
|
||||
if self.delays:
|
||||
delay_names = self.delays
|
||||
else:
|
||||
delay_names = [filename]
|
||||
delay_names = [num_value]
|
||||
|
||||
imported_sets = []
|
||||
|
||||
for i, value in enumerate(delay_names):
|
||||
kwargs = {'value': value, 'filename': self.fname, 'name': filename, 'group': filename, 'y_err': None}
|
||||
kwargs = {
|
||||
'value': value,
|
||||
'filename': self.fname,
|
||||
'name': filename,
|
||||
'group': filename,
|
||||
'y_err': None
|
||||
}
|
||||
|
||||
num_y = len(y)
|
||||
if mode == 'Points':
|
||||
single_len = 1
|
||||
cls = Points
|
||||
stepsize = 1
|
||||
elif mode == 'FID':
|
||||
cls = FID
|
||||
single_len = 1
|
||||
stepsize = 1
|
||||
|
||||
if mode in ['spectrum', 'fid', 'bds']:
|
||||
# complex data types
|
||||
single_len = 2
|
||||
stepsize = 2
|
||||
elif mode == 'Spectrum':
|
||||
cls = Spectrum
|
||||
single_len = 2
|
||||
stepsize = 2
|
||||
else:
|
||||
raise ValueError(f'Unknown mode {mode}, mot ´Points´, ´FID´ or ´Spectrum´.')
|
||||
|
||||
cls = {'points': Points, 'fid': FID, 'spectrum': Spectrum, 'bds': BDS, 'dsc': DSC}[mode]
|
||||
|
||||
for j in range(1, num_y+1, stepsize):
|
||||
if col_names is not None:
|
||||
@ -155,8 +190,8 @@ class AsciiReader:
|
||||
# more than one axis, append column number
|
||||
kwargs['name'] = filename + '_' + str(y[j-1])
|
||||
|
||||
if j+num_y+1 < raw_data.shape[2]:
|
||||
kwargs['y_err'] = raw_data[i, j+num_y+1]
|
||||
if j+num_y < raw_data.shape[2]:
|
||||
kwargs['y_err'] = raw_data[i, :, j+num_y]
|
||||
|
||||
imported_sets.append(cls(x=raw_data[i, :, 0], y=raw_data[i, :, j:j+single_len].T, **kwargs))
|
||||
|
||||
|
@ -5,6 +5,8 @@ import re
|
||||
from collections import namedtuple
|
||||
|
||||
import numpy as np
|
||||
from scipy.stats import linregress
|
||||
|
||||
try:
|
||||
from scipy.integrate import simpson
|
||||
except ImportError:
|
||||
@ -33,7 +35,7 @@ class DSCSample:
|
||||
|
||||
self.read_file(fname)
|
||||
|
||||
def read_file(self, fname: str | Path):
|
||||
def read_file(self, fname: str | Path) -> None:
|
||||
fname = Path(fname)
|
||||
|
||||
# file contains weird deg C character in stupiod ISO encoding
|
||||
@ -44,11 +46,9 @@ class DSCSample:
|
||||
if 'Heat from' in line:
|
||||
match = DSCSample.NUMBER_RE.findall(line)
|
||||
self.steps.append(('h', float(match[3]), float(match[1])+273.15, float(match[2])+273.15))
|
||||
|
||||
elif 'Cool from' in line:
|
||||
match = DSCSample.NUMBER_RE.findall(line)
|
||||
self.steps.append(('c', float(match[3]), float(match[1])+273.15, float(match[2])+273.15))
|
||||
|
||||
elif 'Hold for' in line:
|
||||
match = DSCSample.NUMBER_RE.findall(line)
|
||||
self.steps.append(('i', float(match[2])+273.15, float(match[1])*60))
|
||||
@ -141,7 +141,7 @@ class DSCCalibrator:
|
||||
def __init__(self):
|
||||
self.sample = None
|
||||
self.empty = None
|
||||
self.reference =[]
|
||||
self.reference = []
|
||||
self.ref_list = []
|
||||
|
||||
def set_measurement(self,
|
||||
@ -193,43 +193,30 @@ class DSCCalibrator:
|
||||
|
||||
for trans_temp, enthalpy in ref_value.transitions:
|
||||
real_tm.append(trans_temp)
|
||||
t_low_lim, t_high_lim = trans_temp - 10, trans_temp + 20
|
||||
t_low_lim, t_high_lim = trans_temp - 10, trans_temp + np.clip(rate, 20, 35)
|
||||
|
||||
low_border = np.argmin(np.abs(measurement[0] - t_low_lim))
|
||||
high_border = np.argmin(np.abs(measurement[0] - t_high_lim))
|
||||
ref_zoom = measurement[:, 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]])
|
||||
|
||||
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])]
|
||||
integ_limit = (np.argmin(np.abs(ref_zoom[0] - peak_max[0] + 3)),
|
||||
np.argmin(np.abs(ref_zoom[0] - peak_max[0] - 3)))
|
||||
|
||||
# substract baseline around reference peaks
|
||||
x_val = np.array([[ref_zoom[0, integ_limit[0]], 1], [ref_zoom[0, integ_limit[1]], 1]])
|
||||
y_val = np.array([ref_zoom[1, integ_limit[0]], ref_zoom[1, integ_limit[1]]])
|
||||
|
||||
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 (+50/rate))
|
||||
ref_grad = np.gradient(ref_zoom[1])
|
||||
max_grad = np.argmax(ref_grad)
|
||||
|
||||
grad_pos = max_grad-int(50 / rate), max_grad
|
||||
|
||||
x_val = np.array([[ref_zoom[0, grad_pos[0]], 1],
|
||||
[ref_zoom[0, grad_pos[1]], 1]])
|
||||
y_val = np.array([ref_zoom[1, grad_pos[0]],
|
||||
ref_zoom[1,grad_pos[1]]])
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
onset = sol[0] * ref_zoom[0] + sol[1]
|
||||
|
||||
melts.append(-sol[1] / sol[0])
|
||||
|
||||
if enthalpy is not None:
|
||||
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]])
|
||||
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
|
||||
|
||||
ref_grad = np.gradient(ref_zoom[1])
|
||||
crossing = np.where(np.diff(np.sign(np.abs(ref_grad)-0.001)))[0]
|
||||
|
||||
almost_flat = np.sort(crossing-np.argmax(ref_zoom[1]))
|
||||
integ_limit = (almost_flat[np.where((almost_flat < -40))[0][-1]]+np.argmax(ref_zoom[1]),
|
||||
almost_flat[np.where((almost_flat > 40))[0][0]]+np.argmax(ref_zoom[1]))
|
||||
|
||||
# subtract baseline around reference peak
|
||||
sol = self.solve_linear_eq(integ_limit, ref_zoom)
|
||||
ref_zoom[1] -= (ref_zoom[0] * sol[0] + sol[1])
|
||||
|
||||
# integrate over peak to calibrate y axis
|
||||
# delta H in J/g: Integrate Peak over time and divide by weight
|
||||
area = simpson(ref_zoom[1, integ_limit[0]:integ_limit[1]],
|
||||
@ -237,6 +224,23 @@ class DSCCalibrator:
|
||||
even='avg') * 1e-3
|
||||
calib_y.append(enthalpy / (area / data.weight))
|
||||
|
||||
else:
|
||||
ref_grad = np.gradient(ref_zoom[1])
|
||||
res = linregress(ref_zoom[0, :len(ref_grad)//5], ref_zoom[1, :len(ref_grad)//5])
|
||||
|
||||
ref_zoom[1] -= (res.slope*ref_zoom[0] + res.intercept)
|
||||
|
||||
# calculate onset slope (use points at position of maximum gradient - 100/rate (+50/rate))
|
||||
ref_grad = np.gradient(ref_zoom[1])
|
||||
max_grad = np.argmax(ref_grad)
|
||||
|
||||
grad_pos = max_grad - max(1, int(160 / rate)), max_grad
|
||||
|
||||
sol = self.solve_linear_eq(grad_pos, ref_zoom)
|
||||
onset = sol[0] * ref_zoom[0] + sol[1]
|
||||
|
||||
melts.append(-sol[1] / sol[0])
|
||||
|
||||
results.append([ref_zoom, onset, ref_zoom[:, grad_pos]])
|
||||
|
||||
if len(melts) > 1:
|
||||
@ -254,7 +258,15 @@ class DSCCalibrator:
|
||||
|
||||
return calib_x, calib_y, results
|
||||
|
||||
def get_data(self, idx, slope='iso'):
|
||||
@staticmethod
|
||||
def solve_linear_eq(limits: tuple, ref_zoom: np.ndarray) -> np.ndarray:
|
||||
x_val = np.array([[ref_zoom[0, limits[0]], 1], [ref_zoom[0, limits[1]], 1]])
|
||||
y_val = np.array([ref_zoom[1, limits[0]], ref_zoom[1, limits[1]]])
|
||||
sol = np.linalg.solve(x_val, y_val)
|
||||
|
||||
return sol
|
||||
|
||||
def get_data(self, idx: int, slope: str = 'iso', limits: tuple[float, float] = None):
|
||||
if self.sample.steps[idx][0] == 'i':
|
||||
raise ValueError('baseline correction is not implemented for isotherms')
|
||||
|
||||
@ -280,10 +292,11 @@ class DSCCalibrator:
|
||||
empty_y = empty_data[1]
|
||||
if self.sample.length(idx) != self.empty.length(idx_empty):
|
||||
with np.errstate(all='ignore'):
|
||||
empty_y = interp1d(empty_data[0], empty_data[1], fill_value='extrapolate')(sample_data[0])
|
||||
empty_y = interp1d(empty_data[2]-empty_data[2, 0], empty_data[1], fill_value='extrapolate')(sample_data[2, 0])
|
||||
|
||||
sample_data[1] -= empty_y
|
||||
drift_value = sample_data.copy()[(2, 1), :]
|
||||
sample_data = sample_data[:, np.isfinite(sample_data[1, :])]
|
||||
|
||||
mean_isotherms = []
|
||||
for offset in [-1, 1]:
|
||||
@ -303,20 +316,39 @@ class DSCCalibrator:
|
||||
drift_value = np.c_[drift_value, isotherm_sample]
|
||||
|
||||
if slope is not None:
|
||||
|
||||
if slope == 'iso':
|
||||
# calculate slope from difference between isotherms
|
||||
m = (mean_isotherms[1] - mean_isotherms[0]) / (sample_data[2, -1] - sample_data[2, 0])
|
||||
offset = sample_data[1, 200]
|
||||
region = sample_data[1:, 200:201]
|
||||
|
||||
else:
|
||||
# calculate mean slope of heat flow from points in the beginning
|
||||
offset = sample_data[1, 200]
|
||||
grad = np.gradient(sample_data[1, :], sample_data[2, :])
|
||||
region = None
|
||||
if limits is not None:
|
||||
if len(limits) != 2:
|
||||
raise ValueError(f'limits must be tuple of len 2, not {limits!r}')
|
||||
min_lim, max_lim = min(limits), max(limits)
|
||||
window = (sample_data[2, :] >= min_lim) * (sample_data[2, :] <= max_lim)
|
||||
region = sample_data[1:, window]
|
||||
|
||||
if region.shape[1] <= 2:
|
||||
# raise ValueError(f'No data inside selected time window {min_lim/60} min and {max_lim/60} min')
|
||||
region = None
|
||||
|
||||
if region is None:
|
||||
# if no limits, use all
|
||||
region = sample_data[1:, 200:-200]
|
||||
|
||||
grad = np.gradient(region[0, :], region[1, :])
|
||||
grad = grad[~np.isnan(grad)]
|
||||
m = grad[(grad < grad.mean()+grad.std()/5)*(grad > grad.mean()-grad.std()/5)].mean()
|
||||
|
||||
sample_data[1] -= m * (sample_data[2] - sample_data[2, 200]) + offset
|
||||
offset = region[0, 0]
|
||||
sample_data[1] -= m * (sample_data[2] - region[1, 0]) + offset
|
||||
line = np.array([[sample_data[2, 0], sample_data[2, -1]],
|
||||
[m * (sample_data[2, 200] - sample_data[2, 200]) + offset,
|
||||
m * (sample_data[2, -1] - sample_data[2, 200]) + offset]])
|
||||
[m * (sample_data[2, 0] - region[1, 0]) + offset,
|
||||
m * (sample_data[2, -1] - region[1, 0]) + offset]])
|
||||
|
||||
else:
|
||||
line = np.array([[sample_data[2, 0], sample_data[2, -1]], [0, 0]])
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user