From f956c111c2bfe983068bbb5c22efcadcb89cebf9 Mon Sep 17 00:00:00 2001 From: Dominik Demuth Date: Sun, 5 Nov 2023 17:11:28 +0100 Subject: [PATCH] regex for numeric value for data from text files; closes #127 --- src/gui_qt/_py/asciidialog.py | 75 ++++++++++-------- src/gui_qt/io/asciireader.py | 68 +++++++++++++--- src/nmreval/io/asciireader.py | 23 ++++-- src/nmreval/utils/__init__.py | 1 + src/resources/_ui/asciidialog.ui | 129 ++++++++++++++++++------------- 5 files changed, 196 insertions(+), 100 deletions(-) diff --git a/src/gui_qt/_py/asciidialog.py b/src/gui_qt/_py/asciidialog.py index 8fee8ed..404f5c0 100644 --- a/src/gui_qt/_py/asciidialog.py +++ b/src/gui_qt/_py/asciidialog.py @@ -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, 1245) + ascii_reader.resize(665, 904) self.verticalLayout = QtWidgets.QVBoxLayout(ascii_reader) self.verticalLayout.setObjectName("verticalLayout") self.tabWidget = QtWidgets.QTabWidget(ascii_reader) @@ -156,39 +156,51 @@ class Ui_ascii_reader(object): 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_4 = QtWidgets.QVBoxLayout() - self.verticalLayout_4.setObjectName("verticalLayout_4") - self.horizontalLayout_4.addLayout(self.verticalLayout_4) self.verticalLayout_3.addWidget(self.groupBox) self.dsdfsf = QtWidgets.QLabel(self.tabWidgetPage1) self.dsdfsf.setObjectName("dsdfsf") self.verticalLayout_3.addWidget(self.dsdfsf) - self.label_8 = QtWidgets.QLabel(self.tabWidgetPage1) - self.label_8.setObjectName("label_8") - self.verticalLayout_3.addWidget(self.label_8) - self.radioButton = QtWidgets.QRadioButton(self.tabWidgetPage1) - self.radioButton.setObjectName("radioButton") + 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.radioButton) - self.verticalLayout_3.addWidget(self.radioButton) - self.lineEdit = QtWidgets.QLineEdit(self.tabWidgetPage1) - self.lineEdit.setObjectName("lineEdit") - self.verticalLayout_3.addWidget(self.lineEdit) - self.radioButton_2 = QtWidgets.QRadioButton(self.tabWidgetPage1) - self.radioButton_2.setObjectName("radioButton_2") - self.buttonGroup_2.addButton(self.radioButton_2) - self.verticalLayout_3.addWidget(self.radioButton_2) - self.lineEdit_2 = QtWidgets.QLineEdit(self.tabWidgetPage1) - self.lineEdit_2.setObjectName("lineEdit_2") - self.verticalLayout_3.addWidget(self.lineEdit_2) - self.radioButton_3 = QtWidgets.QRadioButton(self.tabWidgetPage1) - self.radioButton_3.setObjectName("radioButton_3") - self.buttonGroup_2.addButton(self.radioButton_3) - self.verticalLayout_3.addWidget(self.radioButton_3) - self.spinBox = QtWidgets.QSpinBox(self.tabWidgetPage1) - self.spinBox.setObjectName("spinBox") - self.verticalLayout_3.addWidget(self.spinBox) + 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) @@ -330,10 +342,11 @@ class Ui_ascii_reader(object): self.label_5.setText(_translate("ascii_reader", "

Δy

")) 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", "

Token:
[abc]: Matches any of a, b, or c
[a-z]: Matches any digit in the range a-z
\\d: Matches any digit in the range 0-9 (equal to [0-9}

Quantifiers:
a*: 0 or more of a
a*: 1 or more of a
a?: 0 or 1 of a

")) + 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.radioButton.setText(_translate("ascii_reader", "Regex")) - self.radioButton_2.setText(_translate("ascii_reader", "Custom value")) - self.radioButton_3.setText(_translate("ascii_reader", "Position in 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")) diff --git a/src/gui_qt/io/asciireader.py b/src/gui_qt/io/asciireader.py index f4b71a3..cbbb771 100644 --- a/src/gui_qt/io/asciireader.py +++ b/src/gui_qt/io/asciireader.py @@ -1,6 +1,6 @@ from __future__ import annotations -from pathlib import Path +import re from nmreval.io.asciireader import AsciiReader from nmreval.utils import NUMBER_RE @@ -18,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) @@ -46,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 @@ -62,8 +67,6 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): self.skippy_checkbox.setCheckState(QtCore.Qt.Unchecked) self.skippy_checkbox.blockSignals(False) - self.check_filename(fname) - return self def set_gui(self): @@ -145,7 +148,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(), @@ -204,9 +207,14 @@ 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 ImportError as e: @@ -223,7 +231,45 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader): def skip_next_dial(self, _: int): self.skip = self.skippy_checkbox.isChecked() - def check_filename(self, filename: str | Path): - self.label_8.setText(str(filename.stem)) - for i in NUMBER_RE.finditer(str(filename.stem)): - print(i) + @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()]}{fname[m.start():m.end()]}{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 + + diff --git a/src/nmreval/io/asciireader.py b/src/nmreval/io/asciireader.py index dddf87d..afed5d8 100644 --- a/src/nmreval/io/asciireader.py +++ b/src/nmreval/io/asciireader.py @@ -11,8 +11,6 @@ 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', ' ', ','] @@ -87,8 +85,15 @@ 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 = 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']: @@ -138,12 +143,18 @@ class AsciiReader: 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) single_len = 1 diff --git a/src/nmreval/utils/__init__.py b/src/nmreval/utils/__init__.py index cc86e30..24e8009 100755 --- a/src/nmreval/utils/__init__.py +++ b/src/nmreval/utils/__init__.py @@ -4,3 +4,4 @@ from .constants import * NUMBER_RE = re.compile(r'[-+]?\d*[+p.]?\d+([eE][-+]?\d+)?', re.MULTILINE) +UNSIGNED_NUMBER_RE = re.compile(r'[-+]?\d*[+p.]?\d+([eE][-+]?\d+)?', re.MULTILINE) diff --git a/src/resources/_ui/asciidialog.ui b/src/resources/_ui/asciidialog.ui index 1c91059..6822011 100644 --- a/src/resources/_ui/asciidialog.ui +++ b/src/resources/_ui/asciidialog.ui @@ -6,8 +6,8 @@ 0 0 - 627 - 1245 + 665 + 904 @@ -301,9 +301,6 @@ - - - @@ -315,50 +312,78 @@ - - - Filename - - - - - - - Regex - - - buttonGroup_2 - - - - - - - - - - Custom value - - - buttonGroup_2 - - - - - - - - - - Position in filename - - - buttonGroup_2 - - - - - + + + + + 1 + + + + + + + Match index + + + + + + + + 0 + 0 + + + + <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> + + + + + + + Regex + + + true + + + buttonGroup_2 + + + + + + + + 0 + 0 + + + + + + + + Custom value + + + buttonGroup_2 + + + + + + + Filename + + + true + + + + @@ -614,8 +639,8 @@ close() - 366 - 485 + 375 + 894 366 @@ -625,7 +650,7 @@ - +