From 4d140e4de6e2b9703f75b050538d8808a83fa3f3 Mon Sep 17 00:00:00 2001 From: Flackermann Date: Wed, 22 Jul 2020 16:02:41 +0200 Subject: [PATCH] 2nd initial commit to make it work in the first place --- CHANGES.txt | 5 + build.sh | 1 + .../lib/eNMRly}/Measurement/Emma.py | 0 .../lib/eNMRly}/Measurement/Juergen1.py | 0 .../lib/eNMRly}/Measurement/Pavel.py | 0 .../lib/eNMRly}/Measurement/Simulated.py | 0 .../lib/eNMRly}/Measurement/__init__.py | 0 .../lib/eNMRly}/Measurement/base.py | 0 .../lib/eNMRly}/Measurement/eNMR_Methods.py | 0 .../lib/eNMRly}/Measurement/tools.py | 0 build/lib/eNMRly/Phasefitting.py | 16 +- build/lib/eNMRly/__init__.py | 6 +- dist/eNMRly-0.0.0-py3-none-any.whl | Bin 0 -> 14066 bytes dist/eNMRly-0.0.0-py3.8.egg | Bin 26736 -> 0 bytes dist/eNMRly-0.0.0.tar.gz | Bin 12221 -> 26849 bytes eNMRly.egg-info/SOURCES.txt | 19 - eNMRly/__pycache__/__init__.cpython-36.pyc | Bin 365 -> 0 bytes {eNMRly.egg-info => eNMRpy.egg-info}/PKG-INFO | 4 +- .../SOURCES.txt | 0 .../dependency_links.txt | 0 .../requires.txt | 0 .../top_level.txt | 1 + {eNMRly => eNMRpy}/GUI/.idea/Tkinter.iml | 0 .../GUI/.idea/dictionaries/florians.xml | 0 {eNMRly => eNMRpy}/GUI/.idea/misc.xml | 0 {eNMRly => eNMRpy}/GUI/.idea/modules.xml | 0 {eNMRly => eNMRpy}/GUI/.idea/workspace.xml | 0 .../Tkinter-learn-checkpoint.ipynb | 0 {eNMRly => eNMRpy}/GUI/MyFirstGUI.py | 0 {eNMRly => eNMRpy}/GUI/Tkinter-learn.ipynb | 0 {eNMRly => eNMRpy}/GUI/__init__.py | 0 eNMRpy/GUI/module/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin {eNMRly => eNMRpy}/GUI/module/test.py | 0 {eNMRly => eNMRpy}/MOSY.py | 0 eNMRpy/Measurement/Emma.py | 179 ++++ eNMRpy/Measurement/Juergen1.py | 203 ++++ eNMRpy/Measurement/Pavel.py | 122 +++ eNMRpy/Measurement/Simulated.py | 36 + eNMRpy/Measurement/__init__.py | 4 + .../__pycache__/Juergen1.cpython-36.pyc | Bin .../__pycache__/Juergen1.cpython-38.pyc | Bin .../__pycache__/MOSY.cpython-36.pyc | Bin .../__pycache__/Measurement.cpython-36.pyc | Bin .../__pycache__/Pavel.cpython-36.pyc | Bin .../__pycache__/Pavel.cpython-38.pyc | Bin .../__pycache__/__init__.cpython-36.pyc | Bin .../__pycache__/__init__.cpython-38.pyc | Bin .../__pycache__/base.cpython-36.pyc | Bin .../__pycache__/base.cpython-38.pyc | Bin .../eNMR_Measurement.cpython-36.pyc | Bin .../__pycache__/eNMR_Methods.cpython-36.pyc | Bin .../__pycache__/eNMR_Methods.cpython-38.pyc | Bin .../__pycache__/relegend.cpython-36.pyc | Bin .../__pycache__/tools.cpython-36.pyc | Bin eNMRpy/Measurement/base.py | 305 ++++++ eNMRpy/Measurement/eNMR_Methods.py | 957 ++++++++++++++++++ eNMRpy/Measurement/tools.py | 49 + {eNMRly => eNMRpy}/Phasefitting.py | 0 {eNMRly => eNMRpy}/__init__.py | 0 .../__pycache__/MOSY.cpython-36.pyc | Bin .../__pycache__/MOSY.cpython-38.pyc | Bin .../__pycache__/Phasefitting.cpython-36.pyc | Bin 19586 -> 19586 bytes .../__pycache__/Phasefitting.cpython-38.pyc | Bin eNMRpy/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 375 bytes .../__pycache__/__init__.cpython-38.pyc | Bin .../__pycache__/eNMR.cpython-36.pyc | Bin setup.py | 6 +- 68 files changed, 1887 insertions(+), 26 deletions(-) create mode 100644 build.sh rename {eNMRly => build/lib/eNMRly}/Measurement/Emma.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/Juergen1.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/Pavel.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/Simulated.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/__init__.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/base.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/eNMR_Methods.py (100%) rename {eNMRly => build/lib/eNMRly}/Measurement/tools.py (100%) create mode 100644 dist/eNMRly-0.0.0-py3-none-any.whl delete mode 100644 dist/eNMRly-0.0.0-py3.8.egg delete mode 100644 eNMRly.egg-info/SOURCES.txt delete mode 100644 eNMRly/__pycache__/__init__.cpython-36.pyc rename {eNMRly.egg-info => eNMRpy.egg-info}/PKG-INFO (98%) rename eNMRly/GUI/module/__init__.py => eNMRpy.egg-info/SOURCES.txt (100%) rename {eNMRly.egg-info => eNMRpy.egg-info}/dependency_links.txt (100%) rename {eNMRly.egg-info => eNMRpy.egg-info}/requires.txt (100%) rename {eNMRly.egg-info => eNMRpy.egg-info}/top_level.txt (50%) rename {eNMRly => eNMRpy}/GUI/.idea/Tkinter.iml (100%) rename {eNMRly => eNMRpy}/GUI/.idea/dictionaries/florians.xml (100%) rename {eNMRly => eNMRpy}/GUI/.idea/misc.xml (100%) rename {eNMRly => eNMRpy}/GUI/.idea/modules.xml (100%) rename {eNMRly => eNMRpy}/GUI/.idea/workspace.xml (100%) rename {eNMRly => eNMRpy}/GUI/.ipynb_checkpoints/Tkinter-learn-checkpoint.ipynb (100%) rename {eNMRly => eNMRpy}/GUI/MyFirstGUI.py (100%) rename {eNMRly => eNMRpy}/GUI/Tkinter-learn.ipynb (100%) rename {eNMRly => eNMRpy}/GUI/__init__.py (100%) create mode 100644 eNMRpy/GUI/module/__init__.py rename {eNMRly => eNMRpy}/GUI/module/__pycache__/__init__.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/GUI/module/test.py (100%) rename {eNMRly => eNMRpy}/MOSY.py (100%) create mode 100644 eNMRpy/Measurement/Emma.py create mode 100644 eNMRpy/Measurement/Juergen1.py create mode 100644 eNMRpy/Measurement/Pavel.py create mode 100644 eNMRpy/Measurement/Simulated.py create mode 100644 eNMRpy/Measurement/__init__.py rename {eNMRly => eNMRpy}/Measurement/__pycache__/Juergen1.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/Juergen1.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/MOSY.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/Measurement.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/Pavel.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/Pavel.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/__init__.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/__init__.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/base.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/base.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/eNMR_Measurement.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/eNMR_Methods.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/eNMR_Methods.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/relegend.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/Measurement/__pycache__/tools.cpython-36.pyc (100%) create mode 100644 eNMRpy/Measurement/base.py create mode 100644 eNMRpy/Measurement/eNMR_Methods.py create mode 100644 eNMRpy/Measurement/tools.py rename {eNMRly => eNMRpy}/Phasefitting.py (100%) rename {eNMRly => eNMRpy}/__init__.py (100%) rename {eNMRly => eNMRpy}/__pycache__/MOSY.cpython-36.pyc (100%) rename {eNMRly => eNMRpy}/__pycache__/MOSY.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/__pycache__/Phasefitting.cpython-36.pyc (99%) rename {eNMRly => eNMRpy}/__pycache__/Phasefitting.cpython-38.pyc (100%) create mode 100644 eNMRpy/__pycache__/__init__.cpython-36.pyc rename {eNMRly => eNMRpy}/__pycache__/__init__.cpython-38.pyc (100%) rename {eNMRly => eNMRpy}/__pycache__/eNMR.cpython-36.pyc (100%) diff --git a/CHANGES.txt b/CHANGES.txt index 2500310..08a7835 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1 +1,6 @@ +Version 0.0.1 +- The subpkg Measurement was added to the build + +Version 0.0.0 - SpecModel.set_mathematical_constraints() now resets all mathematical constraints before new assignment, which can be deactivated by setting reset=False + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..776ad21 --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +python3 setup.py sdist bdist_wheel diff --git a/eNMRly/Measurement/Emma.py b/build/lib/eNMRly/Measurement/Emma.py similarity index 100% rename from eNMRly/Measurement/Emma.py rename to build/lib/eNMRly/Measurement/Emma.py diff --git a/eNMRly/Measurement/Juergen1.py b/build/lib/eNMRly/Measurement/Juergen1.py similarity index 100% rename from eNMRly/Measurement/Juergen1.py rename to build/lib/eNMRly/Measurement/Juergen1.py diff --git a/eNMRly/Measurement/Pavel.py b/build/lib/eNMRly/Measurement/Pavel.py similarity index 100% rename from eNMRly/Measurement/Pavel.py rename to build/lib/eNMRly/Measurement/Pavel.py diff --git a/eNMRly/Measurement/Simulated.py b/build/lib/eNMRly/Measurement/Simulated.py similarity index 100% rename from eNMRly/Measurement/Simulated.py rename to build/lib/eNMRly/Measurement/Simulated.py diff --git a/eNMRly/Measurement/__init__.py b/build/lib/eNMRly/Measurement/__init__.py similarity index 100% rename from eNMRly/Measurement/__init__.py rename to build/lib/eNMRly/Measurement/__init__.py diff --git a/eNMRly/Measurement/base.py b/build/lib/eNMRly/Measurement/base.py similarity index 100% rename from eNMRly/Measurement/base.py rename to build/lib/eNMRly/Measurement/base.py diff --git a/eNMRly/Measurement/eNMR_Methods.py b/build/lib/eNMRly/Measurement/eNMR_Methods.py similarity index 100% rename from eNMRly/Measurement/eNMR_Methods.py rename to build/lib/eNMRly/Measurement/eNMR_Methods.py diff --git a/eNMRly/Measurement/tools.py b/build/lib/eNMRly/Measurement/tools.py similarity index 100% rename from eNMRly/Measurement/tools.py rename to build/lib/eNMRly/Measurement/tools.py diff --git a/build/lib/eNMRly/Phasefitting.py b/build/lib/eNMRly/Phasefitting.py index bf82a5c..77a7909 100644 --- a/build/lib/eNMRly/Phasefitting.py +++ b/build/lib/eNMRly/Phasefitting.py @@ -299,10 +299,12 @@ def __init__(self, n_peaks, verbose=False, model='Lorentz'): if verbose: self.params.pretty_print() - def set_mathematical_constraints(self, expr): + 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') @@ -310,6 +312,16 @@ def set_mathematical_constraints(self, expr): 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('=') @@ -318,6 +330,8 @@ def set_mathematical_constraints(self, expr): 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 diff --git a/build/lib/eNMRly/__init__.py b/build/lib/eNMRly/__init__.py index 41358e7..0d3523a 100644 --- a/build/lib/eNMRly/__init__.py +++ b/build/lib/eNMRly/__init__.py @@ -1,2 +1,6 @@ -#__all__ = ['Measurement', 'Phasefitting', 'MOSY'] +__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/dist/eNMRly-0.0.0-py3-none-any.whl b/dist/eNMRly-0.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..6f1dc74650ac3e7541233b20f550cb0ce80d31b4 GIT binary patch literal 14066 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 literal 0 HcmV?d00001 diff --git a/dist/eNMRly-0.0.0-py3.8.egg b/dist/eNMRly-0.0.0-py3.8.egg deleted file mode 100644 index 07d6efa7248db498e8f7e4df6eb07cb5b1850bde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26736 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 z 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 gd>%dzpNG%G=i&44dH6hh?*94z0TqZX!P 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 Zzis)t{=dTep#P6hPY*f(1P2u4e*hhC0TloM diff --git a/eNMRly.egg-info/SOURCES.txt b/eNMRly.egg-info/SOURCES.txt deleted file mode 100644 index 8c580a9..0000000 --- a/eNMRly.egg-info/SOURCES.txt +++ /dev/null @@ -1,19 +0,0 @@ -MANIFEST.in -README.txt -setup.py -eNMRly/MOSY.py -eNMRly/Phasefitting.py -eNMRly/__init__.py -eNMRly.egg-info/PKG-INFO -eNMRly.egg-info/SOURCES.txt -eNMRly.egg-info/dependency_links.txt -eNMRly.egg-info/requires.txt -eNMRly.egg-info/top_level.txt -eNMRly/Measurement/Emma.py -eNMRly/Measurement/Juergen1.py -eNMRly/Measurement/Pavel.py -eNMRly/Measurement/Simulated.py -eNMRly/Measurement/__init__.py -eNMRly/Measurement/base.py -eNMRly/Measurement/eNMR_Methods.py -eNMRly/Measurement/tools.py \ No newline at end of file diff --git a/eNMRly/__pycache__/__init__.cpython-36.pyc b/eNMRly/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 0c5811de0f51cab67d033eca3f643a9a2fcdb97e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 365 zcmYk1zfQ#<6vm-{ZmZrKM_o+r+C>HvCu3Y4dTs1rWFfVj+oDhi<(kyhm(f@1>f|dp zNozDh&Ue0i2Y!LG)oP!f9J4os(1)+T6?k{$t3UU`QG+Ssm!`VkN zQZZd%1PPB{vCkGI%93%fG*IW7gZ#vvlVSVQ#wfoj?=w8cMfD8w*ORgq@SsUCxK?+>1J;Grk5IdwOgQt@5M#B|ZZQ5Cb0&x1#+c}Y YW9-xnV|t-9-$}TfzS^f=7I-=DZzfw}lmGw# diff --git a/eNMRly.egg-info/PKG-INFO b/eNMRpy.egg-info/PKG-INFO similarity index 98% rename from eNMRly.egg-info/PKG-INFO rename to eNMRpy.egg-info/PKG-INFO index c11bb7f..9a4eee2 100644 --- a/eNMRly.egg-info/PKG-INFO +++ b/eNMRpy.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 -Name: eNMRly -Version: 0.0.0 +Name: eNMRpy +Version: 0.0.1 Summary: nmrglue-based package for the import and analysis of electrophoretic NMR-data Home-page: UNKNOWN Author: Florian Ackermann diff --git a/eNMRly/GUI/module/__init__.py b/eNMRpy.egg-info/SOURCES.txt similarity index 100% rename from eNMRly/GUI/module/__init__.py rename to eNMRpy.egg-info/SOURCES.txt diff --git a/eNMRly.egg-info/dependency_links.txt b/eNMRpy.egg-info/dependency_links.txt similarity index 100% rename from eNMRly.egg-info/dependency_links.txt rename to eNMRpy.egg-info/dependency_links.txt diff --git a/eNMRly.egg-info/requires.txt b/eNMRpy.egg-info/requires.txt similarity index 100% rename from eNMRly.egg-info/requires.txt rename to eNMRpy.egg-info/requires.txt diff --git a/eNMRly.egg-info/top_level.txt b/eNMRpy.egg-info/top_level.txt similarity index 50% rename from eNMRly.egg-info/top_level.txt rename to eNMRpy.egg-info/top_level.txt index 6687f9e..b8f8767 100644 --- a/eNMRly.egg-info/top_level.txt +++ b/eNMRpy.egg-info/top_level.txt @@ -1 +1,2 @@ eNMRly +eNMRpy diff --git a/eNMRly/GUI/.idea/Tkinter.iml b/eNMRpy/GUI/.idea/Tkinter.iml similarity index 100% rename from eNMRly/GUI/.idea/Tkinter.iml rename to eNMRpy/GUI/.idea/Tkinter.iml diff --git a/eNMRly/GUI/.idea/dictionaries/florians.xml b/eNMRpy/GUI/.idea/dictionaries/florians.xml similarity index 100% rename from eNMRly/GUI/.idea/dictionaries/florians.xml rename to eNMRpy/GUI/.idea/dictionaries/florians.xml diff --git a/eNMRly/GUI/.idea/misc.xml b/eNMRpy/GUI/.idea/misc.xml similarity index 100% rename from eNMRly/GUI/.idea/misc.xml rename to eNMRpy/GUI/.idea/misc.xml diff --git a/eNMRly/GUI/.idea/modules.xml b/eNMRpy/GUI/.idea/modules.xml similarity index 100% rename from eNMRly/GUI/.idea/modules.xml rename to eNMRpy/GUI/.idea/modules.xml diff --git a/eNMRly/GUI/.idea/workspace.xml b/eNMRpy/GUI/.idea/workspace.xml similarity index 100% rename from eNMRly/GUI/.idea/workspace.xml rename to eNMRpy/GUI/.idea/workspace.xml diff --git a/eNMRly/GUI/.ipynb_checkpoints/Tkinter-learn-checkpoint.ipynb b/eNMRpy/GUI/.ipynb_checkpoints/Tkinter-learn-checkpoint.ipynb similarity index 100% rename from eNMRly/GUI/.ipynb_checkpoints/Tkinter-learn-checkpoint.ipynb rename to eNMRpy/GUI/.ipynb_checkpoints/Tkinter-learn-checkpoint.ipynb diff --git a/eNMRly/GUI/MyFirstGUI.py b/eNMRpy/GUI/MyFirstGUI.py similarity index 100% rename from eNMRly/GUI/MyFirstGUI.py rename to eNMRpy/GUI/MyFirstGUI.py diff --git a/eNMRly/GUI/Tkinter-learn.ipynb b/eNMRpy/GUI/Tkinter-learn.ipynb similarity index 100% rename from eNMRly/GUI/Tkinter-learn.ipynb rename to eNMRpy/GUI/Tkinter-learn.ipynb diff --git a/eNMRly/GUI/__init__.py b/eNMRpy/GUI/__init__.py similarity index 100% rename from eNMRly/GUI/__init__.py rename to eNMRpy/GUI/__init__.py diff --git a/eNMRpy/GUI/module/__init__.py b/eNMRpy/GUI/module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eNMRly/GUI/module/__pycache__/__init__.cpython-36.pyc b/eNMRpy/GUI/module/__pycache__/__init__.cpython-36.pyc similarity index 100% rename from eNMRly/GUI/module/__pycache__/__init__.cpython-36.pyc rename to eNMRpy/GUI/module/__pycache__/__init__.cpython-36.pyc diff --git a/eNMRly/GUI/module/test.py b/eNMRpy/GUI/module/test.py similarity index 100% rename from eNMRly/GUI/module/test.py rename to eNMRpy/GUI/module/test.py diff --git a/eNMRly/MOSY.py b/eNMRpy/MOSY.py similarity index 100% rename from eNMRly/MOSY.py rename to eNMRpy/MOSY.py diff --git a/eNMRpy/Measurement/Emma.py b/eNMRpy/Measurement/Emma.py new file mode 100644 index 0000000..5528e0c --- /dev/null +++ b/eNMRpy/Measurement/Emma.py @@ -0,0 +1,179 @@ +## 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/eNMRpy/Measurement/Juergen1.py b/eNMRpy/Measurement/Juergen1.py new file mode 100644 index 0000000..111c9ff --- /dev/null +++ b/eNMRpy/Measurement/Juergen1.py @@ -0,0 +1,203 @@ +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/eNMRpy/Measurement/Pavel.py b/eNMRpy/Measurement/Pavel.py new file mode 100644 index 0000000..4b57c69 --- /dev/null +++ b/eNMRpy/Measurement/Pavel.py @@ -0,0 +1,122 @@ +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/eNMRpy/Measurement/Simulated.py b/eNMRpy/Measurement/Simulated.py new file mode 100644 index 0000000..2f4fc70 --- /dev/null +++ b/eNMRpy/Measurement/Simulated.py @@ -0,0 +1,36 @@ +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/eNMRpy/Measurement/__init__.py b/eNMRpy/Measurement/__init__.py new file mode 100644 index 0000000..12bbe9d --- /dev/null +++ b/eNMRpy/Measurement/__init__.py @@ -0,0 +1,4 @@ +__all__ = ['Pavel', 'Juergen1'] +from .Pavel import Pavel +from .Juergen1 import Juergen1 +print('Measurement Module imported') diff --git a/eNMRly/Measurement/__pycache__/Juergen1.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/Juergen1.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/Juergen1.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/Juergen1.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/Juergen1.cpython-38.pyc b/eNMRpy/Measurement/__pycache__/Juergen1.cpython-38.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/Juergen1.cpython-38.pyc rename to eNMRpy/Measurement/__pycache__/Juergen1.cpython-38.pyc diff --git a/eNMRly/Measurement/__pycache__/MOSY.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/MOSY.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/MOSY.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/MOSY.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/Measurement.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/Measurement.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/Measurement.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/Measurement.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/Pavel.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/Pavel.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/Pavel.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/Pavel.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/Pavel.cpython-38.pyc b/eNMRpy/Measurement/__pycache__/Pavel.cpython-38.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/Pavel.cpython-38.pyc rename to eNMRpy/Measurement/__pycache__/Pavel.cpython-38.pyc diff --git a/eNMRly/Measurement/__pycache__/__init__.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/__init__.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/__init__.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/__init__.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/__init__.cpython-38.pyc b/eNMRpy/Measurement/__pycache__/__init__.cpython-38.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/__init__.cpython-38.pyc rename to eNMRpy/Measurement/__pycache__/__init__.cpython-38.pyc diff --git a/eNMRly/Measurement/__pycache__/base.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/base.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/base.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/base.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/base.cpython-38.pyc b/eNMRpy/Measurement/__pycache__/base.cpython-38.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/base.cpython-38.pyc rename to eNMRpy/Measurement/__pycache__/base.cpython-38.pyc diff --git a/eNMRly/Measurement/__pycache__/eNMR_Measurement.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/eNMR_Measurement.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/eNMR_Measurement.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/eNMR_Measurement.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/eNMR_Methods.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/eNMR_Methods.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/eNMR_Methods.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/eNMR_Methods.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/eNMR_Methods.cpython-38.pyc b/eNMRpy/Measurement/__pycache__/eNMR_Methods.cpython-38.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/eNMR_Methods.cpython-38.pyc rename to eNMRpy/Measurement/__pycache__/eNMR_Methods.cpython-38.pyc diff --git a/eNMRly/Measurement/__pycache__/relegend.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/relegend.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/relegend.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/relegend.cpython-36.pyc diff --git a/eNMRly/Measurement/__pycache__/tools.cpython-36.pyc b/eNMRpy/Measurement/__pycache__/tools.cpython-36.pyc similarity index 100% rename from eNMRly/Measurement/__pycache__/tools.cpython-36.pyc rename to eNMRpy/Measurement/__pycache__/tools.cpython-36.pyc diff --git a/eNMRpy/Measurement/base.py b/eNMRpy/Measurement/base.py new file mode 100644 index 0000000..cc73e87 --- /dev/null +++ b/eNMRpy/Measurement/base.py @@ -0,0 +1,305 @@ +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/eNMRpy/Measurement/eNMR_Methods.py b/eNMRpy/Measurement/eNMR_Methods.py new file mode 100644 index 0000000..670a1e9 --- /dev/null +++ b/eNMRpy/Measurement/eNMR_Methods.py @@ -0,0 +1,957 @@ +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/eNMRpy/Measurement/tools.py b/eNMRpy/Measurement/tools.py new file mode 100644 index 0000000..97a2875 --- /dev/null +++ b/eNMRpy/Measurement/tools.py @@ -0,0 +1,49 @@ +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/eNMRly/Phasefitting.py b/eNMRpy/Phasefitting.py similarity index 100% rename from eNMRly/Phasefitting.py rename to eNMRpy/Phasefitting.py diff --git a/eNMRly/__init__.py b/eNMRpy/__init__.py similarity index 100% rename from eNMRly/__init__.py rename to eNMRpy/__init__.py diff --git a/eNMRly/__pycache__/MOSY.cpython-36.pyc b/eNMRpy/__pycache__/MOSY.cpython-36.pyc similarity index 100% rename from eNMRly/__pycache__/MOSY.cpython-36.pyc rename to eNMRpy/__pycache__/MOSY.cpython-36.pyc diff --git a/eNMRly/__pycache__/MOSY.cpython-38.pyc b/eNMRpy/__pycache__/MOSY.cpython-38.pyc similarity index 100% rename from eNMRly/__pycache__/MOSY.cpython-38.pyc rename to eNMRpy/__pycache__/MOSY.cpython-38.pyc diff --git a/eNMRly/__pycache__/Phasefitting.cpython-36.pyc b/eNMRpy/__pycache__/Phasefitting.cpython-36.pyc similarity index 99% rename from eNMRly/__pycache__/Phasefitting.cpython-36.pyc rename to eNMRpy/__pycache__/Phasefitting.cpython-36.pyc index 90798a85220d67730f9c3467f4cfafc650c3f67a..eb988f63ed394b3d05a6684f4e0245e427317781 100644 GIT binary patch delta 16 XcmZpg$=EcLk=2-&mut^P)> zCY&G;KHc5ZJH2=3)A@Yw{rC{x2q7On|6btHrB8F_3nLp^5k}caP&ukbw8w!6WKabI z4n-)VYD@{nEW%`;yim`NOOnQJQnt9t6+`h!y;n;0Pjg{@UEZbCMMeFD;(2;x7F@PU zTV%_0;$nbW2!Lu=vLwo}fJZ0+u literal 0 HcmV?d00001 diff --git a/eNMRly/__pycache__/__init__.cpython-38.pyc b/eNMRpy/__pycache__/__init__.cpython-38.pyc similarity index 100% rename from eNMRly/__pycache__/__init__.cpython-38.pyc rename to eNMRpy/__pycache__/__init__.cpython-38.pyc diff --git a/eNMRly/__pycache__/eNMR.cpython-36.pyc b/eNMRpy/__pycache__/eNMR.cpython-36.pyc similarity index 100% rename from eNMRly/__pycache__/eNMR.cpython-36.pyc rename to eNMRpy/__pycache__/eNMR.cpython-36.pyc diff --git a/setup.py b/setup.py index 2491dc1..fab28a7 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ from setuptools import setup setup( - name='eNMRly', - version='0.0.0', + name='eNMRpy', + version='0.0.1', author='Florian Ackermann', author_email='nairolf.ackermann@gmail.com', - packages=['eNMRly'], + packages=['eNMRpy', 'eNMRly.Measurement'], #scripts=['bin/script1','bin/script2'], #url='http://pypi.python.org/pypi/PackageName/', license='LICENSE.txt',