From f538fb6cf7c744253c742199fa6aa67f0f311d87 Mon Sep 17 00:00:00 2001 From: Flackermann Date: Mon, 31 Aug 2020 13:43:22 +0200 Subject: [PATCH] release 0.0.3 --- build/lib/eNMRly/MOSY.py | 352 ------- build/lib/eNMRly/Measurement/Emma.py | 179 ---- build/lib/eNMRly/Measurement/Juergen1.py | 203 ---- build/lib/eNMRly/Measurement/Pavel.py | 122 --- build/lib/eNMRly/Measurement/Simulated.py | 36 - build/lib/eNMRly/Measurement/__init__.py | 4 - build/lib/eNMRly/Measurement/base.py | 305 ------ build/lib/eNMRly/Measurement/eNMR_Methods.py | 957 ------------------ build/lib/eNMRly/Measurement/tools.py | 49 - build/lib/eNMRly/Phasefitting.py | 646 ------------ build/lib/eNMRly/__init__.py | 6 - build/lib/eNMRpy/MOSY.py | 11 +- build/lib/eNMRpy/Measurement/Flo.py | 153 +++ build/lib/eNMRpy/Measurement/Pavel.py | 11 +- build/lib/eNMRpy/Measurement/__init__.py | 5 +- build/lib/eNMRpy/Measurement/base.py | 2 +- build/lib/eNMRpy/Measurement/eNMR_Methods.py | 205 +++- build/lib/eNMRpy/Phasefitting.py | 77 +- build/lib/eNMRpy/__init__.py | 8 +- build/lib/eNMRpy/tools.py | 41 +- dist/eNMRpy-0.0.2.tar.gz | Bin 28972 -> 0 bytes ...-any.whl => eNMRpy-0.0.3-py3-none-any.whl} | Bin 70176 -> 73491 bytes dist/eNMRpy-0.0.3.tar.gz | Bin 0 -> 31054 bytes eNMRpy.egg-info/PKG-INFO | 4 +- eNMRpy.egg-info/SOURCES.txt | 1 + eNMRpy.egg-info/requires.txt | 2 +- 26 files changed, 408 insertions(+), 2971 deletions(-) delete mode 100644 build/lib/eNMRly/MOSY.py delete mode 100644 build/lib/eNMRly/Measurement/Emma.py delete mode 100644 build/lib/eNMRly/Measurement/Juergen1.py delete mode 100644 build/lib/eNMRly/Measurement/Pavel.py delete mode 100644 build/lib/eNMRly/Measurement/Simulated.py delete mode 100644 build/lib/eNMRly/Measurement/__init__.py delete mode 100644 build/lib/eNMRly/Measurement/base.py delete mode 100644 build/lib/eNMRly/Measurement/eNMR_Methods.py delete mode 100644 build/lib/eNMRly/Measurement/tools.py delete mode 100644 build/lib/eNMRly/Phasefitting.py delete mode 100644 build/lib/eNMRly/__init__.py create mode 100644 build/lib/eNMRpy/Measurement/Flo.py delete mode 100644 dist/eNMRpy-0.0.2.tar.gz rename dist/{eNMRpy-0.0.2-py3-none-any.whl => eNMRpy-0.0.3-py3-none-any.whl} (52%) create mode 100644 dist/eNMRpy-0.0.3.tar.gz diff --git a/build/lib/eNMRly/MOSY.py b/build/lib/eNMRly/MOSY.py deleted file mode 100644 index 0cacdad..0000000 --- a/build/lib/eNMRly/MOSY.py +++ /dev/null @@ -1,352 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd - -class MOSY(object): - """ - takes an eNMR Measurement obj and yields as MOSY object for MOSY processing and depiction. - the Measurement object needs to be processed (fouriertransformed in F2) before passing it to this class - """ - def __init__(self, obj): - self.obj = obj - self.eNMRraw = obj.eNMRraw - self.eNMRraw['data'] = None - self.eNMRraw['fid'] = None - for row in range(len(obj.data[:,0])): - self.eNMRraw.set_value(row, 'data', pd.Series(obj.data[row,:])) - #self.eNMRraw.set_value(row, 'fid', pd.Series(obj.fid[row,:])) - data_sorted = np.array(obj.eNMRraw['data'].tolist()) - self.data = data_sorted.astype('complex').copy() - self.TD1 = len(self.data[:,0]) - self.mscale = None - self.mX = None - self.mY = None -# self.mZ = None - - def zerofilling_old(self, n=128, dimension=0): - """ - n: number of total datapoints along the F1-dimension (Voltage) - dimension 0 = F1 - dimension 1 = F2 - """ - if dimension == 0: - self.data = np.concatenate((self.data, - [[0 for x in range(len(self.data[0,:]))] for y in range(n-len(self.data[:,0]))])) - if dimension == 1: - self.data = np.concatenate((self.data, - [[0 for x in range(n-len(self.data[0,:]))] for y in range(len(self.data[:,0]))]), axis=1) - print('got it!') - - def zerofilling(self, n=128, dimension=0): - """ - n: number of total datapoints along the F1-dimension (Voltage) - dimension 0 = F1 - dimension 1 = F2 - """ - print('zero filling started!') - - if dimension == 0: - old_shape = self.data.shape - new_shape = old_shape - new_shape = n, old_shape[1] - print(old_shape, new_shape) - data_new = np.zeros(new_shape, dtype=complex) - for i in range(old_shape[0]): - data_new[i,:] = self.data[i,:] - self.data = data_new - del(data_new) - - if dimension == 1: - - old_shape = self.data.shape - new_shape = old_shape - new_shape = old_shape[0], n - print(old_shape, new_shape) - data_new = np.zeros(new_shape, dtype=complex) - for i in range(old_shape[1]): - data_new[:,i] = self.data[:,i] - self.data = data_new - del(data_new) - - print('zero filling finished!') - - def fft_F1 (self): - """ - Fourier Transformation from nmrglue.proc_base.fft) - along the F1 dimension - """ - - from nmrglue.proc_base import fft - - #data_temp = np.zeros(self.data.shape) - for n in range(len(self.data[0,:])): - self.data[:,n] = fft(self.data[:,n]) - print("done") - - def calc_MOSY(self, u_max = None, n_zf=2**12, mobility_scale=True, include_0V=True, electrode_distance=2.2e-2, old_zf=False): - - ''' - Calculates the MOSY according to the States-Haberkorn-Ruben method described for MOSY applications by AUTOR NENNEN! - - u_max: - maximum Voltage to be used for processing. - n_zf: - number of zerofilling for the F1 dimension - mobility_scale: - prints the converted mobilty y-scale if True - include_0V: - include the 0V spectrum or not. - ''' - x = self.obj._x_axis - - if u_max is None: - u_max = 10000 - - if include_0V: - positive = self.eNMRraw[(self.eNMRraw[x] >= 0) - &(self.eNMRraw[x] <= u_max)].sort_values(x) - negative = self.eNMRraw[(self.eNMRraw[x] <= 0) - &(self.eNMRraw[x] >= -u_max)].sort_values(x, ascending=False) - else: - positive = self.eNMRraw[self.eNMRraw[x] >0].sort_values(x) - negative = self.eNMRraw[self.eNMRraw[x] <0].sort_values(x, ascending=False) - - - #dataset conversion for the SHR-method - #if include_0V: - #positive = self.eNMRraw[(self.eNMRraw['U / [V]'] >= 0) - #&(self.eNMRraw['U / [V]'] <= u_max)].sort_values('U / [V]') - #negative = self.eNMRraw[(self.eNMRraw['U / [V]'] <= 0) - #&(self.eNMRraw['U / [V]'] >= -u_max)].sort_values('U / [V]', ascending=False) - #else: - #positive = self.eNMRraw[self.eNMRraw['U / [V]'] >0].sort_values('U / [V]') - #negative = self.eNMRraw[self.eNMRraw['U / [V]'] <0].sort_values('U / [V]', ascending=False) - - SHR_real = np.zeros((len(positive['data']), len(positive['data'].iloc[0]))) - SHR_imag = np.zeros((len(positive['data']), len(positive['data'].iloc[0]))) - - for n in range(1, len(positive['data'])): - SHR_real[n,:] = positive['data'].iloc[n].real + negative['data'].iloc[n].real - SHR_imag[n,:] = positive['data'].iloc[n].imag - negative['data'].iloc[n].imag - - SHR = SHR_real + SHR_imag*1j - del(SHR_real) - del(SHR_imag) - self.data = SHR - del(SHR) - if old_zf: - self.zerofilling_old(n=n_zf) - else: - self.zerofilling(n=n_zf) - self.fft_F1() - - X = np.array([self.obj.ppm for y in range(len(self.data[:,0]))]) - Y = np.array([[y for x in range(len(self.data[0,:]))] for y in range(len(self.data[:,0]))]) - - Y = ((0.5*n_zf-Y)*self.TD1/n_zf*360)/self.TD1 # conversion to ° phasechange per Uink - Y = Y/self.obj.uInk # ° phasechange per Volt - - if mobility_scale: - #Y = (Y*self.obj.d)/(self.obj.gamma*self.obj.delta*self.obj.Delta*self.obj.g) # old version with self.d - Y = (Y*electrode_distance)/(self.obj.gamma*self.obj.delta*self.obj.Delta*self.obj.g) - - self.mX = X - del(X) - self.mY = -Y - del(Y) - - - def plot_MOSY(self, xlim=None, ylim=(-1e-8, 1e-8), yscale='linear', - save=False, savepath='', - tight_layout=False, dpi=300, y_autoscale=True, - h_ratio=7, w_ratio=8, figsize=(5,5), - latex_backend=False, hspace=0, wspace=0, - **kwargs): - - from matplotlib import gridspec - """ - yscale: - .. ACCEPTS: [ 'linear' | 'log' | 'symlog' | 'logit' | ... ] - **kwargs: - are passed to the .contour() - for example: - levels: list of values to be drawn as height lines - - :returns: figure - """ - fig= plt.figure(figsize=figsize) - - gs = gridspec.GridSpec(2, 2, width_ratios=[1, w_ratio], height_ratios=[1,h_ratio]) - - mitte = fig.add_subplot(gs[3]) - oben = fig.add_subplot(gs[1], sharex=mitte, frameon=False) - links = fig.add_subplot(gs[2], sharey=mitte, frameon=False) - - #fig.subplots_adjust(hspace=0, wspace=0) - - mitte.yaxis.tick_right() - mitte.contour(self.mX, self.mY, self.data.real, **kwargs) - mitte.set_ylim(ylim) - mitte.set_xlim(xlim) - links.set_xticks([]) - - # calculation and plotting of the projections taking the maxima - links.plot(np.amax(self.data, axis=1), self.mY[:,0], 'k') - links.set_xlim(links.get_xlim()[::-1]) - oben.plot(self.mX[0], np.amax(self.data, axis=0), 'k') - - #axis format - oben.tick_params(bottom='off') - links.tick_params(left='off') - oben.set_yticks([]) - plt.setp(oben.get_xticklabels(), visible=False); - plt.setp(links.get_yticklabels(), visible=False); - plt.setp(links.get_yaxis(), visible=False); - if latex_backend: - mitte.set_ylabel(r'$\mu\;/\;(\textrm{m}^2 \textrm{V}^{-1} \textrm{s}^{-1})$') - else: - mitte.set_ylabel(r'$\mu$ / (m$^2$V$^{-1}$s$^{-1})$') - mitte.yaxis.set_label_position("right") - mitte.set_xlabel(r'$\delta$ / ppm') - mitte.set_yscale(yscale) - mitte.ticklabel_format(axis='y', style='sci', scilimits=(0,2)) - mitte.grid() - - fig.subplots_adjust(hspace=hspace, wspace=wspace) - - def autoscale_y(bx, margin=0.1): - """This function rescales the y-axis based on the data that is visible given the current xlim of the axis. - ax -- a matplotlib axes object - margin -- the fraction of the total height of the y-data to pad the upper and lower ylims""" - - def get_bottom_top(line): - xd = line.get_xdata() - yd = line.get_ydata() - lo,hi = bx.get_xlim() - try: - y_displayed = yd[((xd>lo) & (xdhi))] - h = np.max(y_displayed) - np.min(y_displayed) - - bot = np.min(y_displayed)#-margin*h - top = np.max(y_displayed)#+margin*h - print(bot, top, h) - return bot,top - - lines = bx.get_lines() - bot,top = np.inf, -np.inf - - for line in lines: - new_bot, new_top = get_bottom_top(line) - if new_bot < bot: bot = new_bot - if new_top > top: top = new_top - - bx.set_ylim(bot,top) - - if y_autoscale: - autoscale_y(oben, xlim) - - if tight_layout: - fig.tight_layout() - - if save: - fig.savefig(savepath, dpi=dpi) - - return fig - - def plot_slices_F1(self, ppm, xlim=None, scaling=None, normalize=False, vline_0=False, annotate=False, annotate_pos=None, legend_loc=None, latex_backend=False, colors=None, figsize=None): - """ - plots slices in F1-direction - - ppm: - list of chemical shift-slices to be plotted - xmin, xmax: - limits for the x-axis - scaling: - list of scaling factors for spectra to adjust intensities - normalize: - normalization of each peak to 1 - vline_0: - prints a vertical line at µ = 0 - annotate: - automatically annotates the maxima of the slices and displays the respective mobility - colors: - list of colors/colorcodes as strings - - :returns: figure - """ - - fig = plt.figure(figsize=figsize) - ax = fig.add_subplot(111) - # getting the standard color cycle - prop_cycle = plt.rcParams['axes.prop_cycle'] - if colors is None: - colors = prop_cycle.by_key()['color'] - - if scaling is None: - scaling = np.ones((len(ppm))) - i=0 - # setting the initial textposition depending on the number of slices - xpostext = 0.5-((len(ppm)-1)*0.1-0.1) - if xpostext < 0.2: - xpostext = 0.2 - ypostext = 0.2 - - for n in ppm: - pos = np.searchsorted(self.obj.ppm, n) - x = self.mY[:,pos] - if normalize: - y = scaling[i]*self.data[:,pos].real/self.data[:,pos].max() - ax.plot(x, y, label=r'$\delta=%.2f$'%n, c=colors[i]) - ax.set_ylabel('normalized intensity') - else: - y = scaling[i]*self.data[:,pos].real - ax.plot(x, y, label=r'$\delta=%.2f$'%n, c=colors[i]) - ax.set_ylabel('intensity / a.u.') - - if annotate: - xmax = x[np.argmax(y)] - ymax = y.max() - text = "$\mu$=%.2E"%(xmax) - bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72) - arrowprops=dict(arrowstyle="->", color=colors[i])#, connectionstyle="angle,angleA=0,angleB=60") - kw = dict(xycoords='data',textcoords="axes fraction", - arrowprops=arrowprops, bbox=bbox_props, ha="right", va="top") - if type(annotate_pos) == list: - xpostext, ypostext = annotate_pos[i] - elif type(annotate_pos) == tuple: - xpostext += annotate[0] - ypostext += annotate[1] - ax.annotate(text, xy=(xmax, ymax), xytext=(xpostext,ypostext), **kw) - #placing of the textboxes - if xpostext >= 1: - xpostext = 0.2 - ypostext += 0.1 - else: - xpostext += 0.2 - i += 1 - - if vline_0: - ax.vlines(0, *ax.get_ylim(), linestyles='dotted') - if latex_backend: - ax.set_xlabel('$\mu$\;/\;(m$^2$V$^{-1}$s$^{-1})$') - else: - ax.set_xlabel('$\mu$ / (m$^2$V$^{-1}$s$^{-1})$') - ax.set_xlim(xlim) - ax.legend(loc=legend_loc) - ax.ticklabel_format(style='sci', scilimits=(0,3)) - return fig - - def export_data(self, path): - """ - saves the X, Y and Z-data to 3 textfiles in order to be used with other programs - """ - pX = path+'_X' - pY = path+'_Y' - pZ = path+'_Z' - np.savetxt(pX, self.mX, delimiter=',') - np.savetxt(pY, self.mY, delimiter=',') - np.savetxt(pZ, self.data, delimiter=',') - diff --git a/build/lib/eNMRly/Measurement/Emma.py b/build/lib/eNMRly/Measurement/Emma.py deleted file mode 100644 index 5528e0c..0000000 --- a/build/lib/eNMRly/Measurement/Emma.py +++ /dev/null @@ -1,179 +0,0 @@ -## coding: utf-8 -# This is the eNMR class. THE library for the evaluation of bruker-eNMR-spectra based on the VC-PowerSource of the -# Schönhoff working group. -# It works with the volt-increment method which calculates the respective voltage with the VC-list -# Further Implementation can be asked for at f_schm52@wwu.de - - - -class eNMR_Emma(eNMR_Measurement): - #''' - #This is the subsubclass of Masurement() and subclass of eNMR_Methods specialised to process data obtained from the experimental Schönhoff set-up - - #path: - #relative or absolute path to the measurements folder - #measurement: - #the to the experiment corresponding EXPNO - #alias: - #Here you can place an individual name relevant for plotting. If None, the path is taken instead. - #Uink: - #voltage increment. Usually extracted from the title file if defined with e.g. "Uink = 10V" - #If Uink cannot be found or is wrong it can be entered manually during the data import. - #The voltage list is calculated from the voltage increment and the vc list when the incrementation loop is used in the pulse program - #dependency: - #'U': voltage dependent eNMR measurement - #'G': fieldgradient dependent eNMR measurement - - #linebroadening: - #setting a standard-value for the linebroadening. - #''' - def __init__(self, path, expno, Uink=None, dependency="U", alias=None, linebroadening=0.5, electrode_distance=2.2e-2): - Measurement.__init__(self, path, expno, linebroadening=linebroadening, alias=alias) - self.dependency = dependency.upper() - - self._x_axis = {"U": "U / [V]", - "G": "g in T/m", - "I": "I / mA", - 'RI': 'RI / V' - }[self.dependency.upper()] - - #self._x_axis = {"G": "g in T/m", - #"U": "U / [V]"}[self.dependency.upper()] - - self.difflist = pd.read_csv(self.dateipfad+"/difflist", - names=["g in T/m"])*0.01 - - self.vcList = pd.DataFrame() - - if self.dic['acqus']['PULPROG'][-3:] == 'var': - polarity = 1 - print('this is a regular measurement! (non-_pol)') - elif self.dic['acqus']['PULPROG'][-3:] == 'pol': - polarity = -1 - print('this is a _pol-Measurement!') - else: - print("no var or pol PULPROG") - - if dependency.upper() == "U": - try: - # takes the title page to extract the volt increment - title = open(self.dateipfad+"/pdata/1/title").read() - # gets the voltage increment using a regular expression - #uimport = findall('[U|u]in[k|c]\s*=?\s*\d+', title)[0] - uimport = findall('[U|u]in[k|c]\s*=+\s*\d+', title)[0] - self.uInk = int(findall('\d+', uimport)[0]) - except ValueError: - print('no volt increment found\nyou may want to put it in manually') - self.uInk = Uink - except IndexError: - print('No Uink found! May not be an eNMR experiment.') - self.uInk = Uink - - self.vcList["U / [V]"] = [i*self.uInk*polarity for i in range(len(self.difflist))] - - elif dependency.upper() == "G": - try: - # takes the title page to extract the volt increment - title = open(self.dateipfad+"/pdata/1/title").read() - # gets the voltage increment using a regular expression - uimport = findall('[U|u]\s*=?\s*\d+', title)[0] - self.uInk = int(findall('\d+', uimport)[0]) - - except ValueError: - print('no volt increment found\nyou may want to put it in manually') - self.uInk = Uink - except IndexError: - print('No Uink found! May not be an eNMR experiment.') - self.uInk = Uink # Uinktext - - #if Uink is not None: - #self.uInk = Uink - - self.vcList["U / [V]"] = [self.uInk*polarity for i in range(len(self.difflist))] - #self.vcList["U / [V]"] = [self.vcList["vc"][n]/2*self.uInk if self.vcList["vc"][n] % 2 == 0 - #else (self.vcList["vc"][n]+1)/2*self.uInk*-1 - #for n in range(len(self.data[:, 0]))] - - #if self.dependency.upper() == "U": - #try: - #self.vcList = pd.read_csv(self.dateipfad+"/vclist", - #names=["vc"]).loc[:len(self.data[:, 0])-1] - - #except: - #print("There is a Problem with the VC-list or you performed a gradient dependent measurement") - #elif self.dependency.upper() == "G": - #self.vcList = pd.DataFrame(np.ones((len(self.data[:, 0]), 1)), - #columns=["vc"]) - #else: - #print("The dependency is not properly selected, try again!") - - #self.difflist = pd.read_csv(self.dateipfad+"/difflist", - #names=["g in T/m"])*0.01 - - #if Uink is not None: - #self.uInk = Uink - - #self.vcList["U / [V]"] = [self.vcList["vc"][n]/2*self.uInk if self.vcList["vc"][n] % 2 == 0 - #else (self.vcList["vc"][n]+1)/2*self.uInk*-1 - #for n in range(len(self.data[:, 0]))] - - # try to open phase data, otherwise create new - try: - self.eNMRraw = pd.read_csv(self.path+"phase_data_"+self.expno+".csv", - index_col=0, sep=" ") - # --> update voltage list - self.eNMRraw["U / [V]"] = self.vcList["U / [V]"] - except: - print("eNMRraw was missing and is generated") - self.vcList["ph0"] = np.zeros(len(self.data.real[:, 0])) - self.eNMRraw = self.vcList - finally: - self.eNMRraw["g in T/m"] = self.difflist - - self.p1 = self.dic["acqus"]["P"][1] - self.d1 = self.dic["acqus"]["D"][1] - - try: - # import of diffusion parameters for newer Spectrometers - import xml.etree.ElementTree as etree - diffpar = etree.parse(self.dateipfad+'/diff.xml') - root = diffpar.getroot() - self.Delta = float(root.findall('DELTA')[0].text)*1e-3 - self.delta = float(root.findall('delta')[0].text)*1e-3 # it should be read as in microseconds at this point due to bruker syntax - print('The diffusion parameters were read from the respectie .XML!') - except: - # determination of the diffusion parameters for Emma - self._d2 = self.dic["acqus"]["D"][2] - self._d5 = self.dic["acqus"]["D"][5] - self._d9 = self.dic["acqus"]["D"][9] - self._d11 = self.dic["acqus"]["D"][11] - self._p19, self._p18, self._p17 = self.dic["acqus"]["P"][19],\ - self.dic["acqus"]["P"][18],\ - self.dic["acqus"]["P"][17] - print('That did not work. Your data is from an old spectrometer!') - # calculating usable parameters - self.delta = self._p17+self._p18 - self._Delta_1 = 0.001*(self._p17*2+self._p18)+(self._d2+self._d9+self._d5+self._d11)*1000+0.001*self.p1+self._d11 - self._Delta_2 = 0.001*(self._p17*2+self._p18)+(self._d2+self._d9+self._d5+self._d11)*1000+0.001*self.p1*2 - self._spoiler = (self._d11+self._p17+self._p19+self._p17)*0.001+self._d2*1000 - self.Delta = self._Delta_1+self._Delta_2+2*self._spoiler - self.Delta *=1e-3 - self.delta *=1e-6 - - - # Elektrodenabstand in m - self.d = electrode_distance - self.g = self.eNMRraw["g in T/m"][0] - - def __add__(self, other): - - for obj in [self, other]: - for k in obj.eNMRraw.columns: - if k[:2] == 'ph': - obj.eNMRraw[k] -= obj.eNMRraw.loc[0, k] - print('%s normalized to 0V'%k) - else: - pass - self.eNMRraw = self.eNMRraw.append(other.eNMRraw) - self.eNMRraw.sort_values('U / [V]', inplace=True) - return self diff --git a/build/lib/eNMRly/Measurement/Juergen1.py b/build/lib/eNMRly/Measurement/Juergen1.py deleted file mode 100644 index 111c9ff..0000000 --- a/build/lib/eNMRly/Measurement/Juergen1.py +++ /dev/null @@ -1,203 +0,0 @@ -from .eNMR_Methods import _eNMR_Methods -import matplotlib.pyplot as plt - -class Juergen1(_eNMR_Methods): - ''' - This is the subsubclass of Masurement() and subclass of eNMR_Methods specialised to process data obtained from the experimental Schönhoff set-up - - path: - relative or absolute path to the measurements folder - measurement: - the to the experiment corresponding EXPNO - alias: - Here you can place an individual name relevant for plotting. If None, the path is taken instead. - Uink: - voltage increment. Usually extracted from the title file if defined with e.g. "Uink = 10V" - If Uink cannot be found or is wrong it can be entered manually during the data import. - The voltage list is calculated from the voltage increment and the vc list when the incrementation loop is used in the pulse program - dependency: - 'U': voltage dependent eNMR measurement - 'G': fieldgradient dependent eNMR measurement - - linebroadening: - setting a standard-value for the linebroadening. - ''' - def __init__(self, path, expno, Uink=None, dependency="U", alias=None, linebroadening=0.5, electrode_distance=2.2e-2): - Measurement.__init__(self, path, expno, linebroadening=linebroadening, alias=alias) - self.dependency = dependency.upper() - - self._x_axis = {"U": "U / [V]", - "G": "g in T/m", - "I": "I / mA", - 'RI': 'RI / V' - }[self.dependency.upper()] - - #self._x_axis = {"G": "g in T/m", - #"U": "U / [V]"}[self.dependency.upper()] - - if dependency.upper() == "U": - try: - # takes the title page to extract the volt increment - title = open(self.dateipfad+"/pdata/1/title").read() - # gets the voltage increment using a regular expression - #uimport = findall('[U|u]in[k|c]\s*=?\s*\d+', title)[0] - uimport = findall('[U|u]in[k|c]\s*=+\s*\d+', title)[0] - self.uInk = int(findall('\d+', uimport)[0]) - except ValueError: - print('no volt increment found\nyou may want to put it in manually') - self.uInk = Uink - except IndexError: - print('No Uink found! May not be an eNMR experiment.') - self.uInk = Uink - - elif dependency.upper() == "G": - try: - # takes the title page to extract the volt increment - title = open(self.dateipfad+"/pdata/1/title").read() - # gets the voltage increment using a regular expression - uimport = findall('[U|u]\s*=?\s*\d+', title)[0] - self.uInk = int(findall('\d+', uimport)[0]) - - except ValueError: - print('no volt increment found\nyou may want to put it in manually') - self.uInk = Uink - except IndexError: - print('No Uink found! May not be an eNMR experiment.') - self.uInk = Uink # Uinktext - - if self.dependency.upper() == "U": - try: - self.vcList = pd.read_csv(self.dateipfad+"/vclist", - names=["vc"]).loc[:len(self.data[:, 0])-1] - except: - print("There is a Problem with the VC-list or you performed a gradient dependent measurement") - elif self.dependency.upper() == "G": - self.vcList = pd.DataFrame(np.ones((len(self.data[:, 0]), 1)), - columns=["vc"]) - else: - print("The dependency is not properly selected, try again!") - - self.difflist = pd.read_csv(self.dateipfad+"/difflist", - names=["g in T/m"])*0.01 - - if Uink is not None: - self.uInk = Uink - - self.vcList["U / [V]"] = [self.vcList["vc"][n]/2*self.uInk if self.vcList["vc"][n] % 2 == 0 - else (self.vcList["vc"][n]+1)/2*self.uInk*-1 - for n in range(len(self.data[:, 0]))] - - # try to open phase data, otherwise create new - try: - self.eNMRraw = pd.read_csv(self.path+"phase_data_"+self.expno+".csv", - index_col=0, sep=" ") - # --> update voltage list - self.eNMRraw["U / [V]"] = self.vcList["U / [V]"] - except: - print("eNMRraw was missing and is generated") - self.vcList["ph0"] = np.zeros(len(self.data.real[:, 0])) - self.eNMRraw = self.vcList - finally: - self.eNMRraw["g in T/m"] = self.difflist - - self.p1 = self.dic["acqus"]["P"][1] - self.d1 = self.dic["acqus"]["D"][1] - - try: - # import of diffusion parameters for newer Spectrometers - import xml.etree.ElementTree as etree - diffpar = etree.parse(self.dateipfad+'/diff.xml') - root = diffpar.getroot() - self.Delta = float(root.findall('DELTA')[0].text)*1e-3 - self.delta = float(root.findall('delta')[0].text)*1e-3 # it should be read as in microseconds at this point due to bruker syntax - print('The diffusion parameters were read from the respectie .XML!') - except: - # determination of the diffusion parameters for Emma - self._d2 = self.dic["acqus"]["D"][2] - self._d5 = self.dic["acqus"]["D"][5] - self._d9 = self.dic["acqus"]["D"][9] - self._d11 = self.dic["acqus"]["D"][11] - self._p19, self._p18, self._p17 = self.dic["acqus"]["P"][19],\ - self.dic["acqus"]["P"][18],\ - self.dic["acqus"]["P"][17] - print('That did not work. Your data is from an old spectrometer!') - # calculating usable parameters - self.delta = self._p17+self._p18 - self._Delta_1 = 0.001*(self._p17*2+self._p18)+(self._d2+self._d9+self._d5+self._d11)*1000+0.001*self.p1+self._d11 - self._Delta_2 = 0.001*(self._p17*2+self._p18)+(self._d2+self._d9+self._d5+self._d11)*1000+0.001*self.p1*2 - self._spoiler = (self._d11+self._p17+self._p19+self._p17)*0.001+self._d2*1000 - self.Delta = self._Delta_1+self._Delta_2+2*self._spoiler - self.Delta *=1e-3 - self.delta *=1e-6 - - - # Elektrodenabstand in m - self.d = electrode_distance - self.g = self.eNMRraw["g in T/m"][0] - - def plot_spec(self, row, xlim=None, figsize=None, invert_xaxis=True, sharey=True):#, ppm=True): - """ - plots row 0 and row n in the range of xmax and xmin - - :returns: figure - """ - - _max = None if xlim is None else xlim[0] - _min = None if xlim is None else xlim[1] - - if type(xlim) is not list: - fig = plt.figure(figsize=figsize) - ax = fig.add_subplot(111) - elif type(xlim) == list: - fig, ax = plt.subplots(ncols=len(xlim), nrows=1, figsize=figsize, sharey=sharey) - - _min = self.ppm[0] if xlim is None else xlim[1] - _max = self.ppm[-1] if xlim is None else xlim[0] - - def plot(r, axes=ax): - - unit = {'U': 'V', 'G': 'T/m', 'I': 'mA', 'RI': 'V'}[self.dependency.upper()] - - axes.plot(self.ppm, self.data[r, ::1].real, label='row %i, %i %s'%(r, self.eNMRraw[self._x_axis].iloc[r], unit)) - - if type(xlim) is not list: - if type(row) ==list: - for r in row: - plot(r) - else: - plot(row) - - ax.set_xlim(xlim) - #ax.set_title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, "U / [V]"])) - ax.set_xlabel("$\delta$ / ppm") - ax.set_ylabel("intensity / a.u.") - - ax.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) - if invert_xaxis: - xlimits = ax.get_xlim() - ax.set_xlim(xlimits[::-1]) - - ax.legend() - - elif type(xlim) == list: - for axis, xlim in zip(ax,xlim): - if type(row) ==list: - for r in row: - plot(r, axis) - else: - plot(row, axis) - - axis.set_xlim(xlim) - #ax.set_title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, "U / [V]"])) - axis.set_xlabel("$\delta$ / ppm") - axis.set_ylabel("intensity / a.u.") - axis.legend() - axis.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) - if invert_xaxis: - xlimits = axis.get_xlim() - axis.set_xlim(xlimits[::-1]) - ax[-1].legend() - #fig.legend() - return fig - - diff --git a/build/lib/eNMRly/Measurement/Pavel.py b/build/lib/eNMRly/Measurement/Pavel.py deleted file mode 100644 index 4b57c69..0000000 --- a/build/lib/eNMRly/Measurement/Pavel.py +++ /dev/null @@ -1,122 +0,0 @@ -from .eNMR_Methods import _eNMR_Methods -import pandas as pd - - -class Pavel(_eNMR_Methods): - ''' - This is the subsubclass of Masurement() and subclass of eNMR_Methods specialised to process data obtained from the experimental Swedish from Pavel set-up - the voltage list is valculated from the vd-values - - path: - relative or absolute path to the measurements folder - expno: - the to the experiment number corresponding EXPNO - dependency: - 'U': voltage dependent eNMR measurement - 'G': fieldgradient dependent eNMR measurement - - alias: - Here you can place an individual name relevant for plotting. If None, the path is taken instead. - linebroadening: - setting a standard-value for the linebroadening. - ''' - def __init__(self, path, expno, dependency='U', alias=None, linebroadening=5, electrode_distance=2.2e-2, cell_resistance=None): - - self.dependency = dependency - self.cell_resistance = cell_resistance - - super().__init__(path, expno, linebroadening=linebroadening, alias=alias) - - #self._x_axis = {"G": 'g in T/m', "U": 'U / [V]'}[dependency.upper()] - # import the diffusion parameters - import xml.etree.ElementTree as etree - diffpar = etree.parse(self.dateipfad+'/diff.xml') - root = diffpar.getroot() - self.Delta = float(root.findall('DELTA')[0].text)*1e-3 - self.delta = float(root.findall('delta')[0].text)*1e-3 #in Seconds - print('The diffusion parameters were read from the respectie .XML!') - - try: - self.vdList = pd.read_csv(self.dateipfad+"/vdlist", - names=["vd"]).loc[:len(self.data[:, 0])-1] - except: - print('no vdList found, generated ones list instead') - self.vdList = pd.DataFrame(np.ones((len(self.data[:, 0]), 1)), - columns=["vd"]) - self.eNMRraw = self.vdList - - #self.vdList["U / [V]"] = hier die Konversion von vdlist zu Spannungsliste - try: - self.difflist = pd.read_csv(self.dateipfad+"/gradlist", - names=["g in T/m"])*0.01 - except: - print('gradlist not found. difflist imported instead') - self.difflist = pd.read_csv(self.dateipfad+"/difflist", - names=["g in T/m"])*0.01 - self.eNMRraw["g in T/m"] = self.difflist - - self.d = electrode_distance - self.g = self.eNMRraw["g in T/m"][0] - - - # converts the vd-List - for i, n in enumerate(self.eNMRraw['vd']): - self.eNMRraw.loc[i, 'vd_temp'] = float(n[:-1]) - # calculates the applied Voltages - - if self.dependency.upper() == "U": - self.eNMRraw[self._x_axis] = [ - 0 if (self.eNMRraw.loc[i,'vd_temp'] <= 0.6) - else - n if i%2==0 - else - n*-1 - for i, n in enumerate(self.eNMRraw['vd_temp']*5)] - - self.uInk = self.eNMRraw['U / [V]'][0] - self.eNMRraw['U / [V]'][1] - if self.uInk == 0: - self.uInk = self.eNMRraw['U / [V]'][0] - self.eNMRraw['U / [V]'][2] - if self.uInk < 0: - self.uInk *= -1 - - elif self.dependency.upper() == "I": - self.uInk = None - self.eNMRraw[self._x_axis] = [ - 0 if (self.eNMRraw.loc[i,'vd_temp'] <= 0.6) - else - n if i%2==0 - else - n*-1 - for i, n in enumerate(self.eNMRraw['vd_temp']) - ] - - elif self.dependency.upper() == "RI": - self.uInk = None - self.eNMRraw[self._x_axis] = [ - 0 if (self.eNMRraw.loc[i,'vd_temp'] <= 0.6) - else - n if i%2==0 - else - n*-1 - for i, n in enumerate(self.eNMRraw['vd_temp']) - ] - self.uInk = self.eNMRraw['RI / V'][0] - self.eNMRraw['RI / V'][1] - if self.uInk == 0: - self.uInk = self.eNMRraw['RI / V'][0] - self.eNMRraw['RI / V'][2] - if self.uInk < 0: - self.uInk *= -1 - # calculation of the Voltage from cell resistance and Current /1000 because of mA - self.eNMRraw[self._x_axis] *= self.cell_resistance/1000 - - def plot_spec(self, row, xlim=None, figsize=None, invert_xaxis=True, sharey=True):#, ppm=True): - from .Juergen1 import Juergen1 as eNMR_Measurement - return eNMR_Measurement.plot_spec(self, row, xlim, figsize, invert_xaxis, sharey)#, ppm=True): - -#class Idependent(Pavel): - #def __init__(self, path, expno, dependency='U', alias=None, linebroadening=5, electrode_distance=2.2e-2): - #Pavel.__init__(path, expno, dependency='U', alias=None, linebroadening=5, electrode_distance=2.2e-2) - - #self.eNMR['I'] = None#vd=Funktion - - #self.eNMR.drop('U / [V]', inplace=True) - diff --git a/build/lib/eNMRly/Measurement/Simulated.py b/build/lib/eNMRly/Measurement/Simulated.py deleted file mode 100644 index 2f4fc70..0000000 --- a/build/lib/eNMRly/Measurement/Simulated.py +++ /dev/null @@ -1,36 +0,0 @@ -class Simulated(eNMR_Methods): - ''' - sublass of eNMR_Methods and Measurement for the import of simulated data via the SpecSim class - ''' - - def __init__(self, sim, alias=None): - self.g = sim.params.spec_par['g'] - self.d = sim.params.spec_par['d'] - self.Delta = sim.params.spec_par['Delta'] - self.delta = sim.params.spec_par['delta'] - self.dependency = 'U' - self.eNMRraw = sim.eNMRraw - self.data = sim.data - self.data_orig = sim.data - self.ppm = sim.ppm - self.eNMRraw['g in T/m'] = self.g - - # the gamma_values in rad/Ts - gamma_values = {'1H':26.7513e7, - '7Li': 10.3962e7, - '19F': 25.1662e7} - self.gamma = gamma_values[sim.params.spec_par['NUC']] - # Umrechnung von rad in ° - self.gamma = self.gamma/2/np.pi*360 - - self.lin_res_dic = {} - self.alias = alias - self.path = 'simulated data/' - self.expno = '0' - self.uInk = self.eNMRraw.loc[1, 'U / [V]'] - self.eNMRraw.loc[0, 'U / [V]'] - self._x_axis = 'U / [V]' - #self._x_axis = {"U": "U / [V]", - #"G": "g in T/m" - #}[self.dependency.upper()] -################################## - diff --git a/build/lib/eNMRly/Measurement/__init__.py b/build/lib/eNMRly/Measurement/__init__.py deleted file mode 100644 index 12bbe9d..0000000 --- a/build/lib/eNMRly/Measurement/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -__all__ = ['Pavel', 'Juergen1'] -from .Pavel import Pavel -from .Juergen1 import Juergen1 -print('Measurement Module imported') diff --git a/build/lib/eNMRly/Measurement/base.py b/build/lib/eNMRly/Measurement/base.py deleted file mode 100644 index cc73e87..0000000 --- a/build/lib/eNMRly/Measurement/base.py +++ /dev/null @@ -1,305 +0,0 @@ -import matplotlib.pyplot as plt -import nmrglue as ng -from re import findall -import numpy as np -import pandas as pd - -class Measurement(object): - """ - This is the base class for the analysis of eNMR raw spectra obtained on Bruker spectrometers with given settings. - if creating an object from an eNMR measurement creates an error, Uink might have failed to be set and needs to be set manually instead. - - path: - relative or absolute path to the measurements folder - measurement: - the to the experiment corresponding EXPNO - alias: - Here you can place an individual name relevant for plotting. If None, the path is taken instead. - """ - - def __init__ (self, path, measurement, alias=None, linebroadening=5, n_zf_F2=2**14): - self.path = path - self.expno = str(measurement) - self.dateipfad = self.path+self.expno - self.alias = alias - self.name = self.dateipfad.split(sep='/')[-2]+'/'+self.expno - self.lineb = linebroadening - - ## correction factor U_0 for normalized spectra - #self.U_0 = 0 - - # takes the title page to extract the volt increment - try: - title = open(self.dateipfad+"/pdata/1/title").read() - # reformatting the title_page removing excess of newlines (\n) - - except UnicodeDecodeError: - title = open(self.dateipfad+"/pdata/1/title", encoding='ISO-8859-15').read() - - title = findall('.+', title) - self.title_page = self.name+'\n' - - for n in title: - self.title_page += n+'\n' - - # read in the bruker formatted data - self.dic, self.data = ng.bruker.read(self.dateipfad) - self.pdata = ng.bruker.read_procs_file(self.dateipfad+"/pdata/1") - # original dataset - self.data_orig = self.data - - # Berechnung der limits der ppm-skala - self._ppm_r = self.pdata["procs"]["OFFSET"] - self._ppm_l = -(self.dic["acqus"]["SW"]-self.pdata["procs"]["OFFSET"]) - self._ppmscale = np.linspace(self._ppm_l, self._ppm_r, n_zf_F2) # np.size(self.data[0,:])) - self.ppm = self._ppmscale - - # Bestimmung der dictionaries für die NMR-Daten aus den Messdateien - # --> wichtig für die Entfernung des digitalen Filters - self.udic = ng.bruker.guess_udic(self.dic, self.data) - self._uc = ng.fileiobase.uc_from_udic(self.udic) - - # getting some other variables - self.TD = self.dic["acqus"]["TD"] - self.fid = self.data - self.n_zf_F2 = n_zf_F2 - - # the gamma_values in rad/Ts - gamma_values = {'1H':26.7513e7, - '7Li': 10.3962e7, - '19F': 25.1662e7} - - self.gamma = gamma_values[self.dic["acqus"]["NUC1"]] - # conversion from rad in ° - self.gamma = self.gamma/2/np.pi*360 - - self.nuc = self.dic["acqus"]["NUC1"] - - # initialize dictionary for linear regression results - self.lin_res_dic = {} - #self.dependency = None - - # END __INIT__ - - def calibrate_ppm(self, ppmshift): - """ - calibrate ppm-scale by adding the desired value. - - :param ppmshift: value added to ppm-scale - :return: nothing - """ - self.ppm = self._ppmscale + ppmshift - - def proc(self, linebroadening=None, phc0=0, phc1=0, zfpoints=None, xmin=None, xmax=None, cropmode="percent"): - """ - processes the spectral data by: - - removing digital filter - - create separate fid data - - linebroadening on spectral data - - zero filling - - fourier transformation - - crops the data after fft set to xmin and xmax on the x-axis and returns the value - when xmin and xmax or not both None - - xmin, xmax: - min and maximum x-values to crop the data for processing (and display) - can take relative or absolute values depending on the cropmode. - - cropmode: changes the x-scale unit - "percent": value from 0% to 100% of the respective x-axis-length --> does not fail - "absolute": takes the absolute values --> may fail - - :return: nothing - """ - - - if linebroadening is None: - linebroadening = self.lineb - - if zfpoints is not None: - zfp = zfpoints - else: - zfp = self.n_zf_F2 - _lineb = linebroadening - - # remove the digital filter - self.data = ng.bruker.remove_digital_filter(self.dic, self.data) - - # process the spectrum - # linebroadening - self.data = ng.proc_base.em(self.data, lb=_lineb/self.dic["acqus"]["SW_h"]) - - # zero fill to 32768 points - try: - self.data = ng.proc_base.zf_size(self.data, zfp) - except ValueError: - zfp = 2**15 - self.data = ng.proc_base.zf_size(self.data, zfp) - # Fourier transform - self.data = ng.proc_base.fft(self.data) - - # Phasecorrection - self.data = ng.proc_autophase.ps(self.data, phc0, phc1) - #correct ppm_scale - self._ppmscale = np.linspace(self._ppm_l, self._ppm_r, zfp) # np.size(self.data[0,:])) - self.ppm = self._ppmscale - - self.data_orig = self.data - - if (xmin is not None) or (xmax is not None): - self.data = self.set_spectral_region(xmin=xmin, xmax=xmax, mode=cropmode) - - def plot_fid(self, xmax=None, xmin=0, step=1): - """ - plots every n-th(step) fid and scales the time axis with xmax and xmin - - :returns: figure - """ - - _xmax = self.TD/2 if xmax is None else xmax - _xmin = xmin - - fig, ax = plt.subplots() - - for n in range(len(self.data[::step, 0])): - ax.plot(self.fid[n, ::1].real) - - ax.set_xlim((_xmin, _xmax)) - ax.set_xlabel("data points") - ax.ticklabel_format(style='sci', scilimits=(0,0), axis='y') - ax.legend([x*step for x in range(len(self.data[::step, 0]))], - ncol=3, - title="row", - loc=1) - #plt.show() - return fig - - def plot_spec(self, row, xlim=None, figsize=None, invert_xaxis=True, sharey=True):#, ppm=True): - """ - plots row 0 and row n in the range of xmax and xmin - - :returns: figure - """ - - - _max = None if xlim is None else xlim[0] - _min = None if xlim is None else xlim[1] - - if type(xlim) is not list: - fig = plt.figure(figsize=figsize) - ax = fig.add_subplot(111) - elif type(xlim) == list: - fig, ax = plt.subplots(ncols=len(xlim), nrows=1, figsize=figsize, sharey=sharey) - - _min = self.ppm[0] if xlim is None else xlim[1] - _max = self.ppm[-1] if xlim is None else xlim[0] - - if type(xlim) is not list: - if type(row) ==list: - for r in row: - ax.plot(self.ppm, self.data[r, ::1].real, label='row %i'%r) - else: - ax.plot(self.ppm, self.data[row, ::1].real, label='row %i'%row) - ax.legend() - ax.set_xlim(xlim) - #ax.set_title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, "U / [V]"])) - ax.set_xlabel("$\delta$ / ppm") - ax.set_ylabel("intensity / a.u.") - - ax.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) - if invert_xaxis: - xlimits = ax.get_xlim() - ax.set_xlim(xlimits[::-1]) - - elif type(xlim) == list: - for axis, xlim in zip(ax,xlim): - if type(row) ==list: - for r in row: - axis.plot(self.ppm, self.data[r, ::1].real, label='row %i'%r) - else: - axis.plot(self.ppm, self.data[row, ::1].real, label='row %i'%row) - - axis.set_xlim(xlim) - #ax.set_title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, "U / [V]"])) - axis.set_xlabel("$\delta$ / ppm") - axis.set_ylabel("intensity / a.u.") - - axis.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) - if invert_xaxis: - xlimits = axis.get_xlim() - axis.set_xlim(xlimits[::-1]) - #fig.legend() - return fig - - def plot_spec_1d(self, xlim=None, figsize=None, invert_xaxis=True):#, ppm=True): - """ - plots row 0 and row n in the range of xmax and xmin - - :returns: figure - """ - _max = None if xlim is None else xlim[0] - _min = None if xlim is None else xlim[1] - - fig = plt.figure(figsize=figsize) - ax = fig.add_subplot(111) - - _min = self.ppm[0] if xlim is None else xlim[1] - _max = self.ppm[-1] if xlim is None else xlim[0] - - ax.plot(self.ppm, self.data[::1].real) - - #ax.legend() - ax.set_xlim(xlim) - #ax.set_title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, "U / [V]"])) - ax.set_xlabel("$\delta$ / ppm") - ax.set_ylabel("intensity / a.u.") - - ax.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) - if invert_xaxis: - xlimits = ax.get_xlim() - ax.set_xlim(xlimits[::-1]) - return fig - - def set_spectral_region(self, xmin, xmax, mode='absolute', ppm=True, original_data=True): - """ - crops the data of the object set to xmin and xmax on the x-axis and returns the value - - mode: changes the x-scale unit - "percent": value from 0% to 100% of the respective x-axis-length --> does not fail - "absolute": takes the absolute values --> may fail - - :returns: cropped_data, cropped_ppmscale - """ - - if (xmin is not None) or (xmax is not None): - - if mode == "percent": - ppmmin = xmin/100*self.data[0, :].size - ppmmax = xmax/100*self.data[0, :].size - if original_data: - self.data = self.data_orig[:, int(ppmmin):int(ppmmax)] - else: - self.data = self.data[:, int(ppmmin):int(ppmmax)] - self.ppm = self._ppmscale[int(ppmmin):int(ppmmax)] - - elif mode == "absolute": - if ppm: - print('xmin: %i' % xmin) - print('xmax: %i' % xmax) - xmax = np.where(self._ppmscale <= xmax)[0][-1] # if xmax is not None else -1 - xmin = np.where(self._ppmscale >= xmin)[0][0] # if xmin is not None else 0 - print('xmin: %i' % xmin) - print('xmax: %i' % xmax) - print(self._ppmscale) - - if original_data: - self.data = self.data_orig[:, xmin:xmax] - else: - self.data = self.data[:, xmin:xmax] - - self.ppm = self._ppmscale[xmin:xmax] - else: - print('oops, you mistyped the mode') - return self.data, self._ppmscale[xmin:xmax] - diff --git a/build/lib/eNMRly/Measurement/eNMR_Methods.py b/build/lib/eNMRly/Measurement/eNMR_Methods.py deleted file mode 100644 index 670a1e9..0000000 --- a/build/lib/eNMRly/Measurement/eNMR_Methods.py +++ /dev/null @@ -1,957 +0,0 @@ -from .base import Measurement -from sklearn.linear_model import huber as hub -import numpy as np -import nmrglue as ng -import matplotlib.pyplot as plt - -class _eNMR_Methods(Measurement): - """ - This is the subclass of Masurement() containing all methods - - path: - relative or absolute path to the measurements folder - measurement: - the to the experiment corresponding EXPNO - alias: - Here you can place an individual name relevant for plotting. If None, the path is taken instead. - """ - # def get_xaxis(self): - # - # if self.dependency is not None: - # return { - # "G": self.eNMRraw["g in T/m"], - # "U": self.eNMRraw["U / [V]"] - # }[self.dependency.upper()] - # else: - # print("an error occured, the dependency is None") - def __init__(self, path, measurement, alias=None, linebroadening=5): - super().__init__(path, measurement, alias=None, linebroadening=5) - - self._x_axis = {"U": "U / [V]", - "G": "g in T/m", - "I": "I / mA", - "RI": "RI / V" - }[self.dependency.upper()] - - def __repr__(self): - return '''%s, expno %s, Delta = %.1fms, ppm range: %.1f to %.1f - delta= %.1fms, g= %.3f T/m, e-distance=%.0fmm'''%( - self.nuc, self.expno, self.Delta*1000, - self.ppm[0], self.ppm[-1], - self.delta*1000, self.g, self.d*1000 - ) - - def __getitem__(self, key): - if type(key) == str: - return self.eNMRraw[key] - elif type(key) == int: - return (self.ppm, self.data[key]) - - #def __add__(self, other): - - - def spam_and_eggs(self): - """ - For Class inheritance test purposes - """ - #self.x_var = {None:None, - #"U": "U / [V]", - #"G": "g in T/m" - #} - print(self.x_var) - print("SPAM and EGGS!") - - - def autophase(self, - returns=False, - method="acme", - progress=False, - period_compensation=True, - normalize=True): - """ - analyzes the phase of the spectral data and returns phased data - - at this point no data cropping --> full spectrum is analyzed - - returns: if True, returns the raw phased Data. If false, returns nothing - - method: chooses the method for the phase correction - "acme": standard method using entropy minimization - "difference": --> _ps_abs_difference_score() - minimizes the linear difference to the first spectrum by fitting p0 - "sqdifference": --> _ps_sq_difference_score() - minimizes the square difference to the first spectrum by fitting p0 - - progress: - prints the progress. In case you are dealing with large datasets - - period_compensation: - corrects for the 360 degree error that occasionaly occurs since a phase of 0 and 360 - is equivalent and indistinguishable - - normalize: - normalizes all phase data to 0 degrees for 0V. This compensates for different reference - phases during the aquisition and phase analysis. - """ - if self.dependency.upper() == 'G': - normalize = False - - data_ph = np.array([]) - for n in range(len(self.data[:, 0])): - if progress: - print("row %i / %i" % (n+1, len(self.data[:, 0]))) - - ##################################### - # using the data with the algorithm - _val = self._autops(self.data[n, :], _fnc=method) - ##################################### - - data_ph = np.append(data_ph, _val) - - if method == 'acme': - self.eNMRraw.loc[n, "ph0acme"] = _val[0] # *-1 - if progress: - clear_output() # keeps the output clean. remove for more info on iteration steps etc. - elif method == 'difference': - self.eNMRraw.loc[n, "ph0diff"] = _val[0] # *-1 - if progress: - clear_output() # keeps the output clean. remove for more info on iteration steps etc. - else: - self.eNMRraw.loc[n, "ph0sqdiff"] = _val[0] # *-1 - if progress: - clear_output() # keeps the output clean. remove for more info on iteration steps etc. - - #corr_enmr = self.eNMRraw.sort_values("U / [V]") - corr_enmr = self.eNMRraw.sort_values(self._x_axis) - - if method == 'acme': - if period_compensation: - for m in range(corr_enmr["ph0acme"].size): - if corr_enmr["ph0acme"].iloc[m]-corr_enmr["ph0acme"].iloc[abs(m-1)] < -300: - corr_enmr["ph0acme"].iloc[m] += 360 - elif corr_enmr["ph0acme"].iloc[m]-corr_enmr["ph0acme"].iloc[abs(m-1)] > 300: - corr_enmr["ph0acme"].iloc[m] -= 360 - self.eNMRraw = corr_enmr - - if normalize: - #self.U_0 = corr_enmr[corr_enmr["U / [V]"] == 0]["ph0acme"][0] - self.U_0 = corr_enmr[corr_enmr[self._x_axis] == 0]["ph0acme"].iloc[0] - for m in range(corr_enmr["ph0acme"].size): - corr_enmr["ph0acme"].iloc[m] -= self.U_0 - self.eNMRraw = corr_enmr - - #self.eNMRraw["ph0acmereduced"] = corr_enmr['ph0acme']/(self.eNMRraw['g in T/m'][0]*self.Delta*self.delta*self.gamma*self.d) - self.eNMRraw["ph0acmereduced"] = corr_enmr['ph0acme']*self.d/(self.eNMRraw['g in T/m'][0]*self.Delta*self.delta*self.gamma) - elif method == 'difference': - if period_compensation: - for m in range(corr_enmr["ph0diff"].size): - if corr_enmr["ph0diff"].iloc[m]-corr_enmr["ph0diff"].iloc[abs(m-1)] < -300: - corr_enmr["ph0diff"].iloc[m] += 360 - elif corr_enmr["ph0diff"].iloc[m]-corr_enmr["ph0diff"].iloc[abs(m-1)] > 300: - corr_enmr["ph0diff"].iloc[m] -= 360 - self.eNMRraw = corr_enmr - - if normalize: - self.U_0 = corr_enmr[corr_enmr[self._x_axis] == 0]["ph0diff"][0] - for m in range(corr_enmr["ph0diff"].size): - corr_enmr["ph0diff"].iloc[m] -= self.U_0 - self.eNMRraw = corr_enmr - - #self.eNMRraw["ph0diffreduced"] = corr_enmr['ph0diff']/(self.eNMRraw['g in T/m'][0]*self.Delta*self.delta*self.gamma*self.d) - self.eNMRraw["ph0diffreduced"] = corr_enmr['ph0diff']*self.d/(self.eNMRraw['g in T/m'][0]*self.Delta*self.delta*self.gamma) - else: - if period_compensation: - for m in range(corr_enmr["ph0sqdiff"].size): - if corr_enmr["ph0sqdiff"].iloc[m]-corr_enmr["ph0sqdiff"].iloc[abs(m-1)] < -300: - corr_enmr["ph0sqdiff"].iloc[m] += 360 - elif corr_enmr["ph0sqdiff"].iloc[m]-corr_enmr["ph0sqdiff"].iloc[abs(m-1)] > 300: - corr_enmr["ph0sqdiff"].iloc[m] -= 360 - self.eNMRraw = corr_enmr - - if normalize: - self.U_0 = corr_enmr[corr_enmr[self._x_axis] == 0]["ph0sqdiff"][0] - for m in range(corr_enmr["ph0sqdiff"].size): - corr_enmr["ph0sqdiff"].iloc[m] -= self.U_0 - self.eNMRraw = corr_enmr - - self.eNMRraw["ph0sqdiffreduced"] = corr_enmr['ph0sqdiff']*self.d/(self.eNMRraw['g in T/m'][0]*self.Delta*self.delta/1000*self.gamma) - - print("done, all went well") - - if returns is True: - return data_ph # , data_spec - - def _autops(self, data, _fnc="acme", p0=0.0): # , p1=0.0): - """ - FS: modified for eNMR purpose. optimizes only p0 - ---------- - Automatic linear phase correction - - Parameters - ---------- - data : ndarray - Array of NMR data. - _fnc : str or function - Algorithm to use for phase scoring. Built in functions can be - specified by one of the following strings: "acme", "peak_minima" - p0 : float - Initial zero order phase in degrees. - p1 : float - Initial first order phase in degrees. - - Returns - ------- - ndata : ndarray - Phased NMR data. - - """ - - from scipy.optimize import fmin - - self._fnc = _fnc - self._p0 = p0 - # self.p1 = p1 - - if not callable(_fnc): - self._fnc = { - # 'peak_minima': _ps_peak_minima_score, - 'acme': self._ps_acme_score, - 'difference': self._ps_abs_difference_score, - 'sqdifference': self._ps_sq_difference_score - }[self._fnc] - - # self.opt ist die optimierte Phase für das Spektrum. - self.opt = self._p0 # [self._p0, self.p1] - self.opt = fmin(self._fnc, x0=self.opt, args=(data, ), disp=0) - - # self.phasedspc = ng.proc_base.ps(data, p0=self.opt[0], p1=self.opt[1]) - - return self.opt # self.phasedspc, self.opt - - @staticmethod - def _ps_acme_score(ph, data): - """ - FS: modified for eNMR purpose. optimizes only p0 - ------------ - Phase correction using ACME algorithm by Chen Li et al. - Journal of Magnetic Resonance 158 (2002) 164-168 - - using only the first derivative for the entropy - - Parameters - ---------- - pd : tuple - Current p0 and p1 values - data : ndarray - Array of NMR data. - - Returns - ------- - score : float - Value of the objective function (phase score) - - """ - stepsize = 1 - - # s0 --> initial spectrum? - s0 = ng.proc_base.ps(data, p0=-ph, p1=0) # , p1=phc1 --> p1=0 lets the algorithm optimize only p0 - data = np.real(s0) - - # Calculation of first derivatives --> hier wird das absolute Spektrum erzeugt - ds1 = np.abs((data[1:]-data[:-1]) / (stepsize*2)) - p1 = ds1 / np.sum(ds1) - - # Calculation of entropy - p1[p1 == 0] = 1 # was macht diese Zeile? das Verstehe ich noch nicht richtig - - h1 = -p1 * np.log(p1) - h1s = np.sum(h1) - - # Calculation of penalty - pfun = 0.0 - as_ = data - np.abs(data) - sumas = np.sum(as_) - - if sumas < 0: - pfun = pfun + np.sum((as_/2) ** 2) - - p = 1000 * pfun - - return h1s + p - - def _ps_abs_difference_score(self, ph, data): - """ - FS: modified for eNMR purpose. optimizes only p0 - ------------ - Parameters - ---------- - pd : tuple - Current p0 and p1 values - data : ndarray - Array of NMR data. - - Returns - ------- - score : float - Value of the objective function (phase score) - """ - - s0 = ng.proc_base.ps(data, p0=-ph, p1=0) # , p1=phc1 --> p1=0 lets the algorithm optimize only p0 - phasedspec = np.real(s0) - - penalty = np.sum(np.abs(self.data[0, :].real - phasedspec)) - - return penalty - - def _ps_sq_difference_score(self, ph, data): - """ - FS: modified for eNMR purpose. optimizes only p0 - ------------ - Parameters - ---------- - pd : tuple - Current p0 and p1 values - data : ndarray - Array of NMR data. - - Returns - ------- - score : float - Value of the objective function (phase score) - """ - - s0 = ng.proc_base.ps(data, p0=-ph, p1=0) # , p1=phc1 --> p1=0 lets the algorithm optimize only p0 - phasedspec = np.real(s0) - - penalty = np.sum(np.square(self.data[0,:].real - phasedspec)) # *1/n-1 wäre die Varianz, - # ist hier egal, da alle Spektren die gleiche Anzahl an Punkten haben. - - return penalty - - def analyze_intensity(self, data='cropped', ph_var='ph0acme', normalize=True, ylim=None): - """ - uses the phase data information to rephase and integrate all spectra and plot a comparison - stores the intensity information in the measurement folder - - data: - 'orig': self.data_orig - 'cropped': self.data - - return: fig - """ - _data = {'orig': self.data_orig, - 'cropped': self.data}[data] - - # x_axis = {}[self.dependency] - - # the correction factor for the normalization is added again. - self.phased = [ng.proc_base.ps(_data[n, :], p0=-(self.eNMRraw.loc[n, ph_var]))# + self.U_0)) - for n in range(len(_data[:, 0]))] - - intensity = np.array([self.phased[n].real.sum() for n in range(len(_data[:, 0]))]) - - if normalize: - intensity /= intensity[0] - - #u = [self.eNMRraw.loc[i, 'U / [V]'] for i, n in enumerate(self.eNMRraw['U / [V]'])] - u = [self.eNMRraw.loc[i, self._x_axis] for i, n in enumerate(self.eNMRraw[self._x_axis])] - - intensity_data = pd.DataFrame() - intensity_data["U"] = u - intensity_data['intensity'] = intensity - intensity_data['ph'] = self.eNMRraw[ph_var]# + self.U_0 - - self.intensity_data = intensity_data - - fig = plt.figure(figsize=(8, 6)) - _ax = plt.subplot(221) - _ax.scatter(intensity_data['U'], intensity_data['intensity'], c='k') - _ax.set_ylabel('intensity / a.u.') - if normalize and (ylim is None): - _ax.set_ylim(0,1.05) - elif normalize: - _ax.set_ylim(*ylim) - _bx = plt.subplot(222, sharey=_ax) - _bx.plot(intensity_data['intensity'], 'ok') - - _cx = plt.subplot(223, sharex=_ax) - _cx.scatter(intensity_data['U'], intensity_data['ph'], c='k') - _cx.set_xlabel('$U$ / V') - _cx.set_ylabel('$\t{\Delta}\phi$ / °') - - _dx = plt.subplot(224, sharex=_bx) - _dx.plot(intensity_data['ph'], 'ok') - _dx.set_xlabel('vc') - - fig.savefig(self.path+'intensity_plot_'+self.expno+".pdf") - - intensity_data.to_csv(self.path+'intensity_data_'+self.expno+".csv") - - return fig#, intensity_data - - def lin_huber(self, epsilon=1.35, ulim=None, y_column='ph0acme'): - """ - robust linear regression method from scikit-learn module based on the least-square method with an additional threshhold (epsilon) for outlying datapoints - outlying datapoints are marked as red datapoints - - epsilon: - threshhold > 1 - ulim: - tuple defining the voltage limits for the regression e.g. ulim = (-100, 100) - y_column: - column(keyword) to be analyzed from the eNMRraw dataset - - stores results in lin_res_dic[y_column] - :returns: nothing - """ - - # select x-axis - #self._x_axis = {"U": "U / [V]", - #"G": "g in T/m" - #}[self.dependency.upper()] - - # convert data - self._eNMRreg = self.eNMRraw[[self._x_axis, y_column]].sort_values(self._x_axis) - - # setting the axis for regression - if ulim is None: - self.umin = min(self.eNMRraw[self._x_axis]) - else: - self.umin = ulim[0] - - if ulim is None: - self.umax = max(self.eNMRraw[self._x_axis]) - else: - self.umax = ulim[1] - - self._npMatrix = np.matrix(self._eNMRreg[(self.eNMRraw[self._x_axis] <= self.umax) - == (self.eNMRraw[self._x_axis] >= self.umin)]) - - self._X_train, self._Y_train = self._npMatrix[:, 0], self._npMatrix[:, 1] - - # regression object - self.huber = hub.HuberRegressor(epsilon=epsilon) - self.huber.fit(self._X_train, self._Y_train) - - # linear parameters - self.m = self.huber.coef_ # slope - self.b = self.huber.intercept_ # y(0) - self._y_pred = self.huber.predict(self._X_train) - self._y_pred = self._y_pred.reshape(np.size(self._X_train), 1) - - # drop the outliers - self._outX_train = np.array(self._X_train[[n == False for n in self.huber.outliers_]]) - self._outY_train = np.array(self._Y_train[[n == False for n in self.huber.outliers_]]) - self._outY_pred = np.array(self._y_pred[[n == False for n in self.huber.outliers_]]) - - # mark outliers in dataset - # self._inliers = [n is not True for n in self.huber.outliers_] - - self.eNMRraw["outlier"] = True - - for n in range(len(self._npMatrix[:, 0])): - self.eNMRraw.loc[self.eNMRraw[self._x_axis] == self._npMatrix[n, 0], "outlier"] = self.huber.outliers_[n] - - # calculation of the slope deviation - _sig_m_a = np.sqrt(np.sum((self._outY_train-self._outY_pred)**2)/(np.size(self._outY_train)-2)) - _sig_m_b = np.sqrt(np.sum((self._outX_train-self._outX_train.mean())**2)) - self.sig_m = _sig_m_a/_sig_m_b - - # debug - #print(self.sig_m) - - # R^2 - self.r_square = self.huber.score(self._outX_train, self._outY_train) - - self.lin_res_dic[y_column] = {'b': self.b, - 'm': self.m, - 'r^2': self.r_square, - 'x': np.array(self._X_train.tolist()).ravel(), - 'y': self._y_pred.ravel(), - 'sig_m': self.sig_m} - - def lin_display(self, ylim=None, show_slope_deviation=True, n_sigma_displayed=1, dpi=500, y_column='ph0acme', textpos=(0.5,0.15), extra_note=''): - """ - displays the linear huber regression - If there is an alias available from measurement object, it will replace the path in the title - - ylim: - set limits of the y-axis - - show_slope_deviation: - display the standard deviation - n_sigma_displayed: - multiplicator of the displayed standard deviation - y_column: - column(keyword) to be analyzed from the eNMRraw dataset - textpos: - tuple for the textposition - extra_note: - added to the text - dpi: - adjusts the output dpi to the required value - - :returns: figure - """ - - textx, texty = textpos - #_x_axis = {"U":"U / [V]", "G":"g in T/m"} - #self._x_axis = {"U":"U / [V]", "G":"g in T/m"}[self.dependency] - - print("formula: y = {0}x + {1}".format(self.m,self.b)) - - # create figure - fig_enmr = plt.figure() - - # sublot phase data - _ax = fig_enmr.add_subplot(111) - - # color format for outliers - colors = ["r" if n else "k" for n in self.eNMRraw.sort_values(self._x_axis)['outlier']] - - _ax.scatter(x=np.ravel(self._eNMRreg[self._x_axis]), - y=np.ravel(self._eNMRreg[y_column]), - marker="o", - c=colors) - _ax.set_ylim(ylim) - - # format the data for plotting - _xdata = np.ravel(self._X_train) - _ydata = np.ravel(self._y_pred) - - # Plot the regression - _ax.plot(_xdata, _ydata, "r-") - - if show_slope_deviation: - _ax.fill_between(_xdata, _xdata*(self.m+n_sigma_displayed*self.sig_m)+self.b, - _xdata*(self.m-n_sigma_displayed*self.sig_m)+self.b, - alpha=0.5, - facecolor="blue") - - # make title - if self.alias is None: - title_printed = r'%s'%((self.path+self.expno).split("/")[-2]+", EXPNO: "+self.expno+extra_note) -# plt.title('LiTFSIDOLDME') # debugging - else: - title_printed = self.alias+", EXPNO: "+self.expno+extra_note -# plt.title(self.alias+", EXPNO: "+self.expno+extra_note) -# plt.title('test2') # for debugging purposes - plt.title(title_printed.replace('_',r' ')) - - if self.dependency.upper() == "U": - plt.xlabel("$U$ / V") - elif self.dependency.upper() == "G": - plt.xlabel("$g$ / $($T$\cdot$m$^{-1})$") - elif self.dependency.upper() == 'I': - plt.xlabel("$I$ / mA") - elif self.dependency.upper() == 'RI': - plt.xlabel("$(R \cdot I)$ / V") - - plt.ylabel("$\Delta\phi$ / °") - - # plotting the Textbox - plt.text(textx, texty, - "y = %.4f $\cdot$ x + %4.2f\n$R^2$=%4.3f; $\sigma_m=$%4.4f"%(self.m, - self.b, - self.r_square, - self.sig_m), - fontsize=14, - bbox={'facecolor':'white', 'alpha':0.7,'pad':10}, - horizontalalignment='center', - verticalalignment='center', - transform=_ax.transAxes) - - return fig_enmr - - def lin_results_display(self, cols, regression=True, normalize=True, colors=None, markers=None, fig=None, x_legend=1.1, y_legend=1.0, ncol_legend=2, ymin=None, ymax=None, figsize=None): - ''' - displays the phase results including the regression. Can take a previous figure from a different eNMR measurement in order to compare results - the relegend() function can be used on these graphs to reprint the legend() for publishing - - cols: - list of keywords for dataselection from the eNMRraw table - colors: - list of colors or a colormap --> see matplotlib documentation - - :returns: figure - ''' - - #self._x_axis = {"U":"U / [V]", "G":"g in T/m"}[self.dependency.upper()] - - if fig is None: - fig = plt.figure(figsize=figsize) - else: - fig = fig - - ax = fig.add_subplot(111) - - if colors is None: - prop_cycle = plt.rcParams['axes.prop_cycle'] - colors = prop_cycle.by_key()['color'] - else: - colors = colors - - if markers is None: - markers = ['o', '^', 's', '+', '*', 'D', 'v', 'x', '<'] - else: - markers = markers - - message = { - 'ph0acme': 'Autophase with entropy minimization', - 'ph0diff': 'Autophase with linear difference minimization', - 'ph0sqdiff': 'Autophase with square difference minimization' - } - - if type(cols) == list: - for i, col in enumerate(cols): - if normalize: - corr = self.eNMRraw.loc[self.eNMRraw['U / [V]']==0, col].iloc[0] - else: - corr = 0 - - try: - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[col]-corr, yerr=self.eNMRraw['%s_err'%col], fmt='o', label=message[col], c=colors[i], marker=markers[i]) - except KeyError: - if col == 'ph0acme': - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[col]-corr, fmt='o', label=message[col], c=colors[i], marker=markers[i]) - else: - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[col]-corr, yerr=self.eNMRraw['%s_err'%col], fmt='o', label='fitted data %s'%col, c=colors[i], marker=markers[i]) - except ValueError: - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[col]-corr, fmt='o', label=message[col], c=colors[i], marker=markers[i]) - if regression: - ax.plot(self.lin_res_dic[col]['x'], self.lin_res_dic[col]['y']-corr, '--', label='%s lin regression'%col, c=colors[i], marker=None) - #if type(cols) == list: - #for i, col in enumerate(cols): - #try: - #ax.errorbar(self._x_axis, col, yerr='%s_err'%col, fmt='o', data=self.eNMRraw, label=message[col], c=colors[i], marker=markers[i]) - #except KeyError: - #ax.errorbar(self._x_axis, col, yerr='%s_err'%col, fmt='o', data=self.eNMRraw, label='fitted data %s'%col, c=colors[i], marker=markers[i]) - #except ValueError: - #ax.errorbar(self._x_axis, col, fmt='o', data=self.eNMRraw, label=message[col], c=colors[i], marker=markers[i]) - #if regression: - #ax.plot(self.lin_res_dic[col]['x'], self.lin_res_dic[col]['y'], '--', label='%s lin regression'%col, c=colors[i], marker=None) - else: - if normalize: - corr = self.eNMRraw.loc[self.eNMRraw['U / [V]']==0, cols][0] - else: - corr = 0 - - try: - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[cols]-corr, yerr=self.eNMRraw['%s_err'%cols], fmt='o', label='Phasefitting of Peak %s' %cols, c=colors[0], marker=markers) - if regression: - ax.plot(self.lin_res_dic[cols]['x'], self.lin_res_dic[cols]['y']-corr, '--', label='%s lin regression'%cols, c=colors[0], marker=markers) - except KeyError: - if cols=='ph0acme': - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[cols]-corr, fmt='o', label=message[cols], c=colors[0], marker=markers) - else: - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[col]-corr, yerr=self.eNMRraw['%s_err'%col], fmt='o', label='fitted data %s'%cols, c=colors[i], marker=markers) - except ValueError: - ax.errorbar(self.eNMRraw[self._x_axis], self.eNMRraw[cols]-corr, fmt='o', label=message[cols], c=colors[0], marker=markers) - if regression: - ax.plot(self.lin_res_dic[cols]['x'], self.lin_res_dic[cols]['y']-corr, '--', label='%s lin regression'%cols, c=colors[0], marker=None) - #else: - #try: - #ax.errorbar(self._x_axis, cols, yerr='%s_err'%cols, fmt='o', data=self.eNMRraw, label='Phasefitting of Peak %s' %cols, c=colors[0], marker=markers) - #if regression: - #ax.plot(self.lin_res_dic[cols]['x'], self.lin_res_dic[cols]['y'], '--', label='%s lin regression'%cols, c=colors[0], marker=markers) - #except KeyError: - #ax.errorbar(self._x_axis, col, yerr='%s_err'%col, fmt='o', data=self.eNMRraw, label='fitted data %s'%cols, c=colors[i], marker=markers) - #except ValueError: - #ax.errorbar(self._x_axis, cols, fmt='o', data=self.eNMRraw, label=message[cols], c=colors[0], marker=markers) - #if regression: - #ax.plot(self.lin_res_dic[cols]['x'], self.lin_res_dic[cols]['y'], '--', label='%s lin regression'%cols, c=colors[0], marker=None) - ax.legend(bbox_to_anchor=(x_legend, y_legend), ncol=ncol_legend) - - xlabel = {'U': r'$U$ / V', - 'G': r'$g$ / (T\,m$^{-1}$)', - 'I': '$I$ / mA', - 'RI': '$(R \cdot I)$ / V' - }[self.dependency.upper()] - - ax.set_xlabel(xlabel) - if normalize: - ax.set_ylabel(r'$\phi-\phi_0$ / °') - else: - ax.set_ylabel(r'$\phi$ / °') - return fig - - def plot_spec_phcorr(self, phasekey='ph0acme', xlim=None, save=False, savepath=None, show=True, ppm=True, orig_data=True, x_legend=1.1, y_legend= 1.0, ncol_legend=2): - """ - plots phase corrected rows - - ppm: When True, plots the x-axis with ppm scale, xmin and xmax then are percentages - if False, plots only the number of datapoints, xmin and xmax then are absolute values - - save: saves the spectrum in the data folder - - show: displays the spectrum {plt.show()} or not - - returns: - phased data within given range - """ - if savepath is None: - path = self.path+"layered_spectra_"+self.expno+".png" - else: - path = savepath - if orig_data: - phasedspc = [ng.proc_base.ps(self.data_orig[n, :], p0=-(self.eNMRraw[phasekey][n]))# obsolet: +self.U_0)) # the correction factor for the normalization is added again. - for n in range(len(self.data_orig[:, 0]))] # p1=self.opt[1]) - else: - phasedspc = [ng.proc_base.ps(self.data[n, :], p0=-(self.eNMRraw[phasekey][n]))# obsolet: +self.U_0)) # the correction factor for the normalization is added again. - for n in range(len(self.data[:, 0]))] # p1=self.opt[1]) - - #xmin = 0 if xmin is None else xmin - fig, ax = plt.subplots() - - if not ppm: - #xmax = self.TD if xmax is None else xmax - #xmax = len(self.data[0,:]) if xmax is None else xmax - for n in range(len(self.data[:, 0])): - ax.plot(phasedspc[n].real) - ax.set_xlim(xlim) - ax.set_xlabel("data points") - ax.set_ylabel("intensity (a.u.)") - - else: - #xmax = self.ppm[0] if xmax is None else xmax - #xmin = self.ppm[-1] if xmin is None else xmin - #ixmax, ixmin = np.where(self.ppm >= xmin)[0][0], np.where(self.ppm >= xmax)[0][1] -# irange = np.where(self._ppmscale <= xmin)[0][0], np.where(self._ppmscale <= xmax)[0][1] - - if orig_data: - for n in range(len(self.data_orig[:, 0])): - # plt.plot(self._ppmscale[ixmin:ixmax], phasedspc[n].real) - ax.plot(self.ppm, phasedspc[n].real, label="row %i" %n) - else: - for n in range(len(self.data[:, 0])): - # plt.plot(self._ppmscale[ixmin:ixmax], phasedspc[n].real) - ax.plot(self.ppm, phasedspc[n].real, label="row %i" %n) - # plt.axis(xmin=self._ppm_l-self.dic["acqus"]["SW"]*xmin/100, - # xmax=self._ppm_r+self.dic["acqus"]["SW"]*(1-xmax/100)) - ax.set_xlim(xlim) - ax.set_xlabel("$\delta$ / ppm") - ax.set_ylabel("intensity / a.u.") - ax.legend(bbox_to_anchor=(x_legend, y_legend), ncol=ncol_legend) - - #ax = plt.gca() - #ax.set_xlim(ax.get_xlim()[::-1]) # inverts the x-axis - ax.ticklabel_format(style='sci', scilimits=(0,0), axis='y') - - if save: - fig.savefig(path, dpi=500) - if show: - plt.show() - #print("ixmax:", ixmax) - #print("ixmin:", ixmin) -# return ixmax, ixmin - phasedspc = np.asarray(phasedspc) - - return fig, ax - #if orig_data: - #return phasedspc[:,ixmin:ixmax] - #else: - #return phasedspc - # ps = ng.proc_base.ps - - def plot_spec_comparison_to_0(self, row, xmax=None, xmin=None, ppm=True): - """ - plots row 0 and row n in the range of xmax and xmin - """ - _max = None if xmax is None else xmax - _min = None if xmin is None else xmin - - fig = plt.figure() - if ppm: - _max = self.ppm[0] if xmax is None else xmax - _min = self.ppm[-1] if xmin is None else xmin - - plt.plot(self.ppm, self.data[0, ::1].real, label='row ' + str(0)) - plt.plot(self.ppm, self.data[row, ::1].real, label='row ' + str(row)) - plt.legend() - plt.axis(xmax=_max, xmin=_min) - plt.title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, self._x_axis])) - plt.xlabel("ppm") - plt.ylabel("intensity / a.u.") - if not ppm: - plt.plot(self.data[0, ::1].real, label='row '+str(0)) - plt.plot(self.data[row, ::1].real, label='row '+str(row)) - plt.legend() - plt.axis(xmax=_max, xmin=_min) - plt.title("this is row %i at %.0f V" % (row, self.eNMRraw.loc[row, self._x_axis])) - plt.xlabel("datapoints")#.sum() - plt.ylabel("intensity / a.u.") - - plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) - - return fig - - def output_data(self): - """ - saves the raw phase data in the measurement-folder as a csv - """ - self.eNMRraw.to_csv(self.path+"phase_data_"+self.expno+".csv", sep=" ") - - - def output_results(self, path=None): - """ - saves the mobility result data in the measurement-folder similar to obj.output_data() - """ - results_output = pd.Series([self.nuc, - self.mu[0], - self.sig_m*self.mu[0], - self.d, - self.g, - self.delta, - self.Delta, - self.uInk], - index=["nucleus", - "µ", - "sig_µ", - "d / m", - "g / (T/m)", - "delta / s", - "Delta / s", - "Uink / V"], - name=self.dateipfad) - if path is None: - results_output.to_csv(self.path+"mobility_data_"+self.expno+".csv") - elif path is not None: - results_output.to_csv(path+"mobility_data_"+self.expno+".csv") - else: - print('ooops!') - - def output_all_results(self, path=None, data=False): - """ - saves the mobility result data in the measurement-folder similar to obj.output_data() - """ - from pandas import ExcelWriter - from openpyxl import load_workbook - - try: - book = load_workbook(path) - writer = pd.ExcelWriter(path, engine='openpyxl') - writer.book = book - writer.sheets = dict((ws.title, ws) for ws in book.worksheets) - except: - writer = ExcelWriter(path, engine='xlsxwriter') - - for key in self.lin_res_dic.keys(): - #self.mobility(key) - results_output = pd.Series([self.nuc, - self.expno, - self.lin_res_dic[key]['mu'][0], - self.lin_res_dic[key]['mu_err'][0], - self.d, - self.g, - self.delta, - self.Delta, - self.uInk], - index=["nucleus", - "expno", - "µ", - "sig_µ", - "d / m", - "g / (T/m)", - "delta / s", - "Delta / s", - "Uink / V"], - name=self.dateipfad) - results_output.to_excel(writer, sheet_name=self.nuc+'expno'+str(self.expno)+'_'+key) - - if data: - try: - self.eNMRraw.drop(['data', 'fid'], 1).to_excel(writer, sheet_name=self.nuc+'expno'+str(self.expno)+'_data') - except KeyError: - self.eNMRraw.to_excel(writer, sheet_name=self.nuc+'expno'+str(self.expno)+'_data') - except: - print('oops, an unexpected error occured') - writer.save() - return - - def mobility(self, y_column='ph0acme', electrode_distance=None, verbose=True): - """ - calculates and returns (mobility, deviation) from the regression data - """ - #self.lin_res_dic = {y_column: {'b': self.b, - #'m': self.m, - #'r^2': self.r_square, - #'y_reg': self._y_pred, - #'sig_m': self.sig_m}} - if electrode_distance is None: - d = self.d - else: - d = electrode_distance - - if self.dependency.upper() == "G": - g = self.uInk - elif self.dependency.upper() == "U": - g = self.g - elif self.dependency.upper() == "I": - g = self.g - elif self.dependency.upper() == "RI": - g = self.g - else: - print("no dependency was set") - - if y_column is None: - self.mu = (self.m*d)/(self.gamma*self.delta*self.Delta*g) - #return self.mu, self.mu*(self.sig_m/self.m) - - else: - m = self.lin_res_dic[y_column]['m'] - sig_m = self.lin_res_dic[y_column]['sig_m'] - self.mu = (m*d)/(self.gamma*self.delta*self.Delta*g) - self.lin_res_dic[y_column]['mu']=self.mu - self.lin_res_dic[y_column]['mu_err']=self.mu*(sig_m/m) - #return self.mu, self.mu*(sig_m/m) - self.sig_m, self.m = sig_m, m - - if verbose: - print ('%.2E (m^2/Vs)'%self.mu[0],'+- %.2E'%(self.mu*(self.sig_m/self.m))) - - return self.mu, self.mu*(self.sig_m/self.m) - - - def analyzePhasecorrection(self, linebroadening=10, lin_threshhold=2.5, - graph_save=False, savepath=None, method="acme", xmin=None, - xmax=None, cropmode="absolute", progress=True, umin=None, umax=None, output_path=None): - - """ - standard phase correction analyzing routine for eNMR - - linebroadening: - sets the linebroadening of the fourier transformation - lin_threshhold: - sets the threshhold for outlier detection of the linear regression - graph_save: - True: saves the graph as a png in the measurment folder - savepath: - None: Takes the standard-values from the measurement - 'string': full path to customized graph - works with .png, .pdf and .eps suffixes - method: chooses between phase correction algorithms - "acme": standard method using entropy minimization - "difference": --> _ps_abs_difference_score() - minimizes the linear difference to the first spectrum by fitting p0 - "sqdifference": --> _ps_sq_difference_score() - minimizes the square difference to the first spectrum by fitting p0 - xmin, xmax: - min and maximum x-values to crop the data for processing (and display) - can take relative or absolute values depending on the cropmode. - - cropmode: changes the x-scale unit - "percent": value from 0% to 100% of the respective x-axis-length --> does not fail - "absolute": takes the absolute values --> may fail - progress: This shows the spectral row that is processed in this moment. May be disabled in order to be able to stop clearing the output. - """ - if output_path is None: - output_path = 'expno_%i_results.xlsx'%self.expno - - self.proc(linebroadening=linebroadening, xmin=xmin, xmax=xmax, cropmode=cropmode) - self.autophase(analyze_only=False, method=method, progress=progress) - self.lin_huber(epsilon=lin_threshhold, umin=umin, umax=umax) - # obj.lin() #---> dies ist die standardn, least squares method. - self.lin_display(save=graph_save, dpi=330, savepath=savepath) - self.mobility() - self.output_all_results(output_path) - diff --git a/build/lib/eNMRly/Measurement/tools.py b/build/lib/eNMRly/Measurement/tools.py deleted file mode 100644 index 97a2875..0000000 --- a/build/lib/eNMRly/Measurement/tools.py +++ /dev/null @@ -1,49 +0,0 @@ -def relegend(fig, new_labels, **kwargs): - ''' - Takes a figure with a legend, gives them new labels (as a list) - - **kwargs: ncol, loc etc. - ncol: number of columns - loc: location of the legend (see matplotlib documentation) - ''' - ax = fig.gca() - handles, labels = ax.get_legend_handles_labels() - ax.legend(handles, new_labels, **kwargs) - -def calibrate_x_axis(fig, val, majorticks=1, new_xlim=None): - """ - this function takes a pyplot figure and shifts the x-axis values by val. - majorticks defines the distance between the major ticklabels. - """ - ax = fig.gca() - xlim = ax.get_xlim() - - if xlim[0] > xlim[-1]: - x1, x2 = ax.get_xlim()[::-1] - else: - x1, x2 = ax.get_xlim() - - ax.set_xticks(np.arange(x1, x2+1, majorticks)-val%1) - ax.set_xticklabels(['%.1f'%f for f in ax.get_xticks()+val]) - if new_xlim is None: - ax.set_xlim(xlim) - else: - ax.set_xlim(new_xlim) - - return spec - -def phc_normalized(phc0, phc1): - """ - nifty function to correct the zero order phase correction - when phasing 1st order distortions. - phc0 -= phc1/(np.pi/2) - - returns: (phc0, phc1) - - best use in conjunctoin with self.proc() - - """ - - #phc0 -= phc1/(np.pi/2) - phc0 -= phc1/(np.pi) - return phc0, phc1 diff --git a/build/lib/eNMRly/Phasefitting.py b/build/lib/eNMRly/Phasefitting.py deleted file mode 100644 index 77a7909..0000000 --- a/build/lib/eNMRly/Phasefitting.py +++ /dev/null @@ -1,646 +0,0 @@ -# Copyright Flo unso - -import numpy as np -import matplotlib.pyplot as plt -import lmfit -import pandas as pd -from collections import OrderedDict - - -def set_peaks(m, n=-1, timeout=-1, xlim=None, **plot_kwargs): #,xlim=(0,10) - """ - m : measurement object - - returns: array of tuples with [(v0, a0), ...] for make_model() - ______________________________________________________________ - should be used in jupyter notebook like this: - - %matplotlib - peaks = set_peaks(m) - %matplotlib inline - make_model(peaks) - """ - - m.plot_spec([0], xlim=xlim, **plot_kwargs) - plt.tight_layout() - plt.legend([]) - plt.text(0.95,0.95,'left mouse button: set coordinate \nright mouse button: delete last coordinate\nmiddle mouse button: exit\n', - horizontalalignment='right',verticalalignment='top', transform=plt.gca().transAxes) - arr = plt.ginput(n=n, timeout=timeout) - - return arr - -def make_model(peaks, print_params=True): - ''' - returns a SpecModel()-object with parameters generated from set_peaks()-array - ''' - - model = SpecModel(len(peaks)) - model.set_initial_values(['v%i'%i for i in range(len(peaks))], [i[0] for i in peaks]) - model.set_initial_values(['a%i'%i for i in range(len(peaks))], [i[1]/100 for i in peaks]) - if print_params: - model.params.pretty_print() - return model - - -def reduce_fitted_phases(Measurementobject, SpecModel): - - m = Measurementobject - for k in ['ph%i'%i for i in range(SpecModel.n)]: - m.eNMRraw[k+'reduced'] = m.eNMRraw[k]*m.d/m.delta/m.Delta/m.g/m.gamma - m.eNMRraw[k+'reduced'] -= m.eNMRraw.loc[m.eNMRraw['U / [V]']==0, k+'reduced'][0] - - -def fit_Measurement(obj_M, obj_S, plot=False, peak_deconvolution=False, savepath=None, fixed_parameters=None): - ''' - obj_M: object of the class eNMR_Measurement - obj_S: object of the class SpecModel - fixed_parameters: List of parameters to be fixed after the first fit - ''' - - fp = [] - - - if fixed_parameters is None: - i = 0 - - elif fixed_parameters is not None: - #finds the matching parameter keys - if len(fixed_parameters[0]) == 2: - fp = fixed_parameters - else: - for i in range(len(fixed_parameters)): - for p in obj_S.params: - if p[0] == fixed_parameters[i]: - fp.append(p) - - i = 1 #counter set to one for the rest of the spectra to be fitted - fig = obj_S.fit(obj_M.ppm, obj_M.data[0], plot=plot, peak_deconvolution=peak_deconvolution) - ph_res = obj_S.get_phasedata() - - for par in ph_res.keys(): - #obj_M.eNMRraw.set_value(row, par, ph_res[par]) - obj_M.eNMRraw.at[0, par] = ph_res[par] - - for p in fp: # fixes all variables listed in fixed_parameters - obj_S.params[p].set(vary=False) - print('%s not varied!'%p) - - if (plot is True) and (savepath is not None): - fig.savefig(savepath+'%.1f'%obj_M.eNMRraw.loc[0, obj_M._x_axis]+'.png', dpi=300) - - print('start fitting') - - for row in range(i, obj_M.data[:,0].size): - fig = obj_S.fit(obj_M.ppm, obj_M.data[row], plot=plot, peak_deconvolution=peak_deconvolution) - ph_res = obj_S.get_phasedata() - - for par in ph_res.keys(): - #obj_M.eNMRraw.set_value(row, par, ph_res[par]) - obj_M.eNMRraw.at[row, par] = ph_res[par] - - if (plot is True) and (savepath is not None): - fig.savefig(savepath+'%.1f'%obj_M.eNMRraw.loc[row, obj_M._x_axis]+'.png', dpi=300) - - for p in fp: # reset all vary-Values - obj_S.params[p].set(vary=True) - - - print('fitting finished') - -def drop_errors(df): - ''' - drops all columns that which keys end with _err --> created from the fitting model - ''' - - df.keys() - #drop the vc, Voltag, Gradient and outlier columns - try: - sel = df.drop(['vc', 'U / [V]', 'g in T/m', 'outlier'], axis=1) - except KeyError: - try: - sel = df.drop(['vd', 'U / [V]', 'g in T/m', 'outlier'], axis=1) - except: - print('no vd or vc found, no standard parameters dropped') - sel = df - #sel = df.drop(['vd', 'U / [V]', 'g in T/m', 'outlier'], axis=1) - # drop all error-columns-boolean - _bool = np.array([k[-4:] != '_err' for k in sel.keys()]) - # elect only the non-error columns - sel = sel[np.array(sel.keys()[_bool])] - return sel - -def plot_correlations(df, method='pearson', without_errors=True, textcolor="#222222", **fig_kwargs): - """ - correlation coefficients plot (heatmap) for any pandas DataFrame - - method: - pearson, kendall, spearman - """ - if without_errors: - df = drop_errors(df) - corr = df.corr(method=method) - columns = corr.keys() - indices = np.array(corr.index) - - fig, ax = plt.subplots(**fig_kwargs) - im = ax.imshow(corr, cmap='Spectral', vmin=-1, vmax=1) - - # We want to show all ticks... - ax.set_xticks(np.arange(len(columns))) - ax.set_yticks(np.arange(len(indices))) - # ... and label them with the respective list entries - ax.set_xticklabels(columns) - ax.set_yticklabels(indices) - - # Rotate the tick labels and set their alignment. - plt.setp(ax.get_xticklabels(), rotation=45, ha="right", - rotation_mode="anchor") - - # Loop over data dimensions and create text annotations. - for i in range(len(indices)): - for j in range(len(columns)): - text = ax.text(j, i, '%.3f'%corr.iloc[i, j], - ha="center", va="center", color=textcolor) - plt.colorbar(im) - fig.tight_layout() - - return fig, corr - - -def lorentz_real(x, x0, _lambda, ph, amplitude): - """ - calculates the real part of the spectrum - here, the conversion from rad to ° happens - """ - def dispersion(x, x0, _lambda): - return -(x-x0)/(_lambda**2+(x-x0)**2) - - def absorption(x, x0, _lambda): - return _lambda/(_lambda**2+(x-x0)**2) - #Umrechnung von ° in rad - ph = (ph/360*2*np.pi) - amplitude, _lambda = abs(amplitude), abs(_lambda) - - return amplitude*(absorption(x, x0, _lambda)*np.cos(ph)-dispersion(x, x0, _lambda)*np.sin(ph)) - -def lorentz_imag(x, x0, _lambda, ph, amplitude): - """ - calculates the real part of the spectrum - here, the conversion from rad to ° happens - """ - def dispersion(x, x0, _lambda): - return -(x-x0)/(_lambda**2+(x-x0)**2) - - def absorption(x, x0, _lambda): - return _lambda/(_lambda**2+(x-x0)**2) - - #Umrechnung von ° in rad - ph = (ph/360*2*np.pi) - amplitude, _lambda = abs(amplitude), abs(_lambda) - - return amplitude*(dispersion(x, x0, _lambda)*np.cos(ph)+absorption(x, x0, _lambda)*np.sin(ph)) - -def gauss(x, x0, s, amp): - return amp*np.exp(-(x-x0)**2/(2*s**2))/(np.sqrt(np.pi*2)*s) - -#def makefunc_Lorentz(n = 1): - #''' - #returns a combination of n lorentz-real as a lambda function - #''' - #s = 'lambda x, baseline' - #for i in range(n): - #s += ', v%i, l%i, ph%i, a%i'%(i, i, i, i) - - #s+=': baseline' - #for i in range(n): - #s += ' + lorentz_real(x, v%i, l%i, ph%i, a%i)'%(i, i, i, i) - - #func = eval(s) - ##func.__name__="Flo's cooles Spektrum" - #func.__name__="Lorentz Peak Superposition" - #return func - -def makefunc_Lorentz_cmplx(n = 1): - ''' - returns a combination of n lorentz-real as a lambda function - ''' - s = 'lambda x, baseline' - for i in range(n): - s += ', v%i, l%i, ph%i, a%i'%(i, i, i, i) - - s+=': baseline' - for i in range(n): - s += ' + lorentz_real(x, v%i, l%i, ph%i, a%i)'%(i, i, i, i) - s += ' + 1j*lorentz_imag(x, v%i, l%i, ph%i, a%i)'%(i, i, i, i) - - func = eval(s) - #func.__name__="Flo's cooles Spektrum" - func.__name__="Lorentz Peak Superposition" - return func - -def makefunc_Voigt(n = 1): - ''' - returns a combination of n Voigt-real as a lambda function - ''' - s = 'lambda x, baseline' - for i in range(n): - s += ', v%i, l%i, ph%i, a%i, s%i'%(i, i, i, i, i) - - s+=': baseline' - for i in range(n): - s += ' + lorentz_real(x, v%i, l%i, ph%i, a%i)*gauss(x, v%i, s%i, 1)'%(i, i, i, i, i, i) - s += ' + 1j*lorentz_imag(x, v%i, l%i, ph%i, a%i)*gauss(x, v%i, s%i, 1)'%(i, i, i, i, i, i) - - func = eval(s) - #func.__name__="Flo's cooles Spektrum" - func.__name__="Voigt Peak Superposition" - return func - -class SpecModel(object): - ''' - This class creates a lmfit Model from s function of n_peaks lorentz-distributions - parameter explanation - an = amplitude n - ln = peak broadness of n - phn = phase angle n - vn = chemical shift v of n - baseline = basline of the whole spectrum - model = 'Lorentz' or 'Voigt' - ''' - - def __init__(self, n_peaks, verbose=False, model='Lorentz'): - - self.mkey = model - _func = {'Lorentz': makefunc_Lorentz_cmplx, - 'Voigt' : makefunc_Voigt}[model] - - self.n = n_peaks - self.model = lmfit.Model(_func(n_peaks)) - self.params = self.model.make_params(verbose=False) - - # This loop initializes all parameters by 1 and sets standard boundaries depending on the respective parameterset - for k in self.params.keys(): - self.set_initial_values(k, 1) - if k[0] == 'a': - self.set_boundaries(k, 0, np.inf) - self.set_initial_values(k, 1e6) - elif k[0] == 'l': - self.set_boundaries(k, 0, np.inf) - self.set_initial_values(k, 1e-2) - elif k[0] == 'p': - self.set_boundaries(k, -180, 180) - elif k[0] =='s': - self.set_boundaries(k, 0, np.inf) - self.set_initial_values(k, 1e-2) - else: - pass - - if verbose: - self.params.pretty_print() - - def set_mathematical_constraints(self, expr=None, reset=True): - """ - expr: mathematical expression without whitespace - - reset: will reset all mathematical constraints - - example 1: - ph0 should be always equal to ph1 - set_mathematical_constraint('ph0=ph1') - example 2: - ph0 should be always ph1 - 90 degree - set_mathematical_constraint('ph0=ph1-90') - """ - - if (expr == None) and (reset): - print('model without constraints') - return - - elif reset: - for i in self.params: - self.params[i].expr = None - print('constraints were reset before new assignment') - - if type(expr) == list: - for e in expr: - a = e.split('=') - self.params[a[0]].expr = a[1] - else: - a = expr.split('=') - self.params[a[0]].expr = a[1] - - - - def set_initial_values(self, par, val): - ''' - takes single parameter par or list of parameters - ''' - #if type(par) == list: - ##i = 0 - #for i in range(self.n): - #self.params[par[i]].value = val[i] - ##i+=1 - if type(par) == list: - i = 0 - for p in par: - self.params[p].value = val[i] - i+=1 - else: - self.params[par].value = val - - def set_boundaries(self, par, min, max): - self.params[par].min, self.params[par].max = min,max - #print(self.params[par]) - - def calc_single_peak(self, x, params=None, peak=0): - ''' - calculates the single peak of a parameter set - ''' - if params is None: - params = self.params - - def make_singlepeak_params(params, peak=0): - ''' - creates a dictionary containing all parameters of the respective peak named as - 0-Peak to pass it to the SpecModel evaluation function of a single peak - ''' - dic = {k[:-1]+'0': params[k]for k in params.keys() if k[-1] == str(peak)} - dic['baseline'] = params['baseline'] - return dic - - single_peak = SpecModel(1, model=self.mkey) - s0par = make_singlepeak_params(params, peak) - return single_peak.model.eval(s0par, x=x) - - def plot_init_spec(self, xdata, single_peaks=True, fig=None): - ''' - plots the spectrum for the initialized parameter set - ''' - if fig is None: - fig, ax = plt.subplots() - else: - ax = fig.gca() - - if single_peaks: - for n in range(self.n): - ax.plot(xdata, self.calc_single_peak(np.array(xdata), params=self.params, peak=n).real, '--', label='peak '+str(n)) - ax.plot(np.array(xdata), self.model.eval(x=np.array(xdata), params=self.params).real, c='k', label='start parameters') - ax.legend() - - shiftlist = list(filter(lambda x: x[0]=='v', self.params.keys())) - for i in range(len(shiftlist)): - x = self.params['v%i'%i].value - y = self.params['a%i'%i].value*100 - s = 'P%i'%i - ax.annotate(s, (x,y), fontsize=16) - - return fig - - def fit(self, xdata, ydata, plot=False, peak_deconvolution=False): - - self.result = self.model.fit(ydata, x=xdata, params=self.params) - - fig = None - if plot: - fig = self.result.plot()[0] - - if peak_deconvolution and plot: - ax = fig.gca() - for n in range(self.n): - ax.plot(xdata, self.calc_single_peak(np.array(xdata), params=self.result.params, peak=n), '--', label='peak '+str(n)) - #if plot:# =='fit': - #fig = self.result.plot_fit() - return fig - - def get_phasedata(self): - dic = {} - #for i in range(self.n): - #dic['ph%i'%i] = self.result.best_values['ph%i'%i] - #dic['ph%i_err'%i] = self.result.params['ph%i'%i].stderr - - for k in self.params.keys(): - dic[k] = self.result.best_values[k] - dic[k+'_err'] = self.result.params[k].stderr - - return dic - - def report(self): - print(self.result.fit_report()) - - def reassign_parameter(self, par, best_values_dic, vary=False): - ''' - reassigns the initial value for the parameter by taking the value from the best-fit dictionary (usually obtained form a test-fit) - - vary: sets if the parameter should be varied in further fittings or not. - ''' - self.params[par].set(best_values_dic[par], vary=vary) - print(par, '=', best_values_dic[par], ';', 'vary =', vary) - - -class SpecSim(object): - ''' - This class lets you simulate peaks with given properties in order compare e.g. MOSY and fitting results to yours if Peaks are overlapping. - ''' - def __init__(self, n_Peaks, ppm=None, xlim=None, n_points=2**11, model="Lorentz", fit_params=None): - """ - model: 'Lorentz' or 'Voigt' - """ - - _func = {'Lorentz': makefunc_Lorentz_cmplx, - 'Voigt' : makefunc_Voigt}[model] - self.data = None - self.func = _func(n_Peaks) - self.pkeys = self.func.__code__.co_varnames - self.pdic = OrderedDict({k: 0.1 for k in self.pkeys}) - if fit_params is not None: - try: - for p in fit_params.keys(): - self.pdic[p] = fit_params[p].value - except: - print('This fit_params are no lmfit-Parameters object') - - print('func_params', self.pkeys) - - if ppm is not None: - self.ppm = ppm - elif xlim is not None: - self.ppm = np.linspace(*xlim, n_points) - else: - self.ppm = None - self.pdic['x'] = self.ppm - - self.eNMRraw = pd.DataFrame() - self.eNMRraw['U / [V]'] = None - self.params = None - - - def set_params(self, par, val): - ''' - sets the parameter par as value value in the parameters dictionary self.pdic - - par: - string or list of strings representing the respective keyword for the parameters - var: single value or list of values. This can be anything, also numpy arrays. - ''' - if type(par) == list: - i=0 - for p in par: - self.pdic[p] = val[i] - i += 1 - else: - self.pdic[par] = val - - def add_noise(self, scale=1): - ''' - adds normal distributed noise to the spectrum - ''' - self.data += scale*np.random.standard_normal(self.data.shape) - - - def calc_spec(self, noise=0): - ''' - calculates the spectrum from the parameters dictionary self.pdic - ''' - self.data = self.func(*self.pdic.values()) - self.add_noise(noise) - return self.data - - def calc_spec_series(self, params, vlist=None, noise=0): - ''' - calculates a series of spectra with the given lists. - if par is a list, then vlist needs to be 2-dimensional and rectangular. - ''' - if vlist is None: - par = params.keys() - vlist = params.values() - #self.Ulim = params.spec_par['Ulim'] - - else: - vlist = np.array(vlist) - - if (vlist.dtype == 'int64') or (vlist.dtype == 'float64'): - if type(par) != list: - self.set_params(par, vlist[0]) - self.data = np.array([self.calc_spec(noise)]) - for i in range(1, len(vlist)): - self.set_params(par, vlist[i]) - self.data = np.append(self.data, np.array([self.calc_spec(noise)]), axis=0) - - elif type(par) == list: - # set all first parameter values - for i in range(len(par)): - self.set_params(par[i], vlist[i][0]) - # create first row of dataset so rest can be appended in the following loop - self.data = np.array([self.calc_spec(noise)]) - for j in range(1, len(vlist[0])): - for i in range(len(par)): - self.set_params(par[i], vlist[i][j]) - self.data = np.append(self.data, np.array([self.calc_spec(noise)]), axis=0) - else: - print('error: vlist is not in a rectangular shape') - - #Instanzvariablen Updaten - self.params = params - self.eNMRraw['U / [V]'] = params.U - - _d = pd.DataFrame(self.data) - self.eNMRraw['data'] = None - for i in range(len(self.data[:,0])): - self.eNMRraw.at[i, 'data'] = _d[i]#test[i] - - for p in params.keys(): - self.eNMRraw[p] = params[p] - - return self.data - - def plot(self, text_pos=None): - fig, ax = plt.subplots() - if self.data.ndim == 1: - ax.plot(self.ppm, self.data.real) - else: - for n in range(len(self.data[:,0])): - ax.plot(self.ppm, self.data[n].real, label=n) - ax.legend() - if text_pos is not None: - ax.text(*text_pos, self.params.spec_par) - return fig - - -class ParameterVariation(OrderedDict): - ''' - OrderedDictionary with extra methods for the creation of datasets - ''' - def __init__(self): - self.spec_par = {'Delta': None, - 'delta': None, - 'g': None, - 'Ulim':None, - 'NUC':None, - } - self.U = None - pass - def values(self): - return np.array(list(super().values())) - - def keys(self): - return list(super().keys()) - - def set_par(self, par, val): - if type(par) != list: - self[par] = val - else: - for i, p in enumerate(par): - self[p] = val[i] - - def set_ph_from_mobility(self, mobilities, n, spec_par=None): - ''' - mobilities: - must be an array. mobilities are sorted according - - - -Name Value Min Max Stderr Vary Expr Brute_Step -a0 4e+05 0 inf to the peaks - - spec_par: dictionary with the spectroscopic parameters in SI units: - Delta in s - delta in s - NUC: 1H, 7Li or 19F - g in T/m - Ulim: Voltage limits in V - d: electrode distance - ''' - if spec_par is None: - spec_par = self.spec_par - - #gamma in MHz/T - #gamma = {"7Li": 5957443266, - #"1H": 15326621020, - #"19F": 14415618125}[spec_par['NUC']] - # gamma in rad/Ts - gamma = {'1H':26.7513e7, - '7Li': 10.3962e7, - '19F': 25.1662e7}[spec_par['NUC']] - # Umrechnung von rad in ° - gamma = gamma/2/np.pi*360 - print(gamma) - - U = np.linspace(*spec_par['Ulim'], n) - print(U) - g = spec_par['g'] - d = spec_par['d'] - Delta = spec_par['Delta'] - delta = spec_par['delta'] - - print(g, d, Delta, delta) - for i, mu in enumerate(mobilities): - self['ph%i'%i] = gamma*Delta*delta*g*(U/d)*mu - print(self) - self.U = U - self.spec_par = spec_par - pass - - - - diff --git a/build/lib/eNMRly/__init__.py b/build/lib/eNMRly/__init__.py deleted file mode 100644 index 0d3523a..0000000 --- a/build/lib/eNMRly/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -__all__ = ['SpecModel'] -from .Phasefitting import SpecModel -from .MOSY import MOSY -from .Measurement import Pavel as Import_eNMR_Measurement -print('%s imported'%__name__) - diff --git a/build/lib/eNMRpy/MOSY.py b/build/lib/eNMRpy/MOSY.py index 0cacdad..0e8fd89 100644 --- a/build/lib/eNMRpy/MOSY.py +++ b/build/lib/eNMRpy/MOSY.py @@ -13,7 +13,8 @@ def __init__(self, obj): self.eNMRraw['data'] = None self.eNMRraw['fid'] = None for row in range(len(obj.data[:,0])): - self.eNMRraw.set_value(row, 'data', pd.Series(obj.data[row,:])) + self.eNMRraw.at[row, 'data'] = pd.Series(obj.data[row,:]) # new syntax + #self.eNMRraw.set_value(row, 'data', pd.Series(obj.data[row,:])) # old syntax #self.eNMRraw.set_value(row, 'fid', pd.Series(obj.fid[row,:])) data_sorted = np.array(obj.eNMRraw['data'].tolist()) self.data = data_sorted.astype('complex').copy() @@ -76,11 +77,11 @@ def fft_F1 (self): along the F1 dimension """ - from nmrglue.proc_base import fft + from nmrglue import proc_base #data_temp = np.zeros(self.data.shape) for n in range(len(self.data[0,:])): - self.data[:,n] = fft(self.data[:,n]) + self.data[:,n] = proc_base.fft(self.data[:,n]) print("done") def calc_MOSY(self, u_max = None, n_zf=2**12, mobility_scale=True, include_0V=True, electrode_distance=2.2e-2, old_zf=False): @@ -126,8 +127,8 @@ def calc_MOSY(self, u_max = None, n_zf=2**12, mobility_scale=True, include_0V=Tr SHR_imag = np.zeros((len(positive['data']), len(positive['data'].iloc[0]))) for n in range(1, len(positive['data'])): - SHR_real[n,:] = positive['data'].iloc[n].real + negative['data'].iloc[n].real - SHR_imag[n,:] = positive['data'].iloc[n].imag - negative['data'].iloc[n].imag + SHR_real[n,:] = positive['data'].iloc[n].values.real + negative['data'].iloc[n].values.real + SHR_imag[n,:] = positive['data'].iloc[n].values.imag - negative['data'].iloc[n].values.imag SHR = SHR_real + SHR_imag*1j del(SHR_real) diff --git a/build/lib/eNMRpy/Measurement/Flo.py b/build/lib/eNMRpy/Measurement/Flo.py new file mode 100644 index 0000000..df800ed --- /dev/null +++ b/build/lib/eNMRpy/Measurement/Flo.py @@ -0,0 +1,153 @@ +from .eNMR_Methods import _eNMR_Methods +import pandas as pd +import xml.etree.ElementTree as etree +import re +import numpy as np + + +class Flo(_eNMR_Methods): + ''' + This is the subsubclass of Masurement() and subclass of eNMR_Methods specialised to process data obtained from the experimental Swedish from Pavel set-up + the voltage list is valculated from the vd-values + + path: + relative or absolute path to the measurements folder + expno: + the to the experiment number corresponding EXPNO + dependency: + 'U': voltage dependent eNMR measurement + 'G': fieldgradient dependent eNMR measurement + + alias: + Here you can place an individual name relevant for plotting. If None, the path is taken instead. + lineb: + setting a standard-value for the linebroadening. + d: + electrode_distance + ''' + def __init__(self, path, expno, dependency='U', alias=None, lineb=.3, d=2.2e-2, cell_resistance=None): + + self.dependency = dependency + self.cell_resistance = cell_resistance + + super().__init__(path, expno, lineb=lineb, alias=alias) + + #self._x_axis = {"G": 'g in T/m', "U": 'U / [V]'}[dependency.upper()] + # import the diffusion parameters + diffpar = etree.parse(self.dateipfad+'/diff.xml') + root = diffpar.getroot() + self.Delta = float(root.findall('DELTA')[0].text)*1e-3 + self.delta = float(root.findall('delta')[0].text)*1e-3 #in Seconds + print('The diffusion parameters were read from the respective .XML!') + + if path[-1] == '/': + pulseprogram = open(path+'pulseprogram') + else: + pulseprogram = open(path+'/pulseprogram') + + self.pulseprogram = pulseprogram.read() + + bitregex = r"(define list.*bit.*|define list.*pol.*)" + vlistregex = r".*Voltage\sList.*=.*" + + # list, reading all lines with bit lists in the pulse program + rawlist = re.findall(bitregex, self.pulseprogram) + rawvlist = re.findall(vlistregex, self.pulseprogram) + + rawvlist = rawvlist[0].split('=') + vlist = eval(rawvlist[1]) + + # array of integers generated from the rawlist. Indexing like [bit, voltagestep] + bitarray = np.array([[int(i) for i in re.findall('{.*}', rawlist[j])[0][1:-1].split(' ')] for j in range(len(rawlist))]) + + + def byte_to_int(bitarray, row): + #converting the array into the correct string + bitstring = str(bitarray[:,row])[1:-1].replace(' ', '') + + # check the last bit for the polarity + if bitstring[-1] == '1': + polarity = 1 + elif bitstring[-1] == '0': + polarity = -1 + # transformation of the bitstring minus polarity, which is the last bit, with a base of 2 + intvar = int(bitstring[:-1], 2) + + return polarity*intvar + + + ulist = [byte_to_int(bitarray, i) for i in range(len(bitarray[0]))] + + if ulist == vlist: + pass + else: + raise ValueError('The decoded voltage list does not match the endcoding voltage list! Revisit your pulse program!') + + self.eNMRraw = pd.DataFrame(ulist, columns=['U / [V]']) + + try: + self.difflist = pd.read_csv(self.dateipfad+"/gradlist", + names=["g in T/m"])*0.01 + except: + print('gradlist not found. difflist imported instead') + self.difflist = pd.read_csv(self.dateipfad+"/difflist", + names=["g in T/m"])*0.01 + + self.eNMRraw["g in T/m"] = self.difflist + + self.d = d + self.g = self.eNMRraw["g in T/m"][0] + + + ## converts the vd-List + #for i, n in enumerate(self.eNMRraw['vd']): + #self.eNMRraw.loc[i, 'vd_temp'] = float(n[:-1]) + ## calculates the applied Voltages + + #if self.dependency.upper() == "U": + #self.eNMRraw[self._x_axis] = [ + #0 if (self.eNMRraw.loc[i,'vd_temp'] <= 0.6) + #else + #n if i%2==0 + #else + #n*-1 + #for i, n in enumerate(self.eNMRraw['vd_temp']*5)] + + #self.uInk = self.eNMRraw['U / [V]'][0] - self.eNMRraw['U / [V]'][1] + #if self.uInk == 0: + #self.uInk = self.eNMRraw['U / [V]'][0] - self.eNMRraw['U / [V]'][2] + #if self.uInk < 0: + #self.uInk *= -1 + + #elif self.dependency.upper() == "I": + #self.uInk = None + #self.eNMRraw[self._x_axis] = [ + #0 if (self.eNMRraw.loc[i,'vd_temp'] <= 0.6) + #else + #n if i%2==0 + #else + #n*-1 + #for i, n in enumerate(self.eNMRraw['vd_temp']) + #] + + #elif self.dependency.upper() == "RI": + #self.uInk = None + #self.eNMRraw[self._x_axis] = [ + #0 if (self.eNMRraw.loc[i,'vd_temp'] <= 0.6) + #else + #n if i%2==0 + #else + #n*-1 + #for i, n in enumerate(self.eNMRraw['vd_temp']) + #] + #self.uInk = self.eNMRraw['RI / V'][0] - self.eNMRraw['RI / V'][1] + #if self.uInk == 0: + #self.uInk = self.eNMRraw['RI / V'][0] - self.eNMRraw['RI / V'][2] + #if self.uInk < 0: + #self.uInk *= -1 + ## calculation of the Voltage from cell resistance and Current /1000 because of mA + #self.eNMRraw[self._x_axis] *= self.cell_resistance/1000 + + def plot_spec(self, row, xlim=None, figsize=None, invert_xaxis=True, sharey=True):#, ppm=True): + from .Juergen1 import Juergen1 as eNMR_Measurement + return eNMR_Measurement.plot_spec(self, row, xlim, figsize, invert_xaxis, sharey)#, ppm=True): diff --git a/build/lib/eNMRpy/Measurement/Pavel.py b/build/lib/eNMRpy/Measurement/Pavel.py index fd821b8..f3d41a3 100644 --- a/build/lib/eNMRpy/Measurement/Pavel.py +++ b/build/lib/eNMRpy/Measurement/Pavel.py @@ -22,7 +22,7 @@ class Pavel(_eNMR_Methods): d: electrode_distance ''' - def __init__(self, path, expno, dependency='U', alias=None, lineb=5, d=2.2e-2, cell_resistance=None): + def __init__(self, path, expno, dependency='U', alias=None, lineb=.3, d=2.2e-2, cell_resistance=None): self.dependency = dependency self.cell_resistance = cell_resistance @@ -40,10 +40,13 @@ def __init__(self, path, expno, dependency='U', alias=None, lineb=5, d=2.2e-2, c try: self.vdList = pd.read_csv(self.dateipfad+"/vdlist", names=["vd"]).loc[:len(self.data[:, 0])-1] + except IndexError: + raise IndexError('Your data is %i-dimensional instead of 2-dimensional. This may be an issue with Topspin. Most likely your measurement was aborted.'%self.data.ndim) except: - print('no vdList found, generated ones list instead') - self.vdList = pd.DataFrame(np.ones((len(self.data[:, 0]), 1)), - columns=["vd"]) + raise FileNotFoundError('no VD-List found!') + #print('no vdList found, generated ones list instead') + #self.vdList = pd.DataFrame(np.ones((len(self.data[:, 0]), 1)), + #columns=["vd"]) self.eNMRraw = self.vdList #self.vdList["U / [V]"] = hier die Konversion von vdlist zu Spannungsliste diff --git a/build/lib/eNMRpy/Measurement/__init__.py b/build/lib/eNMRpy/Measurement/__init__.py index 12bbe9d..9f66bf2 100644 --- a/build/lib/eNMRpy/Measurement/__init__.py +++ b/build/lib/eNMRpy/Measurement/__init__.py @@ -1,4 +1,3 @@ -__all__ = ['Pavel', 'Juergen1'] from .Pavel import Pavel -from .Juergen1 import Juergen1 -print('Measurement Module imported') +#from .Juergen1 import Juergen1 +#print('%s imported'%__name__) diff --git a/build/lib/eNMRpy/Measurement/base.py b/build/lib/eNMRpy/Measurement/base.py index df33fbc..3a6b90e 100644 --- a/build/lib/eNMRpy/Measurement/base.py +++ b/build/lib/eNMRpy/Measurement/base.py @@ -21,7 +21,7 @@ class Measurement(object): linebroadening """ - def __init__ (self, path, expno, alias=None, lineb=5, n_zf_F2=2**14): + def __init__ (self, path, expno, alias=None, lineb=.3, n_zf_F2=2**14): self.path = path self.expno = str(expno) self.dateipfad = self.path+self.expno diff --git a/build/lib/eNMRpy/Measurement/eNMR_Methods.py b/build/lib/eNMRpy/Measurement/eNMR_Methods.py index 56c5343..544e294 100644 --- a/build/lib/eNMRpy/Measurement/eNMR_Methods.py +++ b/build/lib/eNMRpy/Measurement/eNMR_Methods.py @@ -4,6 +4,7 @@ import nmrglue as ng import matplotlib.pyplot as plt import pandas as pd +import lmfit as lf class _eNMR_Methods(Measurement): """ @@ -388,6 +389,80 @@ def analyze_intensity(self, data='cropped', ph_var='ph0acme', normalize=True, yl return fig, intensity_data + def linreg(self, ulim=None, y_column='ph0'): + """ + standard linear regression method based on the least-square method + + ulim: + tuple defining the voltage limits for the regression e.g. ulim = (-100, 100) + y_column: + column(keyword) to be analyzed from the eNMRraw dataset + + stores results in lin_res_dic[y_column] + :returns: nothing + """ + + # select x-axis + #self._x_axis = {"U": "U / [V]", + #"G": "g in T/m" + #}[self.dependency.upper()] + + # convert data + _eNMRreg = self.eNMRraw[[self._x_axis, y_column]].sort_values(self._x_axis) + + # setting the axis for regression + if ulim is None: + umin = min(self.eNMRraw[self._x_axis]) + else: + umin = ulim[0] + + if ulim is None: + umax = max(self.eNMRraw[self._x_axis]) + else: + umax = ulim[1] + + _nparray = np.array(_eNMRreg[(self.eNMRraw[self._x_axis] <= umax) + == (self.eNMRraw[self._x_axis] >= umin)]) + _X_train, _Y_train = _nparray[:, 0], _nparray[:, 1] + + + def lineareq(x, m, b): + return x*m+b + + + # regression object + linmodel = lf.Model(lineareq) + linparams = linmodel.make_params() + linparams['b'].set(0) + linparams['m'].set(1) + result = linmodel.fit(_Y_train, x=_X_train, params=linparams) + + # linear parameters + m = result.best_values['m'] # slope + b = result.best_values['b'] # y(0) + _Y_pred = result.best_fit + + # calculation of the slope deviation + _sig_m_a = np.sqrt(np.sum((_Y_train-_Y_pred)**2)/(np.size(_Y_train)-2)) + _sig_m_b = np.sqrt(np.sum((_X_train-_X_train.mean())**2)) + sig_m = _sig_m_a/_sig_m_b + + # debug + #print(self.sig_m) + + # R^2 + r_square = 1 - result.residual.var() / np.var(_Y_train) + + self.lin_res_dic[y_column] = {'b': b, + 'm': m, + 'sig_m': sig_m, + 'r_square': r_square, + 'x': np.array(_X_train.tolist()).ravel(), + 'y': np.array(_Y_train.tolist()).ravel(), + 'y_fitted': _Y_pred.ravel(), + } + return + # should be replaced by a more recent function since it will be deprecated def lin_huber(self, epsilon=3, ulim=None, y_column='ph0'): """ @@ -411,64 +486,65 @@ def lin_huber(self, epsilon=3, ulim=None, y_column='ph0'): #}[self.dependency.upper()] # convert data - self._eNMRreg = self.eNMRraw[[self._x_axis, y_column]].sort_values(self._x_axis) + _eNMRreg = self.eNMRraw[[self._x_axis, y_column]].sort_values(self._x_axis) # setting the axis for regression if ulim is None: - self.umin = min(self.eNMRraw[self._x_axis]) + umin = min(self.eNMRraw[self._x_axis]) else: - self.umin = ulim[0] + umin = ulim[0] if ulim is None: - self.umax = max(self.eNMRraw[self._x_axis]) + umax = max(self.eNMRraw[self._x_axis]) else: - self.umax = ulim[1] + umax = ulim[1] - self._npMatrix = np.matrix(self._eNMRreg[(self.eNMRraw[self._x_axis] <= self.umax) - == (self.eNMRraw[self._x_axis] >= self.umin)]) + _npMatrix = np.matrix(_eNMRreg[(self.eNMRraw[self._x_axis] <= umax) + == (self.eNMRraw[self._x_axis] >= umin)]) - self._X_train, self._Y_train = self._npMatrix[:, 0], self._npMatrix[:, 1] + _X_train, _Y_train = _npMatrix[:, 0], _npMatrix[:, 1] # regression object - self.huber = hub.HuberRegressor(epsilon=epsilon) - self.huber.fit(self._X_train, self._Y_train) + huber = hub.HuberRegressor(epsilon=epsilon) + huber.fit(_X_train, _Y_train) # linear parameters - self.m = self.huber.coef_ # slope - self.b = self.huber.intercept_ # y(0) - self._y_pred = self.huber.predict(self._X_train) - self._y_pred = self._y_pred.reshape(np.size(self._X_train), 1) + m = huber.coef_ # slope + b = huber.intercept_ # y(0) + _y_pred = huber.predict(_X_train) + _y_pred = _y_pred.reshape(np.size(_X_train), 1) # drop the outliers - self._outX_train = np.array(self._X_train[[n == False for n in self.huber.outliers_]]) - self._outY_train = np.array(self._Y_train[[n == False for n in self.huber.outliers_]]) - self._outY_pred = np.array(self._y_pred[[n == False for n in self.huber.outliers_]]) + _outX_train = np.array(_X_train[[n == False for n in huber.outliers_]]) + _outY_train = np.array(_Y_train[[n == False for n in huber.outliers_]]) + _outY_pred = np.array(_y_pred[[n == False for n in huber.outliers_]]) # mark outliers in dataset # self._inliers = [n is not True for n in self.huber.outliers_] self.eNMRraw["outlier"] = True - for n in range(len(self._npMatrix[:, 0])): - self.eNMRraw.loc[self.eNMRraw[self._x_axis] == self._npMatrix[n, 0], "outlier"] = self.huber.outliers_[n] + for n in range(len(_npMatrix[:, 0])): + self.eNMRraw.loc[self.eNMRraw[self._x_axis] == _npMatrix[n, 0], "outlier"] = huber.outliers_[n] # calculation of the slope deviation - _sig_m_a = np.sqrt(np.sum((self._outY_train-self._outY_pred)**2)/(np.size(self._outY_train)-2)) - _sig_m_b = np.sqrt(np.sum((self._outX_train-self._outX_train.mean())**2)) - self.sig_m = _sig_m_a/_sig_m_b + _sig_m_a = np.sqrt(np.sum((_outY_train-_outY_pred)**2)/(np.size(_outY_train)-2)) + _sig_m_b = np.sqrt(np.sum((_outX_train-_outX_train.mean())**2)) + sig_m = _sig_m_a/_sig_m_b # debug #print(self.sig_m) # R^2 - self.r_square = self.huber.score(self._outX_train, self._outY_train) + r_square = huber.score(_outX_train, _outY_train) - self.lin_res_dic[y_column] = {'b': self.b, - 'm': self.m, - 'r^2': self.r_square, - 'x': np.array(self._X_train.tolist()).ravel(), - 'y': self._y_pred.ravel(), - 'sig_m': self.sig_m} + self.lin_res_dic[y_column] = {'b': b, + 'm': m, + 'r_square': r_square, + 'x': np.array(_X_train.tolist()).ravel(), + 'y': _Y_train, + 'y_fitted': _y_pred.ravel(), + 'sig_m': sig_m} def lin_display(self, ylim=None, show_slope_deviation=True, n_sigma_displayed=1, dpi=500, y_column='ph0', textpos=(0.5,0.15), extra_note=''): """ @@ -498,7 +574,8 @@ def lin_display(self, ylim=None, show_slope_deviation=True, n_sigma_displayed=1, #_x_axis = {"U":"U / [V]", "G":"g in T/m"} #self._x_axis = {"U":"U / [V]", "G":"g in T/m"}[self.dependency] - print("formula: y = {0}x + {1}".format(self.m,self.b)) + print("formula: y = {0}x + {1}".format(self.lin_res_dic[y_column]['m'] + ,self.lin_res_dic[y_column]['b'])) # create figure fig_enmr = plt.figure() @@ -507,24 +584,35 @@ def lin_display(self, ylim=None, show_slope_deviation=True, n_sigma_displayed=1, _ax = fig_enmr.add_subplot(111) # color format for outliers - colors = ["r" if n else "k" for n in self.eNMRraw.sort_values(self._x_axis)['outlier']] - - _ax.scatter(x=np.ravel(self._eNMRreg[self._x_axis]), - y=np.ravel(self._eNMRreg[y_column]), + try: + colors = ["r" if n else "k" for n in self.eNMRraw.sort_values(self._x_axis)['outlier']] + except KeyError: + colors = ["k" for n in self.eNMRraw[y_column]] + print('no outliers registered') + pass + + #_ax.scatter(x=np.ravel(self._eNMRreg[self._x_axis]), + #y=np.ravel(self._eNMRreg[y_column]), + _ax.scatter(x=np.ravel(self.eNMRraw[self._x_axis]), + y=np.ravel(self.eNMRraw[y_column]), marker="o", c=colors) _ax.set_ylim(ylim) # format the data for plotting - _xdata = np.ravel(self._X_train) - _ydata = np.ravel(self._y_pred) + _xdata = np.ravel(self.lin_res_dic[y_column]['x']) + _ydata = np.ravel(self.lin_res_dic[y_column]['y_fitted']) # Plot the regression _ax.plot(_xdata, _ydata, "r-") if show_slope_deviation: - _ax.fill_between(_xdata, _xdata*(self.m+n_sigma_displayed*self.sig_m)+self.b, - _xdata*(self.m-n_sigma_displayed*self.sig_m)+self.b, + #_ax.fill_between(_xdata, _xdata*(self.m+n_sigma_displayed*self.sig_m)+self.b, + #_xdata*(self.m-n_sigma_displayed*self.sig_m)+self.b, + #alpha=0.5, + #facecolor="blue") + _ax.fill_between(_xdata, _xdata*(self.lin_res_dic[y_column]['m']+n_sigma_displayed*self.lin_res_dic[y_column]['sig_m'])+self.lin_res_dic[y_column]['b'], + _xdata*(self.lin_res_dic[y_column]['m']-n_sigma_displayed*self.lin_res_dic[y_column]['sig_m'])+self.lin_res_dic[y_column]['b'], alpha=0.5, facecolor="blue") @@ -551,10 +639,10 @@ def lin_display(self, ylim=None, show_slope_deviation=True, n_sigma_displayed=1, # plotting the Textbox plt.text(textx, texty, - "y = %.4f $\cdot$ x + %4.2f\n$R^2$=%4.3f; $\sigma_m=$%4.4f"%(self.m, - self.b, - self.r_square, - self.sig_m), + "y = %.4f $\cdot$ x + %4.2f\n$R^2$=%4.3f; $\sigma_m=$%4.4f"%(self.lin_res_dic[y_column]['m'], + self.lin_res_dic[y_column]['b'], + self.lin_res_dic[y_column]['r_square'], + self.lin_res_dic[y_column]['sig_m']), fontsize=14, bbox={'facecolor':'white', 'alpha':0.7,'pad':10}, horizontalalignment='center', @@ -732,6 +820,9 @@ def output_results(self, path=None): """ saves the mobility result data in the measurement-folder similar to obj.output_data() """ + from warnings import warn + warn('this function was renamed output_properties_csv') + results_output = pd.Series([self.nuc, self.mu[0], self.sig_m*self.mu[0], @@ -755,11 +846,18 @@ def output_results(self, path=None): results_output.to_csv(path+"mobility_data_"+self.expno+".csv") else: print('ooops!') + + def output_properties_csv(self, path=None): + """ + saves the mobility result data in the measurement-folder similar to obj.output_phase_data() + """ + self.output_results(self, path) def output_all_results(self, path=None, data=False): """ saves the mobility result data in the measurement-folder similar to obj.output_data() """ + from pandas import ExcelWriter from openpyxl import load_workbook @@ -808,11 +906,12 @@ def mobility(self, y_column='ph0acme', electrode_distance=None, verbose=True): """ calculates and returns (mobility, deviation) from the regression data """ - #self.lin_res_dic = {y_column: {'b': self.b, - #'m': self.m, - #'r^2': self.r_square, - #'y_reg': self._y_pred, - #'sig_m': self.sig_m}} + + #self.lin_res_dic = {y_column: {'b': self.b, + #'m': self.m, + #'r^2': self.r_square, + #'y_reg': self._y_pred, + #'sig_m': self.sig_m}} if electrode_distance is None: d = self.d else: @@ -836,14 +935,14 @@ def mobility(self, y_column='ph0acme', electrode_distance=None, verbose=True): else: m = self.lin_res_dic[y_column]['m'] sig_m = self.lin_res_dic[y_column]['sig_m'] - self.mu = (m*d)/(self.gamma*self.delta*self.Delta*g) - self.lin_res_dic[y_column]['mu']=self.mu - self.lin_res_dic[y_column]['mu_err']=self.mu*(sig_m/m) + mu = (m*d)/(self.gamma*self.delta*self.Delta*g) + self.lin_res_dic[y_column]['mu']= mu + self.lin_res_dic[y_column]['mu_err']= mu*(sig_m/m) #return self.mu, self.mu*(sig_m/m) - self.sig_m, self.m = sig_m, m + #self.sig_m, self.m = sig_m, m if verbose: - print ('%.2E (m^2/Vs)'%self.mu[0],'+- %.2E'%(self.mu*(self.sig_m/self.m))) + print ('%.2E (m^2/Vs)'%self.lin_res_dic[y_column]['mu'],'+- %.2E'%(self.lin_res_dic[y_column]['mu']*(self.lin_res_dic[y_column]['sig_m']/self.lin_res_dic[y_column]['m']))) - return self.mu, self.mu*(self.sig_m/self.m) + return self.lin_res_dic[y_column]['mu'], self.lin_res_dic[y_column]['mu']*(self.lin_res_dic[y_column]['sig_m']/self.lin_res_dic[y_column]['m']) diff --git a/build/lib/eNMRpy/Phasefitting.py b/build/lib/eNMRpy/Phasefitting.py index 7ac100f..5d4ac5f 100644 --- a/build/lib/eNMRpy/Phasefitting.py +++ b/build/lib/eNMRpy/Phasefitting.py @@ -39,7 +39,7 @@ def set_peaks(m, n=-1, timeout=-1, xlim=None, **plot_kwargs): #,xlim=(0,10) return arr -def peakpicker(x, y=None, width=10, threshold=1e5): +def peakpicker(x, y=None, inverse_order=False, width=10, threshold=1e5): """ tool to automatically determine peak positions in an x-y spectrum @@ -47,6 +47,10 @@ def peakpicker(x, y=None, width=10, threshold=1e5): x [optional]: Measurement-object of which the first slice is taken y: intensity + inverse_order: + inverts the order of peaks in the array + this could be helpful when passing it to make_model + width: scan width. should be even numbered @@ -105,7 +109,11 @@ def check_peak(y_out, width=width): # i-width//2 ensures that the right coordinates are given for the respective peak peaks.append((x[i-width//2], y[i-width//2].real)) - return np.array(peaks) + if inverse_order: + return np.array(peaks)[::-1] + + elif not inverse_order: + return np.array(peaks) def make_model(peaks, print_params=True): @@ -137,50 +145,55 @@ def reduce_fitted_phases(Measurementobject, SpecModel): m.eNMRraw[k+'reduced'] = m.eNMRraw[k]*m.d/m.delta/m.Delta/m.g/m.gamma m.eNMRraw[k+'reduced'] -= m.eNMRraw.loc[m.eNMRraw['U / [V]']==0, k+'reduced'][0] -def fit_Measurement(obj_M, obj_S, plot=False, peak_deconvolution=False, savepath=None, fixed_parameters=None): +def fit_Measurement(obj_M, obj_S, fixed_parameters=None, plot=False, savepath=None, **plot_kwargs): ''' function to fit a the series of voltage dependent spectra contained in a typical eNMR measurement obj_M: object of the class eNMR_Measurement obj_S: object of the class SpecModel fixed_parameters: List of parameters to be fixed after the first fit - ''' - fp = [] + **plot_kwargs are passed to SpecModel.fit: + peak_deconvolution=False, parse_complex='abs','real, or 'imag' + ''' - if fixed_parameters is None: - i = 0 + i=0 + + if fixed_parameters is not None: + fp = [] # working list of parameter-objects - elif fixed_parameters is not None: - #finds the matching parameter keys - if len(fixed_parameters[0]) == 2: - fp = fixed_parameters - else: - for i in range(len(fixed_parameters)): - for p in obj_S.params: - if p[0] == fixed_parameters[i]: - fp.append(p) - - i = 1 #counter set to one for the rest of the spectra to be fitted - fig = obj_S.fit(obj_M.ppm, obj_M.data[0], plot=plot, peak_deconvolution=peak_deconvolution) + # iterates the list of parameters + for k in obj_S.params.keys(): + # if the parameter name p[0] matches any string in fixed_parameters + if any(k == np.array(fixed_parameters)): + # append the parameter to the working list + fp.append(k) + + + fig = obj_S.fit(obj_M.ppm, obj_M.data[0], **plot_kwargs) + print('row 0 fitted including fixed_parameters being varied') ph_res = obj_S.get_result_values() for par in ph_res.keys(): - #obj_M.eNMRraw.set_value(row, par, ph_res[par]) + # saves the results from row 0 in the eNMRraw-DataFrame obj_M.eNMRraw.at[0, par] = ph_res[par] - + + for p in fp: # fixes all variables listed in fixed_parameters + obj_S.params[p].set(obj_S.result.params[p].value) obj_S.params[p].set(vary=False) print('%s not varied!'%p) if (plot is True) and (savepath is not None): fig.savefig(savepath+'%.1f'%obj_M.eNMRraw.loc[0, obj_M._x_axis]+'.png', dpi=300) - - print('start fitting') + + i = 1 #counter set to one for the rest of the spectra to be fitted + + print('start fitting from row %i'%i) for row in range(i, obj_M.data[:,0].size): - fig = obj_S.fit(obj_M.ppm, obj_M.data[row], plot=plot, peak_deconvolution=peak_deconvolution) + fig = obj_S.fit(obj_M.ppm, obj_M.data[row], plot=plot, **plot_kwargs) ph_res = obj_S.get_result_values() for par in ph_res.keys(): @@ -190,8 +203,8 @@ def fit_Measurement(obj_M, obj_S, plot=False, peak_deconvolution=False, savepath if (plot is True) and (savepath is not None): fig.savefig(savepath+'%.1f'%obj_M.eNMRraw.loc[row, obj_M._x_axis]+'.png', dpi=300) - for p in fp: # reset all vary-Values - obj_S.params[p].set(vary=True) + #for p in fp: # reset all vary-Values + #obj_S.params[p].set(vary=True) print('fitting finished') @@ -494,13 +507,16 @@ def plot_init_spec(self, xdata, single_peaks=False, fig=None): return fig - def fit(self, xdata, ydata, plot=False, peak_deconvolution=False): + def fit(self, xdata, ydata, plot=False, peak_deconvolution=False, parse_complex='real', figsize=None): """ method to fit a single spectrum consisting of xdata and ydata with the previously defined model and parameters stores the result in self.result + parse_complex: representation of the complex data when plotting + "real", "imag", or "abs" + returns a matplotlib figure if plot=True """ @@ -509,8 +525,11 @@ def fit(self, xdata, ydata, plot=False, peak_deconvolution=False): fig = None if plot: - fig = self.result.plot()[0] - + fig = self.result.plot(parse_complex=parse_complex)[0] + if figsize != None: + fig = plt.gcf() + fig.set_size_inches(figsize) + if peak_deconvolution and plot: ax = fig.gca() for n in range(self.n): diff --git a/build/lib/eNMRpy/__init__.py b/build/lib/eNMRpy/__init__.py index 4a29139..1cc7071 100644 --- a/build/lib/eNMRpy/__init__.py +++ b/build/lib/eNMRpy/__init__.py @@ -13,10 +13,10 @@ under eNMRpy.MOSY """ -from .Phasefitting import SpecModel -from .MOSY import MOSY +#from .Phasefitting import SpecModel +#from .MOSY import MOSY # Pavel Class as the Std Import-Class since it is mostly used in our working group from .Measurement import Pavel as Import_eNMR_Measurement - -print('%s imported'%__name__) +from . import tools +#print('%s imported'%__name__) diff --git a/build/lib/eNMRpy/tools.py b/build/lib/eNMRpy/tools.py index fff04d1..9a207e4 100644 --- a/build/lib/eNMRpy/tools.py +++ b/build/lib/eNMRpy/tools.py @@ -9,7 +9,9 @@ class Load_eNMRpy_data(_eNMR_Methods): returns: eNMR-Measurement-object which can be used as usual. ''' - def __init__(self, path): + def __init__(self, path, load_spectral_data=True): + + self.load_spectral_data=load_spectral_data if path.split('.')[-1] != 'eNMRpy': raise NameError('the given path does not correspond to a .eNMRpy file!') @@ -17,22 +19,41 @@ def __init__(self, path): f = open(path) - dic = eval(f.read().replace('array','')) + dic = eval(f.read().replace('array','').replace('matrix', '')) for k in dic: if '_type_pd.DataFrame' in k: setattr(self, k.replace('_type_pd.DataFrame',''), pd.read_json(dic[k])) + elif ((k=='data') or (k=='ppm')) and (load_spectral_data != True): + pass else: setattr(self, k, dic[k]) - self.data = np.loadtxt(StringIO(self.data), dtype=complex) - self.ppm = np.loadtxt(StringIO(self.ppm), dtype=float) - - # derived instance variables - self.data_orig = self.data - self._ppmscale = np.linspace(self._ppm_l, self._ppm_r, self.n_zf_F2) # np.size(self.data[0,:])) - #self.ppm = self._ppmscale - self.fid = self.data + if load_spectral_data: + self.data = np.loadtxt(StringIO(self.data), dtype=complex) + self.ppm = np.loadtxt(StringIO(self.ppm), dtype=float) + + # derived instance variables + self.data_orig = self.data + self._ppmscale = np.linspace(self._ppm_l, self._ppm_r, self.n_zf_F2) # np.size(self.data[0,:])) + #self.ppm = self._ppmscale + self.fid = self.data + + def __repr__(self): + if self.load_spectral_data: + return '''%s, expno %s, Delta = %.1fms, ppm range: %.1f to %.1f + delta= %.1fms, g= %.3f T/m, e-distance=%.0fmm'''%( + self.nuc, self.expno, self.Delta*1000, + self.ppm[0], self.ppm[-1], + self.delta*1000, self.g, self.d*1000 + ) + else: + return '''spectral data not loaded! + %s, expno %s, Delta = %.1fms, + delta= %.1fms, g= %.3f T/m, e-distance=%.0fmm'''%( + self.nuc, self.expno, self.Delta*1000, + self.delta*1000, self.g, self.d*1000 + ) def plot_fid(self): raise ValueError('this method is not available in loaded .eNMRpy file! Sorry! please consider the original data') diff --git a/dist/eNMRpy-0.0.2.tar.gz b/dist/eNMRpy-0.0.2.tar.gz deleted file mode 100644 index 8aab9c97fb72344ea722837e424ee6cc9fe2c45f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28972 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 diff --git a/dist/eNMRpy-0.0.2-py3-none-any.whl b/dist/eNMRpy-0.0.3-py3-none-any.whl similarity index 52% rename from dist/eNMRpy-0.0.2-py3-none-any.whl rename to dist/eNMRpy-0.0.3-py3-none-any.whl index c5c1ec54e31c8825506c76d3b62d3873ff9b57fc..fbd322b16883684396fbca9095b5ba3856fca52c 100644 GIT binary patch delta 33235 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 F{vVoWorwSd delta 25870 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 U0R*VTZ%;paTj+=o0dLU%11xg~X8-^I diff --git a/dist/eNMRpy-0.0.3.tar.gz b/dist/eNMRpy-0.0.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d649f31fefa51e960558012aa1ca577e853be67e GIT binary patch literal 31054 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 repository. When using this for scientific pusposes, please cite this paper. - Further documentation will be available soon. + For documentation please read the read the docs page. # Installation diff --git a/eNMRpy.egg-info/SOURCES.txt b/eNMRpy.egg-info/SOURCES.txt index eb1f41b..d4ab98b 100644 --- a/eNMRpy.egg-info/SOURCES.txt +++ b/eNMRpy.egg-info/SOURCES.txt @@ -11,6 +11,7 @@ eNMRpy.egg-info/dependency_links.txt eNMRpy.egg-info/requires.txt eNMRpy.egg-info/top_level.txt eNMRpy/Measurement/Emma.py +eNMRpy/Measurement/Flo.py eNMRpy/Measurement/Juergen1.py eNMRpy/Measurement/Pavel.py eNMRpy/Measurement/Simulated.py diff --git a/eNMRpy.egg-info/requires.txt b/eNMRpy.egg-info/requires.txt index 02a07f5..5c82272 100644 --- a/eNMRpy.egg-info/requires.txt +++ b/eNMRpy.egg-info/requires.txt @@ -1,5 +1,5 @@ nmrglue -lmfit +lmfit>=1.0.1 pandas matplotlib>=3.1.3 numpy