klassez 0.2.2.dev0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {klassez-0.2.2.dev0 → klassez-0.3.1}/PKG-INFO +2 -1
  2. {klassez-0.2.2.dev0 → klassez-0.3.1}/docs/requirements.txt +1 -0
  3. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/Spectra.py +198 -22
  4. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/__init__.py +1 -1
  5. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/fit.py +4 -2
  6. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/misc.py +143 -46
  7. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/processing.py +68 -7
  8. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/sim.py +95 -76
  9. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/tables/gamma.dic +0 -0
  10. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez.egg-info/PKG-INFO +2 -1
  11. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez.egg-info/requires.txt +1 -0
  12. {klassez-0.2.2.dev0 → klassez-0.3.1}/pyproject.toml +1 -0
  13. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T1_90.procs +0 -0
  14. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T2_10.procs +0 -0
  15. {klassez-0.2.2.dev0 → klassez-0.3.1}/LICENSE.txt +0 -0
  16. {klassez-0.2.2.dev0 → klassez-0.3.1}/MANIFEST.in +0 -0
  17. {klassez-0.2.2.dev0 → klassez-0.3.1}/README.md +0 -0
  18. {klassez-0.2.2.dev0 → klassez-0.3.1}/docs/Makefile +0 -0
  19. {klassez-0.2.2.dev0 → klassez-0.3.1}/docs/make.bat +0 -0
  20. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/anal.py +0 -0
  21. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/config.py +0 -0
  22. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/figures.py +0 -0
  23. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/gui.py +0 -0
  24. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/tables/nuclei_jeol.dic +0 -0
  25. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez/tables/xterm_colors +0 -0
  26. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez.egg-info/SOURCES.txt +0 -0
  27. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez.egg-info/dependency_links.txt +0 -0
  28. {klassez-0.2.2.dev0 → klassez-0.3.1}/klassez.egg-info/top_level.txt +0 -0
  29. {klassez-0.2.2.dev0 → klassez-0.3.1}/setup.cfg +0 -0
  30. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T1_0.00.procs +0 -0
  31. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T1_10.00.procs +0 -0
  32. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T1_180.00.procs +0 -0
  33. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T1_90.00.procs +0 -0
  34. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/T2_10.00.procs +0 -0
  35. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/acqus_1D +0 -0
  36. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/acqus_1D.fvf +0 -0
  37. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/acqus_1D.procs +0 -0
  38. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/acqus_2D +0 -0
  39. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/acqus_2D.procs +0 -0
  40. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/cnvg +0 -0
  41. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/fit_1D.py +0 -0
  42. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/read_process_1D.py +0 -0
  43. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/read_process_2D.py +0 -0
  44. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test.log +0 -0
  45. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test.py +0 -0
  46. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test_1D_fit.fvf +0 -0
  47. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test_1D_fit.ivf +0 -0
  48. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test_1D_fit_R1.png +0 -0
  49. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test_1D_fit_R2.png +0 -0
  50. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test_1D_fit_full.png +0 -0
  51. {klassez-0.2.2.dev0 → klassez-0.3.1}/test/test_1D_fit_rhist.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: klassez
3
- Version: 0.2.2.dev0
3
+ Version: 0.3.1
4
4
  Summary: A collection of functions for NMR data handling
5
5
  Author: Letizia Fiorucci
6
6
  Author-email: Francesco Bruno <bruno@cerm.unifi.it>
@@ -22,6 +22,7 @@ Requires-Dist: seaborn
22
22
  Requires-Dist: nmrglue
23
23
  Requires-Dist: matplotlib>=3.8
24
24
  Requires-Dist: csaps
25
+ Requires-Dist: pyyaml
25
26
  Provides-Extra: jeolproc
26
27
  Requires-Dist: jeol_parser; extra == "jeolproc"
27
28
  Provides-Extra: docs
@@ -8,3 +8,4 @@ seaborn
8
8
  nmrglue
9
9
  matplotlib>=3.8
10
10
  csaps
11
+ pyyaml
@@ -128,7 +128,7 @@ class Spectrum_1D:
128
128
  isexp : bool
129
129
  True if this is an experimental dataset, False if it is simulated
130
130
  spect : str
131
- Data file format. Allowed: ``'bruker'``, ``'varian'``, ``'magritek'``, ``'oxford'``, ``'jeol'``
131
+ Data file format. Allowed: ``'bruker'``, ``'varian'``, ``'magritek'``, ``'oxford'``, ``'jeol'``, ``'nana'``
132
132
 
133
133
  Returns
134
134
  -------
@@ -412,6 +412,7 @@ class Spectrum_1D:
412
412
  :func:`klassez.processing.apk`
413
413
  """
414
414
 
415
+ q = deepcopy(self.S)
415
416
  self.S, (p0, p1) = processing.apk(self.ppm, self.S, self.acqus['SFO1'], alpha, winsize, ap1)
416
417
 
417
418
  if update:
@@ -594,7 +595,7 @@ class Spectrum_1D:
594
595
  def write_acqus(self, other_dir=None):
595
596
  """
596
597
  Write the acqus dictionary in a file named ``<self.filename>.acqus``.
597
- Calls :func:`klassez.misc.write_acqus_1D`
598
+ Calls :func:`klassez.misc.write_acqus`
598
599
 
599
600
  Parameters
600
601
  ----------
@@ -607,13 +608,13 @@ class Spectrum_1D:
607
608
 
608
609
  .. seealso::
609
610
 
610
- :func:`klassez.misc.write_acqus_1D`
611
+ :func:`klassez.misc.write_acqus`
611
612
  """
612
613
  if other_dir:
613
- path = Path(other_dir) / f'{self.filename}.acqus'
614
+ path = Path(other_dir) / f'{self.filename}'
614
615
  else:
615
- path = self.datadir / f'{self.filename}.acqus'
616
- misc.write_acqus_1D(self.acqus, path=path)
616
+ path = self.datadir / f'{self.filename}'
617
+ misc.write_acqus(self.acqus, filename=path, ext='acqus')
617
618
 
618
619
  def write_procs(self, other_dir=None):
619
620
  """
@@ -632,7 +633,7 @@ class Spectrum_1D:
632
633
  path = Path(other_dir) / f'{self.filename}'
633
634
  else:
634
635
  path = self.datadir / f'{self.filename}'
635
- path.with_suffix('.procs').write_text(f'{self.procs}')
636
+ misc.write_yml(self.procs, path, ext='procs')
636
637
 
637
638
  def read_procs(self, other_dir=None):
638
639
  """
@@ -653,10 +654,10 @@ class Spectrum_1D:
653
654
  Dictionary of processing parameters
654
655
  """
655
656
  if other_dir:
656
- path = Path(other_dir) / f'{self.filename}.procs'
657
+ path = Path(other_dir) / f'{self.filename}'
657
658
  else:
658
- path = self.datadir / f'{self.filename}.procs'
659
- procs = eval(path.read_text().replace('array', 'np.array'))
659
+ path = self.datadir / f'{self.filename}'
660
+ procs = misc.read_yml(path.with_suffix('.procs'))
660
661
  # Check if it was read correctly
661
662
  if isinstance(procs, dict):
662
663
  return procs
@@ -1276,7 +1277,11 @@ class Spectrum_2D:
1276
1277
  self.datadir, self.filename = misc.get_datadir_filename(in_file, isexp)
1277
1278
 
1278
1279
  if isexp is False: # Simulate the data
1279
- self.acqus = sim.load_sim_2D(in_file) # Read the acqus dictionary from the file
1280
+ if isinstance(in_file, dict): # using the provided dictionary
1281
+ self.acqus = deepcopy(in_file)
1282
+ else: # or building it from the file
1283
+ self.acqus = sim.load_sim_2D(in_file)
1284
+ # Generate the FID
1280
1285
  self.acqus['FnMODE'] = 'States-TPPI'
1281
1286
  self.acqus['GRPDLY'] = 0
1282
1287
  self.fid = sim.sim_2D(in_file, pv=pv) # Read the FID
@@ -2006,7 +2011,7 @@ class Spectrum_2D:
2006
2011
  def write_acqus(self, other_dir=None):
2007
2012
  """
2008
2013
  Write the ``acqus`` dictionary in a file named "filename.acqus".
2009
- Calls :func:`klassez.misc.write_acqus_1D`
2014
+ Calls :func:`klassez.misc.write_acqus`
2010
2015
 
2011
2016
  Parameters
2012
2017
  ----------
@@ -2019,14 +2024,14 @@ class Spectrum_2D:
2019
2024
 
2020
2025
  .. seealso::
2021
2026
 
2022
- :func:`klassez.misc.write_acqus_1D`
2027
+ :func:`klassez.misc.write_acqus`
2023
2028
  """
2024
2029
  if other_dir:
2025
2030
  path = Path(other_dir) / f'{self.filename}'
2026
2031
  else:
2027
2032
  path = self.datadir / f'{self.filename}'
2028
2033
 
2029
- misc.write_acqus_2D(self.acqus, path=path.with_suffix('.acqus'))
2034
+ misc.write_acqus(self.acqus, filename=path, ext='acqus')
2030
2035
 
2031
2036
  def write_procs(self, other_dir=None):
2032
2037
  """
@@ -2045,7 +2050,7 @@ class Spectrum_2D:
2045
2050
  path = Path(other_dir) / f'{self.filename}'
2046
2051
  else:
2047
2052
  path = self.datadir / f'{self.filename}'
2048
- path.with_suffix('.procs').write_text(f'{self.procs}')
2053
+ misc.write_yml(self.procs, path, ext='procs')
2049
2054
 
2050
2055
  def read_procs(self, other_dir=None):
2051
2056
  """
@@ -2066,10 +2071,10 @@ class Spectrum_2D:
2066
2071
  Dictionary of processing parameters
2067
2072
  """
2068
2073
  if other_dir:
2069
- path = Path(other_dir) / f'{self.filename}.procs'
2074
+ path = Path(other_dir) / f'{self.filename}'
2070
2075
  else:
2071
- path = self.datadir / f'{self.filename}.procs'
2072
- procs = eval(path.read_text().replace('array', 'np.array'))
2076
+ path = self.datadir / f'{self.filename}'
2077
+ procs = misc.read_yml(path.with_suffix('.procs'))
2073
2078
  # Check if it was read correctly
2074
2079
  if isinstance(procs, dict):
2075
2080
  return procs
@@ -2200,6 +2205,7 @@ class Spectrum_2D:
2200
2205
  label = f'{a:.2f}:{b:.2f}'
2201
2206
  # Compute the trace
2202
2207
  f2 = anal.get_trace(self.rr, self.ppm_f2, self.ppm_f1, a, b, column=False)
2208
+ f2 = processing.hilbert(f2)
2203
2209
  # Store it
2204
2210
  self.trf2[label] = f2
2205
2211
  self.Trf2[label] = pSpectrum_1D(f2, acqus=misc.split_acqus_2D(self.acqus)[1], procs=misc.split_procs_2D(self.procs)[1], istrace=True, filename=f'T2_{label}')
@@ -2839,6 +2845,79 @@ class Pseudo_2D(Spectrum_2D):
2839
2845
 
2840
2846
  return values
2841
2847
 
2848
+ def adjph_rw(self, auto=True, alpha=3, winsize=50, ap1=True, from_procs=True, update=True):
2849
+ """
2850
+ Performs automatic phase correction of each transient independently.
2851
+ The values are saved in ``self.procs['phtr']``.
2852
+
2853
+ Parameters
2854
+ ----------
2855
+ auto : bool
2856
+ ``True`` for using :func:`klassez.processing.apk`, ``False`` to phase each transient manually.
2857
+ alpha : float
2858
+ Factor that multiplies the std of the spectrum to set the threshold
2859
+ winsize : float
2860
+ Minimum size of the window that can contain peaks /Hz
2861
+ ap1 : bool
2862
+ ``True`` to adjust both zero and first order, ``False`` for only phase zero
2863
+ from_procs : bool
2864
+ If ``True``, uses the values in ``self.procs['phtr']``
2865
+ update : bool
2866
+ Choose if to update the ``procs`` dictionary or not
2867
+
2868
+ Returns
2869
+ -------
2870
+ phases : 2darray
2871
+ Employed values for each transient, in the form ``[p0, p1, pivot]``.
2872
+
2873
+
2874
+ .. seealso::
2875
+
2876
+ :func:`klassez.processing.apk`
2877
+
2878
+ :func:`klassez.processing.adjph_rowwise`
2879
+ """
2880
+ newS = deepcopy(self.S)
2881
+ # If there is not the correct key in procs, act like from_procs is False
2882
+ if 'phtr' not in self.procs:
2883
+ from_procs = False
2884
+
2885
+ if from_procs:
2886
+ # Key found
2887
+ print('Phase correction row-wise applied with values from procs.', c='violet')
2888
+ # Copy to avoid conflict when calling write_procs at the end with update=True
2889
+ phases = np.asarray(deepcopy(self.procs['phtr']))
2890
+
2891
+ for k, transient in enumerate(self.S):
2892
+ # Unpack the values
2893
+ p0, p1, pv = phases[k]
2894
+ # Apply the correction
2895
+ newS[k], *_ = processing.ps(transient, self.ppm_f2, p0=p0, p1=p1, pivot=pv)
2896
+
2897
+ elif auto: # from_procs = False and auto = True
2898
+ # Calls the correct function in processing
2899
+ newS, phases = processing.adjph_rowwise(self.ppm_f2, self.S, self.acqus['SFO1'],
2900
+ alpha=alpha, winsize=winsize, ap1=ap1)
2901
+
2902
+ else: # from_procs = False and auto = False
2903
+ # Placeholder
2904
+ phases = []
2905
+ for k, transient in enumerate(self.S):
2906
+ print(f'Phasing transient {k+1} of {self.S.shape[0]}', c='violet')
2907
+ # Call manual phase correction in interactive panel
2908
+ newS[k], values = processing.ps(transient, self.ppm_f2)
2909
+ phases.append(values)
2910
+ # Make array to avoid conflict
2911
+ phases = np.asarray(phases)
2912
+
2913
+ self.S = newS
2914
+ if update:
2915
+ # Add/replace the key in procs and write the file
2916
+ self.procs['phtr'] = np.array([vvv for vvv in phases.tolist()])
2917
+ self.write_procs()
2918
+
2919
+ return phases
2920
+
2842
2921
  def pknl(self):
2843
2922
  """
2844
2923
  Reverses the effect of the digital filter by applying a first order phase correction.
@@ -2916,6 +2995,7 @@ class Pseudo_2D(Spectrum_2D):
2916
2995
  label = f'{a:.2f}:{b:.2f}'
2917
2996
  # Compute the trace
2918
2997
  f2 = anal.get_trace(self.rr, self.ppm_f2, self.ppm_f1, a, b, column=False)
2998
+ f2 = processing.hilbert(f2)
2919
2999
  # Store it
2920
3000
  self.trf2[label] = f2
2921
3001
  self.Trf2[label] = pSpectrum_1D(f2, acqus=self.acqus, procs=self.procs, istrace=True)
@@ -3867,7 +3947,7 @@ class DOSY_T1:
3867
3947
  path = Path(other_dir) / f'{self.filename}'
3868
3948
  else:
3869
3949
  path = self.datadir / f'{self.filename}'
3870
- path.with_suffix('.procs').write_text(f'{self.procs}')
3950
+ misc.write_yml(self.procs, path, ext='procs')
3871
3951
 
3872
3952
  def read_procs(self, other_dir=None):
3873
3953
  """
@@ -3891,7 +3971,7 @@ class DOSY_T1:
3891
3971
  path = Path(other_dir) / f'{self.filename}.procs'
3892
3972
  else:
3893
3973
  path = self.datadir / f'{self.filename}.procs'
3894
- procs = eval(path.read_text().replace('array', 'np.array'))
3974
+ procs = misc.read_yml(path.with_suffix('.procs'))
3895
3975
  # Check if it was read correctly
3896
3976
  if isinstance(procs, dict):
3897
3977
  return procs
@@ -3996,7 +4076,7 @@ class DOSY_T1:
3996
4076
  figures.plot_p3D(x_f1, x_f2, idx_scale, self.S.real, dim=dim, Neg=Neg,
3997
4077
  name=name, X_label=X_label, Y_label=Y_label, lvl0=lvl0)
3998
4078
 
3999
- def integrate(self, filename=None):
4079
+ def integrate(self, filename=None, keys=None):
4000
4080
  """
4001
4081
  Create the attribute ``self.D`` as ``klassez.fit.DosyFit_pp3D``, and integrate the planes along the `31` direction.
4002
4082
 
@@ -4004,6 +4084,8 @@ class DOSY_T1:
4004
4084
  ----------
4005
4085
  filename : str or None
4006
4086
  Custom filename for data and plots. If ``None``, ``self.filename`` is used.
4087
+ keys : list of str
4088
+ Keys that identify the regions to integrate. If None, the integration is performed interactively through GUI
4007
4089
 
4008
4090
  Returns
4009
4091
  -------
@@ -4017,4 +4099,98 @@ class DOSY_T1:
4017
4099
  if filename is None:
4018
4100
  filename = self.filename
4019
4101
  filename = Path(filename)
4020
- self.D = fit.DosyFit_pp3D(self, datadir=filename, filename=filename)
4102
+ self.D = fit.DosyFit_pp3D(self, datadir=filename, filename=filename, keys=keys)
4103
+
4104
+
4105
+ class DOSY_SSel(DOSY_T1):
4106
+ """
4107
+ Class: 3D DOSY spectrum
4108
+
4109
+ It should be acquired with the sequence described in
4110
+ `Novakovic M. et al. (2025), Nature Communications, 16(1), 4628`_,
4111
+ Supporting Information, Figure 12a.
4112
+ This sequence, adapted in the pulse program ``stebpgp1s193D.rav``,
4113
+ evolves the big delta (``d20``) in dimension F2 and the gradient strength according to
4114
+ the `difflist` in dimension F1.
4115
+ This sequence is actually equivalent to acquiring separate DOSY with different ``d20``.
4116
+
4117
+
4118
+ .. _Novakovic M. et al. (2025), Nature Communications, 16(1), 4628: https://www.nature.com/articles/s41467-025-59759-2
4119
+
4120
+ Attributes
4121
+ ----------
4122
+ datadir : str
4123
+ Path to the input file/dataset directory
4124
+ filename : str
4125
+ Base of the name of the file, without extensions
4126
+ fid : 3darray
4127
+ FID
4128
+ acqus : dict
4129
+ Dictionary of acqusition parameters
4130
+ ngdic : dict
4131
+ Created only if it is an experimental spectrum. Generated by :func:`nmrglue.<spectrometer>.read`, contains all the information on the spectrometer and on the spectrum.
4132
+ procs : dict
4133
+ Dictionary of processing parameters
4134
+ S : 3darray
4135
+ Complex (or hypercomplex, depending on ``FnMODE``) spectrum
4136
+ x_f1 : 1darray
4137
+ gradient list
4138
+ x_f2 : 1darray
4139
+ big delta list
4140
+ freq_f2 : 1darray
4141
+ Frequency scale of the direct dimension, in Hz
4142
+ ppm_f2 : 1darray
4143
+ ppm scale of the direct dimension
4144
+ """
4145
+
4146
+ def __init__(self, in_file):
4147
+ """
4148
+ Initialize the class.
4149
+
4150
+ Parameters
4151
+ ----------
4152
+ in_file : str
4153
+ path to file to read, or to the folder of the spectrum
4154
+ """
4155
+ self.datadir, self.filename = misc.get_datadir_filename(in_file, isexp=True)
4156
+
4157
+ with warnings.catch_warnings(): # Suppress errors due to CONVDTA in TopSpin
4158
+ warnings.simplefilter("ignore")
4159
+ # Read the data, acquisition parameters...
4160
+ dic, data = ng.bruker.read(in_file, cplex=True)
4161
+ self.ngdic = dic
4162
+ self.fid = data
4163
+ self.acqus = misc.makeacqus_pp3D(dic)
4164
+ self.fid = np.reshape(self.fid, (self.acqus['TD1'], self.acqus['TD2'], -1))
4165
+ self.acqus['BYTORDA'] = dic['acqus']['BYTORDA']
4166
+ self.acqus['DTYPA'] = dic['acqus']['DTYPA']
4167
+
4168
+ del dic
4169
+ del data
4170
+
4171
+ # Get group delay points
4172
+ try:
4173
+ self.acqus['GRPDLY'] = int(self.ngdic['acqus']['GRPDLY'])
4174
+ except Exception:
4175
+ self.acqus['GRPDLY'] = 0
4176
+
4177
+ # Load the difflist
4178
+ self.x_f1 = np.loadtxt(self.datadir / 'difflist')
4179
+ self.label_f1 = r'Gradient /G cm$^{-1}$'
4180
+
4181
+ # Load the F2 list
4182
+ # It should be the only file in lists/vd/ but we do not know its name
4183
+ fqlistpath = next((self.datadir / 'lists' / 'f1').iterdir())
4184
+ # Print a notification
4185
+ print(f'Found {fqlistpath} to be imported as FQLIST', c='tab:blue')
4186
+ # Actual loading and storage in an attribute
4187
+ fqlist = np.loadtxt(fqlistpath)
4188
+ self.x_f2 = fqlist
4189
+ self.label_f2 = 'Freq /Hz'
4190
+
4191
+ # Procs dictionary
4192
+ if (self.datadir / self.filename).with_suffix('.procs').exists():
4193
+ self.procs = self.read_procs()
4194
+ else:
4195
+ self.procs = self._default_procs(self.acqus)
4196
+ self.write_procs()
@@ -17,4 +17,4 @@ from .config import CM, CM_2D, COLORS, cron, safe_kws, textcolor, _print, cprint
17
17
 
18
18
  print = cprint
19
19
 
20
- __version__ = "0.2.2.dev0"
20
+ __version__ = "0.3.1"
@@ -5830,7 +5830,7 @@ class DosyFit_pp3D(fit.DosyFit):
5830
5830
  Same structure and shape of ``self.i_guess``
5831
5831
 
5832
5832
  """
5833
- def __init__(self, S, datadir=None, filename=None):
5833
+ def __init__(self, S, datadir=None, filename=None, keys=None):
5834
5834
  """
5835
5835
  Initialize the class.
5836
5836
  This function will slice ``S`` along the DOSY dimension (`31`) and store the planes in the ``self.planes`` attribute.
@@ -5845,6 +5845,8 @@ class DosyFit_pp3D(fit.DosyFit):
5845
5845
  Custom path where to save files and figures. If ``None``, *./``filename``* is created
5846
5846
  filename : str
5847
5847
  Custom filename for files and figures. If ``None``, ``S.filename`` is used.
5848
+ keys : list of str
5849
+ Keys that identify the regions to integrate. If None, the integration is performed interactively through GUI
5848
5850
  """
5849
5851
  # Filename
5850
5852
  if filename is None: # Same name of the input dataset
@@ -5871,7 +5873,7 @@ class DosyFit_pp3D(fit.DosyFit):
5871
5873
  self.planes = [S.getplane(k, dim='31') for k in range(len(S.x_f2))]
5872
5874
 
5873
5875
  # Compute the integrals for all the planes
5874
- self.integrate_planes()
5876
+ self.integrate_planes(keys=keys)
5875
5877
 
5876
5878
  def integrate_planes(self, keys=None, use_bas=False):
5877
5879
  """
@@ -8,6 +8,7 @@ import scipy.io.wavfile as WF
8
8
  import math
9
9
  import warnings
10
10
  import scipy.linalg as slinalg
11
+ import yaml
11
12
  from copy import deepcopy
12
13
  from pathlib import Path
13
14
 
@@ -74,7 +75,7 @@ def read_fid_acqus_1D(in_file, spect='bruker'):
74
75
  in_file : str or Path
75
76
  Path where to find the data
76
77
  spect : str
77
- Data format. Valid options are: ``'bruker'``, ``'varian'``, ``'magritek'``, ``'oxford'``, ``'jeol'``
78
+ Data format. Valid options are: ``'bruker'``, ``'varian'``, ``'magritek'``, ``'oxford'``, ``'jeol'``, ``'nana'``
78
79
 
79
80
  Returns
80
81
  -------
@@ -129,6 +130,13 @@ def read_fid_acqus_1D(in_file, spect='bruker'):
129
130
  data = cplx[0] + 1j*cplx[1]
130
131
  return data, acqus, dic
131
132
 
133
+ def read_nana(path):
134
+ """ .dx files nanalysis """
135
+ dic, cplx = ng.jcampdx.read(path)
136
+ acqus = misc.makeacqus_1D_nana(dic)
137
+ data = cplx[0] + 1j*cplx[1]
138
+ return data, acqus, dic
139
+
132
140
  def read_jeol(path):
133
141
  """ Jeol files """
134
142
  dic = jeol_parser.parse(path)
@@ -158,6 +166,8 @@ def read_fid_acqus_1D(in_file, spect='bruker'):
158
166
  fid, acqus, ngdic = read_magritek(in_file)
159
167
  case 'oxford':
160
168
  fid, acqus, ngdic = read_oxford(in_file)
169
+ case 'nana':
170
+ fid, acqus, ngdic = read_nana(in_file)
161
171
  case 'jeol':
162
172
  fid, acqus, ngdic = read_jeol(in_file)
163
173
  case _:
@@ -166,10 +176,11 @@ def read_fid_acqus_1D(in_file, spect='bruker'):
166
176
  # Add the file version to acqus
167
177
  acqus['spect'] = spect
168
178
  # Look for group delay points: if there are not, put it to 0
169
- try:
170
- acqus['GRPDLY'] = int(ngdic['acqus']['GRPDLY'])
171
- except Exception:
172
- acqus['GRPDLY'] = 0
179
+ if 'GRPDLY' not in acqus.keys():
180
+ try:
181
+ acqus['GRPDLY'] = int(ngdic['acqus']['GRPDLY'])
182
+ except Exception:
183
+ acqus['GRPDLY'] = 0
173
184
 
174
185
  return fid, acqus, ngdic
175
186
 
@@ -339,6 +350,7 @@ def makeacqus_1D_spinsolve(dic):
339
350
  def makeacqus_1D_oxford(dic):
340
351
  """
341
352
  Given a NMRGLUE dictionary from a 1D spectrum (generated by :func:`nmrglue.jcampdx.read`), this function builds the acqus file with only the "important" parameters.
353
+ For Oxford Instruments benchtop NMR.
342
354
 
343
355
  Parameters
344
356
  ----------
@@ -371,6 +383,41 @@ def makeacqus_1D_oxford(dic):
371
383
  return acqus
372
384
 
373
385
 
386
+ def makeacqus_1D_nana(dic):
387
+ """
388
+ Given a NMRGLUE dictionary from a 1D spectrum (generated by :func:`nmrglue.jcampdx.read`), this function builds the acqus file with only the "important" parameters.
389
+ For NANALYSIS benctop NMR
390
+
391
+ Parameters
392
+ ----------
393
+ dic : dict
394
+ NMRglue dictionary returned by ng.jcampdx.read
395
+
396
+ Returns
397
+ -------
398
+ acqus : dict
399
+ Dictionary with only few parameters
400
+ """
401
+ acqus = {}
402
+
403
+ acqus['nuc'] = dic['.OBSERVENUCLEUS'][-1].replace('^', '')
404
+ acqus['SFO1'] = eval(dic['$SFO1'][-1])
405
+ acqus['SW'] = eval(dic['$SWH'][-1])
406
+ acqus['TD'] = int(eval(dic['$TD'][-1]) // 2) # Fuckin' Nana
407
+ acqus['o1'] = eval(dic['$O1'][-1])
408
+
409
+ acqus['SWp'] = acqus['SW'] / np.abs(acqus['SFO1'])
410
+ acqus['B0'] = acqus['SFO1'] / sim.gamma[acqus['nuc']]
411
+ acqus['o1p'] = acqus['o1'] / acqus['SFO1']
412
+ acqus['dw'] = 1 / acqus['SW']
413
+ acqus['t1'] = np.linspace(0, acqus['TD']*acqus['dw'], acqus['TD'])
414
+ acqus['AQ'] = acqus['t1'][-1]
415
+ acqus['fidres'] = 1 / (acqus['TD'] * acqus['dw'])
416
+
417
+ acqus['GRPDLY'] = - eval(dic['$GRPDLY'][-1])
418
+ return acqus
419
+
420
+
374
421
  def makeacqus_1D_jeol(dic):
375
422
  """
376
423
  Given a dictionary from a 1D spectrum (generated by ``jeol_parser.parse``), this function builds the acqus file with only the "important" parameters.
@@ -496,7 +543,7 @@ def makeacqus_pp3D(dic):
496
543
  return acqus
497
544
 
498
545
 
499
- def write_acqus_1D(acqus, path='sim_in_1D'):
546
+ def write_acqus(acqus, filename='sim_in', ext='acqus'):
500
547
  """
501
548
  Writes the input file for a simulated spectrum, basing on a dictionary of parameters.
502
549
 
@@ -504,51 +551,29 @@ def write_acqus_1D(acqus, path='sim_in_1D'):
504
551
  ----------
505
552
  acqus : dict
506
553
  The dictionary containing the parameters for the simulation
507
- path : str, optional
554
+ filename : str
508
555
  Directory where the file will be saved.
556
+ ext : str
557
+ Extension of the filename
509
558
  """
510
- f = open(path, 'w')
511
- keylist = acqus.keys()
512
- for key in keylist:
513
- if key[:1] == 't':
514
- pass
515
- else:
516
- if isinstance(acqus[key], (list, tuple)):
517
- f.write('{}\t'.format(key))
518
- for w in acqus[key]:
519
- f.write('{}, '.format(w))
520
- f.write('\n')
521
- else:
522
- f.write('{}\t{}\n'.format(key, acqus[key]))
523
-
524
- f.close()
559
+ path = Path(filename)
525
560
 
561
+ # Remove the acquistion timescales as they can be recomputed easily
562
+ exclude = ['t1', 't2', 't3']
563
+ acqus_to_write = {key: value for key, value in acqus.items()
564
+ if key not in exclude}
526
565
 
527
- def write_acqus_2D(acqus, path='sim_in_2D'):
528
- """
529
- Writes the input file for a simulated spectrum, basing on a dictionary of parameters.
566
+ # If there are the parameters for the simulated peaks, move them at the end
567
+ if 'shifts' in acqus.keys():
568
+ move = ['shifts', 'fwhm', 'amplitudes', 'b', 'phases', 'mult', 'Jconst', ]
569
+ reordered = {k: v for k, v in acqus_to_write.items()
570
+ if k not in move}
571
+ for key in move:
572
+ if key in acqus_to_write:
573
+ reordered[key] = acqus_to_write[key]
574
+ acqus_to_write = reordered
530
575
 
531
- Parameters
532
- ----------
533
- acqus : dict
534
- The dictionary containing the parameters for the simulation
535
- path : str, optional
536
- Directory where the file will be saved.
537
- """
538
- f = open(path, 'w')
539
- keylist = acqus.keys()
540
- for key in keylist:
541
- if key[:1] == 't':
542
- pass
543
- else:
544
- if isinstance(acqus[key], (list, tuple)):
545
- f.write('{}\t'.format(key))
546
- for w in acqus[key]:
547
- f.write('{}, '.format(w))
548
- f.write('\n')
549
- else:
550
- f.write('{}\t{}\n'.format(key, acqus[key]))
551
- f.close()
576
+ misc.write_yml(acqus_to_write, path, ext, sort_keys=False)
552
577
 
553
578
 
554
579
  def calcres(fqscale):
@@ -1901,3 +1926,75 @@ def get_extent(data):
1901
1926
  ``max(data) - min(data)``
1902
1927
  """
1903
1928
  return np.max(data) - np.min(data)
1929
+
1930
+
1931
+ def safe_convert(obj):
1932
+ """
1933
+ Transforms strange pythonic objects like
1934
+ numpy arrays and scalars into something writable.
1935
+ """
1936
+ # Called in loop in order to work also for nested objects
1937
+ if isinstance(obj, dict): # for dictionaries
1938
+ return {k: safe_convert(v) for k, v in obj.items()}
1939
+ if isinstance(obj, (list, tuple, set)): # for lists
1940
+ return [safe_convert(v) for v in obj]
1941
+ if isinstance(obj, np.ndarray): # for arrays
1942
+ return safe_convert(obj.tolist())
1943
+ if isinstance(obj, np.generic): # for np.float64 and similar
1944
+ return obj.item()
1945
+ return obj
1946
+
1947
+
1948
+ def write_yml(obj, filename, ext='yml', sort_keys=True):
1949
+ """
1950
+ Write ``obj`` in a yml-styled text file, with one entry per line.
1951
+
1952
+ Parameters
1953
+ ----------
1954
+ obj : list or dict
1955
+ Object to write
1956
+ filename : str or Path
1957
+ Path for the file to write
1958
+ ext : str
1959
+ Extension of the filename
1960
+ sort_keys : bool
1961
+ ``False`` to keep the insertion order
1962
+
1963
+ Returns
1964
+ -------
1965
+ None
1966
+
1967
+ .. seealso::
1968
+
1969
+ :func:`klassez.misc.read_yml`
1970
+
1971
+ """
1972
+
1973
+ # Convert if str, then add/replace correct extension
1974
+ path = Path(filename).with_suffix(f'.{ext}')
1975
+ # Open file and write
1976
+ with path.open('w') as f:
1977
+ # default_flow_style if True writes all obj in only one line
1978
+ yaml.safe_dump(misc.safe_convert(obj), f,
1979
+ default_flow_style=False,
1980
+ sort_keys=sort_keys)
1981
+
1982
+
1983
+ def read_yml(filename):
1984
+ """
1985
+ Reads a yml-styled text file.
1986
+
1987
+ Parameters
1988
+ ----------
1989
+ filename : str or Path
1990
+ Path to the file to be read
1991
+
1992
+ Returns
1993
+ -------
1994
+ obj : object
1995
+ Read file
1996
+ """
1997
+ path = Path(filename)
1998
+ with path.open('r') as f:
1999
+ obj = yaml.safe_load(f)
2000
+ return obj
@@ -2701,7 +2701,7 @@ def rpbc(data, split_imag=False, n=5, basl_method='huber', basl_thresh=0.2, basl
2701
2701
 
2702
2702
  :func:`klassez.fit.SINC_phase`
2703
2703
 
2704
- :func:`klassez.processing.polyn_basl`
2704
+ :func:`klassez.fit.polyn_basl`
2705
2705
 
2706
2706
  :class:`klassez.fit.CostFunc`
2707
2707
  """
@@ -2881,14 +2881,15 @@ def align(ppm_scale, data, lims, u_off=0.5, ref_idx=0):
2881
2881
 
2882
2882
  # Circular-shift the spectrum (first shift then abs)
2883
2883
  roll_s = np.abs(processing.roll_dirac(s, ppm_scale, par['u']))
2884
- # Compute its integral
2885
- int_roll_s = processing.integral(roll_s)
2886
2884
 
2887
2885
  # Normalize the spectra in the calibration region
2888
2886
  A, _ = fit.fit_int(s_ref_abs, roll_s, q=0)
2889
2887
  roll_s *= A
2890
- A_int, _ = fit.fit_int(int_s_ref, int_roll_s, q=0)
2891
- int_roll_s *= A_int
2888
+ # Compute its integral
2889
+ int_roll_s = processing.integral(roll_s)
2890
+ if 0:
2891
+ A_int, _ = fit.fit_int(int_s_ref, int_roll_s, q=0)
2892
+ int_roll_s *= A_int
2892
2893
 
2893
2894
  # Compute the residuals
2894
2895
  res = s_ref_abs - roll_s
@@ -3430,6 +3431,10 @@ def apk(ppm, data, SFO1, alpha=3, winsize=50, ap1=True, seethrough=False):
3430
3431
 
3431
3432
  # -----------------------------------------------------------------------------------
3432
3433
 
3434
+ if not np.iscomplexobj(data):
3435
+ # TODO warning
3436
+ data = processing.hilbert(data)
3437
+
3433
3438
  # Get the peak-only regions
3434
3439
  slices, _ = processing.mask_sgn_basl(ppm, data, SFO1, alpha, winsize)
3435
3440
 
@@ -3448,7 +3453,11 @@ def apk(ppm, data, SFO1, alpha=3, winsize=50, ap1=True, seethrough=False):
3448
3453
  plt.show()
3449
3454
 
3450
3455
  # Smooth the data with a 5 Hz gaussian filter
3451
- gbdata = processing.smooth_g(data, 1/misc.hz2pt(misc.ppm2freq(ppm, SFO1), 5))
3456
+ fqscale = misc.ppm2freq(ppm, SFO1)
3457
+ # Minimum must be 1 pt width otherwise error div by 0
3458
+ fw_min_width = misc.calcres(fqscale)
3459
+ fw = max(fw_min_width, misc.hz2pt(fqscale, 5))
3460
+ gbdata = processing.smooth_g(data, 1/fw)
3452
3461
  # Phase each region independently with only p0
3453
3462
  p0s = phase0(gbdata, slices)
3454
3463
 
@@ -3491,7 +3500,7 @@ def apk(ppm, data, SFO1, alpha=3, winsize=50, ap1=True, seethrough=False):
3491
3500
  p1 = 0
3492
3501
 
3493
3502
  # Apply the correction
3494
- datap, *_ = processing.ps(data, p0=p0, p1=p1)
3503
+ datap, *_ = processing.ps(data, ppm, p0=p0, p1=p1)
3495
3504
 
3496
3505
  print('APK: p0: {:.3f}, p1: {:.3f}\n'.format(p0, p1), c='violet')
3497
3506
 
@@ -4338,3 +4347,55 @@ def roll_dirac(y, x, off=0, onfid=False):
4338
4347
  y_t_roll = y_t * dirac_t
4339
4348
  y_roll = processing.ft(y_t_roll)
4340
4349
  return y_roll
4350
+
4351
+
4352
+ def adjph_rowwise(ppm, data, SFO1, alpha=3, winsize=50, ap1=True):
4353
+ """
4354
+ Performs automatic phase correction of a pseudo_2D dataset, with :func:`klassez.processing.apk`, each row independently.
4355
+ Returns the phased spectrum and the employed values.
4356
+
4357
+ Parameters
4358
+ ----------
4359
+ ppm : 1darray
4360
+ ppm scale of the spectrum
4361
+ data : 2darray
4362
+ Spectrum
4363
+ SFO1 : float
4364
+ Nucleus' Larmor frequency /MHz
4365
+ alpha : float
4366
+ Factor that multiplies the std of the spectrum to set the threshold
4367
+ winsize : float
4368
+ Minimum size of the window that can contain peaks /Hz
4369
+ ap1 : bool
4370
+ True to adjust both zero and first order, False for only phase zero
4371
+
4372
+ Returns
4373
+ -------
4374
+ new_data : 2darray
4375
+ Phased spectrum
4376
+ phases : 2darray
4377
+ Employed values for phasing, in the form ``[p0, p1, pivot]``
4378
+
4379
+ .. seealso::
4380
+
4381
+ :func:`klassez.processing.apk`
4382
+ """
4383
+ # Make a shallow copy while checking to have the imaginary part
4384
+ if np.iscomplexobj(data):
4385
+ new_data = deepcopy(data)
4386
+ else:
4387
+ warnings.warn('data is not complex. Performing Hilbert transform.', stacklevel=3)
4388
+ new_data = processing.hilbert(data)
4389
+
4390
+ # Placeholder
4391
+ phases = []
4392
+ # Start the loop
4393
+ for k, transient in enumerate(new_data):
4394
+ print(f'Phasing transient {k+1} of {new_data.shape[0]}', c='violet')
4395
+ # Call apk for each transient and save the values
4396
+ new_data[k], values = processing.apk(ppm, transient, SFO1, alpha=alpha, winsize=winsize, ap1=ap1)
4397
+ phases.append(values)
4398
+
4399
+ # apk uses as pivot the mean of the ppm scale --> add this info
4400
+ phases = np.column_stack([phases, [round(np.mean(ppm), 3) for w in range(data.shape[0])]])
4401
+ return new_data, phases
@@ -2,7 +2,9 @@
2
2
 
3
3
  import sys
4
4
  import numpy as np
5
-
5
+ from copy import deepcopy
6
+ import re
7
+ import ast
6
8
  from importlib.resources import open_text, files
7
9
  from pathlib import Path
8
10
  from scipy import special
@@ -120,13 +122,13 @@ def multiplet(u, Int, m='s', J=[]):
120
122
  return u_in, I_in
121
123
 
122
124
 
123
- def load_sim_1D(File):
125
+ def load_sim_1D(filename):
124
126
  """
125
127
  Creates a dictionary from the spectral parameters listed in the input file.
126
128
 
127
129
  Parameters
128
130
  ----------
129
- File : str
131
+ filename : str
130
132
  Path to the input file location
131
133
 
132
134
  Returns
@@ -134,51 +136,56 @@ def load_sim_1D(File):
134
136
  dic : dict
135
137
  Dictionary of the parameters, ready to be read from the simulation functions.
136
138
  """
137
- inp = Path(File).read_text().splitlines(keepends=True)
138
- keys = []
139
- vals = []
140
- for i in range(len(inp)):
141
- if inp[i] == '\n' or inp[i][0] == '#':
142
- continue # skip empty lines or comments
143
- inp[i] = inp[i].replace('\t', ' ')
144
- line = inp[i].split(' ', 1) # separate key from the rest
145
- line[0] = line[0].replace(' ', '')
146
- line[0] = line[0].replace('x_g', 'b')
147
- keys.append(line[0])
148
-
149
- rest = line[1].strip()
150
- if '#' in rest:
151
- rest = rest.split('#')[0]
152
- try:
153
- value = eval(rest)
154
- except Exception:
155
- value = (f'{rest}')
156
- vals.append(value)
157
-
158
- dic = {}
159
- for i, key in enumerate(keys):
160
- if 'nuc' in key: # Remove unwanted spaces
161
- vals[i] = vals[i].replace(' ', '')
162
- dic[key] = vals[i]
163
- if 'phases' not in keys:
164
- dic['phases'] = tuple([0 for w in dic['shifts']])
165
- else:
166
- dic['phases'] = tuple([w * np.pi / 180 for w in dic['phases']])
167
- if 'mult' not in keys: # Multiplicity
168
- dic['mult'] = tuple(['s' for w in dic['shifts']])
169
- else:
170
- dic['mult'] = tuple(dic['mult'].strip(',').replace(' ', '').split(','))
171
- if 'Jconst' not in keys: # Coupling constants
172
- dic['Jconst'] = tuple([0 for w in dic['shifts']])
173
-
139
+ # Remove tabulations because they kill everything
140
+ path = Path(filename)
141
+ text = path.read_text()
142
+ if '\t' in text:
143
+ text = text.replace('\t', 4*' ')
144
+ with path.open('w') as f:
145
+ f.write(text)
146
+
147
+ # Make the dictionary
148
+ dic = misc.read_yml(path)
149
+ # Correct aliases
150
+ alias = {'shifts': 'cs', 'fwhm': 'lw', 'amplitudes': 'A', 'b': 'x_g',
151
+ 'phases': 'ph', 'mult': 'm', 'Jconst': 'J', }
152
+ newdic = deepcopy(dic)
153
+ for key, item in alias.items():
154
+ if item in dic:
155
+ newdic[key] = dic[item]
156
+ newdic.pop(item)
157
+ dic = newdic
158
+
159
+ # Check if the mandatory keys are all there
160
+ for key in ['shifts', 'fwhm', 'amplitudes']:
161
+ if key not in dic:
162
+ raise NameError(f'Missing {key} in the input file.')
163
+ # Add the non-mandatory keys if missing with default values
164
+ defaults = {'b': 0, 'phases': 0, 'mult': 's', 'Jconst': 0, }
165
+ for key in alias:
166
+ # Check if there is
167
+ if key in dic:
168
+ if key == 'mult':
169
+ # ddd --> "ddd" so that ast.literal_eval does not raise error
170
+ if isinstance(dic[key], str):
171
+ dic[key] = re.sub(r'[^\[\],\s]+', r'"\g<0>"', dic[key])
172
+ # Convert a string into a list
173
+ dic[key] = list(ast.literal_eval(f'{dic[key]}'))
174
+ elif key in defaults:
175
+ # Use default value
176
+ dic[key] = [defaults[key] for w in range(len(dic['amplitudes']))]
177
+
178
+ # Compute extra
174
179
  dic['B0'] = np.abs(dic['B0']) * np.sign(sim.gamma[dic['nuc']])
175
- dic['TD'] = int(dic['TD'])
176
180
  dic['SFO1'] = dic['B0'] * sim.gamma[dic['nuc']]
181
+ dic['o1'] = dic['o1p'] * dic['SFO1']
182
+ if isinstance(dic['TD'], str):
183
+ dic['TD'] = eval(dic['TD'])
184
+ dic['TD'] = int(dic['TD'])
177
185
  dic['SW'] = dic['SWp'] * np.abs(dic['SFO1'])
178
186
  dic['dw'] = 1/dic['SW']
179
187
  dic['t1'] = np.linspace(0, dic['TD']*dic['dw'], dic['TD'])
180
188
  dic['AQ'] = dic['t1'][-1]
181
- dic['o1'] = dic['o1p'] * dic['SFO1']
182
189
 
183
190
  return dic
184
191
 
@@ -211,8 +218,8 @@ def sim_1D(File, pv=False):
211
218
  shifts = np.array(in_file['shifts']) # Chemical shift /ppm
212
219
  amplitudes = in_file['amplitudes'] # Relative intensity of the signals
213
220
  fwhm = np.array(in_file['fwhm']) # Full width at half maximum of the signals
214
- b = in_file['b'] # Fraction of gaussianity of the FID
215
- phases = in_file['phases']
221
+ b = np.array(in_file['b']) # Fraction of gaussianity of the FID
222
+ phases = np.array(in_file['phases']) * np.pi / 180 # Convert to radians
216
223
 
217
224
  freq = misc.ppm2freq(shifts, B0=in_file['SFO1'], o1p=in_file['o1p']) # peaks center frequency
218
225
 
@@ -229,13 +236,13 @@ def sim_1D(File, pv=False):
229
236
  return fid
230
237
 
231
238
 
232
- def load_sim_2D(File, states=True):
239
+ def load_sim_2D(filename, states=True):
233
240
  """
234
241
  Creates a dictionary from the spectral parameters listed in the input file.
235
242
 
236
243
  Parameters
237
244
  ----------
238
- File : str
245
+ filename : str
239
246
  Path to the input file location
240
247
  states : bool
241
248
  If FnMODE is States or States-TPPI, set it to True to get the correct timescale.
@@ -245,36 +252,48 @@ def load_sim_2D(File, states=True):
245
252
  dic : dict
246
253
  Dictionary of the parameters, ready to be read from the simulation functions.
247
254
  """
248
- inp = Path(File).read_text().splitlines(keepends=True)
249
- keys = []
250
- vals = []
251
- for i in range(len(inp)):
252
- if inp[i] == '\n' or inp[i][0] == '#':
253
- continue
254
- inp[i] = inp[i].replace('\t', ' ')
255
- line = inp[i].split(' ', 1) # separate key from the rest
256
- line[0] = line[0].replace(' ', '')
257
- line[0] = line[0].replace('x_g', 'b')
258
- keys.append(line[0])
259
-
260
- rest = line[1].strip()
261
- if '#' in rest:
262
- rest = rest.split('#')[0]
263
- try:
264
- value = eval(rest)
265
- except Exception:
266
- value = (f'{rest}')
267
- vals.append(value)
268
-
269
- dic = {}
270
- for i, key in enumerate(keys):
271
- if 'nuc' in key: # Remove unwanted spaces
272
- vals[i] = vals[i].replace(' ', '')
273
- dic[key] = vals[i]
274
-
275
- for key, value in dic.items():
276
- if 'TD' in key:
277
- dic[key] = int(value)
255
+ # Remove tabulations because they kill everything
256
+ path = Path(filename)
257
+ text = path.read_text()
258
+ if '\t' in text:
259
+ text = text.replace('\t', 4*' ')
260
+ with path.open('w') as f:
261
+ f.write(text)
262
+
263
+ # Make the dictionary
264
+ dic = misc.read_yml(path)
265
+
266
+ # Correct aliases
267
+ alias = {'shifts_f1': 'csf1', 'shifts_f2': 'csf2',
268
+ 'fwhm_f1': 'lwf1', 'fwhm_f2': 'lwf2',
269
+ 'amplitudes': 'A', 'b': 'x_g', }
270
+ newdic = deepcopy(dic)
271
+ for key, item in alias.items():
272
+ if item in dic:
273
+ newdic[key] = dic[item]
274
+ newdic.pop(item)
275
+ dic = newdic
276
+
277
+ # Check if the mandatory keys are all there
278
+ for key in ['shifts_f1', 'shifts_f2',
279
+ 'fwhm_f1', 'fwhm_f2', 'amplitudes']:
280
+ if key not in dic:
281
+ raise NameError(f'Missing {key} in the input file.')
282
+ # Add the non-mandatory keys if missing with default values
283
+ defaults = {'b': 0, }
284
+ for key in alias:
285
+ # Check if there is
286
+ if key in dic:
287
+ # Convert a string into a list
288
+ dic[key] = list(ast.literal_eval(f'{dic[key]}'))
289
+ elif key in defaults:
290
+ # Use default value
291
+ dic[key] = [defaults[key] for w in range(len(dic['amplitudes']))]
292
+
293
+ for key in ['TD1', 'TD2']:
294
+ if isinstance(dic[key], str):
295
+ dic[key] = eval(dic[key])
296
+ dic[key] = int(dic[key])
278
297
  dic['SFO1'] = dic['B0'] * sim.gamma[dic['nuc1']] # Larmor frequency /MHz
279
298
  dic['SFO2'] = dic['B0'] * sim.gamma[dic['nuc2']] # Larmor frequency /MHz
280
299
  dic['SW1'] = np.abs(dic['SW1p'] * dic['SFO1']) # spectral width
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: klassez
3
- Version: 0.2.2.dev0
3
+ Version: 0.3.1
4
4
  Summary: A collection of functions for NMR data handling
5
5
  Author: Letizia Fiorucci
6
6
  Author-email: Francesco Bruno <bruno@cerm.unifi.it>
@@ -22,6 +22,7 @@ Requires-Dist: seaborn
22
22
  Requires-Dist: nmrglue
23
23
  Requires-Dist: matplotlib>=3.8
24
24
  Requires-Dist: csaps
25
+ Requires-Dist: pyyaml
25
26
  Provides-Extra: jeolproc
26
27
  Requires-Dist: jeol_parser; extra == "jeolproc"
27
28
  Provides-Extra: docs
@@ -6,6 +6,7 @@ seaborn
6
6
  nmrglue
7
7
  matplotlib>=3.8
8
8
  csaps
9
+ pyyaml
9
10
 
10
11
  [docs]
11
12
  sphinx
@@ -29,6 +29,7 @@ dependencies = [
29
29
  'nmrglue',
30
30
  'matplotlib>=3.8',
31
31
  'csaps',
32
+ 'pyyaml',
32
33
  ]
33
34
  [project.optional-dependencies]
34
35
  jeolproc = ['jeol_parser']
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes