diff --git a/DocSrc/DeStijl.tex b/DocSrc/DeStijl.tex index ea5d503..8f1ecc9 100755 --- a/DocSrc/DeStijl.tex +++ b/DocSrc/DeStijl.tex @@ -1,4 +1,4 @@ -% Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +% Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen % %This file is part of ssNake. % diff --git a/DocSrc/ReferenceManual.tex b/DocSrc/ReferenceManual.tex index 4d529b7..f73c14b 100755 --- a/DocSrc/ReferenceManual.tex +++ b/DocSrc/ReferenceManual.tex @@ -1,4 +1,4 @@ -% Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +% Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen % %This file is part of ssNake. % @@ -1534,14 +1534,14 @@ \subsection{Diffusion Curve} This routine can be used to fit diffusion curves. The equation used for this is: \begin{equation} - y = \text{amp} \cdot (\text{const} + \text{coeff} \cdot \exp(-(\gamma \delta x)^2 D (\Delta -\delta / 3.0))) + y = \text{amp} \cdot (\text{const} + \text{coeff} \cdot \exp(-(2\pi\gamma \delta x)^2 D (\Delta -\delta / 3.0))) \end{equation} -Here, $\gamma$ represent the $\gamma$-value of the isotope that is measured, $\delta$ is the length +Here, $\gamma$ represent the $\gamma$-value of the isotope that is measured in Hz/T, $\delta$ is the length of the gradient pulse, $\Delta$ is the time between the gradient pulses (i.e. the time from the start of gradient pulse 1, to the start of gradient pulse 2). $D$ is the diffusion constant, which is what we will fit. The `const' and `coeff' parameters have the same meaning as in the relaxation -curve fit. Note that the $x$-axis should be in terms of the gradient strength (in Tesla). ssNake -will still show `seconds' as a unit though (1 s = 1 T). +curve fit. Note that the $x$-axis should be in terms of the gradient strength (in Tesla/metre). ssNake +will still show `seconds' as a unit though (1 s = 1 T/m). @@ -1645,6 +1645,12 @@ \subsection{Quadrupole} If spinning sidebands are not important in the calculation, it might be useful to switch to `infinite' for speed considerations. + + + + + + \subsubsection*{Equations} Quadrupole systems are influenced by both the first and second order quadrupolar coupling. The first order quadrupolar coupling has only rank 2 terms, while the second order has rank 0, 2 and 4 @@ -1726,6 +1732,27 @@ \subsubsection*{Equations} With $B = \left( \dfrac{C_\text{Q}}{4 I (2 I - 1)} \right) ^ 2 \dfrac{2}{\nu_0}$, where $\nu_0$ is the Larmor frequency of the nucleus. + +\subsection{Quadrupole+CSA} +Quadrupole + CSA fitting combines first and second order quadrupolar effects and chemical shift anisotropy. Both spinning and static spectra can be simulated. For the effects of the individual quadrupole or CSA settings, see above at their respective sections. + +Three additional parameters that need to be given are the three angles defining the rotation of the CSA tensor with respect to the EFG tensor ($\alpha$, $\beta$, $\gamma$). +We order the EFG tensor values in the following way: $|V_{zz}|>|V_{xx}|>|V_{yy}|$. +We define the CSA and quadrupole angles in the same way as SIMPSON does, but we rotate via +an Z-X-Z euler rotation, while SIMPSON uses Z-Y-Z. This leads to the following conversion table, showing the ssNake angles when using $\alpha$, $\beta$, $\gamma$ in another software package: + +\begin{center} +\begin{tabular}{cc} +\toprule +\textbf{Software} & \textbf{ssNake equivalent}\\ +\midrule +Simpson & $\alpha + 90^\circ$, $\beta$, $\gamma + 90^\circ$ \\ +wsolids & $\alpha + 90^\circ$, $\beta$, $\gamma$ \\ +\bottomrule +\end{tabular} +\end{center} + + \subsection{Czjzek} This fitting routine can be used to simulate the 1D spectrum of a quadrupolar nucleus that experiences a Czjzek distribution (normal or extended Czjzek). This fitting routine behaves @@ -1813,6 +1840,21 @@ \subsubsection*{Equations} the extended Czjzek distribution, around a twofold speed increase can be gained in this way. ssNake tests for the \texttt{numba} package itself, so regular installation of this package should be sufficient. +\subsection{MQMAS} +This fitting routine can be used to fit an MQMAS spectrum. It is a 2D fitting routine, showing a contour plot of the data. The fit result is plot on top of this using a different contour colour. As there are now two plotting dimensions, there are two Lorentz and Gauss broadening settings. + +For simulation of MQMAS data, we need to define the quadrupolar parameters as before (see the section on fitting of quadrupole lines). In this case, no satellite signals are included. Only infinite MAS spinning speeds are supported (no spinning sidebands are calculated). Moreover, it is an ideal simulation, so no excitation profiles are taken into account. + +Apart from the spin quantum number I, MQMAS simulation also requires selected the multiple quantum transition to be supplied. This is labelled `MQ' in the interface. + +Two additional parameters that need to be supplied are the shearing and spectral width scale factors. These are required for the processing of MQMAS data, as is also described in our advanced tutorial on MQMAS processing. By default, the shearing is set at 0, and the sw scaling at 1. This results in unsheared data, and can be used to fit experimental data that has also not been sheared. + +The required sharing and sw scaling factors depend on both I and MQ. Using the 'Auto' push button in the interface, the correct values are filled in in the boxes, depending on the current I and MQ setting. + + +\subsection{Czjzek MQMAS} +This routine can be used to fit an MQMAS spectrum of a species that is influenced by a Czjzek distribution. The required inputs are describe in the section on 1D Czjzek fitting, and regular MQMAS fitting respectively. Note that this again means that firstly a library needs to be generated of a $C_\text{Q}$ and $\eta$ grid. The spectrum sharing and sw scaling are as described in the MQMAS section. + \subsection{External} Simulating NMR data for fitting can be straightforward, if idealized line shapes are requires. These @@ -2156,7 +2198,7 @@ \subsubsection*{Use of the tool} Using the chemical shift conversion tool is quite easy. You can fill in all the necessary values for one convention, and push the `Go' button next to it to convert it to all the other definitions. Note that ssNake also checks the definitions. So if you mess up the order of the Standard Convention, for example, it will be put in the correct order. The same is done for values that are outside the defined limits (e.g. $\eta$ and $\kappa$). Pushing `Reset' will reset all the values to 0. \subsection{Quadrupole coupling conversion tool} -Nuclei with a spin quantum number greater than 1/2 have an asymmetric charge distribution. This leads to a quadrupolar moment, and thus to a coupling between the nucleus and the electronic field gradient at the nuclear site. Just as for chemical shift, there are multiple convention in use to describe the resulting quadrupolar tensor. Apart from being a symmetric tensor, Laplace relation states that the sum of the field gradients in all direction should be zero (otherwise the nuclei would experience a net force). Due to this, there is no isotropic term, and the quadrupolar coupling can be described by two numbers: the strength and the asymmetry. Of these there are two conventions in use: C$_\text{Q}$ and $\omegaup_\text{Q}$. When the field gradients in the three independent directions are $V_\text{xx}$, $V_\text{yy}$ and $V_\text{xx}$ (with $|V_\text{zz}| \geq |V_\text{yy}| \geq |V_\text{xx}|$), C$_\text{Q}$ is defined as: +Nuclei with a spin quantum number greater than 1/2 have an asymmetric charge distribution. This leads to a quadrupolar moment, and thus to a coupling between the nucleus and the electronic field gradient at the nuclear site. Just as for chemical shift, there are multiple convention in use to describe the resulting quadrupolar tensor. Apart from being a symmetric tensor, Laplace relation states that the sum of the field gradients in all direction should be zero (otherwise the nuclei would experience a net force). Due to this, there is no isotropic term, and the quadrupolar coupling can be described by two numbers: the strength and the asymmetry. Of these there are two conventions in use: C$_\text{Q}$ and $\omegaup_\text{Q}$. When the field gradients in the three independent directions are $V_\text{xx}$, $V_\text{yy}$ and $V_\text{xx}$ (with $|V_\text{zz}| \geq |V_\text{xx}| \geq |V_\text{yy}|$), C$_\text{Q}$ is defined as: \begin{equation} C_\text{Q} = \dfrac{eV_\text{zz}Q}{\hbar} = \dfrac{e^2qQ}{\hbar} \end{equation} @@ -2170,7 +2212,7 @@ \subsection{Quadrupole coupling conversion tool} \end{equation} For both definitions, the asymmetry is defined as: \begin{equation} -\eta = \dfrac{V_\text{xx} -V_\text{yy}}{V_\text{zz}} +\eta = \dfrac{V_\text{yy} -V_\text{xx}}{V_\text{zz}} \end{equation} which, due to the ordering of the fields gradient components, is always between 1 and 0. diff --git a/DocSrc/Title.tex b/DocSrc/Title.tex index 31dc8d3..4b093fe 100755 --- a/DocSrc/Title.tex +++ b/DocSrc/Title.tex @@ -1,4 +1,4 @@ -% Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +% Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen % %This file is part of ssNake. % @@ -27,7 +27,7 @@ \large Wouter Franssen \& Bas van Meerten \vspace{1cm} -\large Version 1.3 +\large Version 1.4b \vfill \includegraphics[width=0.5\textwidth]{Images/logo.pdf}\ diff --git a/README.md b/README.md index 7fd1ee5..ea59ee7 100644 --- a/README.md +++ b/README.md @@ -9,18 +9,18 @@ Requirements ------------ ssNake requires: -- [python](http://python.org/download/) >= 2.7 or [python](http://python.org/download/) >= 3.4 +- [python](http://python.org/download/) >= 3.4 And the following python packages are required[1]: - [numpy](http://sourceforge.net/projects/numpy/files/NumPy/) >= 1.11.0 - [matplotlib](http://matplotlib.org/) >= 1.5.0 - [scipy](http://sourceforge.net/projects/scipy/files/scipy/) >= 0.14.1 -- [PyQt4](http://www.riverbankcomputing.com/software/pyqt/download) >= 4.11.4 +- [PyQt5](http://www.riverbankcomputing.com/software/pyqt/download) >= 5.15.0 - [h5py](http://www.h5py.org/) >= 2.5.0 (for loading Matlab data) On Ubuntu and Debian these packages can be installed using the package manager: ``` -sudo apt-get install python python-numpy python-matplotlib python-scipy python-qt4 python-h5py +sudo apt-get install python3 python3-numpy python3-matplotlib python3-scipy python3-pyqt5 python3-h5py ``` On Windows (and macOS) these packages can easily be installed by downloading [Anaconda](https://www.anaconda.com/distribution/). @@ -35,7 +35,7 @@ Installation ### Linux and macOS ### To install ssNake, copy the ssNake directory to your favourite location (/usr/local/, for example). -ssNake can then be run by executing 'python /InstallPath/src/ssNake.py'. +ssNake can then be run by executing 'python3 /InstallPath/src/ssNake.py'. Aliases or symlinks can be used to create a shortcut to start the program. When multiple versions of Python are installed make sure that the correct one is used to start ssNake. This can be done either by modifying the PATH environment variable or by starting ssNake with the full path to the Python binary, for example by using '/anaconda3/bin/python /InstallPath/src/ssNake.py' diff --git a/ReferenceManual.pdf b/ReferenceManual.pdf index 41fb3a3..63f6ed7 100644 Binary files a/ReferenceManual.pdf and b/ReferenceManual.pdf differ diff --git a/Tutorial/Tutorial.pdf b/Tutorial/Tutorial.pdf index 3b3bf5a..8b376cb 100644 Binary files a/Tutorial/Tutorial.pdf and b/Tutorial/Tutorial.pdf differ diff --git a/Tutorial/src/DeStijl.tex b/Tutorial/src/DeStijl.tex index 2d18268..a811557 100644 --- a/Tutorial/src/DeStijl.tex +++ b/Tutorial/src/DeStijl.tex @@ -1,4 +1,4 @@ -% Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +% Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen % %This file is part of ssNake. % diff --git a/Tutorial/src/Title.tex b/Tutorial/src/Title.tex index 6c2ff6c..eb2d413 100644 --- a/Tutorial/src/Title.tex +++ b/Tutorial/src/Title.tex @@ -1,4 +1,4 @@ -% Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +% Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen % %This file is part of ssNake. % @@ -27,7 +27,7 @@ \large Wouter Franssen \& Bas van Meerten \vspace{1cm} -\large Version 1.3 +\large Version 1.4b \vfill \includegraphics[width=0.7\textwidth]{Images/logo.pdf}\ diff --git a/Tutorial/src/Tutorial.tex b/Tutorial/src/Tutorial.tex index 8e31666..f689578 100644 --- a/Tutorial/src/Tutorial.tex +++ b/Tutorial/src/Tutorial.tex @@ -1,4 +1,4 @@ -% Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +% Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen % %This file is part of ssNake. % diff --git a/src/Czjzek.py b/src/Czjzek.py index d2bf43b..14ec287 100644 --- a/src/Czjzek.py +++ b/src/Czjzek.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/fitting.py b/src/fitting.py index 9fd028c..7abee08 100644 --- a/src/fitting.py +++ b/src/fitting.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -72,7 +72,6 @@ class TabFittingWindow(QtWidgets.QWidget): This handles the different tabs for multifitting. """ - PRECIS = 4 MINMETHOD = 'Powell' NUMFEVAL = 150 @@ -101,6 +100,7 @@ def __init__(self, father, oldMainWindow, mainFitType): self.queue = None self.tabs = QtWidgets.QTabWidget(self) self.tabs.setTabPosition(2) + self.PRECIS = self.father.defaultPrecis self.mainFitWindow = FittingWindow(father, oldMainWindow, self, self.mainFitType) self.current = self.mainFitWindow.current self.tabs.setTabsClosable(True) @@ -155,6 +155,7 @@ def addSpectrum(self): wsIndex, fitName, accept = NewTabDialog.getFitInput(self, self.father.workspaceNames, list(FITTYPEDICT.keys()), self.mainFitType) if not accept: return + # need to convert units to first tab unit here self.subFitWindows.append(FittingWindow(self.father, self.father.workspaces[wsIndex], self, fitName, False)) self.tabs.insertTab(self.tabs.count() - 1, self.subFitWindows[-1], self.father.workspaceNames[wsIndex]) self.tabs.setCurrentIndex(len(self.subFitWindows)) @@ -1072,6 +1073,8 @@ def __init__(self, parent, rootwindow, isMain=True): rmsdFrame.addWidget(self.rmsdEdit, 0, 1) self.setRMSD() self.checkFitParamList(self.getRedLocList()) + colorList = mpl.rcParams['axes.prop_cycle'].by_key()['color'] + self.fit_color_list = colorList[2:] + colorList[0:2] def togglePick(self): # Dummy function for fitting routines which require peak picking @@ -1113,9 +1116,9 @@ def defaultValues(self): tmpVal = {key: None for key in self.SINGLENAMES + self.MULTINAMES} for name in self.SINGLENAMES: if name in self.DEFAULTS.keys(): - tmpVal[name] = self.DEFAULTS[name] + tmpVal[name] = np.array(self.DEFAULTS[name], dtype=object) else: - tmpVal[name] = [0.0, False] + tmpVal[name] = np.array([0.0, False], dtype=object) for name in self.MULTINAMES: if name in self.DEFAULTS.keys(): tmpVal[name] = np.repeat([np.array(self.DEFAULTS[name], dtype=object)], self.FITNUM, axis=0) @@ -1138,7 +1141,7 @@ def setRMSD(self, val=None): self.rmsdEdit.setText(('%#.' + str(self.rootwindow.tabWindow.PRECIS) + 'g') % val) self.rmsdEdit.setCursorPosition(0) - def addMultiLabel(self, name, text, num): + def addMultiLabel(self, name, text, num, tooltip=""): """ Creates a label for a parameter with multiple sites and adds it to frame3. @@ -1150,6 +1153,8 @@ def addMultiLabel(self, name, text, num): The text on the label. num : int The column to place the label widget. + tootip : str + A description of the parameter to be shown as tooltip. Returns ------- @@ -1163,7 +1168,8 @@ def addMultiLabel(self, name, text, num): tick.stateChanged.connect(lambda state, self=self: self.changeAllTicks(state, name)) self.frame3.addWidget(tick, 1, num) label = wc.QLabel(text) - self.frame3.addWidget(label, 1, num+1) + label.setToolTip(tooltip) + self.frame3.addWidget(label, 1, num + 1) return tick, label def changeAllTicks(self, state, name): @@ -1287,6 +1293,12 @@ def checkInputs(self): self.fitParamList[locList][name][i][0] = inp return True + def changeAxMult(self, oldAxMult): + """ + Dummy function for changing the units. + """ + pass + def getNumExp(self): """ Returns the number of sites as set in the box. @@ -1443,6 +1455,16 @@ def paramToFile(self): report += "# Data: " + self.parent.data.name + "\n" report += "# Trace: " + printLocList + "\n" report += "# Dimension: D" + str(self.parent.axes[-1]+1) + "\n" + if self.parent.spec() == 1: + if self.parent.viewSettings["ppm"][-1]: + axUnit = 'ppm' + else: + axUnits = ['Hz', 'kHz', 'MHz'] + axUnit = axUnits[self.parent.getAxType()] + elif self.parent.spec() == 0: + axUnits = ['s', 'ms', "us"] + axUnit = axUnits[self.parent.getAxType()] + report += "# Units: " + axUnit + "\n" if self.removeLimits[locList]["limits"]: report += "# Excluded: " limitStr = str(self.removeLimits[locList]["limits"]) @@ -1526,6 +1548,8 @@ def fileToParam(self): preReport = splitReport[0].split('\n') preParams = {} removeLimits = {'invert' : False, 'limits': []} + savedAxType = None + savedPPM = False for line in preReport: tmp = line.strip() if tmp.startswith("#!"): @@ -1536,6 +1560,17 @@ def fileToParam(self): removeLimits["invert"] = True else: removeLimits["limits"] = safeEval(tmp.split(":")[1]) + elif tmp.startswith("# Units:"): + tmp2 = tmp.split(":")[1] + if 'ppm' in tmp2: + savedPPM = True + savedAxType = 0 + elif 'MHz' in tmp2 or 'us' in tmp2: + savedAxType = 2 + elif 'kHz' in tmp2 or 'ms' in tmp2: + savedAxType = 1 + else: + savedAxType = 0 singleReport = splitReport[1].split('\n') multiReport = splitReport[2].split('\n') singleNames = singleReport[0].split() @@ -1556,6 +1591,13 @@ def fileToParam(self): multiVals = self.__interpretParam(multiVals) self.extraFileToParam(preParams, postParams) self.setParamFromList(singleNames, singleVals, multiNames, multiVals, removeLimits) + if savedAxType is not None: + savedAxMult = self.parent.getAxMult(self.parent.spec(), + savedAxType, + savedPPM, + self.parent.freq(), + self.parent.ref()) + self.changeAxMult(savedAxMult) self.dispParams() self.parent.showFid() return True @@ -2256,8 +2298,8 @@ def __init__(self, parent, rootwindow, isMain=True): self.numExp.addItems([str(x + 1) for x in range(self.FITNUM)]) self.numExp.currentIndexChanged.connect(self.changeNum) self.frame3.addWidget(self.numExp, 0, 0, 1, 2) - self.addMultiLabel("Coefficient", "Coefficient:", 0) - self.addMultiLabel("T", "T [s]:", 2) + self.addMultiLabel("Coefficient", "Coefficient:", 1) + self.addMultiLabel("T", "T [s]:", 3) self.xlog = QtWidgets.QCheckBox('x-log') self.xlog.stateChanged.connect(self.setLog) self.optframe.addWidget(self.xlog, 0, 0, QtCore.Qt.AlignTop) @@ -2265,11 +2307,16 @@ def __init__(self, parent, rootwindow, isMain=True): self.ylog.stateChanged.connect(self.setLog) self.optframe.addWidget(self.ylog, 1, 0, QtCore.Qt.AlignTop) for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j], ('%#.' + str(self.rootwindow.tabWindow.PRECIS) + 'g') % self.fitParamList[locList][self.MULTINAMES[j]][i][0])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def reset(self): @@ -2332,7 +2379,7 @@ class DiffusionParamFrame(AbstractParamFrame): SINGLENAMES = ['Amplitude', 'Constant'] MULTINAMES = ['Coefficient', 'D'] - FUNC_LABEL = u"Amplitude * (Constant + Coefficient * exp(-(γ * δ * x)² * D * (Δ - δ / 3.0)))" + FUNC_LABEL = u"Amplitude * (Constant + Coefficient * exp(-(2 * π * γ * δ * x)² * D * (Δ - δ / 3.0)))" def __init__(self, parent, rootwindow, isMain=True): """ @@ -2375,8 +2422,8 @@ def __init__(self, parent, rootwindow, isMain=True): self.numExp.addItems([str(x + 1) for x in range(self.FITNUM)]) self.numExp.currentIndexChanged.connect(self.changeNum) self.frame3.addWidget(self.numExp, 0, 0, 1, 2) - self.addMultiLabel('Coefficient', "Coefficient:", 0) - self.addMultiLabel('D', "D [m^2/s]:", 2) + self.addMultiLabel('Coefficient', "Coefficient:", 1) + self.addMultiLabel('D', "D [m^2/s]:", 3) self.frame3.setColumnStretch(20, 1) self.frame3.setAlignment(QtCore.Qt.AlignTop) self.xlog = QtWidgets.QCheckBox('x-log') @@ -2386,11 +2433,16 @@ def __init__(self, parent, rootwindow, isMain=True): self.ylog.stateChanged.connect(self.setLog) self.optframe.addWidget(self.ylog, 1, 0) for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j], '')) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def reset(self): @@ -2557,16 +2609,21 @@ def __init__(self, parent, rootwindow, isMain=True): self.numExp.addItems([str(x + 1) for x in range(self.FITNUM)]) self.numExp.currentIndexChanged.connect(self.changeNum) self.frame3.addWidget(self.numExp, 0, 0, 1, 2) - self.addMultiLabel("Position", "Position [" + self.axUnit + "]:", 0) - self.addMultiLabel("Integral", "Integral:", 2) - self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 4) - self.addMultiLabel("Gauss", "Gauss [Hz]:", 6) + self.addMultiLabel("Position", "Position [" + self.axUnit + "]:", 1) + self.addMultiLabel("Integral", "Integral:", 3) + self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 5) + self.addMultiLabel("Gauss", f"Gauss [{self.axUnit}]:", 7) for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def reset(self): @@ -2594,6 +2651,18 @@ def checkResults(self, numExp, struc): if struc["Gauss"][i][0] == 1: self.fitParamList[locList]["Gauss"][i][0] = abs(self.fitParamList[locList]["Gauss"][i][0]) + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Position"])): + if not isinstance(self.fitParamList[locList]["Position"][j][0], tuple): + self.fitParamList[locList]["Position"][j][0] *= newAxMult/oldAxMult + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult + ############################################################################## @@ -2725,51 +2794,56 @@ def __init__(self, parent, rootwindow, isMain=True): else: axUnit = ['Hz', 'kHz', 'MHz'][self.parent.getAxType()] # Labels - self.addMultiLabel("Definition1", "", 0) - self.addMultiLabel("Definition2", "", 2) - self.addMultiLabel("Definition3", "", 4) + self.addMultiLabel("Definition1", "", 1) + self.addMultiLabel("Definition2", "", 3) + self.addMultiLabel("Definition3", "", 5) self.label11 = wc.QLabel(u'δ' + '11 [' + axUnit + '] :') self.label22 = wc.QLabel(u'δ' + '22 [' + axUnit + '] :') self.label33 = wc.QLabel(u'δ' + '33 [' + axUnit + '] :') - self.frame3.addWidget(self.label11, 1, 1) - self.frame3.addWidget(self.label22, 1, 3) - self.frame3.addWidget(self.label33, 1, 5) + self.frame3.addWidget(self.label11, 1, 2) + self.frame3.addWidget(self.label22, 1, 4) + self.frame3.addWidget(self.label33, 1, 6) self.labelxx = wc.QLabel(u'δ' + 'xx [' + axUnit + '] :') self.labelyy = wc.QLabel(u'δ' + 'yy [' + axUnit + '] :') self.labelzz = wc.QLabel(u'δ' + 'zz [' + axUnit + '] :') self.labelxx.hide() self.labelyy.hide() self.labelzz.hide() - self.frame3.addWidget(self.labelxx, 1, 1) - self.frame3.addWidget(self.labelyy, 1, 3) - self.frame3.addWidget(self.labelzz, 1, 5) + self.frame3.addWidget(self.labelxx, 1, 2) + self.frame3.addWidget(self.labelyy, 1, 4) + self.frame3.addWidget(self.labelzz, 1, 6) self.labeliso = wc.QLabel(u'δ' + 'iso [' + axUnit + '] :') self.labelaniso = wc.QLabel(u'δ' + 'aniso [' + axUnit + '] :') self.labeleta = wc.QLabel(u'η:') self.labeliso.hide() self.labelaniso.hide() self.labeleta.hide() - self.frame3.addWidget(self.labeliso, 1, 1) - self.frame3.addWidget(self.labelaniso, 1, 3) - self.frame3.addWidget(self.labeleta, 1, 5) + self.frame3.addWidget(self.labeliso, 1, 2) + self.frame3.addWidget(self.labelaniso, 1, 4) + self.frame3.addWidget(self.labeleta, 1, 6) self.labeliso2 = wc.QLabel(u'δ' + 'iso [' + axUnit + '] :') self.labelspan = wc.QLabel(u'Ω [' + axUnit + '] :') self.labelskew = wc.QLabel(u'κ:') self.labeliso2.hide() self.labelspan.hide() self.labelskew.hide() - self.frame3.addWidget(self.labeliso2, 1, 1) - self.frame3.addWidget(self.labelspan, 1, 3) - self.frame3.addWidget(self.labelskew, 1, 5) - self.addMultiLabel("Integral", "Integral:", 6) - self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 8) - self.addMultiLabel("Gauss", "Gauss [Hz]:", 10) + self.frame3.addWidget(self.labeliso2, 1, 2) + self.frame3.addWidget(self.labelspan, 1, 4) + self.frame3.addWidget(self.labelskew, 1, 6) + self.addMultiLabel("Integral", "Integral:", 7) + self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 9, "Lorentzian broadening (transverse relaxation)") + self.addMultiLabel("Gauss", f"Gauss [{axUnit}]:", 11, "Gaussian broadening (FWHM of chemical shift distribution)") for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def MASChange(self, MAStype): @@ -2971,6 +3045,23 @@ def checkResults(self, numExp, struc): if self.shiftDefType == 3: self.fitParamList[locList]['Definition3'][i][0] = 1 - abs(abs(self.fitParamList[locList]['Definition3'][i][0] + 1)%4 - 2) + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Definition1"])): + if not isinstance(self.fitParamList[locList]["Definition1"][j][0], tuple): + self.fitParamList[locList]["Definition1"][j][0] *= newAxMult/oldAxMult + if not isinstance(self.fitParamList[locList]["Definition2"][j][0], tuple): + self.fitParamList[locList]["Definition2"][j][0] *= newAxMult/oldAxMult + if self.shiftDefType in [0, 1]: + if not isinstance(self.fitParamList[locList]["Definition3"][j][0], tuple): + self.fitParamList[locList]["Definition3"][j][0] *= newAxMult/oldAxMult +# for j in range(len(self.fitParamList[locList]["Gauss"])): # same j index as for Position/Definition1 + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult ############################################################################## @@ -2988,7 +3079,7 @@ class QuadDeconvParamFrame(AbstractParamFrame): Ioptions = ['1', '3/2', '2', '5/2', '3', '7/2', '4', '9/2'] Ivalues = [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5] SINGLENAMES = ["Offset", "Multiplier", "Spinspeed"] - MULTINAMES = ["Position", "Cq", 'eta', "Integral", "Lorentz", "Gauss"] + MULTINAMES = ["Position", "Cq", 'eta', "Integral", "Lorentz", "Gauss", "LorentzST"] EXTRANAMES = ['spinType', 'satBool', 'angle', 'cheng', 'I', 'numssb'] MASTYPES = ["Static", "Finite MAS", "Infinite MAS"] @@ -3007,7 +3098,7 @@ def __init__(self, parent, rootwindow, isMain=True): """ self.FITFUNC = simFunc.quadFunc self.fullInt = np.sum(parent.getData1D()) * parent.sw() / float(len(parent.getData1D())) - self.DEFAULTS = {"Offset": [0.0, True], "Multiplier": [1.0, True], "Spinspeed": [10.0, True], "Position": [0.0, False], "Cq": [1.0, False], 'eta': [0.0, False], "Integral": [self.fullInt, False], "Lorentz": [1.0, False], "Gauss": [0.0, True]} + self.DEFAULTS = {"Offset": [0.0, True], "Multiplier": [1.0, True], "Spinspeed": [10.0, True], "Position": [0.0, False], "Cq": [1.0, False], 'eta': [0.0, False], "Integral": [self.fullInt, False], "Lorentz": [1.0, False], "Gauss": [0.0, True], "LorentzST": [1.0, False]} self.extraDefaults = {'I': 1, 'Satellites': False, 'cheng': 15, 'spinType': 0, 'rotorAngle': "arctan(sqrt(2))", 'numssb': 32, "Spinspeed": '10.0'} super(QuadDeconvParamFrame, self).__init__(parent, rootwindow, isMain) self.optframe.addWidget(wc.QLabel("MAS:"), 2, 0) @@ -3062,18 +3153,24 @@ def __init__(self, parent, rootwindow, isMain=True): else: axUnit = ['Hz', 'kHz', 'MHz'][self.parent.getAxType()] # Labels - self.addMultiLabel("Position", u"Position [" + axUnit + "]:", 0) - self.addMultiLabel("Cq", u"CQ [MHz]:", 2) - self.addMultiLabel("eta", u"η:", 4) - self.addMultiLabel("Integral", "Integral:", 6) - self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 8) - self.addMultiLabel("Gauss", "Gauss [Hz]:", 10) + self.addMultiLabel("Position", u"Position [" + axUnit + "]:", 1, "Isotropic chemical shift") + self.addMultiLabel("Cq", u"CQ [MHz]:", 3, "Quadrupolar anisotopy") + self.addMultiLabel("eta", u"η:", 5, "Quadrupolar asymmetry (0-1)") + self.addMultiLabel("Integral", "Integral:", 7) + self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 9, "Lorentzian broadening of central transition (transverse relaxation rate)") + self.addMultiLabel("Gauss", f"Gauss [{axUnit}]:", 11, "Gaussian broadening (FWHM of chemical shift distribution)") + self.addMultiLabel("LorentzST", "ST Lorentz [Hz]:", 13, "Lorentzian broadening of satellite transition(transverse relaxation rate)") for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def MASChange(self, MAStype): @@ -3177,6 +3274,8 @@ def checkResults(self, numExp, struc): for i in range(numExp): if struc["Lorentz"][i][0] == 1: self.fitParamList[locList]["Lorentz"][i][0] = abs(self.fitParamList[locList]["Lorentz"][i][0]) + if struc["LorentzST"][i][0] == 1: + self.fitParamList[locList]["LorentzST"][i][0] = abs(self.fitParamList[locList]["LorentzST"][i][0]) if struc["Gauss"][i][0] == 1: self.fitParamList[locList]["Gauss"][i][0] = abs(self.fitParamList[locList]["Gauss"][i][0]) if struc['eta'][i][0] == 1: @@ -3184,6 +3283,19 @@ def checkResults(self, numExp, struc): if struc["Cq"][i][0] == 1: self.fitParamList[locList]["Cq"][i][0] = abs(self.fitParamList[locList]["Cq"][i][0]) + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Position"])): + if not isinstance(self.fitParamList[locList]["Position"][j][0], tuple): + self.fitParamList[locList]["Position"][j][0] *= newAxMult/oldAxMult +# for j in range(len(self.fitParamList[locList]["Gauss"])): # same j index s for Position + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult + ################################################################################# @@ -3193,7 +3305,7 @@ class QuadCSADeconvParamFrame(AbstractParamFrame): Ioptions = ['1/2', '1', '3/2', '2', '5/2', '3', '7/2', '4', '9/2'] Ivalues = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5] SINGLENAMES = ["Offset", "Multiplier", "Spinspeed"] - MULTINAMES = ["Definition1", "Definition2", "Definition3", "Cq", 'eta', "Alpha", "Beta", "Gamma", "Integral", "Lorentz", "Gauss"] + MULTINAMES = ["Definition1", "Definition2", "Definition3", "Cq", 'eta', "Alpha", "Beta", "Gamma", "Integral", "Lorentz", "Gauss", "LorentzST"] EXTRANAMES = ['spinType', 'satBool', 'angle', 'shiftdef', 'cheng', 'I', 'numssb'] MASTYPES = ["Static", "Finite MAS", "Infinite MAS"] DEFTYPES = [u'δ11 - δ22 - δ33', @@ -3223,7 +3335,7 @@ def __init__(self, parent, rootwindow, isMain=True): self.DEFAULTS = {"Offset": [0.0, True], "Multiplier": [1.0, True], "Spinspeed": [10.0, True], "Definition1": [0.0, False], "Definition2": [0.0, False], "Definition3": [0.0, False], "Cq": [1.0, False], 'eta': [0.0, False], "Integral": [self.fullInt, False], - "Lorentz": [1.0, False], "Gauss": [0.0, True], "Alpha": [0.0, True], + "Lorentz": [1.0, False], "Gauss": [0.0, True], "LorentzST": [1.0, False], "Alpha": [0.0, True], "Beta": [0.0, True], "Gamma": [0.0, True]} self.extraDefaults = {'I': 2, 'Satellites': False, 'cheng': 15, 'spinType': 0, 'shiftdef': 0, 'rotorAngle': "arctan(sqrt(2))", 'numssb': 32, "Spinspeed": '10.0'} super(QuadCSADeconvParamFrame, self).__init__(parent, rootwindow, isMain) @@ -3285,56 +3397,62 @@ def __init__(self, parent, rootwindow, isMain=True): else: axUnit = ['Hz', 'kHz', 'MHz'][self.parent.getAxType()] # Labels - self.addMultiLabel("Definition1", "", 0) - self.addMultiLabel("Definition2", "", 2) - self.addMultiLabel("Definition3", "", 4) + self.addMultiLabel("Definition1", "", 1, "CSA tensor discontinuity 1") + self.addMultiLabel("Definition2", "", 3, "CSA tensor discontinuity 2") + self.addMultiLabel("Definition3", "", 5, "CSA tensor discontinuity 3") self.label11 = wc.QLabel(u'δ' + '11 [' + axUnit + '] :') self.label22 = wc.QLabel(u'δ' + '22 [' + axUnit + '] :') self.label33 = wc.QLabel(u'δ' + '33 [' + axUnit + '] :') - self.frame3.addWidget(self.label11, 1, 1) - self.frame3.addWidget(self.label22, 1, 3) - self.frame3.addWidget(self.label33, 1, 5) + self.frame3.addWidget(self.label11, 1, 2) + self.frame3.addWidget(self.label22, 1, 4) + self.frame3.addWidget(self.label33, 1, 6) self.labelxx = wc.QLabel(u'δ' + 'xx [' + axUnit + '] :') self.labelyy = wc.QLabel(u'δ' + 'yy [' + axUnit + '] :') self.labelzz = wc.QLabel(u'δ' + 'zz [' + axUnit + '] :') self.labelxx.hide() self.labelyy.hide() self.labelzz.hide() - self.frame3.addWidget(self.labelxx, 1, 1) - self.frame3.addWidget(self.labelyy, 1, 3) - self.frame3.addWidget(self.labelzz, 1, 5) + self.frame3.addWidget(self.labelxx, 1, 2) + self.frame3.addWidget(self.labelyy, 1, 4) + self.frame3.addWidget(self.labelzz, 1, 6) self.labeliso = wc.QLabel(u'δ' + 'iso [' + axUnit + '] :') self.labelaniso = wc.QLabel(u'δ' + 'aniso [' + axUnit + '] :') self.labeleta = wc.QLabel(u'η:') self.labeliso.hide() self.labelaniso.hide() self.labeleta.hide() - self.frame3.addWidget(self.labeliso, 1, 1) - self.frame3.addWidget(self.labelaniso, 1, 3) - self.frame3.addWidget(self.labeleta, 1, 5) + self.frame3.addWidget(self.labeliso, 1, 2) + self.frame3.addWidget(self.labelaniso, 1, 4) + self.frame3.addWidget(self.labeleta, 1, 6) self.labeliso2 = wc.QLabel(u'δ' + 'iso [' + axUnit + '] :') self.labelspan = wc.QLabel(u'Ω [' + axUnit + '] :') self.labelskew = wc.QLabel(u'κ:') self.labeliso2.hide() self.labelspan.hide() self.labelskew.hide() - self.frame3.addWidget(self.labeliso2, 1, 1) - self.frame3.addWidget(self.labelspan, 1, 3) - self.frame3.addWidget(self.labelskew, 1, 5) - self.addMultiLabel("Cq", u"CQ [MHz]:", 6) - self.addMultiLabel("eta", u"η:", 8) - self.addMultiLabel("Alpha", u"α [deg]:", 10) - self.addMultiLabel("Beta", u"β [deg]:", 12) - self.addMultiLabel("Gamma", u"γ [deg]:", 14) - self.addMultiLabel("Integral", "Integral:", 16) - self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 18) - self.addMultiLabel("Gauss", "Gauss [Hz]:", 20) + self.frame3.addWidget(self.labeliso2, 1, 2) + self.frame3.addWidget(self.labelspan, 1, 4) + self.frame3.addWidget(self.labelskew, 1, 6) + self.addMultiLabel("Cq", u"CQ [MHz]:", 7, "Quadrupolar anisotropy") + self.addMultiLabel("eta", u"η:", 9, "Quadrupolar asymmetry") + self.addMultiLabel("Alpha", u"α [deg]:", 11, "euler angle defining CSA orientation in Quad Frame") + self.addMultiLabel("Beta", u"β [deg]:", 13, "euler angle defining CSA orientation in Quad Frame") + self.addMultiLabel("Gamma", u"γ [deg]:", 15, "euler angle defining CSA orientation in Quad Frame") + self.addMultiLabel("Integral", "Integral:", 17) + self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 19, "Lorentzian broadening of central transition (transverse relaxation rate)") + self.addMultiLabel("Gauss", f"Gauss [{axUnit}]:", 21, "Gaussian broadening (FWHM of chemical shift distribution)") + self.addMultiLabel("LorentzST", "LorentzST [Hz]:", 23, "Lorentzian broadening of satellite transitions (transverse relaxation rate)") for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def MASChange(self, MAStype): @@ -3524,6 +3642,8 @@ def checkResults(self, numExp, struc): for i in range(numExp): if struc["Lorentz"][i][0] == 1: self.fitParamList[locList]["Lorentz"][i][0] = abs(self.fitParamList[locList]["Lorentz"][i][0]) + if struc["LorentzST"][i][0] == 1: + self.fitParamList[locList]["LorentzST"][i][0] = abs(self.fitParamList[locList]["LorentzST"][i][0]) if struc["Gauss"][i][0] == 1: self.fitParamList[locList]["Gauss"][i][0] = abs(self.fitParamList[locList]["Gauss"][i][0]) if struc['eta'][i][0] == 1: @@ -3549,6 +3669,24 @@ def checkResults(self, numExp, struc): if self.fitParamList[locList]["Gamma"][i][0] > 90: self.fitParamList[locList]["Gamma"][i][0] = 180 - self.fitParamList[locList]["Gamma"][i][0] + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Definition1"])): + if not isinstance(self.fitParamList[locList]["Definition1"][j][0], tuple): + self.fitParamList[locList]["Definition1"][j][0] *= newAxMult/oldAxMult + if not isinstance(self.fitParamList[locList]["Definition2"][j][0], tuple): + self.fitParamList[locList]["Definition2"][j][0] *= newAxMult/oldAxMult + if self.shiftDefType in [0, 1]: + if not isinstance(self.fitParamList[locList]["Definition3"][j][0], tuple): + self.fitParamList[locList]["Definition3"][j][0] *= newAxMult/oldAxMult +# for j in range(len(self.fitParamList[locList]["Gauss"])): # same j index as for Definition1 + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult + ############################################################################## @@ -3989,19 +4127,24 @@ def __init__(self, parent, rootwindow, isMain=True): axUnit = 'ppm' else: axUnit = ['Hz', 'kHz', 'MHz'][self.parent.getAxType()] - self.addMultiLabel("Position", "Pos [" + axUnit + "]:", 0) - self.addMultiLabel("Sigma", u"σ [MHz]:", 2) - self.addMultiLabel("Cq0", u"CQ0 [MHz]:", 4) - self.addMultiLabel("eta0", u"η0:", 6) - self.addMultiLabel("Integral", "Integral:", 8) - self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 10) - self.addMultiLabel("Gauss", "Gauss [Hz]:", 12) + self.addMultiLabel("Position", "Pos [" + axUnit + "]:", 1, "Isotropic chemical shift") + self.addMultiLabel("Sigma", u"σ [MHz]:", 3, "Quadrupolar anisotropy variance: most probable (average) Cq is 2*σ") + self.addMultiLabel("Cq0", u"CQ0 [MHz]:", 5) + self.addMultiLabel("eta0", u"η0:", 7) + self.addMultiLabel("Integral", "Integral:", 9) + self.addMultiLabel("Lorentz", "Lorentz [Hz]:", 11, "Lorentzian broadening (transverse relaxation rate)") + self.addMultiLabel("Gauss", f"Gauss [{axUnit}]:", 13, "Gaussian broadening (FWHM of chemical shift distribution") for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def reset(self): @@ -4154,6 +4297,19 @@ def checkResults(self, numExp, struc): if struc['eta0'][i][0] == 1: self.fitParamList[locList]['eta0'][i][0] = 1 - abs(abs(self.fitParamList[locList]['eta0'][i][0])%2 - 1) + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Position"])): + if not isinstance(self.fitParamList[locList]["Position"][j][0], tuple): + self.fitParamList[locList]["Position"][j][0] *= newAxMult/oldAxMult +# for j in range(len(self.fitParamList[locList]["Gauss"])): # same j index as for Position + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult + ################################################################################# @@ -4566,7 +4722,7 @@ class MqmasDeconvParamFrame(AbstractParamFrame): Ivalues = [1.5, 2.5, 3.5, 4.5] MQvalues = [3, 5, 7, 9] SINGLENAMES = ["Offset", "Multiplier", "Spinspeed"] - MULTINAMES = ["Position", "Cq", 'eta', "Integral", "Lorentz2", "Gauss2", "Lorentz1", "Gauss1"] + MULTINAMES = ["Position", "Gauss", "Cq", 'eta', "Integral", "Lorentz", "Lorentz1"] # , "Gauss2", "Gauss1" EXTRANAMES = ['spinType', 'angle', 'numssb', 'cheng', 'I', 'MQ', 'shear', 'scale'] MASTYPES = ["Static", "Finite MAS", "Infinite MAS"] @@ -4586,9 +4742,9 @@ def __init__(self, parent, rootwindow, isMain=True): self.FITFUNC = simFunc.mqmasFunc self.fullInt = np.sum(parent.getData1D()) * parent.sw() / float(parent.getData1D().shape[-1]) * parent.sw(-2) / float(parent.getData1D().shape[-2]) self.DEFAULTS = {"Offset": [0.0, True], "Multiplier": [1.0, True], "Spinspeed": [10.0, True], - "Position": [0.0, False], "Cq": [1.0, False], 'eta': [0.0, False], - "Integral": [self.fullInt, False], "Lorentz2": [10.0, False], "Gauss2": [0.0, True], - "Lorentz1": [10.0, False], "Gauss1": [0.0, True]} + "Position": [0.0, False], "Gauss": [0.0, False], "Cq": [1.0, False], 'eta': [0.0, False], + "Integral": [self.fullInt, False], "Lorentz": [10.0, False], # "Gauss2": [0.0, True], + "Lorentz1": [10.0, False] } # ,"Gauss1": [0.0, True] } self.extraDefaults = {'spinType': 2, 'angle': "arctan(sqrt(2))", 'numssb': 32, 'cheng': 15, 'I': 0, 'MQ': 0, 'shear': '0.0', 'scale': '1.0'} super(MqmasDeconvParamFrame, self).__init__(parent, rootwindow, isMain) self.optframe.addWidget(wc.QLabel("MAS:"), 2, 0) @@ -4654,20 +4810,26 @@ def __init__(self, parent, rootwindow, isMain=True): else: axUnit = ['Hz', 'kHz', 'MHz'][self.parent.getAxType()] # Labels - self.addMultiLabel("Position", u"Position [" + axUnit + "]:", 0) - self.addMultiLabel("Cq", u"CQ [MHz]:", 2) - self.addMultiLabel("eta", u"η:", 4) - self.addMultiLabel("Integral", "Integral:", 6) - self.addMultiLabel("Lorentz2", "Lorentz 2 [Hz]:", 8) - self.addMultiLabel("Gauss2", "Gauss 2 [Hz]:", 10) - self.addMultiLabel("Lorentz1", "Lorentz 1 [Hz]:", 12) - self.addMultiLabel("Gauss1", "Gauss 1 [Hz]:", 14) + self.addMultiLabel("Position", u"Position [" + axUnit + "]:", 1, "Isotropic chemical shift") + self.addMultiLabel("Gauss", f"σCS [{axUnit}]:", 3, "Gaussian broadening (FWHM of chemical shift distribution)") + self.addMultiLabel("Cq", u"CQ [MHz]:", 5, "Quadrupolar anisotropy") + self.addMultiLabel("eta", u"η:", 7, "Quadrupolar asymmetry") + self.addMultiLabel("Integral", "Integral:", 9) + self.addMultiLabel("Lorentz", "Lorentz 2 [Hz]:", 11, "Lorentzian broadening (transverse relaxation rate) in direct dimension") + self.addMultiLabel("Lorentz1", "Lorentz 1 [Hz]:", 13, "Lorentzian broadening (transverse relaxation rate) in indirect dimension") +# self.addMultiLabel("Gauss2", "Gauss 2 [Hz]:", 15) +# self.addMultiLabel("Gauss1", "Gauss 1 [Hz]:", 17) for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def reset(self): @@ -4798,17 +4960,32 @@ def checkResults(self, numExp, struc): for i in range(numExp): if struc["Lorentz1"][i][0] == 1: self.fitParamList[locList]["Lorentz1"][i][0] = abs(self.fitParamList[locList]["Lorentz1"][i][0]) - if struc["Gauss1"][i][0] == 1: - self.fitParamList[locList]["Gauss1"][i][0] = abs(self.fitParamList[locList]["Gauss1"][i][0]) - if struc["Lorentz2"][i][0] == 1: - self.fitParamList[locList]["Lorentz2"][i][0] = abs(self.fitParamList[locList]["Lorentz2"][i][0]) - if struc["Gauss2"][i][0] == 1: - self.fitParamList[locList]["Gauss2"][i][0] = abs(self.fitParamList[locList]["Gauss2"][i][0]) + if struc["Gauss"][i][0] == 1: + self.fitParamList[locList]["Gauss"][i][0] = abs(self.fitParamList[locList]["Gauss"][i][0]) +# if struc["Gauss1"][i][0] == 1: +# self.fitParamList[locList]["Gauss1"][i][0] = abs(self.fitParamList[locList]["Gauss1"][i][0]) + if struc["Lorentz"][i][0] == 1: + self.fitParamList[locList]["Lorentz"][i][0] = abs(self.fitParamList[locList]["Lorentz"][i][0]) +# if struc["Gauss2"][i][0] == 1: +# self.fitParamList[locList]["Gauss2"][i][0] = abs(self.fitParamList[locList]["Gauss2"][i][0]) if struc['eta'][i][0] == 1: self.fitParamList[locList]['eta'][i][0] = 1 - abs(abs(self.fitParamList[locList]['eta'][i][0]) % 2 - 1) if struc["Cq"][i][0] == 1: self.fitParamList[locList]["Cq"][i][0] = abs(self.fitParamList[locList]["Cq"][i][0]) + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Position"])): + if not isinstance(self.fitParamList[locList]["Position"][j][0], tuple): + self.fitParamList[locList]["Position"][j][0] *= newAxMult/oldAxMult +# for j in range(len(self.fitParamList[locList]["Gauss"])): # same j index as for Position + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult + ############################################################################## class MqmasCzjzekParamFrame(AbstractParamFrame): @@ -4819,7 +4996,7 @@ class MqmasCzjzekParamFrame(AbstractParamFrame): Ivalues = [1.5, 2.5, 3.5, 4.5] MQvalues = [3, 5, 7, 9] SINGLENAMES = ["Offset", "Multiplier"] - MULTINAMES = ["Position", "Sigma", 'SigmaCS', "Cq0", 'eta0', "Integral", "Lorentz2", "Gauss2", "Lorentz1", "Gauss1"] + MULTINAMES = ["Position", 'Gauss', "Sigma", "Cq0", 'eta0', "Integral", "Lorentz", "Lorentz1"] #, "Gauss2", "Gauss1"] EXTRANAMES = ['method', 'd', 'MQ', 'shear', 'scale'] TYPES = ['Normal', 'Extended'] @@ -4839,9 +5016,9 @@ def __init__(self, parent, rootwindow, isMain=True): self.FITFUNC = simFunc.mqmasCzjzekFunc self.fullInt = np.sum(parent.getData1D()) * parent.sw() / float(parent.getData1D().shape[-1]) * parent.sw(-2) / float(parent.getData1D().shape[-2]) self.DEFAULTS = {"Offset": [0.0, True], "Multiplier": [1.0, True], "Position": [0.0, False], - "Sigma": [1.0, False], 'SigmaCS': [10.0, False], "Cq0": [0.0, True], - 'eta0': [0.0, True], "Integral": [self.fullInt, False], "Lorentz2": [10.0, False], - "Gauss2": [0.0, True], "Lorentz1": [10.0, False], "Gauss1": [0.0, True]} + "Sigma": [1.0, False], 'Gauss': [10.0, False], "Cq0": [0.0, True], + 'eta0': [0.0, True], "Integral": [self.fullInt, False], "Lorentz": [10.0, False], + "Lorentz1": [10.0, False]} #, "Gauss2": [0.0, True], "Gauss1": [0.0, True]} self.extraDefaults = {'mas': 2, 'method': 0, 'd': 5, 'cheng': 15, 'I': 3/2.0, 'MQ': 0, 'shear': '0.0', 'scale': '1.0', 'cqsteps': 50, 'etasteps': 10, 'cqmax': 4, 'cqmin': 0, 'etamax': 1, 'etamin': 0, 'libName': "Not available", 'lib': None, 'cqLib': None, 'etaLib': None} super(MqmasCzjzekParamFrame, self).__init__(parent, rootwindow, isMain) @@ -4890,22 +5067,27 @@ def __init__(self, parent, rootwindow, isMain=True): axUnit = 'ppm' else: axUnit = ['Hz', 'kHz', 'MHz'][self.parent.getAxType()] - self.addMultiLabel("Position", "Pos [" + axUnit + "]:", 0) - self.addMultiLabel("Sigma", u"σ [MHz]:", 2) - self.addMultiLabel("SigmaCS", u"σCS [Hz]:", 4) - self.addMultiLabel("Cq0", u"CQ0 [MHz]:", 6) - self.addMultiLabel("eta0", u"η0:", 8) - self.addMultiLabel("Integral", "Integral:", 10) - self.addMultiLabel("Lorentz2", "Lorentz 2 [Hz]:", 12) - self.addMultiLabel("Gauss2", "Gauss 2 [Hz]:", 14) - self.addMultiLabel("Lorentz1", "Lorentz 1 [Hz]:", 16) - self.addMultiLabel("Gauss1", "Gauss 1 [Hz]:", 18) + self.addMultiLabel("Position", "Pos [" + axUnit + "]:", 1, "Isotropic chemical shift") + self.addMultiLabel("Gauss", f"σCS [{axUnit}]:", 3, "Gaussian broadening (FWHM of chemical shift distribution)") + self.addMultiLabel("Sigma", u"σQ [MHz]:", 5, "Quadrupolar anisotropy variance: most probable (average) Cq is 2*σ") + self.addMultiLabel("Cq0", u"CQ0 [MHz]:", 7) + self.addMultiLabel("eta0", u"η0:", 9) + self.addMultiLabel("Integral", "Integral:", 11) + self.addMultiLabel("Lorentz", "Lorentz 2 [Hz]:", 13, "Lorentzian broadening (transverse relaxation rate) in direct dimension") + self.addMultiLabel("Lorentz1", "Lorentz 1 [Hz]:", 15, "Lorentzian broadening (transverse relaxation rate) in indirect dimension") +# self.addMultiLabel("Gauss2", "Gauss 2 [Hz]:", 17) +# self.addMultiLabel("Gauss1", "Gauss 1 [Hz]:", 19) for i in range(self.FITNUM): + colorbar = QtWidgets.QWidget() + colorbar.setMaximumWidth(5) + colorbar.setMinimumWidth(5) + colorbar.setStyleSheet(f"QWidget {{ background-color : {self.fit_color_list[i%len(self.fit_color_list)]};}}") + self.frame3.addWidget(colorbar, i + 2, 0) for j in range(len(self.MULTINAMES)): self.ticks[self.MULTINAMES[j]].append(QtWidgets.QCheckBox('')) - self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j) + self.frame3.addWidget(self.ticks[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) self.entries[self.MULTINAMES[j]].append(wc.FitQLineEdit(self, self.MULTINAMES[j])) - self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 1) + self.frame3.addWidget(self.entries[self.MULTINAMES[j]][i], i + 2, 2 * j + 2) self.reset() def reset(self): @@ -5058,20 +5240,34 @@ def checkResults(self, numExp, struc): """ locList = self.getRedLocList() for i in range(numExp): + if struc["Gauss"][i][0] == 1: + self.fitParamList[locList]["Gauss"][i][0] = abs(self.fitParamList[locList]["Gauss"][i][0]) if struc["Lorentz1"][i][0] == 1: self.fitParamList[locList]["Lorentz1"][i][0] = abs(self.fitParamList[locList]["Lorentz1"][i][0]) - if struc["Gauss1"][i][0] == 1: - self.fitParamList[locList]["Gauss1"][i][0] = abs(self.fitParamList[locList]["Gauss1"][i][0]) - if struc["Lorentz2"][i][0] == 1: - self.fitParamList[locList]["Lorentz2"][i][0] = abs(self.fitParamList[locList]["Lorentz2"][i][0]) - if struc["Gauss2"][i][0] == 1: - self.fitParamList[locList]["Gauss2"][i][0] = abs(self.fitParamList[locList]["Gauss2"][i][0]) +# if struc["Gauss1"][i][0] == 1: +# self.fitParamList[locList]["Gauss1"][i][0] = abs(self.fitParamList[locList]["Gauss1"][i][0]) + if struc["Lorentz"][i][0] == 1: + self.fitParamList[locList]["Lorentz"][i][0] = abs(self.fitParamList[locList]["Lorentz"][i][0]) +# if struc["Gauss2"][i][0] == 1: +# self.fitParamList[locList]["Gauss2"][i][0] = abs(self.fitParamList[locList]["Gauss2"][i][0]) if struc['eta0'][i][0] == 1: #eta is between 0--1 in a continuous way. self.fitParamList[locList]['eta0'][i][0] = 1 - abs(abs(self.fitParamList[locList]['eta0'][i][0]) % 2 - 1) if struc["Cq0"][i][0] == 1: self.fitParamList[locList]["Cq0"][i][0] = abs(self.fitParamList[locList]["Cq0"][i][0]) + def changeAxMult(self, oldAxMult): + """ + Changing the units of the parameters which depend on the plot units. + """ + newAxMult = self.parent.getCurrentAxMult() + locList = self.getRedLocList() + for j in range(len(self.fitParamList[locList]["Position"])): + if not isinstance(self.fitParamList[locList]["Position"][j][0], tuple): + self.fitParamList[locList]["Position"][j][0] *= newAxMult/oldAxMult +# for j in range(len(self.fitParamList[locList]["Gauss"])): # same j index as for Position + if not isinstance(self.fitParamList[locList]["Gauss"][j][0], tuple): + self.fitParamList[locList]["Gauss"][j][0] *= newAxMult/oldAxMult class NewTabDialog(QtWidgets.QDialog): diff --git a/src/functions.py b/src/functions.py index fc083fc..d069c57 100644 --- a/src/functions.py +++ b/src/functions.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/hypercomplex.py b/src/hypercomplex.py index f682618..2409253 100644 --- a/src/hypercomplex.py +++ b/src/hypercomplex.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/loadIsotopes.py b/src/loadIsotopes.py index dc1607c..2c1baa6 100644 --- a/src/loadIsotopes.py +++ b/src/loadIsotopes.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/nmrTable.py b/src/nmrTable.py index 9683db5..8bb813a 100644 --- a/src/nmrTable.py +++ b/src/nmrTable.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -19,13 +19,8 @@ # along with ssNake. If not, see . import sys -try: - from PyQt5 import QtGui, QtCore, QtWidgets - QT = 5 -except ImportError: - from PyQt4 import QtGui, QtCore - from PyQt4 import QtGui as QtWidgets - QT = 4 +from PyQt5 import QtGui, QtCore, QtWidgets +QT = 5 import os import math import loadIsotopes @@ -489,10 +484,7 @@ def __init__(self, parent): grid.addWidget(self.orderType,0,1) self.table = QtWidgets.QTableWidget(1,8) - if QT == 4: - self.table.horizontalHeader().setResizeMode(QtWidgets.QHeaderView.ResizeToContents) - elif QT == 5: - self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) self.table.verticalHeader().setVisible(False) self.table.setHorizontalHeaderLabels(['Nucleus','Name','Spin','Abundance [%]','Q [fm^2]','Frequency ratio [%]', 'Frequency [MHz]','Sensitivity [1H]']) diff --git a/src/nus.py b/src/nus.py index 1ba978e..8417434 100644 --- a/src/nus.py +++ b/src/nus.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/reimplement.py b/src/reimplement.py index a246d59..9347a95 100644 --- a/src/reimplement.py +++ b/src/reimplement.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/safeEval.py b/src/safeEval.py index 59d4d70..32d831c 100644 --- a/src/safeEval.py +++ b/src/safeEval.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/saveFigure.py b/src/saveFigure.py index 160be6f..680bb44 100644 --- a/src/saveFigure.py +++ b/src/saveFigure.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/simFunctions.py b/src/simFunctions.py index b8ef688..038a0bf 100644 --- a/src/simFunctions.py +++ b/src/simFunctions.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -379,7 +379,7 @@ def diffusionFunc(x, freq, sw, axMult, extra, amp, const, coeff, D): """ x = x[-1] gamma, delta, triangle = extra - return amp * (const + coeff * np.exp(-(abs(gamma) *1e6 * abs(delta) * x)**2 * abs(D) * (abs(triangle) - abs(delta) / 3.0))) + return amp * (const + coeff * np.exp(-(abs(gamma) *1e6 * 2 * np.pi * abs(delta) * x)**2 * abs(D) * (abs(triangle) - abs(delta) / 3.0))) def functionRun(x, freq, sw, axMult, extra, *parameters): """ @@ -440,7 +440,7 @@ def externalFitRunScript(x, freq, sw, axMult, extra, bgrnd, mult, *parameters): The value by which the output curve is multiplied. *parameters The parameters used in the fit. - The last three values are interpreted as [amp, lor, gauss] and are used as the amplitude, the Lorentzian apodization (Hz) and Gaussian apodization (Hz). + The last three values are interpreted as [amp, lor, gauss] and are used as the amplitude, the Lorentzian apodization (Hz) and Gaussian apodization (chemical shift distribution) in axMult unit. Should have be three longer than names. Returns @@ -450,6 +450,7 @@ def externalFitRunScript(x, freq, sw, axMult, extra, bgrnd, mult, *parameters): """ names, command, script, output, spec = extra amp, lor, gauss = parameters[-3:] + gauss /= axMult x = x[-1] if script is None: return None @@ -498,7 +499,7 @@ def fib(n): int The n+2 Fibonacci number. """ - start = np.array([[1, 1], [1, 0]], dtype='int64') + start = np.array([[1, 1], [1, 0]], dtype=np.int64) temp = start[:] for i in range(n): temp = np.dot(start, temp) @@ -527,7 +528,7 @@ def zcw_angles(m, symm=0): The weights of the different orientations. """ samples, fib_1, fib_2 = fib(m) - js = np.arange(samples, dtype='Float64') / samples + js = np.arange(samples, dtype=np.float64) / samples if symm == 0: c = (1., 2., 1.) elif symm == 1: @@ -569,7 +570,7 @@ def peakSim(x, freq, sw, axMult, extra, bgrnd, mult, pos, amp, lor, gauss): lor : float The Lorentzian broadening of the peak. gauss : float - The Gaussian broadening of the peak. + The Gaussian broadening of the peak (in Hz*axMult), corresponding to CS distribution. Returns ------- @@ -578,6 +579,7 @@ def peakSim(x, freq, sw, axMult, extra, bgrnd, mult, pos, amp, lor, gauss): """ x = x[-1] pos /= axMult + gauss /= axMult if pos < np.min(x) or pos > np.max(x): return np.zeros_like(x) lor = np.abs(lor) @@ -621,7 +623,7 @@ def makeSpectrum(x, sw, v, gauss, lor, weight): inten *= len(inten) / abs(sw) return inten -def makeMQMASSpectrum(x, sw, v, gauss, lor, weight): +def makeMQMASSpectrum(x, sw, v, gauss, lor, weight, slope): """ Creates an 2D FID from a list of frequencies with corresponding weights. Also applies Lorentzian and Gaussian broadening. @@ -644,6 +646,8 @@ def makeMQMASSpectrum(x, sw, v, gauss, lor, weight): Lorentzian broadening in Hz for the first and second dimension. weight : ndarray The weights corresponding to the frequencies. Should have the same length as the arrays in v. + slope : slope (t2/t1) along which the CS and CS distribution is refocused + to simulate CS distribution with gaussian g=broadening Returns ------- @@ -657,10 +661,22 @@ def makeMQMASSpectrum(x, sw, v, gauss, lor, weight): t2 = np.fft.fftfreq(length2, sw[-1]/float(length2)) diff2 = (x[-1][1] - x[-1][0])*0.5 t1 = t1[:, np.newaxis] - final, _, _ = np.histogram2d(v[0], v[1], [length1, length2], range=[[x[-2][0]-diff1, x[-2][-1]+diff1], [x[-1][0]-diff2, x[-1][-1]+diff2]], weights=weight) + minD1 = x[-2][0] - diff1 + maxD1 = x[-2][-1] + diff1 + minD2 = x[-1][0] - diff2 + maxD2 = x[-1][-1] + diff2 + if minD1 > maxD1: + minD1, maxD1 = maxD1, minD1 + v[0] *= -1 + if minD2 > maxD2: + minD2, maxD2 = maxD2, minD2 + v[1] *= -1 + final, _, _ = np.histogram2d(v[0], v[1], [length1, length2], range=[[minD1, maxD1], [minD2, maxD2]], weights=weight) final = np.fft.ifftn(final) - apod2 = np.exp(-np.pi * np.abs(lor[1] * t2) - ((np.pi * np.abs(gauss[1]) * t2)**2) / (4 * np.log(2))) - apod1 = np.exp(-np.pi * np.abs(lor[0] * t1) - ((np.pi * np.abs(gauss[0]) * t1)**2) / (4 * np.log(2))) + apod2 = np.exp(-np.pi * np.abs(lor[1] * t2) - + ((np.pi * np.abs(gauss[1]) * (t2 + t1*slope))**2) / (4 * np.log(2))) + apod1 = np.exp(-np.pi * np.abs(lor[0] * t1) - + ((np.pi * np.abs(gauss[0]) * t1)**2) / (4 * np.log(2))) final *= apod1 * apod2 * length1 / abs(sw[-2]) * length2 / abs(sw[-1]) return final @@ -748,15 +764,15 @@ def csaFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t33, a """ shiftdef, numssb, angle, D2, weight, MAStype = extra extra = [False, 0.5, numssb, angle, D2, None, weight, MAStype, shiftdef] - return quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t33, 0.0, 0.0, 0.0, 0.0, 0.0, amp, lor, gauss) + return quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t33, 0.0, 0.0, 0.0, 0.0, 0.0, amp, lor, gauss, 0) -def quadFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, amp, lor, gauss): +def quadFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, amp, lor, gauss, lorST): """ Uses the quadCSAFunc function for the specific case where the CSA interaction is zero. """ satBool, I, numssb, angle, D2, D4, weight, MAStype = extra extra = [satBool, I, numssb, angle, D2, D4, weight, MAStype, 0] - return quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, pos, pos, cq, eta, 0.0, 0.0, 0.0, amp, lor, gauss) + return quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, pos, pos, cq, eta, 0.0, 0.0, 0.0, amp, lor, gauss, lorST) def quadFreqBase(I, m1, m2, cq, eta, freq, angle, D2, D4, numssb, spinspeed): """ @@ -831,7 +847,7 @@ def quadFreqBase(I, m1, m2, cq, eta, freq, angle, D2, D4, numssb, spinspeed): v = np.matmul(dat2, spinD2) + np.matmul(dat4, spinD4) return v, vConstant -def quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t33, cq, eta, alphaCSA, betaCSA, gammaCSA, amp, lor, gauss): +def quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t33, cq, eta, alphaCSA, betaCSA, gammaCSA, amp, lor, gauss, lorST): """ Calculates an FID of a powder averaged site under influence of CSA and a quadrupole interaction. This function works for static, finite, and infinite spinnning. @@ -878,6 +894,8 @@ def quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t3 The Lorentzian broadening of the peak. gauss : float The Gaussian broadening of the peak. + lorST : float + The Lorentzian broadening of STs of the peak. Returns ------- @@ -901,6 +919,7 @@ def quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t3 t33 = 1 - abs(abs(t33 + 1)%4 - 2) tensor = np.array(func.shiftConversion([t11, t22, t33], shiftdef)[1], dtype=float) tensor /= float(axMult) + gauss /= axMult cq *= 1e6 eta = 1 - abs(abs(eta) % 2 - 1) # Force eta to 0--1 in a continuous way: 0.9 == 1.1, 0 == 2 spinspeed *= 1e3 @@ -926,7 +945,11 @@ def quadCSAFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, t11, t22, t3 tot = weight if spinspeed not in (0.0, np.inf): v, tot = carouselAveraging(spinspeed, v, weight, vConstant) - spectrum += eff * makeSpectrum(x, sw, v, gauss, lor, tot) + if m == -0.5: + lb = lor + else: + lb = lorST + spectrum += eff * makeSpectrum(x, sw, v, gauss, lb, tot) return mult * amp * spectrum def quadCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, cq0, eta0, amp, lor, gauss): @@ -967,7 +990,7 @@ def quadCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, cq0, eta lor : float The Lorentzian broadening of the peak. gauss : float - The Gaussian broadening of the peak. + The Gaussian broadening of the peak in the units defined by axMult. Returns ------- @@ -981,6 +1004,7 @@ def quadCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, cq0, eta cq0 = 0 eta0 = 0 pos /= axMult + gauss /= axMult sigma = abs(sigma) * 1e6 cq0 *= 1e6 czjzek = Czjzek.czjzekIntensities(sigma, d, cq, eta, cq0, eta0) @@ -991,7 +1015,7 @@ def quadCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, cq0, eta apod = np.exp(2j * np.pi * pos * t - np.pi * np.abs(lor) * np.abs(t) - ((np.pi * np.abs(gauss) * t)**2) / (4 * np.log(2))) return mult * amp * fid * apod -def mqmasFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, amp, lor2, gauss2, lor1, gauss1): +def mqmasFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, sigmaCS, cq, eta, amp, lor2, lor1, gauss2=0, gauss1=0): """ Calculates a 2-D FID of an MQMAS spectrum. This function works for static, finite, and infinite spinnning. @@ -1029,6 +1053,8 @@ def mqmasFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, The spinning speed in kHz. pos : float The isotropic chemical shift value defined in terms of axMult. + sigmaCS : float + The Gaussian broadening along the chemical shift axis in units defined by axMult. cq : float The quadrupole coupling constant Cq given in MHz. eta : float @@ -1038,11 +1064,11 @@ def mqmasFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, lor2 : float The Lorentzian broadening in the direct dimension. gauss2 : float - The Gaussian broadening in the direct dimension. + The Gaussian broadening in the direct dimension defined (not used). lor1 : float The Lorentzian broadening in the indirect dimension. gauss1 : float - The Gaussian broadening in the indirect dimension. + The Gaussian broadening in the indirect dimension (not used). Returns ------- @@ -1057,6 +1083,7 @@ def mqmasFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, elif MAStype == 2: spinspeed = np.inf pos /= axMult + sigmaCS /= axMult spinspeed *= 1e3 cq *= 1e6 eta = 1 - abs(abs(eta)%2 - 1) @@ -1070,13 +1097,14 @@ def mqmasFunc(x, freq, sw, axMult, extra, bgrnd, mult, spinspeed, pos, cq, eta, v2 += pos v1 += mq*pos - v2 * shear v1 *= scale - return mult * amp * makeMQMASSpectrum(x, sw, [np.real(v1.flatten()), np.real(v2.flatten())], [gauss1, gauss2], [lor1, lor2], np.real(tot).flatten()) + slope = (mq-shear)*scale # t1/t2 slope along which to apply CS gaussian distribution + return mult * amp * makeMQMASSpectrum(x, sw, [np.real(v1.flatten()), np.real(v2.flatten())], [0, sigmaCS], [lor1, lor2], np.real(tot).flatten(), slope) -def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, sigmaCS, cq0, eta0, amp, lor2, gauss2, lor1, gauss1): +def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigmaCS, sigma, cq0, eta0, amp, lor2, lor1, gauss2=0, gauss1=0): """ Calculates a 2-D FID of an MQMAS spectrum with an (extended) Czjzek distribution using a library of 1-D spectra. - Parameters + , shear_factorParameters ---------- x : list of ndarray A list of axes values for the simulation. @@ -1108,7 +1136,7 @@ def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, sigmaCS sigma : float The width of the Czjzek distribution in MHz. sigmaCS : float - The Gaussian broadening along the chemical shift axis in Hz. + The Gaussian broadening along the chemical shift axis in units defined by axMult. cq0 : float The extended Czjzek quadrupole coupling constant Cq0 given in MHz (only used when method=True). eta0 : float @@ -1118,11 +1146,11 @@ def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, sigmaCS lor2 : float The Lorentzian broadening in the direct dimension. gauss2 : float - The Gaussian broadening in the direct dimension. + The Gaussian broadening in the direct dimension (not used). lor1 : float The Lorentzian broadening in the indirect dimension. gauss1 : float - The Gaussian broadening in the indirect dimension. + The Gaussian broadening in the indirect dimension (not used). Returns ------- @@ -1138,6 +1166,7 @@ def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, sigmaCS cq0 = 0 eta0 = 0 pos /= axMult + sigmaCS /= axMult sigma *= 1e6 czjzek = Czjzek.czjzekIntensities(sigma, d, cq, eta, cq0, eta0) length2 = len(x[-1]) @@ -1147,8 +1176,6 @@ def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, sigmaCS t1 = np.fft.fftfreq(length1, sw[-2]/float(length1)) t1 = t1[:, np.newaxis] t2 = np.fft.fftfreq(length2, sw[-1]/float(length2)) - apod2 = np.exp(-np.pi * np.abs(lor2 * t2) - ((np.pi * np.abs(gauss2) * t2)**2) / (4 * np.log(2))) - apod1 = np.exp(-np.pi * np.abs(lor1 * t1) - ((np.pi * np.abs(gauss1) * t1)**2) / (4 * np.log(2))) V40 = 1.0 / 140 * (18 + eta**2) T40_m = mq * (18 * I * (I + 1) - 34 * (mq/2.0)**2 - 5) T40_1 = (18 * I * (I + 1) - 34 * (0.5)**2 - 5) @@ -1166,6 +1193,10 @@ def mqmasCzjzekFunc(x, freq, sw, axMult, extra, bgrnd, mult, pos, sigma, sigmaCS posIndirect = pos * (mq - shearFactor) * scale offsetMat = np.exp(2j * np.pi * (posIndirect * t1 + (pos - x[-1][length2//2])*t2)) shiftGauss = np.exp(-((np.pi * np.abs(sigmaCS) * (t2 + t1*(mq-shearFactor)*scale))**2) / (4 * np.log(2))) +# apod2 = np.exp(-np.pi * np.abs(lor2 * t2) - ((np.pi * np.abs(gauss2) * t2)**2) / (4 * np.log(2))) +# apod1 = np.exp(-np.pi * np.abs(lor1 * t1) - ((np.pi * np.abs(gauss1) * t1)**2) / (4 * np.log(2))) + apod2 = np.exp(-np.pi * np.abs(lor2 * t2) ) + apod1 = np.exp(-np.pi * np.abs(lor1 * t1) ) fid *= offsetMat * apod1 * apod2 * shiftGauss shearMat = np.exp((shearFactor-shear) * 2j * np.pi * t1 * x[-1]) fid = np.fft.fft(fid, axis=1) * shearMat @@ -1207,5 +1238,5 @@ def genLib(length, minCq, maxCq, minEta, maxEta, numCq, numEta, extra, freq, sw, x = np.fft.fftshift(np.fft.fftfreq(length, 1/float(sw))) lib = np.zeros((len(cq), length), dtype=complex) for i, (cqi, etai) in enumerate(zip(cq, eta)): - lib[i] = quadFunc([x], [freq], [sw], 1.0, extra, 0.0, 1.0, spinspeed, 0.0, cqi, etai, 1.0, 0.0, 0.0) + lib[i] = quadFunc([x], [freq], [sw], 1.0, extra, 0.0, 1.0, spinspeed, 0.0, cqi, etai, 1.0, 0.0, 0.0, 0.0) return lib, cq*1e6, eta diff --git a/src/specIO.py b/src/specIO.py index d1b4638..37af643 100644 --- a/src/specIO.py +++ b/src/specIO.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -409,9 +409,9 @@ def loadVarianFile(filePath): fid = fid[0][:] if spec: # flip if spectrum fid = np.flipud(fid) - masterData = sc.Spectrum(fid, (filePath, None), [freq], [sw], [bool(int(spec))], ref=[reffreq]) + masterData = sc.Spectrum(fid, (filePath, None), [freq], [sw], spec=[bool(int(spec))], ref=[reffreq]) else: - masterData = sc.Spectrum(fid, (filePath, None), [freq1, freq], [sw1, sw], [bool(int(spec))] * 2, ref=[reffreq1, reffreq]) + masterData = sc.Spectrum(fid, (filePath, None), [freq1, freq], [sw1, sw], spec=[bool(int(spec))]*2, ref=[reffreq1, reffreq]) masterData.addHistory("Varian data loaded from " + filePath) try: masterData.metaData['# Scans'] = str(pars['nt']) @@ -509,7 +509,7 @@ def loadPipe(filePath): for i in range(NDIM): if spec[-1 - i] == 1: data[k] = np.flip(data[k], NDIM -1 - i) - masterData = sc.Spectrum(hc.HComplexData(data, hyper), (filePath, None), freq[4 - NDIM:4], sw[4 - NDIM:4], spec[4 - NDIM:4], ref=ref[4 - NDIM:4]) + masterData = sc.Spectrum(hc.HComplexData(data, hyper), (filePath, None), freq[4-NDIM:4], sw[4-NDIM:4], spec[4-NDIM:4], ref=ref[4-NDIM:4]) masterData.addHistory("NMRpipe data loaded from " + filePath) return masterData @@ -721,7 +721,7 @@ def loadJEOLDelta(filePath): for i in range(NDIM): if spec[-1 - i] == 1: data[k] = np.flip(data[k], NDIM -1 - i) - masterData = sc.Spectrum(hc.HComplexData(np.array(data), hyper), (filePath, None), freq, sw, spec, ref=ref, dFilter=dFilter) + masterData = sc.Spectrum(hc.HComplexData(np.array(data), hyper), (filePath, None), freq, sw, spec=spec, ref=ref, dFilter=dFilter) masterData.addHistory("JEOL Delta data loaded from " + filePath) return masterData @@ -804,10 +804,10 @@ def loadJSONFile(filePath): (filePath, None), list(struct['freq']), list(struct['sw']), - list(struct['spec']), - list(np.array(struct['wholeEcho'], dtype=bool)), - list(ref), - xaxA, + spec=list(struct['spec']), + wholeEcho=list(np.array(struct['wholeEcho'], dtype=bool)), + ref=list(ref), + xaxArray=xaxA, history=history, metaData=metaData, dFilter=dFilter) @@ -923,10 +923,10 @@ def loadMatlabFile(filePath): (filePath, None), list(mat['freq'][0, 0][0]), list(mat['sw'][0, 0][0]), - list(mat['spec'][0, 0][0]), - list(np.array(mat['wholeEcho'][0, 0][0]) > 0), - list(ref), - xaxA, + spec=list(mat['spec'][0, 0][0]), + wholeEcho=list(np.array(mat['wholeEcho'][0, 0][0]) > 0), + ref=list(ref), + xaxArray=xaxA, history=history, metaData=metaData, dFilter=dFilter) @@ -988,10 +988,10 @@ def loadMatlabFile(filePath): (filePath, None), list(np.array(mat['freq'])[:, 0]), list(np.array(mat['sw'])[:, 0]), - list(np.array(mat['spec'])[:, 0]), - list(np.array(mat['wholeEcho'])[:, 0] > 0), - list(ref), - xaxA, + spec=list(np.array(mat['spec'])[:, 0]), + wholeEcho=list(np.array(mat['wholeEcho'])[:, 0] > 0), + ref=list(ref), + xaxArray=xaxA, history=history, metaData=metaData, dFilter=dFilter) @@ -1040,7 +1040,7 @@ def loadMatNMRFile(filePath): if mat['DefaultAxisRefkHzTD2'][0][0][0][0]: ref[1] = freq[1] + 1e3*mat['DefaultAxisRefkHzTD2'][0][0][0][0] history = list(mat['History'][0][0]) - masterData = sc.Spectrum(data, (filePath, None), freq, sw, spec, ref=ref, history=history) + masterData = sc.Spectrum(data, (filePath, None), freq, sw, spec=spec, ref=ref, history=history) masterData.addHistory("MatNMR data loaded from " + filePath) return masterData @@ -1150,7 +1150,9 @@ def loadBrukerTopspin(filePath): else: Dir = filePath pars = [] - for File in ['acqus', 'acqu2s', 'acqu3s']: + # makes list of par file names + parFileN = ['acqus'] + [f'acqu{int(x)}s' for x in range(2,9)] + for File in parFileN: if os.path.exists(Dir + os.path.sep + File): pars.append(brukerTopspinGetPars(Dir + os.path.sep + File)) SIZE = [x['TD'] for x in pars] @@ -1177,7 +1179,7 @@ def loadBrukerTopspin(filePath): newSize[0] = int(directSize / 2) ComplexData = ComplexData.reshape(*newSize[-1::-1]) ComplexData = ComplexData[..., 0:int(SIZE[0]/2)] #Cut off placeholder data - masterData = sc.Spectrum(ComplexData, (filePath, None), FREQ[-1::-1], SW[-1::-1], [False] * dim, ref = REF[-1::-1], dFilter=dFilter) + masterData = sc.Spectrum(ComplexData, (filePath, None), FREQ[-1::-1], SW[-1::-1], spec=[False]*dim, ref=REF[-1::-1], dFilter=dFilter) # TODO: Inserting metadata should be made more generic try: masterData.metaData['# Scans'] = str(pars[0]['NS']) @@ -1199,6 +1201,13 @@ def loadBrukerTopspin(filePath): masterData.metaData['Recycle Delay [s]'] = str(pars[0]['D'][1]) except Exception: pass + try: + import time + masterData.metaData['Time Completed'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(pars[0]['DATE']))) + except Exception: + pass + + masterData.addHistory("Bruker TopSpin data loaded from " + filePath) return masterData @@ -1250,7 +1259,7 @@ def loadBrukerImagingTime(filePath): newSize[0] = int(directSize / 2) ComplexData = ComplexData.reshape(newSize[-1::-1]) ComplexData = ComplexData[..., 0:int(SIZE[0]/2)] #Cut off placeholder data - masterData = sc.Spectrum(ComplexData, (filePath, None), FREQ, SW, [False] * dim) + masterData = sc.Spectrum(ComplexData, (filePath, None), FREQ, SW, spec=[False]*dim) return masterData def loadBrukerWinNMR(filePath): @@ -1310,7 +1319,7 @@ def loadBrukerWinNMR(filePath): raw = np.fromfile(f, np.float32, SIZE) raw = raw.newbyteorder(ByteOrder) #Load with right byte order ComplexData = np.array(raw[0:len(raw):2]) + 1j * np.array(raw[1:len(raw):2]) - masterData = sc.Spectrum(ComplexData, (filePath, None), [FREQ], [SW], [spec], ref=[REF]) + masterData = sc.Spectrum(ComplexData, (filePath, None), [FREQ], [SW], spec=[spec], ref=[REF]) if not spec: try: masterData.metaData['# Scans'] = str(pars['NS']) @@ -1347,12 +1356,22 @@ def loadBrukerSpectrum(filePath): if os.path.exists(Dir + os.path.sep + File): pars.append(brukerTopspinGetPars(Dir + os.path.sep + File)) SIZE = [x['SI'] for x in pars] - XDIM = [x['XDIM'] for x in pars] + try: + XDIM = [x['XDIM'] for x in pars] + except KeyError: + XDIM = SIZE # If not specified the data is assumed to be 1D SW = [x['SW_p'] for x in pars] FREQ = [x['SF'] * 1e6 for x in pars] OFFSET = [x['OFFSET'] for x in pars] - DtypeP = [np.dtype(np.int32), np.dtype(np.float32), np.dtype(np.float64)][pars[0]['DTYPP']] #The byte orders that is used - DtypeP = DtypeP.newbyteorder(['L', 'B'][pars[0]['BYTORDP']]) + SCALE = 1 + if 'NC_proc' in pars[0]: # Set intensity scaling parameter + SCALE = 2**pars[0]['NC_proc'] + try: + DtypeP = [np.dtype(np.int32), np.dtype(np.float32), np.dtype(np.float64)][pars[0]['DTYPP']] #The byte orders that is used + DtypeP = DtypeP.newbyteorder(['L', 'B'][pars[0]['BYTORDP']]) + except KeyError: + DtypeP = np.dtype(np.int32) # When these parameters are not available the defaults are used + DtypeP = DtypeP.newbyteorder('L') # The byte orders that is used as stored in BYTORDP proc parameter: # '< or L' =little endian, '>' or 'B' = big endian REF = [] @@ -1391,7 +1410,8 @@ def loadBrukerSpectrum(filePath): DATA[index] = np.reshape(DATA[index], [int(SIZE[2]/XDIM[2]), int(SIZE[1]/XDIM[1]), int(SIZE[0]/XDIM[0]), XDIM[2], XDIM[1], XDIM[0]]) DATA[index] = np.concatenate(np.concatenate(np.concatenate(DATA[index], 2), 2), 2) spec = [True] - masterData = sc.Spectrum(hc.HComplexData(DATA, hyper), (filePath, None), FREQ[-1::-1], SW[-1::-1], spec * dim, ref=REF[-1::-1]) + DATA = [x * SCALE for x in DATA] + masterData = sc.Spectrum(hc.HComplexData(DATA, hyper), (filePath, None), FREQ[-1::-1], SW[-1::-1], spec=spec*dim, ref=REF[-1::-1]) masterData.addHistory("Bruker spectrum data loaded from " + filePath) #Try to load main acqus and get some additional pars try: @@ -1441,7 +1461,7 @@ def loadBrukerImaging(filePath): # DATA = np.flipud(raw) DATA = raw.reshape(SIZE) - masterData = sc.Spectrum(DATA, (filePath, None), FREQ, SW, SPEC) + masterData = sc.Spectrum(DATA, (filePath, None), FREQ, SW, spec=SPEC) return masterData def chemGetPars(folder): @@ -1548,9 +1568,9 @@ def loadChemFile(filePath): spec = [False] if sizeTD1 == 1: data = data[0][:] - masterData = sc.Spectrum(data, (filePath, None), [freq * 1e6], [sw], spec) + masterData = sc.Spectrum(data, (filePath, None), [freq*1e6], [sw], specspec) else: - masterData = sc.Spectrum(data, (filePath, None), [freq * 1e6] * 2, [sw1, sw], spec * 2) + masterData = sc.Spectrum(data, (filePath, None), [freq*1e6]*2, [sw1, sw], spec=spec*2) masterData.addHistory("Chemagnetics data loaded from " + filePath) try: if isinstance(pars['na'], list): @@ -1618,7 +1638,7 @@ def loadMagritek(filePath): ComplexData = Data[0:Data.shape[0]:2] - 1j * Data[1:Data.shape[0]:2] ComplexData = ComplexData.reshape((sizeTD1, sizeTD2)) ComplexData[:, 0] *= 2 - masterData = sc.Spectrum(ComplexData, (filePath, None), [freq] * 2, [sw1, sw], [False] * 2, ref=[ref1, ref]) + masterData = sc.Spectrum(ComplexData, (filePath, None), [freq]*2, [sw1, sw], spec=[False]*2, ref=[ref1, ref]) elif Files1D: File = 'data.1d' with open(Dir + os.path.sep + File, 'rb') as f: @@ -1626,7 +1646,7 @@ def loadMagritek(filePath): Data = raw[-2 * sizeTD2::] ComplexData = Data[0:Data.shape[0]:2] - 1j * Data[1:Data.shape[0]:2] ComplexData[0] *= 2 - masterData = sc.Spectrum(ComplexData, (filePath, None), [freq], [sw], [False], ref=[ref]) + masterData = sc.Spectrum(ComplexData, (filePath, None), [freq], [sw], spec=[False], ref=[ref]) try: masterData.metaData['# Scans'] = H['nrScans'] masterData.metaData['Acquisition Time [s]'] = str(int(H['nrPnts']) * float(H['dwellTime']) * 1e-6) @@ -1754,9 +1774,9 @@ def LAST(f, x): return ((x) & (~0 << (8 - f))) elif 'SPE' in TYPE: spec = [True] if NI == 1: - masterData = sc.Spectrum(data, (filePath, None), [0], [SW], spec) + masterData = sc.Spectrum(data, (filePath, None), [0], [SW], spec=spec) else: - masterData = sc.Spectrum(data, (filePath, None), [0, 0], [SW1, SW], spec * 2) + masterData = sc.Spectrum(data, (filePath, None), [0, 0], [SW1, SW], spec=spec*2) masterData.addHistory("SIMPSON data loaded from " + filePath) return masterData @@ -1926,7 +1946,7 @@ def loadJCAMP(filePath): imagDat = imagDat * factor[2] fullData = realDat - 1j * imagDat sw = 1.0 / ((last - first) / (nPoints - 1)) - masterData = sc.Spectrum(fullData, (filePath, None), [freq], [sw], [False]) + masterData = sc.Spectrum(fullData, (filePath, None), [freq], [sw], spec=[False]) elif 'NMRSPECTRUM' in dataType: spectDat = np.array([]) for line in data[spectDataPos[0]:spectDataPos[1] + 1]: @@ -1938,7 +1958,7 @@ def loadJCAMP(filePath): elif Xunit == 'PPM': sw = abs(FirstX - LastX) * freq sw = sw + sw / NPoints - masterData = sc.Spectrum(spectDat, (filePath, None), [freq], [sw], [True], ref=[None]) + masterData = sc.Spectrum(spectDat, (filePath, None), [freq], [sw], spec=[True], ref=[None]) return masterData def saveASCIIFile(filePath, spectrum, axMult=1): @@ -2035,7 +2055,7 @@ def loadAscii(filePath, asciiInfo=None): data = matrix if xaxis is not None: xaxis = [xaxis] - masterData = sc.Spectrum(data, (filePath, asciiInfo), [freq], [sw], [dataSpec], ref=[None], xaxArray=xaxis) + masterData = sc.Spectrum(data, (filePath, asciiInfo), [freq], [sw], spec=[dataSpec], ref=[None], xaxArray=xaxis) elif dataDimension == 2: if dataOrder == 'XRI': data = np.transpose(matrix[:, 1::2] + 1j * matrix[:, 2::2]) @@ -2049,7 +2069,7 @@ def loadAscii(filePath, asciiInfo=None): data = np.transpose(matrix) if xaxis is not None: xaxis = [None, xaxis] - masterData = sc.Spectrum(data, (filePath, asciiInfo), [freq, freq], [1, sw], [False, dataSpec], ref=[None, None], xaxArray=xaxis) + masterData = sc.Spectrum(data, (filePath, asciiInfo), [freq, freq], [1, sw], spec=[False, dataSpec], ref=[None, None], xaxArray=xaxis) else: return masterData.addHistory("ASCII data loaded from " + filePath) @@ -2088,7 +2108,7 @@ def loadMinispec(filePath): if line: temp = np.fromstring(line, sep='\t') totaldata = np.append(totaldata, temp[0] + 1j * temp[1]) - masterData = sc.Spectrum(totaldata, (filePath, None), [0], [sw], [False]) + masterData = sc.Spectrum(totaldata, (filePath, None), [0], [sw], spec=[False]) masterData.addHistory("Minispec data loaded from " + filePath) return masterData @@ -2131,9 +2151,9 @@ def loadBrukerEPR(filePath): data = np.fromfile(f, np.float32, numOfPoints) if dataIs2D: data = np.reshape(data, (numYPoints, numXPoints)) - masterData = sc.Spectrum(data, (filePath, None), [centerField, centerField], [sweepWidth, sweepWidth], [False, True], ref=[None,0]) + masterData = sc.Spectrum(data, (filePath, None), [centerField, centerField], [sweepWidth, sweepWidth], spec=[False, True], ref=[None,0]) else: - masterData = sc.Spectrum(data, (filePath, None), [centerField], [sweepWidth], [True], ref=[0]) + masterData = sc.Spectrum(data, (filePath, None), [centerField], [sweepWidth], spec=[True], ref=[0]) masterData.addHistory("Bruker EPR data loaded from " + filePath) return masterData @@ -2293,7 +2313,7 @@ def loadMestreC(filePath): if dim == 2: for i, _ in enumerate(data): data[i] = data[i].reshape(*points[-1::-1]) - masterData = sc.Spectrum(hc.HComplexData(np.array(data), hyper), (filePath, None), freq, sw, [spec] * dim, ref=ref, dFilter=dFilter[0]) + masterData = sc.Spectrum(hc.HComplexData(np.array(data), hyper), (filePath, None), freq, sw, spec=[spec]*dim, ref=ref, dFilter=dFilter[0]) return masterData def loadDMfit(filePath): @@ -2330,6 +2350,6 @@ def loadDMfit(filePath): center = xaxis[len(xaxis)//2] if center != 0.0: ref = freq - center - masterData = sc.Spectrum(data, (filePath, None), [freq], [sw], [True], ref=[ref], xaxArray=[xaxis]) + masterData = sc.Spectrum(data, (filePath, None), [freq], [sw], spec=[True], ref=[ref], xaxArray=[xaxis]) masterData.addHistory("DMfit data loaded from " + filePath) return masterData diff --git a/src/spectrum.py b/src/spectrum.py index 8b2249c..57ba585 100644 --- a/src/spectrum.py +++ b/src/spectrum.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -43,7 +43,7 @@ class Spectrum(object): The functions for processing are methods of this object. """ - def __init__(self, data, filePath, freq, sw, spec=None, wholeEcho=None, ref=None, xaxArray=None, history=None, metaData=None, name='', dFilter=None): + def __init__(self, data, filePath, freq, sw, spec=None, wholeEcho=None, ref=None, xaxArray=None, customXax=None, history=None, metaData=None, name='', dFilter=None): """ Initializes the Spectrum object. @@ -75,6 +75,9 @@ def __init__(self, data, filePath, freq, sw, spec=None, wholeEcho=None, ref=None The x-axis values per dimension. The list should have the same number of entries as the number of dimensions of data. By default the x-axes are generated based on the sw values. + customXax : list of booleans, optional + Which of the arrays are custom values and have not been automatically generated based on frequency, reference, and spectral width. + By default all of them are False history : list of strings, optional The processing history of the data. By default the history is set to an empty list. @@ -118,6 +121,10 @@ def __init__(self, data, filePath, freq, sw, spec=None, wholeEcho=None, ref=None self.resetXax() else: self.xaxArray = xaxArray + if customXax is not None and len(customXax) == len(self.xaxArray): + self.customXax = customXax + else: + self.customXax = [False] * len(self.xaxArray) if history is None: self.history = [] # list of strings describing all performed operations else: @@ -299,8 +306,10 @@ def resetXax(self, axis=None): if axis is not None: axis = self.checkAxis(axis) val = [axis] + self.customXax[axis] = False else: val = range(self.ndim()) + self.customXax = [False] * self.ndim() for i in val: if self.spec[i] == 0: self.xaxArray[i] = np.arange(self.shape()[i]) / (self.sw[i]) @@ -309,7 +318,7 @@ def resetXax(self, axis=None): if self.ref[i] is not None: self.xaxArray[i] += self.freq[i] - self.ref[i] - def setXax(self, xax, axis=-1): + def setXax(self, xax, axis=-1, custom=True): """ Sets the x-axis of a given dimension. @@ -321,6 +330,9 @@ def setXax(self, xax, axis=-1): axis : int, optional The dimension for which to set the x-axis. By default the last axis is used. + custom : bool, optional + Whether the new x-axis is a custom axis. + True by default Raises ------ @@ -331,11 +343,13 @@ def setXax(self, xax, axis=-1): if len(xax) != self.shape()[axis]: raise SpectrumException("Length of new x-axis does not match length of the data") oldXax = self.xaxArray[axis] + oldCustom = self.customXax[axis] self.xaxArray[axis] = xax + self.customXax[axis] = custom self.addHistory("X-axis of dimension " + str(axis + 1) + " was set to " + str(xax).replace('\n', '')) self.redoList = [] if not self.noUndo: - self.undoList.append(lambda self: self.setXax(oldXax, axis)) + self.undoList.append(lambda self: self.setXax(oldXax, axis, oldCustom)) def insert(self, data, pos, axis=-1): """ @@ -356,7 +370,7 @@ def insert(self, data, pos, axis=-1): if self.noUndo: returnValue = None else: - if np.all(self.data.hyper == data.hyper): # If both sets have same hyper: easy undo can be used + if np.all(self.data.hyper == data.hyper) and not self.customXax[axis]: # If both sets have same hyper and it does not have a custom x-axis: easy undo can be used returnValue = lambda self: self.delete(range(pos, pos + data.shape()[axis]), axis) else: # Otherwise: do a deep copy of the class copyData = copy.deepcopy(self) @@ -395,6 +409,7 @@ def delete(self, pos, axis=-1): raise SpectrumException('Cannot delete all data') self.data = tmpData self.xaxArray[axis] = np.delete(self.xaxArray[axis], pos) + self.customXax[axis] = True if isinstance(pos, np.ndarray): if pos.ndim == 0: pos = int(pos) @@ -650,7 +665,7 @@ def normalize(self, mult, scale=1.0, type=0, axis=-1, select=slice(None)): """ axis = self.checkAxis(axis) try: - self.data *= mult * scale + self.data[select] *= mult * scale except ValueError as error: raise SpectrumException('Normalize: ' + str(error)) if type == 0: @@ -703,22 +718,24 @@ def concatenate(self, axis=-1): By default the last dimension is used. """ axis = self.checkAxis(axis) - splitVal = self.shape()[axis] + splitVal = self.shape()[0] copyData = None - if self.data.isComplex(axis): + if self.data.isComplex(axis) or self.customXax[0] or self.customXax[axis]: if not self.noUndo: copyData = copy.deepcopy(self) - self.data = self.data.real(axis) + if self.data.isComplex(axis): + self.data = self.data.real(axis) invAxis = self.ndim() - axis self.data = self.data.concatenate(axis) self.data.removeDim(invAxis) - self.freq = np.delete(self.freq, axis) - self.sw = np.delete(self.sw, axis) - self.spec = np.delete(self.spec, axis) - self.wholeEcho = np.delete(self.wholeEcho, axis) - self.ref = np.delete(self.ref, axis) - del self.xaxArray[axis] - self.resetXax() + self.freq = np.delete(self.freq, 0) + self.sw = np.delete(self.sw, 0) + self.spec = np.delete(self.spec, 0) + self.wholeEcho = np.delete(self.wholeEcho, 0) + self.ref = np.delete(self.ref, 0) + del self.xaxArray[0] + del self.customXax[0] + self.resetXax(axis) self.addHistory("Concatenated dimension " + str(axis + 1)) self.redoList = [] if not self.noUndo: @@ -741,6 +758,10 @@ def split(self, sections, axis=-1): By default the last dimension is used. """ axis = self.checkAxis(axis) + copyData = None + if self.customXax[axis]: + if not self.noUndo: + copyData = copy.deepcopy(self) self.data = self.data.split(sections, axis) self.data.insertDim(0) self.freq = np.insert(self.freq, 0, self.freq[axis]) @@ -749,12 +770,16 @@ def split(self, sections, axis=-1): self.wholeEcho = np.insert(self.wholeEcho, 0, self.wholeEcho[axis]) self.ref = np.insert(self.ref, 0, self.ref[axis]) self.xaxArray.insert(0, []) + self.customXax.insert(0, False) self.resetXax(0) self.resetXax(axis + 1) self.addHistory("Split dimension " + str(axis + 1) + " into " + str(sections) + " sections") self.redoList = [] if not self.noUndo: - self.undoList.append(lambda self: self.concatenate(axis)) + if copyData is not None: + self.undoList.append(lambda self: self.restoreData(copyData, lambda self: self.split(sections, axis))) + else: + self.undoList.append(lambda self: self.concatenate(axis)) def real(self, axis=-1): """ @@ -1020,6 +1045,7 @@ def matrixManip(self, pos1=None, pos2=None, axis=-1, which=0): self.spec = np.delete(self.spec, axis) self.wholeEcho = np.delete(self.wholeEcho, axis) del self.xaxArray[axis] + del self.customXax[axis] self.data.removeDim(axis) else: self.data = tmpdata[0] @@ -1691,6 +1717,9 @@ def setFreq(self, freq=None, sw=None, axis=-1): By default the last dimension is used. """ axis = self.checkAxis(axis) + copyData = None + if self.customXax[axis] and not self.noUndo: + copyData = copy.deepcopy(self) oldFreq = self.freq[axis] oldSw = self.sw[axis] if freq is None: @@ -1703,7 +1732,10 @@ def setFreq(self, freq=None, sw=None, axis=-1): self.addHistory("Frequency set to " + str(freq * 1e-6) + " MHz and sw set to " + str(sw * 1e-3) + " kHz for dimension " + str(axis + 1)) self.redoList = [] if not self.noUndo: - self.undoList.append(lambda self: self.setFreq(oldFreq, oldSw, axis)) + if copyData is not None: + self.undoList.append(lambda self: self.restoreData(copyData, lambda self: self.setFreq(freq, sw, axis))) + else: + self.undoList.append(lambda self: self.setFreq(oldFreq, oldSw, axis)) def scaleSw(self, scale, axis=-1): """ @@ -1718,13 +1750,19 @@ def scaleSw(self, scale, axis=-1): By default the last dimension is used. """ axis = self.checkAxis(axis) + copyData = None + if self.customXax[axis] and not self.noUndo: + copyData = copy.deepcopy(self) oldSw = self.sw[axis] self.sw[axis] = float(scale) * oldSw self.resetXax(axis) self.addHistory("Sw scaled by factor " + str(scale) + " for dimension " + str(axis + 1)) self.redoList = [] if not self.noUndo: - self.undoList.append(lambda self: self.scaleSw(1.0 / scale, axis)) + if copyData is not None: + self.undoList.append(lambda self: self.restoreData(copyData, lambda self: self.scaleSw(scale, axis))) + else: + self.undoList.append(lambda self: self.scaleSw(1.0 / scale, axis)) def setRef(self, ref=None, axis=-1): """ @@ -1741,6 +1779,9 @@ def setRef(self, ref=None, axis=-1): By default the last dimension is used. """ axis = self.checkAxis(axis) + copyData = None + if self.customXax[axis] and not self.noUndo: + copyData = copy.deepcopy(self) oldRef = self.ref[axis] if ref is None: self.ref[axis] = None @@ -1751,7 +1792,10 @@ def setRef(self, ref=None, axis=-1): self.resetXax(axis) self.redoList = [] if not self.noUndo: - self.undoList.append(lambda self: self.setRef(oldRef, axis)) + if copyData is not None: + self.undoList.append(lambda self: self.restoreData(copyData, lambda self: self.setRef(ref, axis))) + else: + self.undoList.append(lambda self: self.setRef(oldRef, axis)) def regrid(self, limits, numPoints, axis=-1): """ @@ -1900,6 +1944,9 @@ def setSpec(self, val, axis=-1): By default the last dimension is used. """ axis = self.checkAxis(axis) + copyData = None + if self.customXax[axis] and not self.noUndo: + copyData = copy.deepcopy(self) oldVal = self.spec[axis] self.spec[axis] = val self.resetXax(axis) @@ -1909,7 +1956,10 @@ def setSpec(self, val, axis=-1): self.addHistory("Dimension " + str(axis + 1) + " set to spectrum") self.redoList = [] if not self.noUndo: - self.undoList.append(lambda self: self.setSpec(oldVal, axis)) + if copyData is not None: + self.undoList.append(lambda self: self.restoreData(copyData, lambda self: self.setSpec(val, axis))) + else: + self.undoList.append(lambda self: self.setSpec(oldVal, axis)) def swapEcho(self, idx, axis=-1): """ @@ -2415,11 +2465,12 @@ def getSlice(self, axes, locList, stack=None): self.filePath, [self.freq[axis] for axis in axes], [self.sw[axis] for axis in axes], - [self.spec[axis] for axis in axes], - [self.wholeEcho[axis] for axis in axes], - [self.ref[axis] for axis in axes], - [self.xaxArray[axis][stack[i]] for i, axis in enumerate(axes)], - self.history, + spec=[self.spec[axis] for axis in axes], + wholeEcho=[self.wholeEcho[axis] for axis in axes], + ref=[self.ref[axis] for axis in axes], + xaxArray=[self.xaxArray[axis][stack[i]] for i, axis in enumerate(axes)], + customXax=[self.customXax[axis] for axis in axes], + history=self.history, name=self.name)) sliceSpec.noUndo = True return sliceSpec @@ -2445,6 +2496,7 @@ def restoreData(self, copyData, returnValue): self.spec = copyData.spec self.wholeEcho = copyData.wholeEcho self.xaxArray = copyData.xaxArray + self.customXax = copyData.customXax self.ref = copyData.ref self.addHistory("Data was restored to a previous state ") self.redoList = [] diff --git a/src/spectrumFrame.py b/src/spectrumFrame.py index b927e50..7f7ac89 100644 --- a/src/spectrumFrame.py +++ b/src/spectrumFrame.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/ssNake.py b/src/ssNake.py index caa88d3..3a9e1d5 100644 --- a/src/ssNake.py +++ b/src/ssNake.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -21,29 +21,16 @@ EXE = False import sys -if sys.version_info.major == 2: - import sip - sip.setapi('QString', 2) - print('DEPRECATION WARNING: From version 1.4 onwards, python2 is no longer supported. Consider upgrading to python3.') import os import importlib -try: - from PyQt5 import QtGui, QtCore, QtWidgets - QT = 5 -except ImportError: - from PyQt4 import QtGui, QtCore - from PyQt4 import QtGui as QtWidgets - QT = 4 - print('DEPRECATION WARNING: From version 1.4 onwards, PyQt4 is no longer supported. Consider upgrading to PyQt5.') +from PyQt5 import QtGui, QtCore, QtWidgets +QT = 5 + QtCore.pyqtRemoveInputHook() import matplotlib # First import matplotlib and Qt -if QT == 4: - matplotlib.use('Qt4Agg') - from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -else: - matplotlib.use('Qt5Agg') - from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +matplotlib.use('Qt5Agg') +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas import multiprocessing # Create splash window @@ -109,7 +96,7 @@ def import_lib(name, nameAs, className, splashStep): np.set_printoptions(threshold=sys.maxsize) QtCore.QLocale.setDefault(QtCore.QLocale('en_US')) -VERSION = 'v1.3' +VERSION = 'v1.4b' # Required library version NPVERSION = '1.11.0' MPLVERSION = '1.5.0' @@ -230,15 +217,13 @@ def handleCopy(self): canvas = self.mainWindow.tabs.currentWidget().canvas else: canvas = self.mainWindow.canvas - if QT == 5: - screen = self.root.primaryScreen() - pixmap = screen.grabWindow(canvas.winId()) - else: - pixmap = QtGui.QPixmap.grabWidget(canvas) + screen = self.root.primaryScreen() + pixmap = screen.grabWindow(canvas.winId()) QtWidgets.QApplication.clipboard().setPixmap(pixmap) def resetDefaults(self): self.defaultUnits = 1 + self.defaultPrecis = 4 self.defaultShowTitle = True self.defaultPPM = False self.defaultWidth = 1 @@ -344,6 +329,7 @@ def loadDefaults(self): self.defaultDiagonalMult = settings.value("contour/diagonalmult", self.defaultDiagonalMult, float) except TypeError: self.dispMsg("Incorrect value in the config file for the diagonal multiplier") + self.defaultPrecis = settings.value("precision", self.defaultPrecis, int) self.defaultMaximized = settings.value("maximized", self.defaultMaximized, bool) try: self.defaultWidth = settings.value("width", self.defaultWidth, int) @@ -385,6 +371,7 @@ def saveDefaults(self): settings.setValue("plot/ygrid", self.defaultGrids[1]) settings.setValue("plot/zeroscroll", self.defaultZeroScroll) settings.setValue("plot/zoomstep", self.defaultZoomStep) + settings.setValue("precision", self.defaultPrecis) settings.setValue("maximized", self.defaultMaximized) settings.setValue("width", self.defaultWidth) settings.setValue("height", self.defaultHeight) @@ -975,9 +962,9 @@ def menuCheck(self): if type(self.mainWindow) is Main1DWindow: self.menuEnable(True) for act in self.specOnlyList: - act.setEnabled(self.mainWindow.current.spec() == 1) # Only on for spec + act.setEnabled(int(self.mainWindow.current.spec() == 1)) # Only on for spec for act in self.fidOnlyList: - act.setEnabled(self.mainWindow.current.spec() == 0) # Only on for FID + act.setEnabled(int(self.mainWindow.current.spec() == 0)) # Only on for FID #Limit functions based on plot type if type(self.mainWindow.current) == views.CurrentMulti or type(self.mainWindow.current) == views.CurrentStacked or type(self.mainWindow.current) == views.CurrentArrayed: for act in self.Only1DPlot: @@ -1551,10 +1538,10 @@ def dataFromFit(self, data, filePath, freq, sw, spec, wholeEcho, ref, xaxArray, filePath, freq, sw, - spec, - wholeEcho, - ref, - xaxArray, + spec=spec, + wholeEcho=wholeEcho, + ref=ref, + xaxArray=xaxArray, history=['Data obtained from fit'], name=name) masterData.resetXax(axes) @@ -3141,14 +3128,14 @@ def frameEnable(self, enable=True): def setLabels(self, position): if len(position) > 3: self.ypos.setText(str(position[3])) - self.deltaypoint.setText('%#.4g' % np.abs(self.oldy - position[4])) - self.ypoint.setText('%#.4g' % position[4]) + self.deltaypoint.setText(('%#.'+str(self.father.father.defaultPrecis)+'g') % np.abs(self.oldy - position[4])) + self.ypoint.setText(('%#.'+str(self.father.father.defaultPrecis)+'g') % position[4]) self.oldy = position[4] - self.deltaxpoint.setText('%#.4g' % np.abs(self.oldx - position[1])) - self.deltaamppoint.setText('%#.4g' % np.abs(self.oldamp - position[2])) + self.deltaxpoint.setText(('%#.'+str(self.father.father.defaultPrecis)+'g') % np.abs(self.oldx - position[1])) + self.deltaamppoint.setText(('%#.'+str(self.father.father.defaultPrecis)+'g') % np.abs(self.oldamp - position[2])) self.xpos.setText(str(position[0])) - self.xpoint.setText('%#.4g' % position[1]) - self.amppoint.setText('%#.4g' % position[2]) + self.xpoint.setText(('%#.'+str(self.father.father.defaultPrecis)+'g') % position[1]) + self.amppoint.setText(('%#.'+str(self.father.father.defaultPrecis)+'g') % position[2]) self.oldx = position[1] self.oldamp = position[2] @@ -3832,7 +3819,7 @@ def checkEval(self, key): def setLorGauss(self,value, type, *args): #type: 'lor' or 'gauss' if self.available: - self.entries[type][0].setText('%.4g' % (float(value) * self.maximum / self.RESOLUTION)) + self.entries[type][0].setText(('%.'+str(self.father.father.defaultPrecis)+'g') % (float(value) * self.maximum / self.RESOLUTION)) if not self.ticks[type].isChecked(): self.ticks[type].setChecked(1) self.apodPreview() @@ -3860,9 +3847,9 @@ def stepLB(self, incr, type): self.ticks[type].setChecked(1) lor, gauss, cos2, cos2Ph, hamming, shift, shifting, shiftingAxis = self.checkInput() if type == 'lor': - self.entries[type][0].setText('%.4g' % (lor + step)) + self.entries[type][0].setText(('%.'+str(self.father.father.defaultPrecis)+'g') % (lor + step)) elif type == 'gauss': - self.entries[type][0].setText('%.4g' % (gauss + step)) + self.entries[type][0].setText(('%.'+str(self.father.father.defaultPrecis)+'g') % (gauss + step)) self.apodPreview() def checkInput(self): @@ -6974,6 +6961,10 @@ def __init__(self, parent): self.showTitleCheck = QtWidgets.QCheckBox("Show title in plot") self.showTitleCheck.setChecked(self.father.defaultShowTitle) grid2.addWidget(self.showTitleCheck, 15, 0, 1, 2) + grid2.addWidget(QtWidgets.QLabel("Significant digits:"), 16, 0) + self.precisSpinBox = wc.SsnakeSpinBox() + self.precisSpinBox.setValue(self.father.defaultPrecis) + grid2.addWidget(self.precisSpinBox, 16, 1) # grid3 definitions grid3.addWidget(QtWidgets.QLabel("Colourmap:"), 0, 0) self.cmEntry = QtWidgets.QComboBox(self) @@ -7065,6 +7056,7 @@ def applyAndClose(self, *args): self.father.defaultGrids[1] = self.ygridCheck.isChecked() self.father.defaultZeroScroll = self.zeroScrollCheck.isChecked() self.father.defaultShowTitle = self.showTitleCheck.isChecked() + self.father.defaultPrecis = self.precisSpinBox.value() self.father.defaultZoomStep = self.ZoomStepSpinBox.value() self.father.defaultColorMap = self.cmEntry.currentText() self.father.defaultContourConst = self.constColorCheck.isChecked() @@ -7181,7 +7173,7 @@ def __init__(self, parent): pythonVersion = pythonVersion[:pythonVersion.index(' ')] from scipy import __version__ as scipyVersion self.text.setText('

ssNake ' + VERSION + '

' + - '

Copyright (©) 2016–2020 Bas van Meerten & Wouter Franssen

' + '

Email: ssnake@science.ru.nl

' + + '

Copyright (©) 2016–2021 Bas van Meerten & Wouter Franssen

' + '

Email: ssnake@science.ru.nl

' + '

Publication: https://doi.org/10.1016/j.jmr.2019.02.006

' + 'Library versions:
Python ' + pythonVersion + '
numpy ' + np.__version__ + '
SciPy ' + scipyVersion + @@ -7350,25 +7342,25 @@ def shiftCalc(self, Type): raise SsnakeException("Shift Conversion: Invalid input in Hertzfeld-Berger Convention") Results = func.shiftConversion(Values, Type) # Do the actual conversion # Standard convention - self.D11.setText('%#.4g' % Results[0][0]) - self.D22.setText('%#.4g' % Results[0][1]) - self.D33.setText('%#.4g' % Results[0][2]) + self.D11.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[0][0]) + self.D22.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[0][1]) + self.D33.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[0][2]) # Convert to haeberlen convention and xxyyzz - self.dxx.setText('%#.4g' % Results[1][0]) - self.dyy.setText('%#.4g' % Results[1][1]) - self.dzz.setText('%#.4g' % Results[1][2]) + self.dxx.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[1][0]) + self.dyy.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[1][1]) + self.dzz.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[1][2]) # Haeberlen def - self.diso.setText('%#.4g' % Results[2][0]) - self.daniso.setText('%#.4g' % Results[2][1]) + self.diso.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[2][0]) + self.daniso.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[2][1]) try: # If a number - self.eta.setText('%#.4g' % Results[2][2]) + self.eta.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[2][2]) except Exception: self.eta.setText('ND') # Convert to Herzfeld-Berger Convention - self.hbdiso.setText('%#.4g' % Results[3][0]) - self.hbdaniso.setText('%#.4g' % Results[3][1]) + self.hbdiso.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[3][0]) + self.hbdaniso.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[3][1]) try: - self.hbskew.setText('%#.4g' % Results[3][2]) + self.hbskew.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Results[3][2]) except Exception: self.hbskew.setText('ND') @@ -7460,7 +7452,9 @@ def __init__(self, parent): self.grid.addWidget(self.WqGroup, 7, 0, 1, 2) self.fieldGroup = QtWidgets.QGroupBox('Field Gradients:') self.fieldFrame = QtWidgets.QGridLayout() - Vxxlabel = wc.QLabel('Vxx [V/m2]:') + # Vxx and Vyy labels interchanged to follow the Quad+CSa fitting deifnitions + # WF: 2021-01 + Vxxlabel = wc.QLabel('Vyy [V/m2]:') self.fieldFrame.addWidget(Vxxlabel, 9, 1) VGO = QtWidgets.QPushButton("Go") self.fieldFrame.addWidget(VGO, 10, 0) @@ -7468,7 +7462,7 @@ def __init__(self, parent): self.Vxx = wc.QLineEdit("ND") self.Vxx.setMinimumWidth(100) self.fieldFrame.addWidget(self.Vxx, 10, 1) - Vyylabel = wc.QLabel('Vyy [V/m2]:') + Vyylabel = wc.QLabel('Vxx [V/m2]:') self.fieldFrame.addWidget(Vyylabel, 9, 2) self.Vyy = wc.QLineEdit("ND") self.Vyy.setMinimumWidth(100) @@ -7534,18 +7528,18 @@ def quadCalc(self, Type): if Result[0][1] is None: self.Eta.setText('ND') else: - self.Eta.setText('%#.4g' % Result[0][1]) - self.Cq.setText('%#.4g' % Result[0][0]) - self.Wq.setText('%#.4g' % Result[1][0]) + self.Eta.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Result[0][1]) + self.Cq.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Result[0][0]) + self.Wq.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Result[1][0]) if Result[2][0] is None: self.Moment.setText('ND') self.Vxx.setText('ND') self.Vyy.setText('ND') self.Vzz.setText('ND') else: - self.Vxx.setText('%#.4g' % Result[2][0]) - self.Vyy.setText('%#.4g' % Result[2][1]) - self.Vzz.setText('%#.4g' % Result[2][2]) + self.Vxx.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Result[2][0]) + self.Vyy.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Result[2][1]) + self.Vzz.setText(('%#.'+str(self.father.defaultPrecis)+'g') % Result[2][2]) def valueReset(self): # Resets all the boxes to 0 self.Cq.setText('0') @@ -8077,10 +8071,7 @@ def checkVersions(): libs = [['numpy', np.__version__, NPVERSION], ['matplotlib', matplotlib.__version__, MPLVERSION], ['scipy', scipyVersion, SPVERSION]] - if sys.version_info.major == 3: - libs.append(['python', str(sys.version_info.major) + '.' + str(sys.version_info.minor), PY3VERSION]) - elif sys.version_info.major == 2: - libs.append(['python', str(sys.version_info.major) + '.' + str(sys.version_info.minor), PY2VERSION]) + libs.append(['python', str(sys.version_info.major) + '.' + str(sys.version_info.minor), PY3VERSION]) messages = [] error = False for elem in libs: diff --git a/src/updateWindow.py b/src/updateWindow.py index addaa16..aef8db3 100644 --- a/src/updateWindow.py +++ b/src/updateWindow.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # diff --git a/src/views.py b/src/views.py index 0798343..007bb3d 100644 --- a/src/views.py +++ b/src/views.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -409,11 +409,13 @@ def fixAxes(self, axes): list: The new axis list """ - if len(axes) != self.NDIM_PLOT: + if len(axes) < self.NDIM_PLOT: fullAxes = np.arange(self.data.ndim()) fullAxes = np.delete(fullAxes, axes) diff = self.NDIM_PLOT - len(axes) return np.append(fullAxes[-diff:], axes[-self.NDIM_PLOT:]) + elif len(axes) > self.NDIM_PLOT: + return axes[-self.NDIM_PLOT:] return axes def rename(self, name): @@ -3246,8 +3248,11 @@ def altScroll(self, event): def plotReset_x_ax(self): if not self.line_xProjData: return - minz = min([min(i) for i in self.line_xProjData]) - maxz = max([max(i) for i in self.line_xProjData]) + minz = min([np.nanmin(i) for i in self.line_xProjData]) + maxz = max([np.nanmax(i) for i in self.line_xProjData]) + if np.isnan(minz): # If there are no acceptable values minz and maxz will be NaN + minz = -0.01 + maxz = 0.01 if minz == maxz: # Prevents setting the limits equal minz -= 0.01 maxz += 0.01 @@ -3260,8 +3265,11 @@ def plotReset_x_ax(self): def plotReset_y_ax(self): if not self.line_yProjData: return - minz = min([min(i) for i in self.line_yProjData]) - maxz = max([max(i) for i in self.line_yProjData]) + minz = min([np.nanmin(i) for i in self.line_yProjData]) + maxz = max([np.nanmax(i) for i in self.line_yProjData]) + if np.isnan(minz): # If there are no acceptable values minz and maxz will be NaN + minz = -0.01 + maxz = 0.01 if minz == maxz: # Prevents setting the limits equal minz -= 0.01 maxz += 0.01 @@ -3571,16 +3579,17 @@ def plotContour(self, line_xdata, line_ydata, line_zdata, color=None, updateOnly PlotNegative = True vmax = max(np.abs(self.viewSettings["minLevels"] * self.differ), np.abs(self.viewSettings["maxLevels"] * self.differ)) vmin = -vmax - if color is not None: - if PlotPositive: - self.ax.contour(X[YposMax[:, None], XposMax], Y[YposMax[:, None], XposMax], line_zdata[YposMax[:, None], XposMax], colors=[color[0]], levels=contourLevels, vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') - if PlotNegative: - self.ax.contour(X[YposMin[:, None], XposMin], Y[YposMin[:, None], XposMin], line_zdata[YposMin[:, None], XposMin], colors=[color[1]], levels=-contourLevels[::-1], vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') - else: - if PlotPositive: - self.ax.contour(X[YposMax[:, None], XposMax], Y[YposMax[:, None], XposMax], line_zdata[YposMax[:, None], XposMax], cmap=get_cmap(self.viewSettings["colorMap"]), levels=contourLevels, vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') - if PlotNegative: - self.ax.contour(X[YposMin[:, None], XposMin], Y[YposMin[:, None], XposMin], line_zdata[YposMin[:, None], XposMin], cmap=get_cmap(self.viewSettings["colorMap"]), levels=-contourLevels[::-1], vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') + if len(line_xdata) > 1 and len(line_ydata) > 1: # Do not plot if too few points + if color is not None: + if PlotPositive: + self.ax.contour(X[YposMax[:, None], XposMax], Y[YposMax[:, None], XposMax], line_zdata[YposMax[:, None], XposMax], colors=[color[0]], levels=contourLevels, vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') + if PlotNegative: + self.ax.contour(X[YposMin[:, None], XposMin], Y[YposMin[:, None], XposMin], line_zdata[YposMin[:, None], XposMin], colors=[color[1]], levels=-contourLevels[::-1], vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') + else: + if PlotPositive: + self.ax.contour(X[YposMax[:, None], XposMax], Y[YposMax[:, None], XposMax], line_zdata[YposMax[:, None], XposMax], cmap=get_cmap(self.viewSettings["colorMap"]), levels=contourLevels, vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') + if PlotNegative: + self.ax.contour(X[YposMin[:, None], XposMin], Y[YposMin[:, None], XposMin], line_zdata[YposMin[:, None], XposMin], cmap=get_cmap(self.viewSettings["colorMap"]), levels=-contourLevels[::-1], vmax=vmax, vmin=vmin, linewidths=self.viewSettings["linewidth"], linestyles='solid') self.setTicks() if updateOnly: self.canvas.draw() @@ -3802,7 +3811,11 @@ def plotContour(self, line_xdata, line_ydata, line_zdata, color=None, updateOnly if updateOnly: # Set some extra stuff if only the contour plot needs updating del self.ax.collections[:] # Clear all plot collections - self.ax.imshow(np.flipud(line_zdata), extent=[line_xdata[0],line_xdata[-1],line_ydata[0],line_ydata[-1]],aspect='auto',cmap=get_cmap(self.viewSettings["pColorMap"])) + vmax = np.max(np.abs(line_zdata)) + vmin = -vmax + + self.ax.imshow(np.flipud(line_zdata), extent=[line_xdata[0],line_xdata[-1],line_ydata[0],line_ydata[-1]], + aspect='auto',cmap=get_cmap(self.viewSettings["pColorMap"]),vmax=vmax,vmin=vmin,interpolation='hanning') self.setTicks() if updateOnly: self.canvas.draw() diff --git a/src/widgetClasses.py b/src/widgetClasses.py index b6777c3..2bc36ca 100644 --- a/src/widgetClasses.py +++ b/src/widgetClasses.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright 2016 - 2020 Bas van Meerten and Wouter Franssen +# Copyright 2016 - 2021 Bas van Meerten and Wouter Franssen # This file is part of ssNake. # @@ -20,7 +20,7 @@ import os import sys from safeEval import safeEval -from ssNake import QtGui, QtCore, QtWidgets, QT +from ssNake import QtGui, QtCore, QtWidgets class SsnakeTabs(QtWidgets.QTabWidget): """ @@ -68,10 +68,7 @@ def __init__(self, parent): self.customContextMenuRequested.connect(self.openMenu) self.setModel(self.dirmodel) self.setRootIndex(self.dirmodel.index('')) - if QT == 4: - self.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) - elif QT == 5: - self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) + self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) self.header().setStretchLastSection(False) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) #self.setToolTip(index.model()->data(index,Qt:isplayRole).toString()); @@ -212,10 +209,7 @@ def wheelEvent(self, event): event : QMouseEvent The mouse event. """ - if QT == 4: - delta = event.delta() - else: - delta = event.angleDelta().y() + delta = event.angleDelta().y() step = self.singleStep() * 3 if QtWidgets.qApp.keyboardModifiers() & QtCore.Qt.ControlModifier and QtWidgets.qApp.keyboardModifiers() & QtCore.Qt.ShiftModifier: step *= 1000 @@ -564,10 +558,7 @@ def wheelEvent(self, event): event : QWheelEvent The event on the spinbox. """ - if QT == 4: - delta = event.delta() - else: - delta = event.angleDelta().y() + event.angleDelta().x() + delta = event.angleDelta().y() + event.angleDelta().x() step = 1 if delta > 0: self.stepBy(step) @@ -615,10 +606,7 @@ def wheelEvent(self, event): event : QWheelEvent The event on the spinbox. """ - if QT == 4: - delta = event.delta() - else: - delta = event.angleDelta().y() + event.angleDelta().x() + delta = event.angleDelta().y() + event.angleDelta().x() step = 1 if delta > 0: self.stepBy(step) @@ -662,10 +650,7 @@ def wheelEvent(self, event): event : QWheelEvent The event on the spinbox. """ - if QT == 4: - delta = event.delta() - else: - delta = event.angleDelta().y() + event.angleDelta().x() + delta = event.angleDelta().y() + event.angleDelta().x() step = 1 if delta > 0: self.stepBy(step)