diff --git a/doc/source/_templates/autosummary/_class.rst b/doc/source/_templates/autosummary/_class.rst deleted file mode 100644 index eae2e87..0000000 --- a/doc/source/_templates/autosummary/_class.rst +++ /dev/null @@ -1,27 +0,0 @@ -{{ objname | escape | underline}} - -Import as {{ fullname }} - -.. currentmodule:: {{ module }} - -.. autoclass:: {{ objname }} - - {% block methods %} - {% if methods %} - .. rubric:: {{ _('Methods') }} - - .. autosummary:: - :toctree: - - {% for item in methods %} - {%- if not item.startswith('_') %} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - {% endif %} - {% endblock %} - -.. minigallery:: {{module}}.{{objname}} - :add-heading: - - diff --git a/doc/source/_templates/autosummary/class.rst b/doc/source/_templates/autosummary/class.rst index 4fa8438..4eca388 100644 --- a/doc/source/_templates/autosummary/class.rst +++ b/doc/source/_templates/autosummary/class.rst @@ -1,14 +1,27 @@ -:mod:`{{ module | escape }}`.{{ objname }} -{{ underline }}================== +{{ fullname | escape | underline}} .. currentmodule:: {{ module }} .. autoclass:: {{ objname }} - :members: - :inherited-members: -.. include:: {{ fullname }}.examples + {% block methods %} -.. raw:: html + {% if methods %} + .. rubric:: {{ _('Methods') }} -
+ {% for item in methods %} + .. automethod:: {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/doc/source/_templates/autosummary/class_with_attributes.rst b/doc/source/_templates/autosummary/class_with_attributes.rst deleted file mode 100644 index 272b80d..0000000 --- a/doc/source/_templates/autosummary/class_with_attributes.rst +++ /dev/null @@ -1,33 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. autoclass:: {{ objname }} - - {%- block methods %} - {% if methods %} - .. rubric:: {{ _('Methods') }} - - {%- for item in methods %} - {% if not item.startswith('_') %} - .. automethod:: {{ item }} - {%- endif %} - {%- endfor %} - {%- endif %} - {% endblock %} - - {%- block attributes %} - {%- if attributes %} - .. rubric:: {{ _('Attributes') }} - - {% for item in attributes %} - .. autoattribute:: {{ item }} - {%- endfor %} - {% endif %} - {% endblock -%} - -.. include:: {{ fullname }}.examples - -.. raw:: html - -
diff --git a/doc/source/api/distributions.rst b/doc/source/api/distributions.rst index c62e52c..c1489a3 100644 --- a/doc/source/api/distributions.rst +++ b/doc/source/api/distributions.rst @@ -1,4 +1,5 @@ .. automodule:: nmreval.distributions - :no-members: - :no-inherited-members: - :no-special-members: + :members: + :inherited-members: + :undoc-members: + diff --git a/doc/source/api/models.rst b/doc/source/api/models.rst index 3bcef09..b5c9ad4 100644 --- a/doc/source/api/models.rst +++ b/doc/source/api/models.rst @@ -1,4 +1,2 @@ .. automodule:: nmreval.models - :no-members: - :no-inherited-members: - :no-special-members: + :members: diff --git a/doc/source/conf.py b/doc/source/conf.py index a23eb00..4f0356f 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -39,7 +39,6 @@ version = release extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', - 'sphinx.ext.inheritance_diagram', 'sphinx.ext.napoleon', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', @@ -55,14 +54,13 @@ intersphinx_mapping = { 'matplotlib': ('https://matplotlib.org/stable', None), } -# autodoc options +# # autodoc options autodoc_typehints = 'none' autodoc_class_signature = 'separated' autoclass_content = 'class' autodoc_member_order = 'groupwise' -# autodoc_default_options = {'members': False, 'inherited-members': False} - -# autosummay options +# +# # autosummay options autosummary_generate = True # spinx-gallery diff --git a/nmreval/gui_qt/_py/fitfunctionwidget.py b/nmreval/gui_qt/_py/fitfunctionwidget.py index 3035f2f..2d44e05 100644 --- a/nmreval/gui_qt/_py/fitfunctionwidget.py +++ b/nmreval/gui_qt/_py/fitfunctionwidget.py @@ -13,7 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") - Form.resize(314, 232) + Form.resize(382, 375) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -23,14 +23,14 @@ class Ui_Form(object): self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setSpacing(3) self.gridLayout.setObjectName("gridLayout") - self.widget = ExpandableWidget(Form) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) + self.typecomboBox = QtWidgets.QComboBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) - self.widget.setSizePolicy(sizePolicy) - self.widget.setObjectName("widget") - self.gridLayout.addWidget(self.widget, 4, 0, 1, 2) + sizePolicy.setHeightForWidth(self.typecomboBox.sizePolicy().hasHeightForWidth()) + self.typecomboBox.setSizePolicy(sizePolicy) + self.typecomboBox.setObjectName("typecomboBox") + self.gridLayout.addWidget(self.typecomboBox, 0, 0, 1, 2) self.complex_widget = QtWidgets.QWidget(Form) self.complex_widget.setObjectName("complex_widget") self.gridLayout_2 = QtWidgets.QGridLayout(self.complex_widget) @@ -53,7 +53,15 @@ class Ui_Form(object): self.label.setSizePolicy(sizePolicy) self.label.setObjectName("label") self.gridLayout_2.addWidget(self.label, 0, 0, 1, 2) - self.gridLayout.addWidget(self.complex_widget, 5, 0, 1, 2) + self.gridLayout.addWidget(self.complex_widget, 6, 0, 1, 2) + self.fitcomboBox = QtWidgets.QComboBox(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fitcomboBox.sizePolicy().hasHeightForWidth()) + self.fitcomboBox.setSizePolicy(sizePolicy) + self.fitcomboBox.setObjectName("fitcomboBox") + self.gridLayout.addWidget(self.fitcomboBox, 1, 0, 1, 2) self.use_function_button = QtWidgets.QToolButton(Form) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) @@ -65,31 +73,6 @@ class Ui_Form(object): self.use_function_button.setArrowType(QtCore.Qt.RightArrow) self.use_function_button.setObjectName("use_function_button") self.gridLayout.addWidget(self.use_function_button, 3, 1, 1, 1) - self.fitcomboBox = QtWidgets.QComboBox(Form) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.fitcomboBox.sizePolicy().hasHeightForWidth()) - self.fitcomboBox.setSizePolicy(sizePolicy) - self.fitcomboBox.setObjectName("fitcomboBox") - self.gridLayout.addWidget(self.fitcomboBox, 1, 0, 1, 2) - self.typecomboBox = QtWidgets.QComboBox(Form) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.typecomboBox.sizePolicy().hasHeightForWidth()) - self.typecomboBox.setSizePolicy(sizePolicy) - self.typecomboBox.setObjectName("typecomboBox") - self.gridLayout.addWidget(self.typecomboBox, 0, 0, 1, 2) - self.fitequation = QtWidgets.QLabel(Form) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.fitequation.sizePolicy().hasHeightForWidth()) - self.fitequation.setSizePolicy(sizePolicy) - self.fitequation.setWordWrap(True) - self.fitequation.setObjectName("fitequation") - self.gridLayout.addWidget(self.fitequation, 2, 0, 1, 2) self.operator_combobox = QtWidgets.QComboBox(Form) self.operator_combobox.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) self.operator_combobox.setFrame(True) @@ -99,9 +82,19 @@ class Ui_Form(object): self.operator_combobox.addItem("") self.operator_combobox.addItem("") self.gridLayout.addWidget(self.operator_combobox, 3, 0, 1, 1) - self.use_combobox = QtWidgets.QComboBox(Form) - self.use_combobox.setObjectName("use_combobox") - self.gridLayout.addWidget(self.use_combobox, 6, 0, 1, 2) + self.functree = FitModelTree(Form) + self.functree.setObjectName("functree") + self.functree.headerItem().setText(0, "1") + self.gridLayout.addWidget(self.functree, 5, 0, 1, 2) + self.fitequation = QtWidgets.QLabel(Form) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fitequation.sizePolicy().hasHeightForWidth()) + self.fitequation.setSizePolicy(sizePolicy) + self.fitequation.setWordWrap(True) + self.fitequation.setObjectName("fitequation") + self.gridLayout.addWidget(self.fitequation, 2, 0, 1, 2) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) @@ -115,9 +108,9 @@ class Ui_Form(object): self.complex_comboBox.setItemText(2, _translate("Form", "Imaginary")) self.label.setText(_translate("Form", "Complex function found")) self.use_function_button.setText(_translate("Form", "Use")) - self.fitequation.setText(_translate("Form", "Equation")) self.operator_combobox.setItemText(0, _translate("Form", "Add")) self.operator_combobox.setItemText(1, _translate("Form", "Multiply")) self.operator_combobox.setItemText(2, _translate("Form", "Subtract")) self.operator_combobox.setItemText(3, _translate("Form", "Divide by")) -from ..lib.expandablewidget import ExpandableWidget + self.fitequation.setText(_translate("Form", "Equation")) +from ..fit.fit_forms import FitModelTree diff --git a/nmreval/gui_qt/data/container.py b/nmreval/gui_qt/data/container.py index 3aae696..1c188e1 100644 --- a/nmreval/gui_qt/data/container.py +++ b/nmreval/gui_qt/data/container.py @@ -15,7 +15,7 @@ from ...lib.symbols import SymbolStyle, symbolcycle from ...data.nmr import Spectrum, FID from ..Qt import QtCore -from ..io.exporters import pgitem_to_dic +from ..io.exporters import GraceExporter from ..lib.decorators import plot_update from ..lib.pg_objects import ErrorBars, PlotItem @@ -33,6 +33,7 @@ class ExperimentContainer(QtCore.QObject): self._fits = [] self._data = data self._manager = kwargs.get('manager') + self.graph = '' self.mode = 'point' self.plot_real = None @@ -374,10 +375,12 @@ class ExperimentContainer(QtCore.QObject): def save(self, fname): ext = fname.suffix if ext == '.agr': - real_dic = pgitem_to_dic(self.plot_real) - from ..io.exporters import GraceExporter + dic = self._manager.graphs[self.graph].get_state() + dic['items'] = [self.plot_real.get_data_opts()] + if self.plot_imag is not None: + dic['items'].append(self.plot_imag.get_data_opts()) - GraceExporter(real_dic).export(fname) + GraceExporter(dic).export(fname) elif ext in ['.dat', '.txt']: self._data.savetxt(fname, err=True) diff --git a/nmreval/gui_qt/fit/fit_forms.py b/nmreval/gui_qt/fit/fit_forms.py index 6d8f25d..354d02d 100644 --- a/nmreval/gui_qt/fit/fit_forms.py +++ b/nmreval/gui_qt/fit/fit_forms.py @@ -409,6 +409,9 @@ class FitTableWidget(QtWidgets.QTableWidget): while self.rowCount(): self.removeRow(0) + self.setColumnCount(2) + self.hideColumn(1) + for (sid, name) in set_ids: item = QtWidgets.QTableWidgetItem(name) item.setCheckState(QtCore.Qt.Checked) @@ -419,7 +422,7 @@ class FitTableWidget(QtWidgets.QTableWidget): item2 = QtWidgets.QTableWidgetItem('') self.setItem(row, 1, item2) - cb = QtWidgets.QComboBox() + cb = QtWidgets.QComboBox(parent=self) cb.addItem('Default') self.setCellWidget(row, 1, cb) diff --git a/nmreval/gui_qt/fit/fitfunction.py b/nmreval/gui_qt/fit/fitfunction.py index 32612b3..0d98c4b 100644 --- a/nmreval/gui_qt/fit/fitfunction.py +++ b/nmreval/gui_qt/fit/fitfunction.py @@ -1,7 +1,6 @@ from itertools import cycle, count from typing import List, Tuple, Union -from .fit_forms import FitModelTree from ..lib import get_icon from .._py.fitfunctionwidget import Ui_Form from ..Qt import QtWidgets, QtCore, QtGui @@ -26,9 +25,6 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form): super().__init__(parent=parent) self.setupUi(self) - self.functree = FitModelTree() - self.widget.setText('Model structure') - self.widget.addWidget(self.functree) self._types = [] self.functions = find_models(models) @@ -57,8 +53,14 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form): for i, op_icon in enumerate(self.op_names): self.operator_combobox.setItemIcon(i, get_icon(op_icon)) - def __len__(self): - return self.use_combobox.count() + def __len__(self) -> int: + num = 0 + iterator = QtWidgets.QTreeWidgetItemIterator(self.functree) + while iterator.value(): + num += 1 + iterator += 1 + + return num @QtCore.pyqtSlot(int, name='on_typecomboBox_currentIndexChanged') def change_group(self, idx: int): @@ -114,11 +116,6 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form): qcolor = QtGui.QColor(color) self.functree.add_function(idx, cnt, op, name, qcolor, **kwargs) - self.use_combobox.addItem(name, userData=cnt) - - self.use_combobox.setItemData(self.use_combobox.count()-1, color, QtCore.Qt.DecorationRole) - self.use_combobox.setCurrentIndex(self.use_combobox.count() - 1) - f = self.functions[idx] if hasattr(f, 'iscomplex') and f.iscomplex: self.iscomplex = True @@ -138,17 +135,11 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form): iterator += 1 self.complex_widget.setVisible(self.iscomplex) - for i in range(self.use_combobox.count()): - if idx == self.use_combobox.itemData(i): - self.use_combobox.removeItem(i) - break - self.itemRemoved.emit(idx) - @QtCore.pyqtSlot(int, name='on_use_combobox_currentIndexChanged') - def show_parameter(self, idx: int): - if self.use_combobox.count(): - self.showFunction.emit(self.use_combobox.itemData(idx, QtCore.Qt.UserRole)) + @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int, name='on_functree_itemClicked') + def show_parameter(self, item: QtWidgets.QTreeWidgetItem, _: int): + self.showFunction.emit(item.data(0, self.functree.counterRole)) def get_selected(self): function_nr, idx = self.functree.get_selected() @@ -217,9 +208,5 @@ class QFunctionWidget(QtWidgets.QWidget, Ui_Form): self.functree.clear() self.functree.blockSignals(False) - self.use_combobox.blockSignals(True) - self.use_combobox.clear() - self.use_combobox.blockSignals(False) - self.complex_comboBox.setCurrentIndex(0) self.complex_widget.hide() diff --git a/nmreval/gui_qt/graphs/graphwindow.py b/nmreval/gui_qt/graphs/graphwindow.py index c4da8ed..ef79125 100644 --- a/nmreval/gui_qt/graphs/graphwindow.py +++ b/nmreval/gui_qt/graphs/graphwindow.py @@ -511,9 +511,11 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow): item = self.real_plots[sid] other_item = self.imag_plots[sid] # should legend be visible? is either real part or imaginary part shown? - if self.listWidget.item(i).checkState() and \ - (item in self.graphic.items() or other_item in self.graphic.items()): - self.legend.addItem(item, convert(item.opts.get('name', ''), old='tex', new='html')) + if self.listWidget.item(i).checkState(): + if item in self.graphic.items(): + self.legend.addItem(item, convert(item.opts.get('name', ''), old='tex', new='html')) + 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): filters = 'All files (*.*);;AGR (*.agr);;SVG (*.svg);;PDF (*.pdf)' @@ -580,12 +582,21 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow): self.set_color(foreground=fg_color, background=bg_color) - def export_graphics(self): + def export_graphics(self) -> dict: dic = self.get_state() dic['items'] = [] + in_legend = [] + for item in self.curves(): - item_dic = item[0].get_data_opts() + plot_item = item[0] + legend_shown = False + for sample, _ in self.legend.items: + if sample.item is plot_item: + legend_shown = True + break + in_legend.append(legend_shown) + item_dic = plot_item.get_data_opts() if len(item) == 2: # plot can show errorbars item_dic['yerr'] = item[1].opts['topData'] @@ -593,6 +604,8 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow): if item_dic: dic['items'].append(item_dic) + dic['in_legend'] = in_legend + return dic def get_state(self) -> dict: diff --git a/nmreval/gui_qt/io/exporters.py b/nmreval/gui_qt/io/exporters.py index b031d68..8dec1ed 100644 --- a/nmreval/gui_qt/io/exporters.py +++ b/nmreval/gui_qt/io/exporters.py @@ -1,9 +1,8 @@ +from __future__ import annotations + from numpy import c_ from ...io.graceeditor import GraceEditor -from ...lib.colors import Colors -from ...lib.lines import LineStyle -from ...lib.symbols import SymbolStyle from ...utils.text import convert from ..Qt import QtGui, QtCore, QtPrintSupport @@ -14,7 +13,7 @@ class GraceExporter: self.__agr = None self.__opts = kwargs - def export(self, outfile: str, mode: (int, str) = 'w'): + def export(self, outfile: str, mode: int | str = 'w'): if mode == 'w': self.__agr = GraceEditor() else: @@ -27,7 +26,7 @@ class GraceExporter: new_g.set_log(x=self.__opts['log'][0], y=self.__opts['log'][1]) new_g.set_onoff('legend', self.__opts['legend']) - new_g.set_property(**{'title': '"' + convert(self.__opts['labels'][2], old="html", new="agr") + '"', + new_g.set_property(**{'title': f'"{convert(self.__opts["labels"][2], old="html", new="agr")}"', 'legend loctype': 'view', 'legend': ', '.join(str(i) for i in new_g.world_to_view(self.__opts['legend_pos']))}) @@ -43,7 +42,7 @@ class GraceExporter: colors = self.__agr.colors new_colors = [] - for item in self.__opts['items']: + for plot_label, item in zip(self.__opts['in_legend'], self.__opts['items']): new_s = self.__agr.new_set(g_idx) sc = item['symbolcolor'] @@ -75,7 +74,12 @@ class GraceExporter: new_s.set_line(**{'color': c_num, 'linewidth': item['linewidth'], 'linestyle': item['linestyle'].to_agr()}) - new_s.set_property(comment='"' + item['name'] + '"', legend='"' + item['name'] + '"') + + if plot_label: + new_s.set_property(comment=f'"{item["name"]}"', + legend=f'"{convert(item["name"], old="tex", new="agr")}"') + else: + new_s.set_property(comment=f'"{item["name"]}"') data = self.__agr.dataset(g_idx, new_s.idx) if 'yerr' in item: @@ -108,36 +112,3 @@ class PDFPrintExporter: painter = QtGui.QPainter(printer) self.graphic.render(painter) painter.end() - - -def pgitem_to_dic(item): - x, y = item.xData, item.yData - if (x is None) or (len(x) == 0): - return - - opts = item.opts - item_dic = { - 'x': x, 'y': y, - 'name': opts['name'], - 'symbolsize': opts['symbolSize'] - } - - if opts['symbol'] is None: - item_dic['symbol'] = SymbolStyle.No - item_dic['symbolcolor'] = Colors.Black - else: - item_dic['symbol'] = SymbolStyle.from_str(opts['symbol']) - item_dic['symbolcolor'] = Colors.from_rgb(*opts['symbolBrush'].color().getRgbF()[:3], normed=True) - - pen = opts['pen'] - - if pen is not None: - item_dic['linestyle'] = LineStyle(pen.style()) - item_dic['linecolor'] = Colors.from_rgb(*pen.color().getRgbF()[:3], normed=True) - item_dic['linewidth'] = pen.widthF() - else: - item_dic['linestyle'] = LineStyle.No - item_dic['linecolor'] = item_dic['symbolcolor'] - item_dic['linewidth'] = 0.0 - - return item_dic diff --git a/nmreval/gui_qt/lib/gol.py b/nmreval/gui_qt/lib/gol.py index 603d56a..6974b03 100644 --- a/nmreval/gui_qt/lib/gol.py +++ b/nmreval/gui_qt/lib/gol.py @@ -35,8 +35,6 @@ def diamond(a): y = a-x pxl.extend([[x, y], [-x, y], [x, -y], [-x, -y]]) - print(np.array(pxl).shape) - return np.array(pxl) @@ -100,7 +98,6 @@ class GameOfLife: self.fill = np.zeros(self.populations) self._populate(fill, pattern) - print(rules) if isinstance(rules, str): try: b_rule, s_rule = predefined_rules[rules] diff --git a/nmreval/gui_qt/lib/pg_objects.py b/nmreval/gui_qt/lib/pg_objects.py index 52f3955..99559a5 100644 --- a/nmreval/gui_qt/lib/pg_objects.py +++ b/nmreval/gui_qt/lib/pg_objects.py @@ -165,8 +165,8 @@ class PlotItem(PlotDataItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.opts['linecolor'] = Colors.Black - self.opts['symbolcolor'] = Colors.Black + self.opts['linecolor'] = (0, 0, 0) + self.opts['symbolcolor'] = (0, 0, 0) if self.opts['pen'] is not None: pen = self.opts['pen'] @@ -251,7 +251,6 @@ class PlotItem(PlotDataItem): x = [] if y is None: y = [] - # scatterArgs['mask'] = self.dataMask if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None): is_finite = np.isfinite(x) & np.isfinite(y) @@ -347,7 +346,6 @@ class PlotItem(PlotDataItem): item_dic['symbolcolor'] = opts['symbolcolor'] pen = opts['pen'] - if pen is not None: item_dic['linestyle'] = LineStyle(pen.style()) item_dic['linecolor'] = opts['linecolor'] diff --git a/nmreval/gui_qt/lib/stuff.py b/nmreval/gui_qt/lib/stuff.py index 9af81bb..e0454cd 100644 --- a/nmreval/gui_qt/lib/stuff.py +++ b/nmreval/gui_qt/lib/stuff.py @@ -180,7 +180,6 @@ class SnakeBoard(Board): self.update() def snake_move(self): - print(self.next_direction) if self.next_direction: turn = self.next_direction.pop() if self.direction != self._direction_pairs[turn]: diff --git a/nmreval/io/graceeditor.py b/nmreval/io/graceeditor.py index b5776db..3949929 100644 --- a/nmreval/io/graceeditor.py +++ b/nmreval/io/graceeditor.py @@ -463,10 +463,16 @@ class GraceGraph(GraceProperties): def set_log(self, x=None, y=None): kwargs = {} - if x is not None: - kwargs['xaxes scale'] = 'Logarithmic' if x else 'Normal' - if y is not None: - kwargs['yaxes scale'] = 'Logarithmic' if y else 'Normal' + for ax, label in [(x, 'x'), (y, 'y')]: + if ax is not None: + if ax: + kwargs[label + 'axes scale'] = 'Logarithmic' + kwargs[label + 'axis ticklabel format'] = 'power' + kwargs[label + 'axis ticklabel prec'] = '0' + else: + kwargs[label + 'axes scale'] = 'Normal' + kwargs[label + 'axis ticklabel format'] = 'general' + kwargs[label + 'axis ticklabel prec'] = '5' for i, line in enumerate(self): m = self._RE_ENTRY.match(line) diff --git a/nmreval/models/__init__.py b/nmreval/models/__init__.py index 68c2498..2a1aa1e 100755 --- a/nmreval/models/__init__.py +++ b/nmreval/models/__init__.py @@ -1,14 +1,14 @@ """ -============= -Fit functions -============= +===================================== +Fit functions (:mod:`nmreval.models`) +===================================== .. currentmodule:: nmreval.models .. autosummary:: :toctree: generated :nosignatures: - :template: autosummary/class_with_attributes.rst + :recursive: Constant Linear diff --git a/nmreval/models/basic.py b/nmreval/models/basic.py index c7bc0c0..6ce6c3b 100644 --- a/nmreval/models/basic.py +++ b/nmreval/models/basic.py @@ -3,7 +3,7 @@ Basic functions *************** -Simple basic functions +Simple functions """ import numpy as np @@ -27,7 +27,7 @@ class Constant: Constant .. math:: - y = c \cdot x + y = c Args: x (array-like): Input values @@ -135,22 +135,7 @@ class Parabola: class PowerLawCross: """ - Crossover between to power laws at position :math:`x_0` - - .. math:: - y \\propto - \\begin{cases} - x^{b_1}, & x \le x_0 \\\\ - x^{b_2}, & x > x_0 - \\end{cases} - - Args: - x (array_like): Input values - c (float): Prefactor - b1 (float): Exponent of first power law - b2 (float): Exponent of second power law - x0 (float): x position of crossover - + Crossover between power laws """ type = 'Basic' name = 'Crossing Power Laws' @@ -158,6 +143,23 @@ class PowerLawCross: @staticmethod def func(x, c, b1, b2, x0): + """ + Crossover between to power laws at position :math:`x_0` + + .. math:: + y \\propto + \\begin{cases} + x^{b_1}, & x \le x_0 \\\\ + x^{b_2}, & x > x_0 + \\end{cases} + + Args: + x (array_like): Input values + c (float): Prefactor + b1 (float): Exponent of first power law + b2 (float): Exponent of second power law + x0 (float): x position of crossover + """ mas = np.nonzero(x > x0) ret_val = c * x**b1 c2 = c * x0**(b1-b2) @@ -167,19 +169,8 @@ class PowerLawCross: class Sine: """ - Sine function - - .. math:: - y = C\sin(a x - \phi) - - Args: - x (array_like): Input values - c (float): Prefactor - a (float): frequency - phi (float): shift - + Wavy sine function """ - type = 'Basic' name = 'Sine' equation = r'C*sin(a*x-\phi)' @@ -187,6 +178,19 @@ class Sine: @staticmethod def func(x, c: float, a: float, phi: float): + """ + Calculate sine function + + .. math:: + y = C\sin(a x - \phi) + + Args: + x (array_like): Input values + c (float): Prefactor + a (float): frequency + phi (float): shift + + """ return c*np.sin(a*x-phi) diff --git a/nmreval/models/diffusion.py b/nmreval/models/diffusion.py index 359fdd1..4e03f92 100644 --- a/nmreval/models/diffusion.py +++ b/nmreval/models/diffusion.py @@ -116,15 +116,16 @@ class Peschier: name = 'Diffusion + Cross-Relaxation' type = 'Diffusion' equation = r'Diffusion with cross-relax f(ast) \rightarrow s(low)' - params = ['M_{0}', 'D', 'T_{1,f}', 'T_{1,s}', 'k_{f}', 'k_{s}', 't_{ev}', 'g'] + params = ['M_{0}', 'D', 'T_{1,f}', 'T_{1,s}', 'k', 'p_{f}', 't_{ev}', 'g'] bounds = [(0, None), (0, None), (0, None), (0, None), (0, None), (0, None)] choices = [(r'\gamma', 'nucleus', gamma)] @staticmethod - def func(x, m0, d, t1f, t1s, kf, ks, tp, g, nucleus=2.67522128e8): + def func(x, m0, d, t1f, t1s, k, pf, tp, g, nucleus: float = 2.67522128e8): q = nucleus*g*tp r1s, r1f = 1 / t1s, 1 / t1f + kf, ks = pf*k, (1-pf)*k a_plus = 0.5 * (d*q*q + kf + ks + r1f + r1s + np.sqrt((d*q*q + kf + r1f - ks - r1s)**2 + 4*kf*ks)) a_minu = 0.5 * (d*q*q + kf + ks + r1f + r1s - np.sqrt((d*q*q + kf + r1f - ks - r1s)**2 + 4*kf*ks)) diff --git a/nmreval/models/relaxation.py b/nmreval/models/relaxation.py index ccd8314..ca4230b 100644 --- a/nmreval/models/relaxation.py +++ b/nmreval/models/relaxation.py @@ -119,20 +119,3 @@ class SatRecMLF: if kind == 'inv': a = 2 return dm * (1 - a * mlf(-(x/t1)**alpha, alpha)) + m0 - - -class PeschierHomogeneous: - name = 'Cross-Relaxation (g=0)' - type = 'Relaxation' - equation = r'Cross-Relaxation f(ast) \rightarrow s(low) (m_{s}(0)=0)' - params = ['M_{0}', 'k_{s}', 'k_{f}', 'T_{1,s}', 'T_{1,f}'] - bounds = [(0, None), (0, None), (0, None), (0, None), (0, None)] - - @staticmethod - def func(x, m, ks, kf, t1s, t1f): - r1f, r1s = 1/t1f, 1/t1s - a_plus = 0.5*((kf + ks + r1f + r1s) + np.sqrt((kf + r1f - ks - r1s)**2 + 4*kf*ks)) - a_minu = 0.5*((kf + ks + r1f + r1s) - np.sqrt((kf + r1f - ks - r1s)**2 + 4*kf*ks)) - - return m*((a_plus - ks - r1s) / (a_plus - a_minu) * np.exp(-a_plus * x) - - (a_minu - ks - r1s) / (a_plus - a_minu) * np.exp(-a_minu * x)) diff --git a/nmreval/models/user_models.py b/nmreval/models/user_models.py deleted file mode 100644 index 5f98df4..0000000 --- a/nmreval/models/user_models.py +++ /dev/null @@ -1,3 +0,0 @@ -from ..configs import config_paths - -print(config_paths() / 'usermodels.py') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff2d408 --- /dev/null +++ b/pyproject.toml @@ -0,0 +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', +] +keywords = ['nmr', 'physics', 'science'] +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', +] +license = {text = "BSD 3-Clause License"} + +[tool.setuptools] +include_package_data = true + + +[tool.setuptools.packages] +find = {} +scripts = bin/evaluate.py + +[tool.setuptools.packages.find] +include =[ + 'nmreval*', + 'resources*', +] + +[tool.setuptools.package_data] +* = *.txt, *.npz, *.png, *.json + + + + + diff --git a/resources/_rc/__init__.py b/resources/_rc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/resources/_ui/fitfunctionwidget.ui b/resources/_ui/fitfunctionwidget.ui index 345ac8c..124e5b2 100644 --- a/resources/_ui/fitfunctionwidget.ui +++ b/resources/_ui/fitfunctionwidget.ui @@ -6,8 +6,8 @@ 0 0 - 314 - 232 + 382 + 375 @@ -35,17 +35,17 @@ 3 - - + + - + 0 0 - + @@ -102,6 +102,16 @@ + + + + + 0 + 0 + + + + @@ -124,42 +134,6 @@ - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Equation - - - true - - - @@ -190,17 +164,38 @@ - - + + + + + 1 + + + + + + + + + 0 + 0 + + + + Equation + + + true + + - ExpandableWidget - QWidget -
..lib.expandablewidget
- 1 + FitModelTree + QTreeWidget +
..fit.fit_forms
diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9eb7cc4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,47 @@ +[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 +keywords = nmr, physics, science +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 +license = BSD 3-Clause License + +[options] +include_package_data = True +packages = find: +scripts = bin/evaluate.py + +[options.packages.find] +include = + nmreval* + resources* + +[options.package_data] +* = *.txt, *.npz, *.png, *.json + + + + + diff --git a/setup.py b/setup.py index 072b099..e730e9c 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,3 @@ -from setuptools import setup - -setup( - name='nmreval', - version='0.1', - packages=['.nmreval'] -) - """A setuptools based setup module. See: @@ -26,193 +18,57 @@ long_description = (here / 'README.md').read_text(encoding='utf-8') # Fields marked as "Optional" may be commented out. setup( - # This is the name of your project. The first time you publish this - # package, this name will be registered for you. It will determine how - # users can install this project, e.g.: + # name='nmreval', # Required + # version='0.1', # Required + # description='A sample Python project', # Optional + # long_description=long_description, # Optional + # long_description_content_type='text/markdown', # Optional (see note above) + # url='https://chaos3.fkp.physik.tu-darmstadt.de/source/nmreval/', # Optional + # author='Dominik Demuth', # Optional + # author_email='dominik.demuth@physik.tu-darmstadt.de', # Optional + # classifiers=[ # Optional + # 'Development Status :: 3 - Alpha', + # 'Intended Audience :: End Users/Desktop', + # 'Intended Audience :: Science/Research', + # 'Topic :: Scientific/Engineering :: Physics', + # 'Topic :: Scientific/Engineering :: Visualization', + # 'License :: OSI Approved :: BSD 3-Clause 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 :: Only', + # ], # - # $ pip install sampleproject - # - # And where it will live on PyPI: https://pypi.org/project/sampleproject/ - # - # There are some restrictions on what makes a valid project name - # specification here: - # https://packaging.python.org/specifications/core-metadata/#name - name='nmreval', # Required - - # Versions should comply with PEP 440: - # https://www.python.org/dev/peps/pep-0440/ - # - # For a discussion on single-sourcing the version across setup.py and the - # project code, see - # https://packaging.python.org/guides/single-sourcing-package-version/ - version='0.1', # Required - - # This is a one-line description or tagline of what your project does. This - # corresponds to the "Summary" metadata field: - # https://packaging.python.org/specifications/core-metadata/#summary - description='A sample Python project', # Optional - - # This is an optional longer description of your project that represents - # the body of text which users will see when they visit PyPI. - # - # Often, this is the same as your README, so you can just read it in from - # that file directly (as we have already done above) - # - # This field corresponds to the "Description" metadata field: - # https://packaging.python.org/specifications/core-metadata/#description-optional - long_description=long_description, # Optional - - # Denotes that our long_description is in Markdown; valid values are - # text/plain, text/x-rst, and text/markdown - # - # Optional if long_description is written in reStructuredText (rst) but - # required for plain-text or Markdown; if unspecified, "applications should - # attempt to render [the long_description] as text/x-rst; charset=UTF-8 and - # fall back to text/plain if it is not valid rst" (see link below) - # - # This field corresponds to the "Description-Content-Type" metadata field: - # https://packaging.python.org/specifications/core-metadata/#description-content-type-optional - long_description_content_type='text/x-rst', # Optional (see note above) - - # This should be a valid link to your project's main homepage. - # - # This field corresponds to the "Home-Page" metadata field: - # https://packaging.python.org/specifications/core-metadata/#home-page-optional - url='https://github.com/pypa/sampleproject', # Optional - - # This should be your name or the name of the organization which owns the - # project. - author='Dominik Demuth', # Optional - - # This should be a valid email address corresponding to the author listed - # above. - author_email='dominik.demuth@physik.tu-darmstadt.de', # Optional - - # Classifiers help users find your project by categorizing it. - # - # For a list of valid classifiers, see https://pypi.org/classifiers/ - classifiers=[ # Optional - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 3 - Alpha', - - # Indicate who your project is intended for - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', - - # Pick your license as you wish - 'License :: OSI Approved :: MIT License', - - # Specify the Python versions you support here. In particular, ensure - # that you indicate you support Python 3. These classifiers are *not* - # checked by 'pip install'. See instead 'python_requires' below. - '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', - ], - - # This field adds keywords for your project which will appear on the - # project page. What does your project relate to? - # - # Note that this is a list of additional keywords, separated - # by commas, to be used to assist searching for the distribution in a - # larger catalog. - keywords='sample, setuptools, development', # Optional - - # When your source code is in a subdirectory under the project root, e.g. - # `src/`, it is necessary to specify the `package_dir` argument. - package_dir={'': 'src'}, # Optional - - # You can just specify package directories manually here if your project is - # simple. Or you can use find_packages(). - # - # Alternatively, if you just want to distribute a single Python file, use - # the `py_modules` argument instead as follows, which will expect a file - # called `my_module.py` to exist: - # - # py_modules=["my_module"], - # - packages=find_packages(where='src'), # Required - - # Specify which Python versions you support. In contrast to the - # 'Programming Language' classifiers above, 'pip install' will check this - # and refuse to install the project if the version does not match. See - # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires - python_requires='>=3.7, <4', - - # This field lists other packages that your project depends on to run. - # Any package you put here will be installed by pip when your project is - # installed, so they must be valid existing projects. - # - # For an analysis of "install_requires" vs pip's requirements files see: - # https://packaging.python.org/discussions/install-requires-vs-requirements/ - install_requires=[ - 'numpy', - 'scipy', - 'matplotlib', - 'bsddb3', - 'pyqtgraph', - 'PyQt4', - 'h5py' - ], # Optional - - # List additional groups of dependencies here (e.g. development - # dependencies). Users will be able to install these using the "extras" - # syntax, for example: - # - # $ pip install sampleproject[dev] - # - # Similar to `install_requires` above, these must be valid existing - # projects. - # extras_require={ # Optional - # 'dev': ['check-manifest'], - # 'test': ['coverage'], + # keywords='nmr, physics, science', # Optional + # package_dir={'': '.'}, # Optional + # packages=find_packages(where='.'), # Required + # include_package_data=True, + # python_requires='>=3.7', + # install_requires=[ + # 'numpy', + # 'scipy', + # 'matplotlib', + # 'bsddb3', + # 'pyqtgraph', + # 'pyqt', + # 'h5py' + # ], # Optional + # # extras_require={ # Optional + # # 'dev': ['check-manifest'], + # # 'test': ['coverage'], + # # }, + # package_data={ # Optional + # 'sample': ['package_data.dat'], + # }, + # data_files=[('my_data', ['data/data_file'])], # Optional + # entry_points={ # Optional + # 'console_scripts': [ + # 'evaluate=bin:evaluate', + # ], # }, - - # If there are data files included in your packages that need to be - # installed, specify them here. - package_data={ # Optional - 'sample': ['package_data.dat'], - }, - - # Although 'package_data' is the preferred approach, in some case you may - # need to place data files outside of your packages. See: - # http://docs.python.org/distutils/setupscript.html#installing-additional-files - # - # In this case, 'data_file' will be installed into '/my_data' - data_files=[('my_data', ['data/data_file'])], # Optional - - # To provide executable scripts, use entry points in preference to the - # "scripts" keyword. Entry points provide cross-platform support and allow - # `pip` to create the appropriate form of executable for the target - # platform. - # - # For example, the following would provide a command called `sample` which - # executes the function `main` from this package when invoked: - entry_points={ # Optional - 'console_scripts': [ - 'sample=bin:main', - ], - }, - - # List additional URLs that are relevant to your project as a dict. - # - # This field corresponds to the "Project-URL" metadata fields: - # https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use - # - # Examples listed include a pattern for specifying where the package tracks - # issues, where the source is hosted, where to say thanks to the package - # maintainers, and where to support the project financially. The key is - # what's used to render the link text on PyPI. # project_urls={ # Optional - # 'Bug Reports': 'https://github.com/pypa/sampleproject/issues', - # 'Funding': 'https://donate.pypi.org', - # 'Say Thanks!': 'http://saythanks.io/to/example', - # 'Source': 'https://github.com/pypa/sampleproject/', + # 'Source': 'https://chaos3.fkp.physik.tu-darmstadt.de/source/nmreval/', # }, )