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.
- pychemstation/analysis/base_spectrum.py +14 -15
- pychemstation/analysis/chromatogram.py +7 -8
- pychemstation/analysis/process_report.py +10 -16
- pychemstation/control/README.md +1 -1
- pychemstation/control/controllers/__init__.py +2 -1
- pychemstation/control/controllers/comm.py +33 -27
- pychemstation/control/controllers/data_aq/method.py +233 -79
- pychemstation/control/controllers/data_aq/sequence.py +104 -84
- pychemstation/control/controllers/devices/injector.py +3 -3
- pychemstation/control/hplc.py +53 -41
- pychemstation/utils/__init__.py +23 -0
- pychemstation/{control/controllers → utils}/abc_tables/abc_comm.py +15 -13
- pychemstation/{control/controllers → utils}/abc_tables/device.py +9 -2
- pychemstation/{control/controllers → utils}/abc_tables/run.py +45 -37
- pychemstation/{control/controllers → utils}/abc_tables/table.py +32 -29
- pychemstation/utils/macro.py +7 -2
- pychemstation/utils/method_types.py +13 -14
- pychemstation/utils/mocking/mock_comm.py +25 -2
- pychemstation/utils/mocking/mock_hplc.py +29 -1
- pychemstation/utils/num_utils.py +3 -3
- pychemstation/utils/sequence_types.py +30 -14
- pychemstation/utils/spec_utils.py +42 -66
- pychemstation/utils/table_types.py +15 -2
- pychemstation/utils/tray_types.py +28 -16
- {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/METADATA +1 -7
- pychemstation-0.10.8.dist-info/RECORD +41 -0
- pychemstation/utils/pump_types.py +0 -7
- pychemstation-0.10.7.dist-info/RECORD +0 -42
- /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
- {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/licenses/LICENSE +0 -0
@@ -3,18 +3,19 @@ from __future__ import annotations
|
|
3
3
|
import os
|
4
4
|
import time
|
5
5
|
import warnings
|
6
|
-
from typing import List, Optional, Union,
|
6
|
+
from typing import Dict, List, Optional, Union, Tuple
|
7
7
|
|
8
8
|
from result import Err, Ok, Result
|
9
9
|
|
10
|
-
from ..abc_tables.run import RunController
|
11
|
-
from ....analysis.process_report import AgilentReport, ReportType
|
12
|
-
from ....control.controllers import CommunicationController
|
13
10
|
from pychemstation.analysis.chromatogram import (
|
14
11
|
TIME_FORMAT,
|
15
12
|
AgilentChannelChromatogramData,
|
16
13
|
AgilentHPLCChromatogram,
|
17
14
|
)
|
15
|
+
|
16
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
17
|
+
from ....control.controllers import CommunicationController
|
18
|
+
from ....utils.abc_tables.run import RunController
|
18
19
|
from ....utils.macro import Command
|
19
20
|
from ....utils.method_types import (
|
20
21
|
HPLCMethodParams,
|
@@ -23,20 +24,18 @@ from ....utils.method_types import (
|
|
23
24
|
PType,
|
24
25
|
TimeTableEntry,
|
25
26
|
)
|
26
|
-
from ....utils.table_types import RegisterFlag, Table, TableOperation
|
27
|
+
from ....utils.table_types import RegisterFlag, T, Table, TableOperation
|
27
28
|
from ..devices.injector import InjectorController
|
28
29
|
|
29
30
|
|
30
31
|
class MethodController(RunController):
|
31
|
-
"""
|
32
|
-
Class containing method related logic
|
33
|
-
"""
|
32
|
+
"""Class containing method related logic."""
|
34
33
|
|
35
34
|
def __init__(
|
36
35
|
self,
|
37
|
-
controller: CommunicationController,
|
38
|
-
src: str,
|
39
|
-
data_dirs: List[str],
|
36
|
+
controller: Optional[CommunicationController],
|
37
|
+
src: Optional[str],
|
38
|
+
data_dirs: Optional[List[str]],
|
40
39
|
table: Table,
|
41
40
|
offline: bool,
|
42
41
|
injector_controller: InjectorController,
|
@@ -51,10 +50,8 @@ class MethodController(RunController):
|
|
51
50
|
offline=offline,
|
52
51
|
)
|
53
52
|
|
54
|
-
def
|
55
|
-
|
56
|
-
self.send(Command.GET_METHOD_CMD)
|
57
|
-
time.sleep(2)
|
53
|
+
def get_current_method_name(self) -> str:
|
54
|
+
self.sleepy_send(Command.GET_METHOD_CMD)
|
58
55
|
res = self.receive()
|
59
56
|
if res.is_ok():
|
60
57
|
return res.ok_value.string_response
|
@@ -79,26 +76,22 @@ class MethodController(RunController):
|
|
79
76
|
raise ValueError("Communication controller is offline!")
|
80
77
|
|
81
78
|
def get_row(self, row: int) -> TimeTableEntry:
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
start_time=self.get_num(row, RegisterFlag.TIME),
|
99
|
-
organic_modifer=om,
|
100
|
-
flow=flow,
|
101
|
-
)
|
79
|
+
function = self.get_text(row, RegisterFlag.FUNCTION)
|
80
|
+
if function == RegisterFlag.FLOW.value:
|
81
|
+
return TimeTableEntry(
|
82
|
+
start_time=self.get_num(row, RegisterFlag.TIME),
|
83
|
+
organic_modifer=None,
|
84
|
+
flow=self.get_num(row, RegisterFlag.TIMETABLE_FLOW),
|
85
|
+
)
|
86
|
+
if function == RegisterFlag.SOLVENT_COMPOSITION.value:
|
87
|
+
return TimeTableEntry(
|
88
|
+
start_time=self.get_num(row, RegisterFlag.TIME),
|
89
|
+
organic_modifer=self.get_num(
|
90
|
+
row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION
|
91
|
+
),
|
92
|
+
flow=None,
|
93
|
+
)
|
94
|
+
raise ValueError("Both flow and organic modifier are empty")
|
102
95
|
|
103
96
|
def get_timetable(self, rows: int):
|
104
97
|
uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
|
@@ -197,7 +190,6 @@ class MethodController(RunController):
|
|
197
190
|
method_dir=method_dir, method_name=method_name
|
198
191
|
)
|
199
192
|
)
|
200
|
-
|
201
193
|
time.sleep(2)
|
202
194
|
self.send(Command.GET_METHOD_CMD)
|
203
195
|
time.sleep(2)
|
@@ -221,6 +213,7 @@ class MethodController(RunController):
|
|
221
213
|
new_stop_time=updated_method.stop_time,
|
222
214
|
new_post_time=updated_method.post_time,
|
223
215
|
)
|
216
|
+
self.validate_timetable(updated_method.timetable)
|
224
217
|
self.edit_method_timetable(updated_method.timetable)
|
225
218
|
|
226
219
|
if save:
|
@@ -231,6 +224,7 @@ class MethodController(RunController):
|
|
231
224
|
)
|
232
225
|
|
233
226
|
def edit_initial_om(self, new_om: Union[int, float]):
|
227
|
+
self._validate_organic_modifier(new_om)
|
234
228
|
initial_organic_modifier: Param = Param(
|
235
229
|
val=new_om,
|
236
230
|
chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
|
@@ -238,13 +232,31 @@ class MethodController(RunController):
|
|
238
232
|
)
|
239
233
|
self._update_param(initial_organic_modifier)
|
240
234
|
|
235
|
+
def _validate_organic_modifier(self, new_om):
|
236
|
+
if not (isinstance(new_om, int) or isinstance(new_om, float)):
|
237
|
+
raise ValueError("Organic modifier must be int or float")
|
238
|
+
if new_om < 0:
|
239
|
+
raise ValueError("Organic modifier must be positive")
|
240
|
+
if new_om > 100:
|
241
|
+
raise ValueError("Organic modifer must be less than 100.")
|
242
|
+
|
241
243
|
def edit_flow(self, new_flow: Union[int, float]):
|
244
|
+
self._validate_flow(new_flow)
|
242
245
|
flow: Param = Param(
|
243
246
|
val=new_flow, chemstation_key=RegisterFlag.FLOW, ptype=PType.NUM
|
244
247
|
)
|
245
248
|
self._update_param(flow)
|
246
249
|
|
250
|
+
def _validate_flow(self, new_flow):
|
251
|
+
if not (isinstance(new_flow, int) or isinstance(new_flow, float)):
|
252
|
+
raise ValueError("Flow must be int or float")
|
253
|
+
if new_flow < 0:
|
254
|
+
raise ValueError("Flow must be positive")
|
255
|
+
if new_flow >= 5.0:
|
256
|
+
raise ValueError("Flow must be less than 5.0")
|
257
|
+
|
247
258
|
def edit_stop_time(self, new_stop_time: Union[int, float]):
|
259
|
+
self.validate_stop_time(new_stop_time)
|
248
260
|
stop_time: Param = Param(
|
249
261
|
val=new_stop_time,
|
250
262
|
chemstation_key=RegisterFlag.MAX_TIME,
|
@@ -257,7 +269,14 @@ class MethodController(RunController):
|
|
257
269
|
)
|
258
270
|
self._update_param(stop_time)
|
259
271
|
|
272
|
+
def validate_stop_time(self, new_stop_time):
|
273
|
+
if not (isinstance(new_stop_time, int) or isinstance(new_stop_time, float)):
|
274
|
+
raise ValueError("Stop time must be int or float")
|
275
|
+
if new_stop_time < 0:
|
276
|
+
raise ValueError("Stop time must be positive")
|
277
|
+
|
260
278
|
def edit_post_time(self, new_post_time: Union[int, float]):
|
279
|
+
self.validate_post_time(new_post_time)
|
261
280
|
post_time: Param = Param(
|
262
281
|
val=new_post_time,
|
263
282
|
chemstation_key=RegisterFlag.POST_TIME,
|
@@ -268,6 +287,12 @@ class MethodController(RunController):
|
|
268
287
|
)
|
269
288
|
self._update_param(post_time)
|
270
289
|
|
290
|
+
def validate_post_time(self, new_post_time):
|
291
|
+
if not (isinstance(new_post_time, int) or isinstance(new_post_time, float)):
|
292
|
+
raise ValueError("Post time must be int or float")
|
293
|
+
if new_post_time < 0:
|
294
|
+
raise ValueError("Post time must be positive")
|
295
|
+
|
271
296
|
def update_method_params(
|
272
297
|
self,
|
273
298
|
new_flow: Union[int, float],
|
@@ -328,59 +353,151 @@ class MethodController(RunController):
|
|
328
353
|
val=method_param.val,
|
329
354
|
)
|
330
355
|
)
|
331
|
-
time.sleep(2)
|
332
356
|
self.download()
|
333
357
|
|
334
358
|
def download(self):
|
335
|
-
self.send("Sleep 1")
|
336
359
|
self.sleepy_send("DownloadRCMethod PMP1")
|
337
|
-
self.send("Sleep 1")
|
338
360
|
|
339
|
-
def _edit_row(
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
361
|
+
def _edit_row(
|
362
|
+
self,
|
363
|
+
row: TimeTableEntry,
|
364
|
+
first_row: bool,
|
365
|
+
time_added: bool,
|
366
|
+
flow_added: bool,
|
367
|
+
om_added: bool,
|
368
|
+
function_added: bool,
|
369
|
+
) -> Tuple[bool, bool, bool, bool]:
|
370
|
+
def add_time():
|
371
|
+
nonlocal time_added
|
372
|
+
nonlocal first_row
|
373
|
+
if not time_added:
|
347
374
|
self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
375
|
+
time_added = True
|
376
|
+
elif not first_row:
|
377
|
+
self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
378
|
+
|
379
|
+
def add_flow():
|
380
|
+
nonlocal flow_added
|
381
|
+
nonlocal function_added
|
382
|
+
if not flow_added:
|
383
|
+
if not function_added:
|
384
|
+
self.add_new_col_text(
|
385
|
+
col_name=RegisterFlag.FUNCTION,
|
386
|
+
val=RegisterFlag.FLOW.value,
|
387
|
+
)
|
388
|
+
function_added = True
|
389
|
+
else:
|
390
|
+
self._edit_row_text(
|
391
|
+
col_name=RegisterFlag.FUNCTION,
|
392
|
+
val=RegisterFlag.FLOW.value,
|
393
|
+
)
|
348
394
|
self.add_new_col_num(
|
349
|
-
col_name=RegisterFlag.
|
350
|
-
val=row.
|
395
|
+
col_name=RegisterFlag.TIMETABLE_FLOW,
|
396
|
+
val=row.flow,
|
351
397
|
)
|
352
|
-
|
353
|
-
|
354
|
-
self.get_num_rows()
|
398
|
+
flow_added = True
|
399
|
+
else:
|
355
400
|
self._edit_row_text(
|
356
401
|
col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
|
357
402
|
)
|
358
|
-
self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
359
403
|
self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
404
|
+
|
405
|
+
def add_om():
|
406
|
+
nonlocal om_added
|
407
|
+
nonlocal function_added
|
408
|
+
if not om_added:
|
409
|
+
if not function_added:
|
410
|
+
self.add_new_col_text(
|
411
|
+
col_name=RegisterFlag.FUNCTION,
|
412
|
+
val=RegisterFlag.SOLVENT_COMPOSITION.value,
|
413
|
+
)
|
414
|
+
function_added = True
|
415
|
+
else:
|
416
|
+
self._edit_row_text(
|
417
|
+
col_name=RegisterFlag.FUNCTION,
|
418
|
+
val=RegisterFlag.SOLVENT_COMPOSITION.value,
|
419
|
+
)
|
420
|
+
self.add_new_col_num(
|
421
|
+
col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
|
422
|
+
val=row.organic_modifer,
|
423
|
+
)
|
424
|
+
om_added = True
|
425
|
+
else:
|
365
426
|
self._edit_row_text(
|
366
427
|
col_name=RegisterFlag.FUNCTION,
|
367
428
|
val=RegisterFlag.SOLVENT_COMPOSITION.value,
|
368
429
|
)
|
369
|
-
self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
370
430
|
self._edit_row_num(
|
371
431
|
col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
|
372
432
|
val=row.organic_modifer,
|
373
433
|
)
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
434
|
+
|
435
|
+
if row.organic_modifer:
|
436
|
+
self.add_row()
|
437
|
+
add_om()
|
438
|
+
add_time()
|
439
|
+
if row.flow:
|
440
|
+
self.add_row()
|
441
|
+
add_flow()
|
442
|
+
add_time()
|
443
|
+
self.download()
|
444
|
+
return time_added, flow_added, om_added, function_added
|
445
|
+
|
446
|
+
# if first_row:
|
447
|
+
# time_added = False
|
448
|
+
# flow_row_method: Callable = (
|
449
|
+
# self.add_new_col_text
|
450
|
+
# if row.flow and not row.organic_modifer
|
451
|
+
# else self._edit_row_text
|
452
|
+
# )
|
453
|
+
# if row.organic_modifer:
|
454
|
+
# self.add_row()
|
455
|
+
# self.add_new_col_text(
|
456
|
+
# col_name=RegisterFlag.FUNCTION,
|
457
|
+
# val=RegisterFlag.SOLVENT_COMPOSITION.value,
|
458
|
+
# )
|
459
|
+
# if not time_added:
|
460
|
+
# time_added = True
|
461
|
+
# self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
462
|
+
# self.add_new_col_num(
|
463
|
+
# col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
|
464
|
+
# val=row.organic_modifer,
|
465
|
+
# )
|
466
|
+
# if row.flow:
|
467
|
+
# self.add_row()
|
468
|
+
# self.get_num_rows()
|
469
|
+
# flow_row_method(
|
470
|
+
# col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
|
471
|
+
# )
|
472
|
+
# if not time_added:
|
473
|
+
# time_added = True
|
474
|
+
# self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
475
|
+
# self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
476
|
+
# self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
477
|
+
# self.download()
|
478
|
+
# else:
|
479
|
+
# if row.organic_modifer:
|
480
|
+
# self.add_row()
|
481
|
+
# self.get_num_rows()
|
482
|
+
# self._edit_row_text(
|
483
|
+
# col_name=RegisterFlag.FUNCTION,
|
484
|
+
# val=RegisterFlag.SOLVENT_COMPOSITION.value,
|
485
|
+
# )
|
486
|
+
# self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
487
|
+
# self._edit_row_num(
|
488
|
+
# col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
|
489
|
+
# val=row.organic_modifer,
|
490
|
+
# )
|
491
|
+
# self.download()
|
492
|
+
# if row.flow:
|
493
|
+
# self.add_row()
|
494
|
+
# self.get_num_rows()
|
495
|
+
# self._edit_row_text(
|
496
|
+
# col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
|
497
|
+
# )
|
498
|
+
# self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
499
|
+
# self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
500
|
+
# self.download()
|
384
501
|
|
385
502
|
def edit_method_timetable(self, timetable_rows: List[TimeTableEntry]):
|
386
503
|
self.get_num_rows()
|
@@ -391,10 +508,23 @@ class MethodController(RunController):
|
|
391
508
|
res = self.get_num_rows()
|
392
509
|
|
393
510
|
self.new_table()
|
394
|
-
self.get_num_rows()
|
395
|
-
|
511
|
+
num_rows = self.get_num_rows()
|
512
|
+
if num_rows.ok_value.num_response != 0:
|
513
|
+
raise ValueError("Should be zero rows!")
|
514
|
+
|
515
|
+
time_added = False
|
516
|
+
flow_added = False
|
517
|
+
om_added = False
|
518
|
+
function_added = False
|
396
519
|
for i, row in enumerate(timetable_rows):
|
397
|
-
self._edit_row(
|
520
|
+
time_added, flow_added, om_added, function_added = self._edit_row(
|
521
|
+
row=row,
|
522
|
+
first_row=i == 0,
|
523
|
+
time_added=time_added,
|
524
|
+
flow_added=flow_added,
|
525
|
+
om_added=om_added,
|
526
|
+
function_added=function_added,
|
527
|
+
)
|
398
528
|
|
399
529
|
def stop(self):
|
400
530
|
"""
|
@@ -441,23 +571,25 @@ class MethodController(RunController):
|
|
441
571
|
if run_completed.is_ok():
|
442
572
|
self.data_files[-1] = run_completed.ok_value
|
443
573
|
else:
|
444
|
-
|
574
|
+
warnings.warn(run_completed.err_value)
|
445
575
|
else:
|
446
|
-
folder = self.
|
447
|
-
|
448
|
-
|
576
|
+
folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
|
577
|
+
i = 0
|
578
|
+
while folder.is_err() and i < 10:
|
579
|
+
folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
|
580
|
+
i += 1
|
449
581
|
if folder.is_ok():
|
450
582
|
self.data_files[-1] = folder.ok_value
|
451
583
|
else:
|
452
584
|
warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
|
453
585
|
warnings.warn(warning)
|
454
586
|
|
455
|
-
def
|
456
|
-
self, most_recent_folder: T
|
587
|
+
def _fuzzy_match_most_recent_folder(
|
588
|
+
self, most_recent_folder: T
|
457
589
|
) -> Result[str, str]:
|
458
590
|
if isinstance(most_recent_folder, str) or isinstance(most_recent_folder, bytes):
|
459
591
|
if os.path.exists(most_recent_folder):
|
460
|
-
return Ok(most_recent_folder)
|
592
|
+
return Ok(str(most_recent_folder))
|
461
593
|
return Err("Folder not found!")
|
462
594
|
raise ValueError("Folder is not a str or byte type.")
|
463
595
|
|
@@ -490,3 +622,25 @@ class MethodController(RunController):
|
|
490
622
|
if len(possible_data.x) > 0:
|
491
623
|
signal.data = possible_data
|
492
624
|
return [metd_report]
|
625
|
+
|
626
|
+
def _validate_row(self, row: TimeTableEntry):
|
627
|
+
if not (row.flow or row.organic_modifer):
|
628
|
+
raise ValueError(
|
629
|
+
"Require one of flow or organic modifier for the method timetable entry!"
|
630
|
+
)
|
631
|
+
if row.flow:
|
632
|
+
self._validate_flow(row.flow)
|
633
|
+
if row.organic_modifer:
|
634
|
+
self._validate_organic_modifier(row.organic_modifer)
|
635
|
+
|
636
|
+
def validate_timetable(self, timetable: List[TimeTableEntry]):
|
637
|
+
start_time = 0.0
|
638
|
+
for i, row in enumerate(timetable):
|
639
|
+
if row.start_time > start_time:
|
640
|
+
start_time = row.start_time
|
641
|
+
elif row.start_time <= start_time:
|
642
|
+
raise ValueError(
|
643
|
+
f"""Every row's start time must be larger than the previous start time.
|
644
|
+
Row {i + 1} ({timetable[i].start_time}) has a smaller or equal starttime than row {i} ({start_time})"""
|
645
|
+
)
|
646
|
+
self._validate_row(row)
|