pychemstation 0.10.3__py3-none-any.whl → 0.10.5__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/__init__.py +1 -1
  2. pychemstation/analysis/__init__.py +1 -6
  3. pychemstation/analysis/base_spectrum.py +7 -7
  4. pychemstation/{utils → analysis}/chromatogram.py +24 -4
  5. pychemstation/analysis/process_report.py +189 -90
  6. pychemstation/control/__init__.py +5 -2
  7. pychemstation/control/controllers/__init__.py +2 -7
  8. pychemstation/control/controllers/comm.py +56 -32
  9. pychemstation/control/controllers/devices/device.py +59 -24
  10. pychemstation/control/controllers/devices/injector.py +33 -10
  11. pychemstation/control/controllers/tables/__init__.py +4 -0
  12. pychemstation/control/controllers/tables/method.py +241 -151
  13. pychemstation/control/controllers/tables/sequence.py +226 -107
  14. pychemstation/control/controllers/tables/table.py +216 -132
  15. pychemstation/control/hplc.py +89 -75
  16. pychemstation/generated/__init__.py +0 -2
  17. pychemstation/generated/pump_method.py +15 -19
  18. pychemstation/utils/injector_types.py +1 -1
  19. pychemstation/utils/macro.py +11 -10
  20. pychemstation/utils/method_types.py +2 -1
  21. pychemstation/utils/parsing.py +0 -11
  22. pychemstation/utils/sequence_types.py +2 -3
  23. pychemstation/utils/spec_utils.py +2 -3
  24. pychemstation/utils/table_types.py +10 -9
  25. pychemstation/utils/tray_types.py +45 -36
  26. {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/METADATA +5 -4
  27. pychemstation-0.10.5.dist-info/RECORD +36 -0
  28. pychemstation/control/controllers/tables/ms.py +0 -21
  29. pychemstation-0.10.3.dist-info/RECORD +0 -37
  30. {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/WHEEL +0 -0
  31. {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/licenses/LICENSE +0 -0
@@ -3,6 +3,7 @@ Abstract module containing shared logic for Method and Sequence tables.
3
3
 
4
4
  Authors: Lucy Hao
5
5
  """
6
+
6
7
  from __future__ import annotations
7
8
 
8
9
  import abc
@@ -23,25 +24,27 @@ from ....analysis.process_report import (
23
24
  TXTProcessor,
24
25
  )
25
26
  from ....control.controllers.comm import CommunicationController
26
- from ....utils.chromatogram import (
27
+ from pychemstation.analysis.chromatogram import (
27
28
  AgilentChannelChromatogramData,
28
29
  AgilentHPLCChromatogram,
29
30
  )
30
31
  from ....utils.macro import Command, HPLCRunningStatus, Response
31
32
  from ....utils.method_types import MethodDetails
32
- from ....utils.sequence_types import SequenceDataFiles, SequenceTable
33
- from ....utils.table_types import RegisterFlag, T, Table, TableOperation
33
+ from ....utils.sequence_types import SequenceTable
34
+ from ....utils.table_types import RegisterFlag, Table, TableOperation, T
34
35
 
35
36
  TableType = Union[MethodDetails, SequenceTable]
36
37
 
37
38
 
38
39
  class TableController(abc.ABC):
39
-
40
- def __init__(self, controller: CommunicationController,
41
- src: Optional[str],
42
- data_dirs: Optional[List[str]],
43
- table: Table,
44
- offline: bool = False):
40
+ def __init__(
41
+ self,
42
+ controller: Optional[CommunicationController],
43
+ src: str,
44
+ data_dirs: List[str],
45
+ table: Table,
46
+ offline: bool = False,
47
+ ):
45
48
  self.controller = controller
46
49
  self.table_locator = table
47
50
  self.table_state: Optional[TableType] = None
@@ -60,7 +63,7 @@ class TableController(abc.ABC):
60
63
  self.src: str = src
61
64
  self.data_dirs: List[str] = data_dirs
62
65
 
63
- self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
66
+ self.spectra: dict[str, AgilentHPLCChromatogram] = {
64
67
  "A": AgilentHPLCChromatogram(),
65
68
  "B": AgilentHPLCChromatogram(),
66
69
  "C": AgilentHPLCChromatogram(),
@@ -70,26 +73,32 @@ class TableController(abc.ABC):
70
73
  "G": AgilentHPLCChromatogram(),
71
74
  "H": AgilentHPLCChromatogram(),
72
75
  }
73
- self.report: Optional[AgilentReport] = None
74
76
  self.uv: Dict[int, AgilentHPLCChromatogram] = {}
75
77
  self.data_files: List = []
76
78
 
77
79
  def receive(self) -> Result[Response, str]:
78
- for _ in range(10):
79
- try:
80
- return self.controller.receive()
81
- except IndexError:
82
- continue
83
- return Err("Could not parse response")
80
+ if self.controller:
81
+ for _ in range(10):
82
+ try:
83
+ return self.controller.receive()
84
+ except IndexError:
85
+ continue
86
+ return Err("Could not parse response")
87
+ else:
88
+ raise ValueError("Controller is offline!")
84
89
 
85
90
  def send(self, cmd: Union[Command, str]):
86
91
  if not self.controller:
87
92
  raise RuntimeError(
88
- "Communication controller must be initialized before sending command. It is currently in offline mode.")
93
+ "Communication controller must be initialized before sending command. It is currently in offline mode."
94
+ )
89
95
  self.controller.send(cmd)
90
96
 
91
97
  def sleepy_send(self, cmd: Union[Command, str]):
92
- self.controller.sleepy_send(cmd)
98
+ if self.controller:
99
+ self.controller.sleepy_send(cmd)
100
+ else:
101
+ raise ValueError("Controller is offline")
93
102
 
94
103
  def sleep(self, seconds: int):
95
104
  """
@@ -100,129 +109,180 @@ class TableController(abc.ABC):
100
109
  self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
101
110
 
102
111
  def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
103
- return self.controller.get_num_val(TableOperation.GET_ROW_VAL.value.format(register=self.table_locator.register,
104
- table_name=self.table_locator.name,
105
- row=row,
106
- col_name=col_name.value))
112
+ if self.controller:
113
+ return self.controller.get_num_val(
114
+ TableOperation.GET_ROW_VAL.value.format(
115
+ register=self.table_locator.register,
116
+ table_name=self.table_locator.name,
117
+ row=row,
118
+ col_name=col_name.value,
119
+ )
120
+ )
121
+ else:
122
+ raise ValueError("Controller is offline")
107
123
 
108
124
  def get_text(self, row: int, col_name: RegisterFlag) -> str:
109
- return self.controller.get_text_val(
110
- TableOperation.GET_ROW_TEXT.value.format(register=self.table_locator.register,
111
- table_name=self.table_locator.name,
112
- row=row,
113
- col_name=col_name.value))
114
-
115
- def add_new_col_num(self,
116
- col_name: RegisterFlag,
117
- val: Union[int, float]):
118
- self.sleepy_send(TableOperation.NEW_COL_VAL.value.format(
119
- register=self.table_locator.register,
120
- table_name=self.table_locator.name,
121
- col_name=col_name,
122
- val=val))
123
-
124
- def add_new_col_text(self,
125
- col_name: RegisterFlag,
126
- val: str):
127
- self.sleepy_send(TableOperation.NEW_COL_TEXT.value.format(
128
- register=self.table_locator.register,
129
- table_name=self.table_locator.name,
130
- col_name=col_name,
131
- val=val))
132
-
133
- def _edit_row_num(self,
134
- col_name: RegisterFlag,
135
- val: Union[int, float],
136
- row: Optional[int] = None):
137
- self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(
138
- register=self.table_locator.register,
139
- table_name=self.table_locator.name,
140
- row=row if row is not None else 'Rows',
141
- col_name=col_name,
142
- val=val))
143
-
144
- def _edit_row_text(self,
145
- col_name: RegisterFlag,
146
- val: str,
147
- row: Optional[int] = None):
148
- self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(
149
- register=self.table_locator.register,
150
- table_name=self.table_locator.name,
151
- row=row if row is not None else 'Rows',
152
- col_name=col_name,
153
- val=val))
125
+ if self.controller:
126
+ return self.controller.get_text_val(
127
+ TableOperation.GET_ROW_TEXT.value.format(
128
+ register=self.table_locator.register,
129
+ table_name=self.table_locator.name,
130
+ row=row,
131
+ col_name=col_name.value,
132
+ )
133
+ )
134
+ else:
135
+ raise ValueError("Controller is offline")
136
+
137
+ def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
138
+ self.sleepy_send(
139
+ TableOperation.NEW_COL_VAL.value.format(
140
+ register=self.table_locator.register,
141
+ table_name=self.table_locator.name,
142
+ col_name=col_name,
143
+ val=val,
144
+ )
145
+ )
146
+
147
+ def add_new_col_text(self, col_name: RegisterFlag, val: str):
148
+ self.sleepy_send(
149
+ TableOperation.NEW_COL_TEXT.value.format(
150
+ register=self.table_locator.register,
151
+ table_name=self.table_locator.name,
152
+ col_name=col_name,
153
+ val=val,
154
+ )
155
+ )
156
+
157
+ def _edit_row_num(
158
+ self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
159
+ ):
160
+ self.sleepy_send(
161
+ TableOperation.EDIT_ROW_VAL.value.format(
162
+ register=self.table_locator.register,
163
+ table_name=self.table_locator.name,
164
+ row=row if row is not None else "Rows",
165
+ col_name=col_name,
166
+ val=val,
167
+ )
168
+ )
169
+
170
+ def _edit_row_text(
171
+ self, col_name: RegisterFlag, val: str, row: Optional[int] = None
172
+ ):
173
+ self.sleepy_send(
174
+ TableOperation.EDIT_ROW_TEXT.value.format(
175
+ register=self.table_locator.register,
176
+ table_name=self.table_locator.name,
177
+ row=row if row is not None else "Rows",
178
+ col_name=col_name,
179
+ val=val,
180
+ )
181
+ )
154
182
 
155
183
  @abc.abstractmethod
156
184
  def get_row(self, row: int):
157
185
  pass
158
186
 
159
187
  def delete_row(self, row: int):
160
- self.sleepy_send(TableOperation.DELETE_ROW.value.format(register=self.table_locator.register,
161
- table_name=self.table_locator.name,
162
- row=row))
188
+ self.sleepy_send(
189
+ TableOperation.DELETE_ROW.value.format(
190
+ register=self.table_locator.register,
191
+ table_name=self.table_locator.name,
192
+ row=row,
193
+ )
194
+ )
163
195
 
164
196
  def add_row(self):
165
197
  """
166
198
  Adds a row to the provided table for currently loaded method or sequence.
167
199
  """
168
- self.sleepy_send(TableOperation.NEW_ROW.value.format(register=self.table_locator.register,
169
- table_name=self.table_locator.name))
200
+ self.sleepy_send(
201
+ TableOperation.NEW_ROW.value.format(
202
+ register=self.table_locator.register, table_name=self.table_locator.name
203
+ )
204
+ )
170
205
 
171
206
  def delete_table(self):
172
207
  """
173
208
  Deletes the table for the current loaded method or sequence.
174
209
  """
175
- self.sleepy_send(TableOperation.DELETE_TABLE.value.format(register=self.table_locator.register,
176
- table_name=self.table_locator.name))
210
+ self.sleepy_send(
211
+ TableOperation.DELETE_TABLE.value.format(
212
+ register=self.table_locator.register, table_name=self.table_locator.name
213
+ )
214
+ )
177
215
 
178
216
  def new_table(self):
179
217
  """
180
218
  Creates the table for the currently loaded method or sequence.
181
219
  """
182
- self.send(TableOperation.CREATE_TABLE.value.format(register=self.table_locator.register,
183
- table_name=self.table_locator.name))
220
+ self.send(
221
+ TableOperation.CREATE_TABLE.value.format(
222
+ register=self.table_locator.register, table_name=self.table_locator.name
223
+ )
224
+ )
184
225
 
185
226
  def get_num_rows(self) -> Result[Response, str]:
186
- self.send(TableOperation.GET_NUM_ROWS.value.format(register=self.table_locator.register,
187
- table_name=self.table_locator.name,
188
- col_name=RegisterFlag.NUM_ROWS))
189
- self.send(Command.GET_ROWS_CMD.value.format(register=self.table_locator.register,
190
- table_name=self.table_locator.name,
191
- col_name=RegisterFlag.NUM_ROWS))
192
- res = self.controller.receive()
227
+ self.send(
228
+ TableOperation.GET_NUM_ROWS.value.format(
229
+ register=self.table_locator.register,
230
+ table_name=self.table_locator.name,
231
+ col_name=RegisterFlag.NUM_ROWS,
232
+ )
233
+ )
234
+ self.send(
235
+ Command.GET_ROWS_CMD.value.format(
236
+ register=self.table_locator.register,
237
+ table_name=self.table_locator.name,
238
+ col_name=RegisterFlag.NUM_ROWS,
239
+ )
240
+ )
241
+ if self.controller:
242
+ res = self.controller.receive()
243
+ else:
244
+ raise ValueError("Controller is offline")
193
245
 
194
246
  if res.is_ok():
195
247
  self.send("Sleep 0.1")
196
- self.send('Print Rows')
248
+ self.send("Print Rows")
197
249
  return res
198
250
  else:
199
251
  return Err("No rows could be read.")
200
252
 
201
253
  def check_hplc_is_running(self) -> bool:
202
- try:
203
- started_running = polling.poll(lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
204
- step=1, max_tries=20)
205
- except Exception as e:
206
- print(e)
207
- return False
208
- if started_running:
209
- self.curr_run_starting_time = time.time()
210
- return started_running
254
+ if self.controller:
255
+ try:
256
+ started_running = polling.poll(
257
+ lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
258
+ step=1,
259
+ max_tries=20,
260
+ )
261
+ except Exception as e:
262
+ print(e)
263
+ return False
264
+ if started_running:
265
+ self.curr_run_starting_time = time.time()
266
+ return started_running
267
+ else:
268
+ raise ValueError("Controller is offline")
211
269
 
212
270
  def check_hplc_run_finished(self) -> Tuple[float, bool]:
213
- done_running = self.controller.check_if_not_running()
214
- if self.curr_run_starting_time and self.timeout:
215
- time_passed = (time.time() - self.curr_run_starting_time)
216
- if time_passed > self.timeout:
217
- enough_time_passed = time_passed >= self.timeout
218
- run_finished = enough_time_passed and done_running
219
- if run_finished:
220
- self._reset_time()
221
- return 0, run_finished
222
- else:
223
- time_left = self.timeout - time_passed
224
- return time_left, self.controller.check_if_not_running()
225
- return 0, self.controller.check_if_not_running()
271
+ if self.controller:
272
+ done_running = self.controller.check_if_not_running()
273
+ if self.curr_run_starting_time and self.timeout:
274
+ time_passed = time.time() - self.curr_run_starting_time
275
+ if time_passed > self.timeout:
276
+ enough_time_passed = time_passed >= self.timeout
277
+ run_finished = enough_time_passed and done_running
278
+ if run_finished:
279
+ self._reset_time()
280
+ return 0, run_finished
281
+ else:
282
+ time_left = self.timeout - time_passed
283
+ return time_left, self.controller.check_if_not_running()
284
+ return 0, self.controller.check_if_not_running()
285
+ raise ValueError("Controller is offline!")
226
286
 
227
287
  def check_hplc_done_running(self) -> Ok[T] | Err[str]:
228
288
  """
@@ -230,19 +290,34 @@ class TableController(abc.ABC):
230
290
 
231
291
  :return: Data file object containing most recent run file information.
232
292
  """
233
- finished_run = False
234
- minutes = math.ceil(self.timeout / 60)
235
- try:
236
- finished_run = not polling.poll(
237
- lambda: self.check_hplc_run_finished()[1],
238
- max_tries=minutes - 1, step=50)
239
- except (polling.TimeoutException, polling.PollingException, polling.MaxCallException):
293
+ if self.timeout is not None:
294
+ finished_run = False
295
+ minutes = math.ceil(self.timeout / 60)
240
296
  try:
241
- finished_run = polling.poll(
297
+ finished_run = not polling.poll(
242
298
  lambda: self.check_hplc_run_finished()[1],
243
- timeout=self.timeout / 2, step=1)
244
- except (polling.TimeoutException, polling.PollingException, polling.MaxCallException):
245
- pass
299
+ max_tries=minutes - 1,
300
+ step=50,
301
+ )
302
+ except (
303
+ polling.TimeoutException,
304
+ polling.PollingException,
305
+ polling.MaxCallException,
306
+ ):
307
+ try:
308
+ finished_run = polling.poll(
309
+ lambda: self.check_hplc_run_finished()[1],
310
+ timeout=self.timeout / 2,
311
+ step=1,
312
+ )
313
+ except (
314
+ polling.TimeoutException,
315
+ polling.PollingException,
316
+ polling.MaxCallException,
317
+ ):
318
+ pass
319
+ else:
320
+ raise ValueError("Timeout value is None, no comparison can be made.")
246
321
 
247
322
  check_folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
248
323
  if check_folder.is_ok() and finished_run:
@@ -250,14 +325,13 @@ class TableController(abc.ABC):
250
325
  elif check_folder.is_ok():
251
326
  try:
252
327
  finished_run = polling.poll(
253
- lambda: self.check_hplc_run_finished()[1],
254
- max_tries=10,
255
- step=50)
328
+ lambda: self.check_hplc_run_finished()[1], max_tries=10, step=50
329
+ )
256
330
  if finished_run:
257
331
  return check_folder
258
332
  except Exception:
259
333
  self._reset_time()
260
- return check_folder
334
+ return self.data_files[-1]
261
335
  return Err("Run did not complete as expected")
262
336
 
263
337
  @abc.abstractmethod
@@ -265,15 +339,21 @@ class TableController(abc.ABC):
265
339
  pass
266
340
 
267
341
  @abc.abstractmethod
268
- def get_data(self, custom_path: Optional[str] = None) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
342
+ def get_data(
343
+ self, custom_path: Optional[str] = None
344
+ ) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
269
345
  pass
270
346
 
271
347
  @abc.abstractmethod
272
- def get_data_uv(self) -> Union[List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]]:
348
+ def get_data_uv(
349
+ self, custom_path: str | None = None
350
+ ) -> Dict[int, AgilentHPLCChromatogram]:
273
351
  pass
274
352
 
275
353
  @abc.abstractmethod
276
- def get_report(self, report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
354
+ def get_report(
355
+ self, custom_path: str, report_type: ReportType = ReportType.TXT
356
+ ) -> List[AgilentReport]:
277
357
  pass
278
358
 
279
359
  def get_uv_spectrum(self, path: str):
@@ -281,22 +361,26 @@ class TableController(abc.ABC):
281
361
  times = data_uv.xlabels
282
362
  wavelengths = data_uv.ylabels
283
363
  absorbances = data_uv.data.transpose()
284
- for (i, w) in enumerate(wavelengths):
364
+ for i, w in enumerate(wavelengths):
285
365
  self.uv[w] = AgilentHPLCChromatogram()
286
366
  self.uv[w].attach_spectrum(times, absorbances[i])
287
367
 
288
- def get_report_details(self, path: str,
289
- report_type: ReportType = ReportType.TXT) -> AgilentReport:
368
+ def get_report_details(
369
+ self, path: str, report_type: ReportType = ReportType.TXT
370
+ ) -> AgilentReport:
290
371
  if report_type is ReportType.TXT:
291
372
  txt_report = TXTProcessor(path).process_report()
292
373
  if txt_report.is_ok():
293
- self.report = txt_report.ok_value
374
+ return txt_report.ok_value
375
+ elif txt_report.is_err():
376
+ raise ValueError(txt_report.err_value)
294
377
  if report_type is ReportType.CSV:
295
378
  csv_report = CSVProcessor(path).process_report()
296
379
  if csv_report.is_ok():
297
- self.report = csv_report.ok_value
298
- return self.report
299
-
380
+ return csv_report.ok_value
381
+ elif csv_report.is_err():
382
+ raise ValueError(csv_report.err_value)
383
+ raise ValueError("Expected one of ReportType.TXT or ReportType.CSV")
300
384
 
301
385
  def get_spectrum_at_channels(self, data_path: str):
302
386
  """