pychemstation 0.10.7__py3-none-any.whl → 0.10.8__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 (31) hide show
  1. pychemstation/analysis/base_spectrum.py +14 -15
  2. pychemstation/analysis/chromatogram.py +7 -8
  3. pychemstation/analysis/process_report.py +10 -16
  4. pychemstation/control/README.md +1 -1
  5. pychemstation/control/controllers/__init__.py +2 -1
  6. pychemstation/control/controllers/comm.py +33 -27
  7. pychemstation/control/controllers/data_aq/method.py +233 -79
  8. pychemstation/control/controllers/data_aq/sequence.py +104 -84
  9. pychemstation/control/controllers/devices/injector.py +3 -3
  10. pychemstation/control/hplc.py +53 -41
  11. pychemstation/utils/__init__.py +23 -0
  12. pychemstation/{control/controllers → utils}/abc_tables/abc_comm.py +15 -13
  13. pychemstation/{control/controllers → utils}/abc_tables/device.py +9 -2
  14. pychemstation/{control/controllers → utils}/abc_tables/run.py +45 -37
  15. pychemstation/{control/controllers → utils}/abc_tables/table.py +32 -29
  16. pychemstation/utils/macro.py +7 -2
  17. pychemstation/utils/method_types.py +13 -14
  18. pychemstation/utils/mocking/mock_comm.py +25 -2
  19. pychemstation/utils/mocking/mock_hplc.py +29 -1
  20. pychemstation/utils/num_utils.py +3 -3
  21. pychemstation/utils/sequence_types.py +30 -14
  22. pychemstation/utils/spec_utils.py +42 -66
  23. pychemstation/utils/table_types.py +15 -2
  24. pychemstation/utils/tray_types.py +28 -16
  25. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/METADATA +1 -7
  26. pychemstation-0.10.8.dist-info/RECORD +41 -0
  27. pychemstation/utils/pump_types.py +0 -7
  28. pychemstation-0.10.7.dist-info/RECORD +0 -42
  29. /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
  30. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/WHEEL +0 -0
  31. {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,7 @@ import logging
2
2
  import os
3
3
  import pickle
4
4
  from abc import ABC, abstractmethod
5
+ from typing import Optional, Union
5
6
 
6
7
  import matplotlib.pyplot as plt
7
8
  import numpy as np
@@ -43,16 +44,15 @@ class AbstractSpectrum(ABC):
43
44
  "baseline",
44
45
  }
45
46
 
46
- def __init__(self, path=None, autosaving=True):
47
+ def __init__(
48
+ self, path: Optional[Union[str, bool]] = None, autosaving: Optional[bool] = True
49
+ ):
47
50
  """Default constructor, loads properties into instance namespace.
48
51
 
49
52
  Can be redefined in ancestor classes.
50
53
 
51
- Args:
52
- path (Union[str, bool], optional): Valid path to save data to.
53
- If omitted, uses ".//spectrum". If False - no folder created.
54
- autosaving (bool, optional): If the True (default) will save the
55
- spectrum when the new one is loaded. Will drop otherwise.
54
+ :param path: Valid path to save data to. If omitted, uses ".//spectrum". If False - no folder created.
55
+ :param autosaving: If the True (default) will save the spectrum when the new one is loaded. Will drop otherwise.
56
56
  """
57
57
 
58
58
  self.autosaving = autosaving
@@ -70,10 +70,10 @@ class AbstractSpectrum(ABC):
70
70
  self.path = os.path.join(".", "spectrum")
71
71
  os.makedirs(self.path, exist_ok=True)
72
72
  else:
73
- try:
73
+ if isinstance(path, str):
74
74
  os.makedirs(path, exist_ok=True)
75
75
  self.path = path
76
- except TypeError: # type(path) -> bool
76
+ else:
77
77
  self.path = "."
78
78
 
79
79
  # creating logger
@@ -86,15 +86,14 @@ class AbstractSpectrum(ABC):
86
86
  self.__init__(path=self.path, autosaving=self.autosaving)
87
87
 
88
88
  @abstractmethod
89
- def load_spectrum(self, x, y, timestamp):
89
+ def load_spectrum(self, x: np.ndarray, y: np.ndarray, timestamp: float):
90
90
  """Loads the spectral data.
91
91
 
92
92
  This method must be redefined in ancestor classes.
93
93
 
94
- Args:
95
- x (:obj: np.array): An array with data to be plotted as "x" axis.
96
- y (:obj: np.array): An array with data to be plotted as "y" axis.
97
- timestamp (float): Timestamp to the corresponding spectrum.
94
+ :param x: An array with data to be plotted as "x" axis.
95
+ :param y: An array with data to be plotted as "y" axis.
96
+ :param timestamp: Timestamp to the corresponding spectrum.
98
97
  """
99
98
 
100
99
  try:
@@ -107,8 +106,8 @@ class AbstractSpectrum(ABC):
107
106
  self.save_data()
108
107
  self._dump()
109
108
 
110
- self.x = x
111
- self.y = y
109
+ self.x: np.ndarray = x
110
+ self.y: np.ndarray = y
112
111
  self.timestamp = timestamp
113
112
 
114
113
  def save_data(self, filename=None, verbose=False):
@@ -3,7 +3,7 @@
3
3
  import os
4
4
  import time
5
5
  from dataclasses import dataclass
6
- from typing import Dict
6
+ from typing import Dict, Tuple
7
7
 
8
8
  import numpy as np
9
9
 
@@ -80,15 +80,14 @@ class AgilentHPLCChromatogram(AbstractSpectrum):
80
80
 
81
81
  ### PUBLIC METHODS TO LOAD RAW DATA ###
82
82
 
83
- def extract_rawdata(self, experiment_dir: str, channel: str):
84
- """
85
- Reads raw data from Chemstation .CH files.
83
+ def extract_rawdata(
84
+ self, experiment_dir: str, channel: str
85
+ ) -> Tuple[np.ndarray, np.ndarray]:
86
+ """Reads raw data from Chemstation .CH files.
86
87
 
87
- Args:
88
- experiment_dir: .D directory with the .CH files
88
+ :param experiment_dir: .D directory with the .CH files
89
89
 
90
- Returns:
91
- np.array(times), np.array(values) Raw chromatogram data
90
+ :returns: np.array(times), np.array(values) Raw chromatogram data
92
91
  """
93
92
  filename = os.path.join(experiment_dir, f"DAD1{channel}")
94
93
  npz_file = filename + ".npz"
@@ -65,8 +65,7 @@ class ReportProcessor(abc.ABC):
65
65
 
66
66
  class CSVProcessor(ReportProcessor):
67
67
  def __init__(self, path: str):
68
- """
69
- Class to process reports in CSV form.
68
+ """Class to process reports in CSV form.
70
69
 
71
70
  :param path: the parent folder that contains the CSV report(s) to parse.
72
71
  """
@@ -84,7 +83,9 @@ class CSVProcessor(ReportProcessor):
84
83
  if "00" in name and file_extension.lower() == "csv":
85
84
  prefix, _, _ = name.partition("00")
86
85
  return prefix
87
- raise FileNotFoundError("Couldn't find the prefix for CSV")
86
+ raise FileNotFoundError(
87
+ "Couldn't find the prefix for CSV, please make sure the post-run settings generate a CSV."
88
+ )
88
89
 
89
90
  def report_contains(self, labels: List[str], want: List[str]):
90
91
  for label in labels:
@@ -104,8 +105,7 @@ class CSVProcessor(ReportProcessor):
104
105
  return all_labels_seen
105
106
 
106
107
  def process_report(self) -> Result[AgilentReport, AnyStr]:
107
- """
108
- Method to parse details from CSV report.
108
+ """Method to parse details from CSV report.
109
109
 
110
110
  :return: subset of complete report details, specifically the sample location, solvents in pumps,
111
111
  and list of peaks at each wavelength channel.
@@ -176,9 +176,7 @@ class CSVProcessor(ReportProcessor):
176
176
 
177
177
 
178
178
  class TXTProcessor(ReportProcessor):
179
- """
180
- Regex matches for column and unit combinations, courtesy of Veronica Lai.
181
- """
179
+ """Regex matches for column and unit combinations, courtesy of Veronica Lai."""
182
180
 
183
181
  _column_re_dictionary = {
184
182
  "Peak": { # peak index
@@ -212,8 +210,7 @@ class TXTProcessor(ReportProcessor):
212
210
  max_ret_time: int = 999,
213
211
  target_wavelength_range=None,
214
212
  ):
215
- """
216
- Class to process reports in CSV form.
213
+ """Class to process reports in CSV form.
217
214
 
218
215
  :param path: the parent folder that contains the CSV report(s) to parse.
219
216
  :param min_ret_time: peaks after this value (min) will be returned
@@ -228,8 +225,7 @@ class TXTProcessor(ReportProcessor):
228
225
  super().__init__(path)
229
226
 
230
227
  def process_report(self) -> Result[AgilentReport, Union[AnyStr, Exception]]:
231
- """
232
- Method to parse details from CSV report.
228
+ """Method to parse details from CSV report.
233
229
  If you want more functionality, use `aghplctools`.
234
230
  `from aghplctools.ingestion.text import pull_hplc_area_from_txt`
235
231
  `signals = pull_hplc_area_from_txt(file_path)`
@@ -281,8 +277,7 @@ class TXTProcessor(ReportProcessor):
281
277
  return Err(e)
282
278
 
283
279
  def parse_area_report(self, report_text: str) -> Dict:
284
- """
285
- Interprets report text and parses the area report section, converting it to dictionary.
280
+ """Interprets report text and parses the area report section, converting it to dictionary.
286
281
  Courtesy of Veronica Lai.
287
282
 
288
283
  :param report_text: plain text version of the report.
@@ -340,8 +335,7 @@ class TXTProcessor(ReportProcessor):
340
335
  return signals
341
336
 
342
337
  def build_peak_regex(self, signal_table: str) -> Pattern[str] | None:
343
- """
344
- Builds a peak regex from a signal table. Courtesy of Veronica Lai.
338
+ """Builds a peak regex from a signal table. Courtesy of Veronica Lai.
345
339
 
346
340
  :param signal_table: block of lines associated with an area table
347
341
  :return: peak line regex object (<=3.6 _sre.SRE_PATTERN, >=3.7 re.Pattern)
@@ -70,7 +70,7 @@ seq_table = SequenceTable(
70
70
  inj_source=InjectionSource.MANUAL
71
71
  ),
72
72
  SequenceEntry(
73
- vial_location=TenVialColumn.ONE,
73
+ vial_location=VialBar.ONE,
74
74
  method="General-Poroshell",
75
75
  num_inj=1,
76
76
  inj_vol=1,
@@ -4,5 +4,6 @@
4
4
 
5
5
  from .comm import CommunicationController
6
6
  from . import data_aq
7
+ from . import devices
7
8
 
8
- __all__ = ["CommunicationController", "data_aq"]
9
+ __all__ = ["CommunicationController", "data_aq", "devices"]
@@ -15,18 +15,23 @@ from typing import Optional, Union, Tuple, List
15
15
 
16
16
  from result import Err, Ok, Result
17
17
 
18
+ from ...utils.abc_tables.abc_comm import ABCCommunicationController
18
19
  from ...utils.macro import (
19
20
  Command,
20
21
  HPLCErrorStatus,
21
22
  Status,
22
23
  str_to_status,
23
24
  )
24
- from .abc_tables.abc_comm import ABCCommunicationController
25
25
 
26
26
 
27
27
  class CommunicationController(ABCCommunicationController):
28
- """
29
- Class that communicates with Agilent using Macros
28
+ """Class that communicates with Agilent using Macros
29
+
30
+ :param comm_dir: the complete directory path that was used in the MACRO file, common file that pychemstation and Chemstation use to communicate.
31
+ :param cmd_file: name of the write file that pychemstation writes MACROs to, in `comm_dir`
32
+ :param reply_file: name of the read file that Chemstation replies to, in `comm_dir
33
+ :param offline: whether or not communication with Chemstation is to be established
34
+ :param debug: if True, prints all send MACROs to an out.txt file
30
35
  """
31
36
 
32
37
  def __init__(
@@ -37,12 +42,6 @@ class CommunicationController(ABCCommunicationController):
37
42
  offline: bool = False,
38
43
  debug: bool = False,
39
44
  ):
40
- """
41
- :param comm_dir:
42
- :param cmd_file: Name of command file
43
- :param reply_file: Name of reply file
44
- :param debug: whether to save log of sent commands
45
- """
46
45
  super().__init__(comm_dir, cmd_file, reply_file, offline, debug)
47
46
 
48
47
  def get_num_val(self, cmd: str) -> Union[int, float]:
@@ -151,21 +150,28 @@ class CommunicationController(ABCCommunicationController):
151
150
 
152
151
  def get_chemstation_dirs(self) -> Tuple[str, str, List[str]]:
153
152
  method_dir, sequence_dir, data_dirs = None, None, None
154
- self.send(Command.GET_METHOD_DIR)
155
- res = self.receive()
156
- if res.is_ok():
157
- method_dir = res.ok_value.string_response
158
- self.send(Command.GET_SEQUENCE_DIR)
159
- res = self.receive()
160
- if res.is_ok():
161
- sequence_dir = res.ok_value.string_response
162
- self.send(Command.GET_DATA_DIRS)
163
- res = self.receive()
164
- if res.is_ok():
165
- data_dirs = res.ok().string_response.split("|")
166
- if method_dir and sequence_dir and data_dirs:
167
- return method_dir, sequence_dir, data_dirs
168
- else:
169
- raise ValueError(
170
- "Please provide the method, sequence and data directories, could not be found."
171
- )
153
+ for _ in range(10):
154
+ self.send(Command.GET_METHOD_DIR)
155
+ res = self.receive()
156
+ if res.is_ok():
157
+ method_dir = res.ok_value.string_response
158
+ self.send(Command.GET_SEQUENCE_DIR)
159
+ res = self.receive()
160
+ if res.is_ok():
161
+ sequence_dir = res.ok_value.string_response
162
+ self.send(Command.GET_DATA_DIRS)
163
+ res = self.receive()
164
+ if res.is_ok():
165
+ data_dirs = res.ok().string_response.split("|")
166
+ if method_dir and sequence_dir and data_dirs:
167
+ if not sequence_dir[0].isalpha():
168
+ sequence_dir = "C:" + sequence_dir
169
+ if not method_dir[0].isalpha():
170
+ method_dir = "C:" + method_dir
171
+ for i, data_dir in enumerate(data_dirs):
172
+ if not data_dir[0].isalpha():
173
+ data_dirs[i] = "C:" + data_dir
174
+ return method_dir, sequence_dir, data_dirs
175
+ raise ValueError(
176
+ f"One of the method: {method_dir}, sequence: {sequence_dir} or data directories: {data_dirs} could not be found, please provide your own."
177
+ )