pychemstation 0.4.7.dev1__py3-none-any.whl → 0.4.7.dev2__py3-none-any.whl

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 (104) hide show
  1. pychemstation/control/__init__.py +3 -2
  2. hein-analytical-control/devices/Agilent/hplc.py → pychemstation/control/comm.py +21 -181
  3. pychemstation/control/method.py +232 -0
  4. pychemstation/control/sequence.py +140 -0
  5. pychemstation/control/table_controller.py +75 -0
  6. pychemstation/utils/__init__.py +0 -2
  7. {ag_hplc_macro/control → pychemstation/utils}/chromatogram.py +2 -1
  8. pychemstation/utils/constants.py +1 -1
  9. hein_analytical_control/devices/Agilent/hplc_param_types.py → pychemstation/utils/macro.py +5 -69
  10. pychemstation/utils/method_types.py +44 -0
  11. pychemstation/utils/sequence_types.py +33 -0
  12. pychemstation/utils/table_types.py +60 -0
  13. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/METADATA +13 -12
  14. pychemstation-0.4.7.dev2.dist-info/RECORD +30 -0
  15. ag_hplc_macro/__init__.py +0 -3
  16. ag_hplc_macro/analysis/__init__.py +0 -1
  17. ag_hplc_macro/analysis/base_spectrum.py +0 -509
  18. ag_hplc_macro/analysis/spec_utils.py +0 -304
  19. ag_hplc_macro/analysis/utils.py +0 -63
  20. ag_hplc_macro/control/__init__.py +0 -5
  21. ag_hplc_macro/control/hplc.py +0 -673
  22. ag_hplc_macro/generated/__init__.py +0 -56
  23. ag_hplc_macro/generated/dad_method.py +0 -367
  24. ag_hplc_macro/generated/pump_method.py +0 -519
  25. ag_hplc_macro/utils/__init__.py +0 -2
  26. ag_hplc_macro/utils/constants.py +0 -15
  27. ag_hplc_macro/utils/hplc_param_types.py +0 -185
  28. hein-analytical-control/__init__.py +0 -3
  29. hein-analytical-control/analysis/__init__.py +0 -1
  30. hein-analytical-control/analysis/base_spectrum.py +0 -509
  31. hein-analytical-control/analysis/spec_utils.py +0 -304
  32. hein-analytical-control/analysis/utils.py +0 -63
  33. hein-analytical-control/devices/Agilent/__init__.py +0 -3
  34. hein-analytical-control/devices/Agilent/chemstation.py +0 -290
  35. hein-analytical-control/devices/Agilent/chromatogram.py +0 -129
  36. hein-analytical-control/devices/Agilent/hplc_param_types.py +0 -141
  37. hein-analytical-control/devices/Magritek/Spinsolve/__init__.py +0 -0
  38. hein-analytical-control/devices/Magritek/Spinsolve/commands.py +0 -495
  39. hein-analytical-control/devices/Magritek/Spinsolve/spectrum.py +0 -822
  40. hein-analytical-control/devices/Magritek/Spinsolve/spinsolve.py +0 -425
  41. hein-analytical-control/devices/Magritek/Spinsolve/utils/__init__.py +0 -5
  42. hein-analytical-control/devices/Magritek/Spinsolve/utils/connection.py +0 -168
  43. hein-analytical-control/devices/Magritek/Spinsolve/utils/constants.py +0 -8
  44. hein-analytical-control/devices/Magritek/Spinsolve/utils/exceptions.py +0 -25
  45. hein-analytical-control/devices/Magritek/Spinsolve/utils/parser.py +0 -340
  46. hein-analytical-control/devices/Magritek/Spinsolve/utils/shimming.py +0 -55
  47. hein-analytical-control/devices/Magritek/Spinsolve/utils/spinsolve_logging.py +0 -43
  48. hein-analytical-control/devices/Magritek/__init__.py +0 -0
  49. hein-analytical-control/devices/OceanOptics/IR/NIRQuest512.py +0 -90
  50. hein-analytical-control/devices/OceanOptics/IR/__init__.py +0 -0
  51. hein-analytical-control/devices/OceanOptics/IR/ir_spectrum.py +0 -191
  52. hein-analytical-control/devices/OceanOptics/Raman/__init__.py +0 -0
  53. hein-analytical-control/devices/OceanOptics/Raman/raman_control.py +0 -46
  54. hein-analytical-control/devices/OceanOptics/Raman/raman_spectrum.py +0 -148
  55. hein-analytical-control/devices/OceanOptics/UV/QEPro2192.py +0 -90
  56. hein-analytical-control/devices/OceanOptics/UV/__init__.py +0 -0
  57. hein-analytical-control/devices/OceanOptics/UV/uv_spectrum.py +0 -227
  58. hein-analytical-control/devices/OceanOptics/__init__.py +0 -0
  59. hein-analytical-control/devices/OceanOptics/oceanoptics.py +0 -115
  60. hein-analytical-control/devices/__init__.py +0 -15
  61. hein-analytical-control/generated/__init__.py +0 -56
  62. hein-analytical-control/generated/dad_method.py +0 -367
  63. hein-analytical-control/generated/pump_method.py +0 -519
  64. hein_analytical_control/__init__.py +0 -3
  65. hein_analytical_control/analysis/__init__.py +0 -1
  66. hein_analytical_control/analysis/base_spectrum.py +0 -509
  67. hein_analytical_control/analysis/spec_utils.py +0 -304
  68. hein_analytical_control/analysis/utils.py +0 -63
  69. hein_analytical_control/devices/Agilent/__init__.py +0 -3
  70. hein_analytical_control/devices/Agilent/chemstation.py +0 -290
  71. hein_analytical_control/devices/Agilent/chromatogram.py +0 -129
  72. hein_analytical_control/devices/Agilent/hplc.py +0 -436
  73. hein_analytical_control/devices/Magritek/Spinsolve/__init__.py +0 -0
  74. hein_analytical_control/devices/Magritek/Spinsolve/commands.py +0 -495
  75. hein_analytical_control/devices/Magritek/Spinsolve/spectrum.py +0 -822
  76. hein_analytical_control/devices/Magritek/Spinsolve/spinsolve.py +0 -425
  77. hein_analytical_control/devices/Magritek/Spinsolve/utils/__init__.py +0 -5
  78. hein_analytical_control/devices/Magritek/Spinsolve/utils/connection.py +0 -168
  79. hein_analytical_control/devices/Magritek/Spinsolve/utils/constants.py +0 -8
  80. hein_analytical_control/devices/Magritek/Spinsolve/utils/exceptions.py +0 -25
  81. hein_analytical_control/devices/Magritek/Spinsolve/utils/parser.py +0 -340
  82. hein_analytical_control/devices/Magritek/Spinsolve/utils/shimming.py +0 -55
  83. hein_analytical_control/devices/Magritek/Spinsolve/utils/spinsolve_logging.py +0 -43
  84. hein_analytical_control/devices/Magritek/__init__.py +0 -0
  85. hein_analytical_control/devices/OceanOptics/IR/NIRQuest512.py +0 -90
  86. hein_analytical_control/devices/OceanOptics/IR/__init__.py +0 -0
  87. hein_analytical_control/devices/OceanOptics/IR/ir_spectrum.py +0 -191
  88. hein_analytical_control/devices/OceanOptics/Raman/__init__.py +0 -0
  89. hein_analytical_control/devices/OceanOptics/Raman/raman_control.py +0 -46
  90. hein_analytical_control/devices/OceanOptics/Raman/raman_spectrum.py +0 -148
  91. hein_analytical_control/devices/OceanOptics/UV/QEPro2192.py +0 -90
  92. hein_analytical_control/devices/OceanOptics/UV/__init__.py +0 -0
  93. hein_analytical_control/devices/OceanOptics/UV/uv_spectrum.py +0 -227
  94. hein_analytical_control/devices/OceanOptics/__init__.py +0 -0
  95. hein_analytical_control/devices/OceanOptics/oceanoptics.py +0 -115
  96. hein_analytical_control/devices/__init__.py +0 -15
  97. hein_analytical_control/generated/__init__.py +0 -56
  98. hein_analytical_control/generated/dad_method.py +0 -367
  99. hein_analytical_control/generated/pump_method.py +0 -519
  100. pychemstation-0.4.7.dev1.dist-info/RECORD +0 -109
  101. /ag_hplc_macro/utils/chemstation.py → /pychemstation/utils/parsing.py +0 -0
  102. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/LICENSE +0 -0
  103. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/WHEEL +0 -0
  104. {pychemstation-0.4.7.dev1.dist-info → pychemstation-0.4.7.dev2.dist-info}/top_level.txt +0 -0
@@ -1,822 +0,0 @@
1
- """Module for NMR spectral data loading and manipulating"""
2
-
3
- # pylint: disable=attribute-defined-outside-init
4
- import os
5
- import logging
6
- import time
7
- from typing import Union
8
-
9
- import numpy as np
10
- import scipy
11
- import nmrglue as ng
12
- import matplotlib.pyplot as plt
13
-
14
- from ....analysis import AbstractSpectrum
15
- from ....analysis.spec_utils import *
16
-
17
- # standard filenames for spectral data
18
- FID_DATA = "data.1d"
19
- ACQUISITION_PARAMETERS = "acqu.par"
20
- PROCESSED_SPECTRUM = "spectrum_processed.1d" # not always present
21
- PROTOCOL_PARAMETERS = "protocol.par"
22
-
23
- # format used in acquisition parameters
24
- TIME_FORMAT = r"%Y-%m-%dT%H:%M:%S.%f"
25
-
26
- # reserved for future use
27
- JCAMP_DX_SPECTRUM = "nmr_fid.dx"
28
- CSV_SPECTRUM = "spectrum.csv"
29
-
30
- # filename for shimming parameters
31
- SHIMMING_PARAMETERS = "shim.par"
32
- SHIMMING_FID = "sample_fid.1d"
33
- SHIMMING_SPECTRUM = "spectrum.1d"
34
-
35
-
36
- class SpinsolveNMRSpectrum(AbstractSpectrum):
37
- """Class for NMR spectrum loading and handling."""
38
-
39
- AXIS_MAPPING = {"x": "time", "y": ""}
40
-
41
- INTERNAL_PROPERTIES = {
42
- "baseline",
43
- "data_path",
44
- "_uc",
45
- }
46
-
47
- def __init__(self, path=None, autosaving=False):
48
-
49
- if path is None:
50
- path = os.path.join(".", "nmr_data")
51
-
52
- self.logger = logging.getLogger("spinsolve.spectrum")
53
-
54
- # updating public properties to include the universal dictionary
55
- self.PUBLIC_PROPERTIES.add("udic")
56
- # and parameters
57
- self.PUBLIC_PROPERTIES.add("parameters")
58
-
59
- # autosaving set to False, since spectra are saved by Spinsolve anyway
60
- super().__init__(path, autosaving)
61
-
62
- # universal dictionary for the acquisition parameters
63
- # placeholder, will be updated when spectral data is loaded
64
- self.udic = ng.fileio.fileiobase.create_blank_udic(1) # 1D spectrum
65
-
66
- # placeholder to store shimming parameters in the current session
67
- self.last_shimming_time = None
68
- self.last_shimming_results = None
69
-
70
- def load_spectrum(self, data_path, start_time=None, preprocessed=True):
71
- """Loads the spectra from the given folder.
72
-
73
- If preprocessed argument is True, loading the spectral data already
74
- processed by the Spinsolve software (fft + autophasing).
75
-
76
- Args:
77
- data_path (str): Path where NMR data has been saved.
78
- start_time (float, optional): Start time of the current experiment,
79
- used to calculate the timestamp for the spectrum. If omitted,
80
- uses the time since Epoch from the spectrum acquisition
81
- parameters.
82
- preprocessed (bool, optional): If True - will load preprocessed (by
83
- Spinsolve software) spectral data. If False (default) - base fid
84
- is loaded and used for further processing.
85
- """
86
-
87
- # this is needed to avoid dropping parameters when called in parent
88
- # class, since _dump() is called there as well
89
- if self.x is not None:
90
- if self.autosaving:
91
- self.save_data()
92
- self._dump()
93
-
94
- # filepaths
95
- param_path = os.path.join(data_path, ACQUISITION_PARAMETERS)
96
- processed_path = os.path.join(data_path, PROCESSED_SPECTRUM)
97
- fid_path = os.path.join(data_path, FID_DATA)
98
-
99
- # loading parameters
100
- try:
101
- self.parameters = self.extract_parameters(param_path)
102
- except FileNotFoundError:
103
- # this happens only if shimming was performed
104
- shim_path = os.path.join(data_path, SHIMMING_PARAMETERS)
105
- self.parameters = self.extract_parameters(shim_path)
106
-
107
- # updating placeholders
108
- self.last_shimming_results = {
109
- parameter: self.parameters[parameter]
110
- for parameter in self.parameters
111
- if parameter.startswith("shim")
112
- }
113
-
114
- # updating last shimming time
115
- self.last_shimming_time = time.strptime(
116
- self.parameters["CurrentTime"], TIME_FORMAT
117
- )
118
-
119
- # updating file names for the shimming
120
- processed_path = os.path.join(data_path, SHIMMING_SPECTRUM)
121
-
122
- # forcing preprocessed to deal with frequency domain not raw FID
123
- preprocessed = True
124
-
125
- self.data_path = data_path
126
-
127
- # extracting the time from acquisition parameters
128
- spectrum_time = time.strptime(self.parameters["CurrentTime"], TIME_FORMAT)
129
-
130
- if start_time is not None:
131
- timestamp = round(time.mktime(spectrum_time) - start_time)
132
- else:
133
- timestamp = round(time.mktime(spectrum_time))
134
-
135
- # loading raw fid data
136
- if not preprocessed:
137
- x_axis, y_real, y_img = self.extract_data(fid_path)
138
- spectrum_data = np.array(y_real + y_img * 1j)
139
-
140
- # updating the universal dictionary
141
- self.udic[0].update(
142
- # spectral width in kHz
143
- sw=self.parameters["bandwidth"] * 1e3,
144
- # carrier frequency
145
- car=self.parameters["bandwidth"] * 1e3 / 2
146
- + self.parameters["lowestFrequency"],
147
- # observed frequency
148
- obs=self.parameters["b1Freq"],
149
- # number of points
150
- size=self.parameters["nrPnts"],
151
- # time domain
152
- time=True,
153
- # label, e.g. 1H
154
- label=self.parameters["rxChannel"],
155
- )
156
-
157
- # changing axis mapping according to raw FID
158
- self.AXIS_MAPPING.update(x="time")
159
-
160
- # creating unit conversion object
161
- self._uc = ng.fileio.fileiobase.uc_from_udic(self.udic)
162
-
163
- # check for the preprocessed file, as it's not always present
164
- elif os.path.isfile(processed_path) and preprocessed:
165
- # loading frequency axis and real part of the complex spectrum data
166
- x_axis, spectrum_data, _ = self.extract_data(processed_path)
167
- # reversing spectrum order to match default nmr order
168
- # i.e. highest - left
169
- x_axis = x_axis[::-1]
170
- spectrum_data = spectrum_data[::-1]
171
- # updating axis mapping
172
- self.AXIS_MAPPING.update(x="ppm")
173
-
174
- else:
175
- self.logger.warning(
176
- "Current version of SpinsolveNMRSpectrum does \
177
- not support raw FID data and no processed spectrum was found. Please change \
178
- settings of the Spinsolve Software to enable default processing"
179
- )
180
- raise AttributeError(
181
- f"Processed spectrum was not found in the \
182
- supplied directory <{data_path}>"
183
- )
184
-
185
- # loading all data
186
- super().load_spectrum(x_axis, spectrum_data, int(timestamp))
187
-
188
- ### PUBLIC METHODS TO LOAD RAW DATA ###
189
-
190
- def extract_data(self, spectrum_path):
191
- """Reads the Spinsolve spectrum file and extracts the spectrum data
192
- from it.
193
-
194
- Data is stored in binary format as C struct data type. First 32 bytes
195
- (8 integers * 4 bytes) contains the header information and can be
196
- discarded. The rest data is stored as float (4 byte) data points for X
197
- axis and complex number (as float, float for real and imaginary parts)
198
- data points for Y axis.
199
-
200
- Refer to the software manual (v 1.16, July 2019 Magritek) for details.
201
-
202
- Args:
203
- spectrum_path: Path to saved NMR spectrum data.
204
-
205
- Returns:
206
- tuple[x_axis, y_data_real, y_data_img]:
207
- x_axis (:obj: np.array, dtype='float32'): X axis points.
208
- y_data_real (:obj: np.array, dtype='float32'): real part of the
209
- complex spectrum data.
210
- y_data_img (:obj: np.array, dtype='float32'): imaginary part of
211
- the complex spectrum data.
212
-
213
- """
214
-
215
- # reading data with numpy fromfile
216
- # the header is discarded
217
- spectrum_data = np.fromfile(spectrum_path, dtype="<f")[8:]
218
-
219
- x_axis = spectrum_data[: len(spectrum_data) // 3]
220
-
221
- # breaking the rest of the data into real and imaginary part
222
- y_real = spectrum_data[len(spectrum_data) // 3 :][::2]
223
- y_img = spectrum_data[len(spectrum_data) // 3 :][1::2]
224
-
225
- return (x_axis, y_real, y_img)
226
-
227
- def extract_parameters(self, params_path):
228
- """Get the NMR parameters from the given folder.
229
-
230
- Args:
231
- params_path (str): Path to saved NMR data.
232
-
233
- Returns:
234
- Dict: Acquisition parameters.
235
- """
236
-
237
- # loading spectrum parameters
238
- spec_params = {}
239
- with open(params_path) as fileobj:
240
- param = fileobj.readline()
241
- while param:
242
- # in form of "Param" = "Value"\n
243
- parameter, value = param.split("=", maxsplit=1)
244
- # stripping from whitespaces, newlines and extra doublequotes
245
- parameter = parameter.strip()
246
- value = value.strip(' \n"')
247
- # special case: userData
248
- # converting to nested dict
249
- if parameter == "userData" and value:
250
- values = value.split(";")
251
- value = {}
252
- for key_value in values:
253
- key, val = key_value.split("=")
254
- value[key] = val
255
- # converting values to float if possible
256
- try:
257
- spec_params[parameter] = float(value)
258
- except (ValueError, TypeError):
259
- spec_params[parameter] = value
260
- param = fileobj.readline()
261
-
262
- return spec_params
263
-
264
- def show_spectrum(
265
- self,
266
- filename=None,
267
- title=None,
268
- label=None,
269
- ):
270
- """Plots the spectral data using matplotlib.pyplot module.
271
-
272
- Redefined from ancestor class to support axis inverting.
273
-
274
- Args:
275
- filename (str, optional): Filename for the current plot. If omitted,
276
- file is not saved.
277
- title (str, optional): Title for the spectrum plot. If omitted, no
278
- title is set.
279
- label (str, optional): Label for the spectrum plot. If omitted, uses
280
- the spectrum timestamp.
281
- """
282
- if label is None:
283
- label = f"{self.timestamp}"
284
-
285
- fig, ax = plt.subplots(figsize=(12, 8))
286
-
287
- ax.plot(
288
- self.x,
289
- self.y,
290
- color="xkcd:navy blue",
291
- label=label,
292
- )
293
-
294
- ax.set_xlabel(self.AXIS_MAPPING["x"])
295
- ax.set_ylabel(self.AXIS_MAPPING["y"])
296
-
297
- if title is not None:
298
- ax.set_title(title)
299
-
300
- # plotting peaks if found
301
- if self.peaks is not None:
302
- plt.scatter(
303
- self.peaks[:, 1],
304
- self.peaks[:, 2],
305
- label="found peaks",
306
- color="xkcd:tangerine",
307
- )
308
-
309
- ax.legend()
310
-
311
- # inverting if ppm scale
312
- if self.AXIS_MAPPING["x"] == "ppm":
313
- ax.invert_xaxis()
314
-
315
- if filename is None:
316
- fig.show()
317
-
318
- else:
319
- path = os.path.join(self.path, "images")
320
- os.makedirs(path, exist_ok=True)
321
- fig.savefig(os.path.join(path, f"{filename}.png"), dpi=150)
322
-
323
- def fft(self, in_place=True):
324
- """Fourier transformation, NMR ordering of the results.
325
-
326
- This is the wrapper around nmrglue.process.proc_base.fft function.
327
- Please refer to original function documentation for details.
328
-
329
- Args:
330
- in_place(bool, optional): If True (default), self.y is updated;
331
- returns new array if False.
332
-
333
- Returns:
334
- Union[:np.array:, None]: If in_place is True, will return new array
335
- after FFT.
336
- """
337
-
338
- if in_place:
339
- self.y = ng.proc_base.fft(self.y)
340
-
341
- # updating x and y axis
342
- self.AXIS_MAPPING.update(x="ppm")
343
- self.x = self._uc.ppm_scale()
344
-
345
- # updating the udic to frequency domain
346
- self.udic[0]["time"] = False
347
- self.udic[0]["freq"] = True
348
-
349
- else:
350
- return ng.proc_base.fft(self.y)
351
-
352
- def autophase(self, in_place=True, function="peak_minima", p0=0.0, p1=0.0):
353
- """Automatic linear phase correction. FFT is performed!
354
-
355
- This is the wrapper around nmrglue.process.proc_autophase.autops
356
- function. Please refer to original function documentation for details.
357
-
358
- Args:
359
- in_place (bool, optional): If True (default), self.y is updated;
360
- returns new array if False.
361
- function (Union[str, Callable], optional): Algorithm to use for
362
- phase scoring. Builtin functions can be specified by one of the
363
- following strings: "acme" or "peak_minima". This refers to
364
- nmrglue.process.proc_autophase.autops function, "peak_minima"
365
- (default) was found to perform best.
366
- p0 (float, optional): Initial zero order phase in degrees.
367
- p1 (float, optional): Initial first order phase in degrees.
368
-
369
- Returns:
370
- Union[:np.array:, None]: If in_place is True, will return new array
371
- after phase correction.
372
- """
373
-
374
- # check if fft was performed
375
- if self.AXIS_MAPPING["x"] == "time":
376
- self.fft()
377
-
378
- autophased = ng.process.proc_autophase.autops(self.y, function, p0, p1)
379
-
380
- if in_place:
381
- self.y = autophased
382
- return
383
-
384
- return autophased
385
-
386
- def correct_baseline(self, in_place=True, wd=20):
387
- """Automatic baseline correction using distribution based
388
- classification method.
389
-
390
- Algorithm described in: Wang et al. Anal. Chem. 2013, 85, 1231-1239
391
-
392
- This is the wrapper around nmrglue.process.proc_bl.baseline_corrector
393
- function. Please refer to original function documentation for details.
394
-
395
- Args:
396
- in_place(bool, optional): If True (default), self.y is updated;
397
- returns new array if False.
398
- wd(float, optional): Median window size in pts.
399
-
400
- Returns:
401
- Union[:np.array:, None]: If in_place is True, will return new array
402
- after baseline correction.
403
- """
404
-
405
- # check if fft was performed
406
- if self.AXIS_MAPPING["x"] == "time":
407
- self.fft()
408
-
409
- corrected = ng.process.proc_bl.baseline_corrector(self.y, wd)
410
-
411
- if in_place:
412
- self.y = corrected
413
- return
414
-
415
- return corrected
416
-
417
- def load_data(self, path):
418
- # overwritten from abstract class to allow updating of unit conversion
419
- super().load_data(path)
420
- self._uc = ng.fileio.fileiobase.uc_from_udic(self.udic)
421
-
422
- # updating axis mapping from "time" default
423
- if self.udic[0]["freq"]:
424
- self.AXIS_MAPPING.update(x="ppm")
425
-
426
- elif self.udic[0]["time"]:
427
- self.AXIS_MAPPING.update(x="time")
428
-
429
- def smooth_spectrum(self, in_place=True, routine="ng", **params):
430
- """Smoothes the spectrum.
431
-
432
- Depending on the routine chosen will use either Savitsky-Golay filter
433
- from scipy.signal module or nmrglue custom function.
434
-
435
- !Note: savgol will cast complex dtype to float!
436
-
437
- Args:
438
- in_place(bool, optional): If True (default), self.y is updated;
439
- returns new array if False.
440
-
441
- routine(str, optional): Smoothing routine.
442
- "ng" (default) will use custom smoothing function from
443
- nmrglue.process.proc_base module.
444
-
445
- "savgol" will use savgol_filter method from scipt.signal module
446
- defined in ancestor method.
447
-
448
- parram in params: Keyword arguments for the chosen routine function.
449
- For "savgol" routine:
450
-
451
- window_length (int): The length of the filter window (i.e.
452
- thenumber of coefficients). window_length must be a
453
- positive odd integer.
454
-
455
- polyorder (int): The order of the polynomial used to fit the
456
- samples. polyorder must be less than window_length.
457
-
458
- For "ng" routine:
459
-
460
- n (int): Size of smoothing windows (+/- points).
461
-
462
- Returns:
463
- Union[:np.array:, None]: If in_place is True, will return new array
464
- after baseline correction.
465
- """
466
-
467
- if routine == "savgol":
468
- super().smooth_spectrum(in_place=in_place, **params)
469
-
470
- elif routine == "ng":
471
- # using default value
472
- if not params:
473
- params = {"n": 5}
474
-
475
- if in_place:
476
- self.y = ng.process.proc_base.smo(self.y, **params)
477
- return
478
-
479
- return ng.process.proc_base.smo(self.y, **params)
480
-
481
- else:
482
- raise ValueError(
483
- 'Please choose either nmrglue ("ng") or Savitsky-\
484
- Golay ("savgol") smoothing routine'
485
- )
486
-
487
- def apodization(self, in_place=True, function="em", **params):
488
- """Applies a chosen window function.
489
-
490
- Args:
491
- in_place (bool, optional): If True (default), self.y is updated;
492
- returns new array if False.
493
-
494
- function (str, optional): Window function of choice.
495
- "em" - exponential multiply window (mimic NMRPipe EM function).
496
- "gm" - Lorentz-to-Gauss window function (NMRPipe GM function).
497
- "gmb" - Gauss-like window function (NMRPipe GMB function).
498
-
499
- param in params: Keyword arguments for the chosen window function:
500
-
501
- For "em":
502
- See reference for nmrglue.proc_base.em and NMRPipe EM
503
- functions.
504
-
505
- lb (float): Exponential decay of the window in terms of a
506
- line broadening in Hz. Negative values will generate an
507
- increasing exponential window, which corresponds to a
508
- line sharpening. The line-broadening parameter is often
509
- selected to match the natural linewidth.
510
-
511
- For "gm":
512
- See reference for nmrglue.proc_base.gm and NMRPipe GM
513
- functions.
514
-
515
- g1 (float): Specifies the inverse exponential to apply in
516
- terms of a line sharpening in Hz. It is usually adjusted
517
- to match the natural linewidth. The default value is
518
- 0.0, which means no exponential term will be applied,
519
- and the window will be a pure Gaussian function.
520
-
521
- g2 (float): Specifies the Gaussian to apply in terms of a
522
- line broadening in Hz. It is usually adjusted to be
523
- larger (x 1.25 - 4.0) than the line sharpening specified
524
- by the g1 attribute.
525
-
526
- g3 (float): Specifies the position of the Gaussian
527
- function's maximum on the FID. It is specified as a
528
- value ranging from 0.0 (Gaussian maximum at the first
529
- point of the FID) to 1.0 (Gaussian maximum at the last
530
- point of the FID). It most applications, the default
531
- value of 0.0 is used.
532
-
533
- For "gmb":
534
- See reference for nmrglue.proc_base.gmb and NMRPipe GMB
535
- functions.
536
-
537
- lb (float): Specifies an exponential factor in the chosen
538
- Gauss window function. This value is usually specified
539
- as a negative number which is about the same size as the
540
- natural linewidth in Hz. The default value is 0.0, which
541
- means no exponential term will be applied.
542
-
543
- gb (float): Specifies a Gaussian factor gb, as used in the
544
- chosen Gauss window function. It is usually specified as
545
- a positive number which is a fraction of 1.0. The
546
- default value is 0.0, which leads to an undefined window
547
- function according to the formula; for this reason, the
548
- Gaussian term is omitted from the calculation when gb
549
- 0.0 is given.
550
-
551
- Returns:
552
- Union[:np.array:, None]: If in_place is True, will return new array
553
- after baseline correction.
554
- """
555
- # TODO check for Fourier transformation!
556
-
557
- if function == "em":
558
- # converting lb value to NMRPipe-like
559
- if "lb" in params:
560
- # deviding by spectral width in Hz
561
- params["lb"] = params["lb"] / self.udic[0]["sw"]
562
-
563
- if in_place:
564
- self.y = ng.process.proc_base.em(self.y, **params)
565
- return
566
-
567
- return ng.process.proc_base.em(self.y, **params)
568
-
569
- elif function == "gm":
570
- # converting values into NMRPipe-like
571
- if "g1" in params:
572
- params["g1"] = params["g1"] / self.udic[0]["sw"]
573
-
574
- if "g2" in params:
575
- params["g2"] = params["g2"] / self.udic[0]["sw"]
576
-
577
- if in_place:
578
- self.y = ng.process.proc_base.gm(self.y, **params)
579
- return
580
-
581
- return ng.process.proc_base.gm(self.y, **params)
582
-
583
- elif function == "gmb":
584
- # converting values into NMRPipe-like
585
- # for formula reference see documentation and source code of
586
- # nmrglue.proc_base.gmb function and NMRPipe GMB command reference
587
- if "lb" in params:
588
- a = np.pi * params["lb"] / self.udic[0]["sw"]
589
- else:
590
- a = 0.0
591
-
592
- if "gb" in params:
593
- b = -a / (2.0 * params["gb"] * self.udic[0]["size"])
594
- else:
595
- b = 0.0
596
-
597
- if in_place:
598
- self.y = ng.process.proc_base.gmb(self.y, a=a, b=b)
599
- return
600
-
601
- return ng.process.proc_base.gmb(self.y, a=a, b=b)
602
-
603
- def zero_fill(self, n=1, in_place=True):
604
- """Zero filling the data by 2**n.
605
-
606
- Args:
607
- n (int): power of 2 to append 0 to the data.
608
- in_place (bool, optional): If True (default), self.y is updated;
609
- returns new array if False.
610
-
611
- Returns:
612
- Union[:np.array:, None]: If in_place is True, will return new array
613
- after baseline correction.
614
- """
615
-
616
- if in_place:
617
- # zero fill is useless when fft performed
618
- if self.AXIS_MAPPING["x"] == "ppm":
619
- self.logger.warning(
620
- "FFT already performed, zero filling \
621
- skipped"
622
- )
623
- return
624
-
625
- # extending y axis
626
- self.y = ng.process.proc_base.zf_double(self.y, n)
627
-
628
- # extending x axis
629
- self.x = np.linspace(self.x[0], self.x[-1] * 2**n, self.y.shape[0])
630
-
631
- # updating udic and uc
632
- self.udic[0].update(size=self.x.size)
633
- self._uc = ng.fileio.fileiobase.uc_from_udic(self.udic)
634
- return
635
-
636
- return ng.process.proc_base.zf_double(self.y, n)
637
-
638
- def generate_peak_regions(
639
- self,
640
- magnitude=True,
641
- derivative=True,
642
- smoothed=True,
643
- d_merge=0.056,
644
- d_expand=0.0,
645
- ):
646
- """Generate regions if interest potentially containing compound peaks
647
- from the spectral data.
648
-
649
- Args:
650
- d_merge (float, Optional): arbitrary interval (in ppm!) to merge
651
- several regions, if the distance between is lower.
652
- d_expand (float, Optional): arbitrary value (in ppm!) to expand the
653
- regions after automatic assigning and filtering.
654
-
655
- Returns:
656
- :obj:np.array: 2D Mx2 array with peak regions indexes (rows) as left
657
- and right borders (columns).
658
- """
659
-
660
- # check if fft was performed
661
- if self.AXIS_MAPPING["x"] != "ppm":
662
- self.logger.warning("Please perform FFT first.")
663
- return np.array([[]])
664
-
665
- # placeholder
666
- peak_map = np.full_like(self.x, False)
667
-
668
- if magnitude:
669
- # looking for peaks in magnitude mode
670
- magnitude_spectrum = np.sqrt(self.y.real**2 + self.y.imag**2)
671
- # mapping
672
- peak_map = np.logical_or(
673
- create_binary_peak_map(magnitude_spectrum), peak_map
674
- )
675
- else:
676
- peak_map = np.logical_or(create_binary_peak_map(self.y), peak_map)
677
-
678
- # additionally in the derivative
679
- if derivative:
680
- try:
681
- derivative_map = create_binary_peak_map(np.gradient(magnitude_spectrum))
682
- except NameError:
683
- derivative_map = create_binary_peak_map(np.gradient(self.y))
684
- # combining
685
- peak_map = np.logical_or(derivative_map, peak_map)
686
-
687
- # and in the smoothed version
688
- if smoothed:
689
- try:
690
- smoothed = scipy.ndimage.gaussian_filter1d(magnitude_spectrum, 3)
691
- except NameError:
692
- # smoothing only supported on non-complex data
693
- smoothed = scipy.ndimage.gaussian_filter1d(self.y.real, 3)
694
- # combining
695
- peak_map = np.logical_or(create_binary_peak_map(smoothed), peak_map)
696
-
697
- # extracting the regions from the full map
698
- regions = combine_map_to_regions(peak_map)
699
-
700
- # Skip further steps if no peaks identified
701
- if not regions.size > 0:
702
- return regions
703
-
704
- # filtering, merging, expanding
705
- regions = filter_regions(self.x, regions)
706
- regions = filter_noisy_regions(self.y, regions)
707
- if d_merge:
708
- regions = merge_regions(self.x, regions, d_merge=d_merge)
709
- if d_expand:
710
- regions = expand_regions(self.x, regions, d_expand=d_expand)
711
-
712
- return regions
713
-
714
- def default_processing(self):
715
- """Default processing.
716
-
717
- Performs several processing methods with attributes chosen
718
- experimentally to achieve best results for the purpose of "fast",
719
- "reliable" and "reproducible" NMR analysis.
720
- """
721
- # TODO add processing for various nucleus
722
- if self.parameters["rxChannel"] == "19F":
723
- self.apodization(function="gm", g1=1.2, g2=4.5)
724
- self.zero_fill()
725
- self.fft()
726
- self.correct_baseline()
727
- self.autophase()
728
- self.correct_baseline()
729
-
730
- def integrate_area(self, area, rule="trapz"):
731
- """Integrate the spectrum within given area.
732
-
733
- Redefined from ancestor method to discard imaginary part of the
734
- resulting integral.
735
-
736
- Args:
737
- area (Tuple[float, float]): Tuple with left and right border (X axis
738
- obviously) for the desired area.
739
- rule (str, optional): Method for integration, "trapz" - trapezoidal
740
- rule (default), "simps" - Simpson's rule.
741
- Returns:
742
- float: Definite integral within given area as approximated by given
743
- method.
744
- """
745
-
746
- result = super().integrate_area(area, rule)
747
-
748
- # discarding imaginary part and returning the absolute value
749
- # due to "NMR-order" of the x axis
750
- return abs(result.real)
751
-
752
- def integrate_regions(self, regions):
753
- """Integrate the given regions using nmrglue integration method.
754
-
755
- Check the corresponding documentation for details.
756
-
757
- Args:
758
- regions (:obj:np.array): 2D Mx2 array, containing left and right
759
- borders for the regions of interest, potentially containing
760
- peak areas (as found by self.generate_peak_regions method).
761
-
762
- Return:
763
- result (:obj:np.array): 1D M-size array contaning integration for
764
- each region of interest.
765
- """
766
-
767
- result = ng.analysis.integration.integrate(
768
- data=self.y,
769
- unit_conv=self._uc,
770
- limits=self.x[regions], # directly get the ppm values
771
- )
772
-
773
- # discarding imaginary part
774
- return np.abs(np.real(result))
775
-
776
- def reference_spectrum(
777
- self,
778
- new_position: float,
779
- reference: Union[float, str] = "highest",
780
- ) -> None:
781
- """Shifts the spectrum x axis according to the new reference.
782
-
783
- If old reference is omitted will shift the spectrum according to the
784
- highest peak.
785
-
786
- Args:
787
- new_position (float): The position to shift the peak to.
788
- reference (Union[float, str]): The current position of the reference
789
- peak or it's indication for shifting: either "highest" (default)
790
- or "closest" for selecting highest or closest to the new
791
- reference peak for shifting.
792
- """
793
-
794
- # find reference if not given
795
- if isinstance(reference, str):
796
- if reference == "highest":
797
- # Looking for highest point
798
- reference = self.x[np.argmax(self.y)]
799
- elif reference == "closest":
800
- # Looking for closest peak among found across whole spectrum
801
- # Specifying area not to update self.peaks
802
- peaks = self.find_peaks(area=(self.x.min(), self.x.max()))
803
- # x coordinate
804
- peaks_xs = peaks[:, 1].real
805
- reference = peaks[np.argmin(np.abs(peaks_xs - new_position))][1].real
806
- else:
807
- self.logger.warning(
808
- 'Please use either "highest" or "closest"\
809
- reference, or give exact value.'
810
- )
811
- return
812
-
813
- new_position, _ = find_nearest_value_index(self.x, new_position)
814
-
815
- diff = new_position - reference
816
-
817
- # shifting the axis
818
- self.x = self.x + diff
819
-
820
- # if peaks are recorded, find new
821
- if self.peaks is not None:
822
- self.find_peaks()