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.
- pychemstation/__init__.py +1 -1
- pychemstation/analysis/__init__.py +1 -6
- pychemstation/analysis/base_spectrum.py +7 -7
- pychemstation/{utils → analysis}/chromatogram.py +24 -4
- pychemstation/analysis/process_report.py +189 -90
- pychemstation/control/__init__.py +5 -2
- pychemstation/control/controllers/__init__.py +2 -7
- pychemstation/control/controllers/comm.py +56 -32
- pychemstation/control/controllers/devices/device.py +59 -24
- pychemstation/control/controllers/devices/injector.py +33 -10
- pychemstation/control/controllers/tables/__init__.py +4 -0
- pychemstation/control/controllers/tables/method.py +241 -151
- pychemstation/control/controllers/tables/sequence.py +226 -107
- pychemstation/control/controllers/tables/table.py +216 -132
- pychemstation/control/hplc.py +89 -75
- pychemstation/generated/__init__.py +0 -2
- pychemstation/generated/pump_method.py +15 -19
- pychemstation/utils/injector_types.py +1 -1
- pychemstation/utils/macro.py +11 -10
- pychemstation/utils/method_types.py +2 -1
- pychemstation/utils/parsing.py +0 -11
- pychemstation/utils/sequence_types.py +2 -3
- pychemstation/utils/spec_utils.py +2 -3
- pychemstation/utils/table_types.py +10 -9
- pychemstation/utils/tray_types.py +45 -36
- {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/METADATA +5 -4
- pychemstation-0.10.5.dist-info/RECORD +36 -0
- pychemstation/control/controllers/tables/ms.py +0 -21
- pychemstation-0.10.3.dist-info/RECORD +0 -37
- {pychemstation-0.10.3.dist-info → pychemstation-0.10.5.dist-info}/WHEEL +0 -0
- {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
|
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
|
33
|
-
from ....utils.table_types import RegisterFlag,
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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,
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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(
|
161
|
-
|
162
|
-
|
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(
|
169
|
-
|
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(
|
176
|
-
|
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(
|
183
|
-
|
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(
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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(
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
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
|
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(
|
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(
|
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(
|
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
|
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(
|
289
|
-
|
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
|
-
|
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
|
-
|
298
|
-
|
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
|
"""
|