pychemstation 0.10.4__py3-none-any.whl → 0.10.6__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 (34) hide show
  1. pychemstation/analysis/__init__.py +8 -1
  2. pychemstation/analysis/chromatogram.py +20 -0
  3. pychemstation/analysis/process_report.py +125 -63
  4. pychemstation/control/__init__.py +2 -0
  5. pychemstation/control/controllers/__init__.py +2 -3
  6. pychemstation/control/controllers/abc_tables/device.py +15 -0
  7. pychemstation/control/controllers/abc_tables/run.py +228 -0
  8. pychemstation/control/controllers/abc_tables/table.py +221 -0
  9. pychemstation/control/controllers/comm.py +25 -106
  10. pychemstation/control/controllers/data_aq/__init__.py +4 -0
  11. pychemstation/control/controllers/{tables → data_aq}/method.py +52 -95
  12. pychemstation/control/controllers/{tables → data_aq}/sequence.py +199 -141
  13. pychemstation/control/controllers/devices/__init__.py +3 -0
  14. pychemstation/control/controllers/devices/injector.py +69 -24
  15. pychemstation/control/hplc.py +15 -17
  16. pychemstation/utils/injector_types.py +23 -3
  17. pychemstation/utils/macro.py +2 -2
  18. pychemstation/utils/method_types.py +1 -1
  19. pychemstation/utils/mocking/__init__.py +0 -0
  20. pychemstation/utils/mocking/abc_comm.py +160 -0
  21. pychemstation/utils/mocking/mock_comm.py +5 -0
  22. pychemstation/utils/mocking/mock_hplc.py +2 -0
  23. pychemstation/utils/sequence_types.py +19 -0
  24. pychemstation/utils/table_types.py +6 -0
  25. pychemstation/utils/tray_types.py +36 -1
  26. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/METADATA +4 -4
  27. pychemstation-0.10.6.dist-info/RECORD +42 -0
  28. pychemstation/control/controllers/devices/device.py +0 -49
  29. pychemstation/control/controllers/tables/ms.py +0 -24
  30. pychemstation/control/controllers/tables/table.py +0 -375
  31. pychemstation-0.10.4.dist-info/RECORD +0 -37
  32. /pychemstation/control/controllers/{tables → abc_tables}/__init__.py +0 -0
  33. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/WHEEL +0 -0
  34. {pychemstation-0.10.4.dist-info → pychemstation-0.10.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,375 +0,0 @@
1
- """
2
- Abstract module containing shared logic for Method and Sequence tables.
3
-
4
- Authors: Lucy Hao
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import abc
10
- import math
11
- import os
12
- import time
13
- import warnings
14
- from typing import Dict, List, Optional, Tuple, Union
15
-
16
- import polling
17
- import rainbow as rb
18
- from result import Err, Result, Ok
19
-
20
- from ....analysis.process_report import (
21
- AgilentReport,
22
- CSVProcessor,
23
- ReportType,
24
- TXTProcessor,
25
- )
26
- from ....control.controllers.comm import CommunicationController
27
- from pychemstation.analysis.chromatogram import (
28
- AgilentChannelChromatogramData,
29
- AgilentHPLCChromatogram,
30
- )
31
- from ....utils.macro import Command, HPLCRunningStatus, Response
32
- from ....utils.method_types import MethodDetails
33
- from ....utils.sequence_types import SequenceTable
34
- from ....utils.table_types import RegisterFlag, T, Table, TableOperation
35
-
36
- TableType = Union[MethodDetails, SequenceTable]
37
-
38
-
39
- class TableController(abc.ABC):
40
- def __init__(
41
- self,
42
- controller: CommunicationController,
43
- src: Optional[str],
44
- data_dirs: Optional[List[str]],
45
- table: Table,
46
- offline: bool = False,
47
- ):
48
- self.controller = controller
49
- self.table_locator = table
50
- self.table_state: Optional[TableType] = None
51
- self.curr_run_starting_time: Optional[float] = None
52
- self.timeout: Optional[float] = None
53
-
54
- if not offline:
55
- if src and not os.path.isdir(src):
56
- raise FileNotFoundError(f"dir: {src} not found.")
57
-
58
- for d in data_dirs:
59
- if not os.path.isdir(d):
60
- raise FileNotFoundError(f"dir: {d} not found.")
61
- if r"\\" in d:
62
- raise ValueError("Data directories should not be raw strings!")
63
- self.src: str = src
64
- self.data_dirs: List[str] = data_dirs
65
-
66
- self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
67
- "A": AgilentHPLCChromatogram(),
68
- "B": AgilentHPLCChromatogram(),
69
- "C": AgilentHPLCChromatogram(),
70
- "D": AgilentHPLCChromatogram(),
71
- "E": AgilentHPLCChromatogram(),
72
- "F": AgilentHPLCChromatogram(),
73
- "G": AgilentHPLCChromatogram(),
74
- "H": AgilentHPLCChromatogram(),
75
- }
76
- self.report: Optional[AgilentReport] = None
77
- self.uv: Dict[int, AgilentHPLCChromatogram] = {}
78
- self.data_files: List = []
79
-
80
- def receive(self) -> Result[Response, str]:
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
-
88
- def send(self, cmd: Union[Command, str]):
89
- if not self.controller:
90
- raise RuntimeError(
91
- "Communication controller must be initialized before sending command. It is currently in offline mode."
92
- )
93
- self.controller.send(cmd)
94
-
95
- def sleepy_send(self, cmd: Union[Command, str]):
96
- self.controller.sleepy_send(cmd)
97
-
98
- def sleep(self, seconds: int):
99
- """
100
- Tells the HPLC to wait for a specified number of seconds.
101
-
102
- :param seconds: number of seconds to wait
103
- """
104
- self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
105
-
106
- def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
107
- return self.controller.get_num_val(
108
- TableOperation.GET_ROW_VAL.value.format(
109
- register=self.table_locator.register,
110
- table_name=self.table_locator.name,
111
- row=row,
112
- col_name=col_name.value,
113
- )
114
- )
115
-
116
- def get_text(self, row: int, col_name: RegisterFlag) -> str:
117
- return self.controller.get_text_val(
118
- TableOperation.GET_ROW_TEXT.value.format(
119
- register=self.table_locator.register,
120
- table_name=self.table_locator.name,
121
- row=row,
122
- col_name=col_name.value,
123
- )
124
- )
125
-
126
- def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
127
- self.sleepy_send(
128
- TableOperation.NEW_COL_VAL.value.format(
129
- register=self.table_locator.register,
130
- table_name=self.table_locator.name,
131
- col_name=col_name,
132
- val=val,
133
- )
134
- )
135
-
136
- def add_new_col_text(self, col_name: RegisterFlag, val: str):
137
- self.sleepy_send(
138
- TableOperation.NEW_COL_TEXT.value.format(
139
- register=self.table_locator.register,
140
- table_name=self.table_locator.name,
141
- col_name=col_name,
142
- val=val,
143
- )
144
- )
145
-
146
- def _edit_row_num(
147
- self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
148
- ):
149
- self.sleepy_send(
150
- TableOperation.EDIT_ROW_VAL.value.format(
151
- register=self.table_locator.register,
152
- table_name=self.table_locator.name,
153
- row=row if row is not None else "Rows",
154
- col_name=col_name,
155
- val=val,
156
- )
157
- )
158
-
159
- def _edit_row_text(
160
- self, col_name: RegisterFlag, val: str, row: Optional[int] = None
161
- ):
162
- self.sleepy_send(
163
- TableOperation.EDIT_ROW_TEXT.value.format(
164
- register=self.table_locator.register,
165
- table_name=self.table_locator.name,
166
- row=row if row is not None else "Rows",
167
- col_name=col_name,
168
- val=val,
169
- )
170
- )
171
-
172
- @abc.abstractmethod
173
- def get_row(self, row: int):
174
- pass
175
-
176
- def delete_row(self, row: int):
177
- self.sleepy_send(
178
- TableOperation.DELETE_ROW.value.format(
179
- register=self.table_locator.register,
180
- table_name=self.table_locator.name,
181
- row=row,
182
- )
183
- )
184
-
185
- def add_row(self):
186
- """
187
- Adds a row to the provided table for currently loaded method or sequence.
188
- """
189
- self.sleepy_send(
190
- TableOperation.NEW_ROW.value.format(
191
- register=self.table_locator.register, table_name=self.table_locator.name
192
- )
193
- )
194
-
195
- def delete_table(self):
196
- """
197
- Deletes the table for the current loaded method or sequence.
198
- """
199
- self.sleepy_send(
200
- TableOperation.DELETE_TABLE.value.format(
201
- register=self.table_locator.register, table_name=self.table_locator.name
202
- )
203
- )
204
-
205
- def new_table(self):
206
- """
207
- Creates the table for the currently loaded method or sequence.
208
- """
209
- self.send(
210
- TableOperation.CREATE_TABLE.value.format(
211
- register=self.table_locator.register, table_name=self.table_locator.name
212
- )
213
- )
214
-
215
- def get_num_rows(self) -> Result[Response, str]:
216
- self.send(
217
- TableOperation.GET_NUM_ROWS.value.format(
218
- register=self.table_locator.register,
219
- table_name=self.table_locator.name,
220
- col_name=RegisterFlag.NUM_ROWS,
221
- )
222
- )
223
- self.send(
224
- Command.GET_ROWS_CMD.value.format(
225
- register=self.table_locator.register,
226
- table_name=self.table_locator.name,
227
- col_name=RegisterFlag.NUM_ROWS,
228
- )
229
- )
230
- res = self.controller.receive()
231
-
232
- if res.is_ok():
233
- self.send("Sleep 0.1")
234
- self.send("Print Rows")
235
- return res
236
- else:
237
- return Err("No rows could be read.")
238
-
239
- def check_hplc_is_running(self) -> bool:
240
- try:
241
- started_running = polling.poll(
242
- lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
243
- step=1,
244
- max_tries=20,
245
- )
246
- except Exception as e:
247
- print(e)
248
- return False
249
- if started_running:
250
- self.curr_run_starting_time = time.time()
251
- return started_running
252
-
253
- def check_hplc_run_finished(self) -> Tuple[float, bool]:
254
- done_running = self.controller.check_if_not_running()
255
- if self.curr_run_starting_time and self.timeout:
256
- time_passed = time.time() - self.curr_run_starting_time
257
- if time_passed > self.timeout:
258
- enough_time_passed = time_passed >= self.timeout
259
- run_finished = enough_time_passed and done_running
260
- if run_finished:
261
- self._reset_time()
262
- return 0, run_finished
263
- else:
264
- time_left = self.timeout - time_passed
265
- return time_left, self.controller.check_if_not_running()
266
- return 0, self.controller.check_if_not_running()
267
-
268
- def check_hplc_done_running(self) -> Ok[T] | Err[str]:
269
- """
270
- Checks if ChemStation has finished running and can read data back
271
-
272
- :return: Data file object containing most recent run file information.
273
- """
274
- finished_run = False
275
- minutes = math.ceil(self.timeout / 60)
276
- try:
277
- finished_run = not polling.poll(
278
- lambda: self.check_hplc_run_finished()[1],
279
- max_tries=minutes - 1,
280
- step=50,
281
- )
282
- except (
283
- polling.TimeoutException,
284
- polling.PollingException,
285
- polling.MaxCallException,
286
- ):
287
- try:
288
- finished_run = polling.poll(
289
- lambda: self.check_hplc_run_finished()[1],
290
- timeout=self.timeout / 2,
291
- step=1,
292
- )
293
- except (
294
- polling.TimeoutException,
295
- polling.PollingException,
296
- polling.MaxCallException,
297
- ):
298
- pass
299
-
300
- check_folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
301
- if check_folder.is_ok() and finished_run:
302
- return check_folder
303
- elif check_folder.is_ok():
304
- try:
305
- finished_run = polling.poll(
306
- lambda: self.check_hplc_run_finished()[1], max_tries=10, step=50
307
- )
308
- if finished_run:
309
- return check_folder
310
- except Exception:
311
- self._reset_time()
312
- return self.data_files[-1]
313
- return Err("Run did not complete as expected")
314
-
315
- @abc.abstractmethod
316
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
317
- pass
318
-
319
- @abc.abstractmethod
320
- def get_data(
321
- self, custom_path: Optional[str] = None
322
- ) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
323
- pass
324
-
325
- @abc.abstractmethod
326
- def get_data_uv(
327
- self,
328
- ) -> Union[
329
- List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]
330
- ]:
331
- pass
332
-
333
- @abc.abstractmethod
334
- def get_report(
335
- self, report_type: ReportType = ReportType.TXT
336
- ) -> List[AgilentReport]:
337
- pass
338
-
339
- def get_uv_spectrum(self, path: str):
340
- data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
341
- times = data_uv.xlabels
342
- wavelengths = data_uv.ylabels
343
- absorbances = data_uv.data.transpose()
344
- for i, w in enumerate(wavelengths):
345
- self.uv[w] = AgilentHPLCChromatogram()
346
- self.uv[w].attach_spectrum(times, absorbances[i])
347
-
348
- def get_report_details(
349
- self, path: str, report_type: ReportType = ReportType.TXT
350
- ) -> AgilentReport:
351
- if report_type is ReportType.TXT:
352
- txt_report = TXTProcessor(path).process_report()
353
- if txt_report.is_ok():
354
- self.report = txt_report.ok_value
355
- if report_type is ReportType.CSV:
356
- csv_report = CSVProcessor(path).process_report()
357
- if csv_report.is_ok():
358
- self.report = csv_report.ok_value
359
- return self.report
360
-
361
- def get_spectrum_at_channels(self, data_path: str):
362
- """
363
- Load chromatogram for any channel in spectra dictionary.
364
- """
365
- for channel, spec in self.spectra.items():
366
- try:
367
- spec.load_spectrum(data_path=data_path, channel=channel)
368
- except FileNotFoundError:
369
- self.spectra[channel] = AgilentHPLCChromatogram()
370
- warning = f"No data at channel: {channel}"
371
- warnings.warn(warning)
372
-
373
- def _reset_time(self):
374
- self.curr_run_starting_time = None
375
- self.timeout = None
@@ -1,37 +0,0 @@
1
- pychemstation/__init__.py,sha256=Sc4z8LRVFMwJUoc_DPVUriSXTZ6PO9MaJ80PhRbKyB8,34
2
- pychemstation/analysis/__init__.py,sha256=dcX7OeHoKdyrECHRCSXgKZN81nOXSAmZRxXzRT0jpDc,126
3
- pychemstation/analysis/base_spectrum.py,sha256=t_VoxAtBph1V7S4fOsziERHiOBkYP0_nH7LTwbTEvcE,16529
4
- pychemstation/analysis/chromatogram.py,sha256=cBfLh58PrBZMg9-u5o_Q-FCuu3MlB0q0ZFm9_2uaciU,3270
5
- pychemstation/analysis/process_report.py,sha256=mUBuMHFNNUa-dP0-OZse9XcDahMNX84cCz705Eg6T3A,12250
6
- pychemstation/control/README.md,sha256=_7ITj4hD17YIwci6UY6xBebC9gPCBpzBFTB_Gx0eJBc,3124
7
- pychemstation/control/__init__.py,sha256=uzfsVAGDhMP6SyV10KAH264ytDLMsMRZXRK5XhWS-rc,102
8
- pychemstation/control/hplc.py,sha256=mV-IO-6wdzB7MuV5LcZYwb4yZibBgEKX2LtbJ9WiKNw,12304
9
- pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
10
- pychemstation/control/controllers/__init__.py,sha256=XXTKee3bQpDJ2EO0k-ekJoZuq1h6WeO_DsOafxkBgTo,247
11
- pychemstation/control/controllers/comm.py,sha256=64VXS0C3BHTKCjuYCadmmxKWiVvMqtZebzyCOJifyUA,7994
12
- pychemstation/control/controllers/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- pychemstation/control/controllers/devices/device.py,sha256=XYOTehPYapL40GmcrtkRtdaZU2yvS4KwkLPRs9RB04U,1492
14
- pychemstation/control/controllers/devices/injector.py,sha256=C8HOxV2s1jvgum57DQ-bRTbJmb644TmZmGybxEoNN3Y,2109
15
- pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- pychemstation/control/controllers/tables/method.py,sha256=SDchncXgoFmzE9MLdVFTKs1-2Mx1EflG8pj5ALiq7Ck,19837
17
- pychemstation/control/controllers/tables/ms.py,sha256=ywrSa60H_5de32jxL2U5n464dIS_NXCC1M89mSiq-fY,723
18
- pychemstation/control/controllers/tables/sequence.py,sha256=SCLPj0c3kU9yApBGM7UbwzaJ5-NgRUj1SPZzSqV4f9Y,14175
19
- pychemstation/control/controllers/tables/table.py,sha256=OGpCS_FKsvl1WNWWWk6ooX1TGNHrGroTjlGqNPE6YNM,12632
20
- pychemstation/generated/__init__.py,sha256=xnEs0QTjeuGYO3tVUIy8GDo95GqTV1peEjosGckpOu0,977
21
- pychemstation/generated/dad_method.py,sha256=xTUiSCvkXcxBUhjVm1YZKu-tHs16k23pF-0xYrQSwWA,8408
22
- pychemstation/generated/pump_method.py,sha256=s3MckKDw2-nZUC5lHrJVvXYdneWP8-9UvblNuGryPHY,12092
23
- pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- pychemstation/utils/injector_types.py,sha256=PXwJK1uXs8hlQ6dWIEbAGfk2BpQJQmN3SlUbL4ntZz0,822
25
- pychemstation/utils/macro.py,sha256=0Kd86_xDB_99R5qcnCpD-FEm1mdGuPMuCaFoYIUkfhc,2937
26
- pychemstation/utils/method_types.py,sha256=dRf12UaWerqPkycFq4nNXZk6HnOKq0ZIyROIuKDKiSI,1639
27
- pychemstation/utils/num_utils.py,sha256=dDs8sLZ_SdtvDKhyhF3IkljiVf16IYqpMTO5tEk9vMk,2079
28
- pychemstation/utils/parsing.py,sha256=mzdpxrH5ux4-_i4CwZvnIYnIwAnRnOptKb3fZyYJcx0,9307
29
- pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
30
- pychemstation/utils/sequence_types.py,sha256=T0IP2iMqorUrdzH4at9Vsmmb3SCAEmN4z1cUlFaeTXw,1089
31
- pychemstation/utils/spec_utils.py,sha256=lS27Xi4mFNDWBfmBqOoxTcVchPAkLK2mSdoaWDOfaPI,10211
32
- pychemstation/utils/table_types.py,sha256=inOVpwSsic31VdVdJkfuq35QfKd7PoNoXY1QnOxZ6Sw,3235
33
- pychemstation/utils/tray_types.py,sha256=9yLRIBn3IPVMbhrFqJQJ5gCQJI7H9DD2cdIFQDp2-8k,5184
34
- pychemstation-0.10.4.dist-info/METADATA,sha256=yXiZCbNWNQmNV4FN1cyMTBRkFtjIzll3KAJE1XnOVTE,5875
35
- pychemstation-0.10.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- pychemstation-0.10.4.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
37
- pychemstation-0.10.4.dist-info/RECORD,,