pychemstation 0.10.8.dev2__py3-none-any.whl → 0.10.9__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/process_report.py +3 -1
- pychemstation/control/controllers/data_aq/method.py +224 -70
- pychemstation/control/controllers/data_aq/sequence.py +95 -67
- pychemstation/control/hplc.py +29 -2
- pychemstation/utils/abc_tables/abc_comm.py +7 -3
- pychemstation/utils/abc_tables/run.py +13 -12
- pychemstation/utils/abc_tables/table.py +18 -13
- pychemstation/utils/device_types.py +7 -0
- pychemstation/utils/macro.py +5 -2
- pychemstation/utils/method_types.py +1 -1
- pychemstation/utils/mocking/mock_comm.py +25 -2
- pychemstation/utils/mocking/mock_hplc.py +29 -1
- pychemstation/utils/sequence_types.py +0 -2
- pychemstation/utils/table_types.py +2 -0
- {pychemstation-0.10.8.dev2.dist-info → pychemstation-0.10.9.dist-info}/METADATA +17 -12
- {pychemstation-0.10.8.dev2.dist-info → pychemstation-0.10.9.dist-info}/RECORD +18 -17
- {pychemstation-0.10.8.dev2.dist-info → pychemstation-0.10.9.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.8.dev2.dist-info → pychemstation-0.10.9.dist-info}/licenses/LICENSE +0 -0
@@ -83,7 +83,9 @@ class CSVProcessor(ReportProcessor):
|
|
83
83
|
if "00" in name and file_extension.lower() == "csv":
|
84
84
|
prefix, _, _ = name.partition("00")
|
85
85
|
return prefix
|
86
|
-
raise FileNotFoundError(
|
86
|
+
raise FileNotFoundError(
|
87
|
+
"Couldn't find the prefix for CSV, please make sure the post-run settings generate a CSV."
|
88
|
+
)
|
87
89
|
|
88
90
|
def report_contains(self, labels: List[str], want: List[str]):
|
89
91
|
for label in labels:
|
@@ -3,17 +3,18 @@ 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 ....analysis.process_report import AgilentReport, ReportType
|
11
|
-
from ....control.controllers import CommunicationController
|
12
10
|
from pychemstation.analysis.chromatogram import (
|
13
11
|
TIME_FORMAT,
|
14
12
|
AgilentChannelChromatogramData,
|
15
13
|
AgilentHPLCChromatogram,
|
16
14
|
)
|
15
|
+
|
16
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
17
|
+
from ....control.controllers import CommunicationController
|
17
18
|
from ....utils.abc_tables.run import RunController
|
18
19
|
from ....utils.macro import Command
|
19
20
|
from ....utils.method_types import (
|
@@ -23,7 +24,7 @@ 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
|
|
@@ -50,9 +51,7 @@ class MethodController(RunController):
|
|
50
51
|
)
|
51
52
|
|
52
53
|
def get_current_method_name(self) -> str:
|
53
|
-
|
54
|
-
self.send(Command.GET_METHOD_CMD)
|
55
|
-
time.sleep(2)
|
54
|
+
self.sleepy_send(Command.GET_METHOD_CMD)
|
56
55
|
res = self.receive()
|
57
56
|
if res.is_ok():
|
58
57
|
return res.ok_value.string_response
|
@@ -77,26 +76,22 @@ class MethodController(RunController):
|
|
77
76
|
raise ValueError("Communication controller is offline!")
|
78
77
|
|
79
78
|
def get_row(self, row: int) -> TimeTableEntry:
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
start_time=self.get_num(row, RegisterFlag.TIME),
|
97
|
-
organic_modifer=om,
|
98
|
-
flow=flow,
|
99
|
-
)
|
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")
|
100
95
|
|
101
96
|
def get_timetable(self, rows: int):
|
102
97
|
uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
|
@@ -195,7 +190,6 @@ class MethodController(RunController):
|
|
195
190
|
method_dir=method_dir, method_name=method_name
|
196
191
|
)
|
197
192
|
)
|
198
|
-
|
199
193
|
time.sleep(2)
|
200
194
|
self.send(Command.GET_METHOD_CMD)
|
201
195
|
time.sleep(2)
|
@@ -219,6 +213,7 @@ class MethodController(RunController):
|
|
219
213
|
new_stop_time=updated_method.stop_time,
|
220
214
|
new_post_time=updated_method.post_time,
|
221
215
|
)
|
216
|
+
self.validate_timetable(updated_method.timetable)
|
222
217
|
self.edit_method_timetable(updated_method.timetable)
|
223
218
|
|
224
219
|
if save:
|
@@ -229,6 +224,7 @@ class MethodController(RunController):
|
|
229
224
|
)
|
230
225
|
|
231
226
|
def edit_initial_om(self, new_om: Union[int, float]):
|
227
|
+
self._validate_organic_modifier(new_om)
|
232
228
|
initial_organic_modifier: Param = Param(
|
233
229
|
val=new_om,
|
234
230
|
chemstation_key=RegisterFlag.SOLVENT_B_COMPOSITION,
|
@@ -236,13 +232,31 @@ class MethodController(RunController):
|
|
236
232
|
)
|
237
233
|
self._update_param(initial_organic_modifier)
|
238
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
|
+
|
239
243
|
def edit_flow(self, new_flow: Union[int, float]):
|
244
|
+
self._validate_flow(new_flow)
|
240
245
|
flow: Param = Param(
|
241
246
|
val=new_flow, chemstation_key=RegisterFlag.FLOW, ptype=PType.NUM
|
242
247
|
)
|
243
248
|
self._update_param(flow)
|
244
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
|
+
|
245
258
|
def edit_stop_time(self, new_stop_time: Union[int, float]):
|
259
|
+
self.validate_stop_time(new_stop_time)
|
246
260
|
stop_time: Param = Param(
|
247
261
|
val=new_stop_time,
|
248
262
|
chemstation_key=RegisterFlag.MAX_TIME,
|
@@ -255,7 +269,14 @@ class MethodController(RunController):
|
|
255
269
|
)
|
256
270
|
self._update_param(stop_time)
|
257
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
|
+
|
258
278
|
def edit_post_time(self, new_post_time: Union[int, float]):
|
279
|
+
self.validate_post_time(new_post_time)
|
259
280
|
post_time: Param = Param(
|
260
281
|
val=new_post_time,
|
261
282
|
chemstation_key=RegisterFlag.POST_TIME,
|
@@ -266,6 +287,12 @@ class MethodController(RunController):
|
|
266
287
|
)
|
267
288
|
self._update_param(post_time)
|
268
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
|
+
|
269
296
|
def update_method_params(
|
270
297
|
self,
|
271
298
|
new_flow: Union[int, float],
|
@@ -311,7 +338,7 @@ class MethodController(RunController):
|
|
311
338
|
)
|
312
339
|
if isinstance(method_param.chemstation_key, list):
|
313
340
|
for register_flag in method_param.chemstation_key:
|
314
|
-
self.
|
341
|
+
self.sleepy_send(
|
315
342
|
setting_command.value.format(
|
316
343
|
register=register,
|
317
344
|
register_flag=register_flag,
|
@@ -319,66 +346,158 @@ class MethodController(RunController):
|
|
319
346
|
)
|
320
347
|
)
|
321
348
|
else:
|
322
|
-
self.
|
349
|
+
self.sleepy_send(
|
323
350
|
setting_command.value.format(
|
324
351
|
register=register,
|
325
352
|
register_flag=method_param.chemstation_key,
|
326
353
|
val=method_param.val,
|
327
354
|
)
|
328
355
|
)
|
329
|
-
time.sleep(2)
|
330
356
|
self.download()
|
331
357
|
|
332
358
|
def download(self):
|
333
|
-
self.send("Sleep 1")
|
334
359
|
self.sleepy_send("DownloadRCMethod PMP1")
|
335
|
-
self.send("Sleep 1")
|
336
360
|
|
337
|
-
def _edit_row(
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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:
|
345
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
|
+
)
|
346
394
|
self.add_new_col_num(
|
347
|
-
col_name=RegisterFlag.
|
348
|
-
val=row.
|
395
|
+
col_name=RegisterFlag.TIMETABLE_FLOW,
|
396
|
+
val=row.flow,
|
349
397
|
)
|
350
|
-
|
351
|
-
|
352
|
-
self.get_num_rows()
|
398
|
+
flow_added = True
|
399
|
+
else:
|
353
400
|
self._edit_row_text(
|
354
401
|
col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value
|
355
402
|
)
|
356
|
-
self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
357
403
|
self._edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
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:
|
363
426
|
self._edit_row_text(
|
364
427
|
col_name=RegisterFlag.FUNCTION,
|
365
428
|
val=RegisterFlag.SOLVENT_COMPOSITION.value,
|
366
429
|
)
|
367
|
-
self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
368
430
|
self._edit_row_num(
|
369
431
|
col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
|
370
432
|
val=row.organic_modifer,
|
371
433
|
)
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
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()
|
382
501
|
|
383
502
|
def edit_method_timetable(self, timetable_rows: List[TimeTableEntry]):
|
384
503
|
self.get_num_rows()
|
@@ -389,10 +508,23 @@ class MethodController(RunController):
|
|
389
508
|
res = self.get_num_rows()
|
390
509
|
|
391
510
|
self.new_table()
|
392
|
-
self.get_num_rows()
|
393
|
-
|
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
|
394
519
|
for i, row in enumerate(timetable_rows):
|
395
|
-
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
|
+
)
|
396
528
|
|
397
529
|
def stop(self):
|
398
530
|
"""
|
@@ -441,10 +573,10 @@ class MethodController(RunController):
|
|
441
573
|
else:
|
442
574
|
warnings.warn(run_completed.err_value)
|
443
575
|
else:
|
444
|
-
folder = self._fuzzy_match_most_recent_folder(self.data_files[-1]
|
576
|
+
folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
|
445
577
|
i = 0
|
446
578
|
while folder.is_err() and i < 10:
|
447
|
-
folder = self._fuzzy_match_most_recent_folder(self.data_files[-1]
|
579
|
+
folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
|
448
580
|
i += 1
|
449
581
|
if folder.is_ok():
|
450
582
|
self.data_files[-1] = folder.ok_value
|
@@ -453,11 +585,11 @@ class MethodController(RunController):
|
|
453
585
|
warnings.warn(warning)
|
454
586
|
|
455
587
|
def _fuzzy_match_most_recent_folder(
|
456
|
-
self, most_recent_folder: T
|
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)
|
@@ -1,7 +1,9 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import time
|
3
5
|
import warnings
|
4
|
-
from typing import Any, Dict, List, Optional, Union,
|
6
|
+
from typing import Any, Dict, List, Optional, Union, Tuple
|
5
7
|
|
6
8
|
from result import Err, Ok, Result
|
7
9
|
from typing_extensions import override
|
@@ -9,6 +11,7 @@ from typing_extensions import override
|
|
9
11
|
from pychemstation.analysis.chromatogram import (
|
10
12
|
AgilentChannelChromatogramData,
|
11
13
|
AgilentHPLCChromatogram,
|
14
|
+
SEQUENCE_TIME_FORMAT,
|
12
15
|
)
|
13
16
|
|
14
17
|
from ....analysis.process_report import AgilentReport, ReportType
|
@@ -118,7 +121,6 @@ class SequenceController(RunController):
|
|
118
121
|
parsed_response = self.get_current_sequence_name()
|
119
122
|
|
120
123
|
assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
|
121
|
-
self.table_state = self.load()
|
122
124
|
|
123
125
|
def get_current_sequence_name(self):
|
124
126
|
self.send(Command.GET_SEQUENCE_CMD)
|
@@ -186,6 +188,8 @@ class SequenceController(RunController):
|
|
186
188
|
def edit_sample_type(
|
187
189
|
self, sample_type: SampleType, row_num: int, save: bool = True
|
188
190
|
):
|
191
|
+
if not isinstance(sample_type, SampleType):
|
192
|
+
raise ValueError("`sample_type` should be of type `SampleType`")
|
189
193
|
self._edit_row_num(
|
190
194
|
row=row_num,
|
191
195
|
col_name=RegisterFlag.SAMPLE_TYPE,
|
@@ -207,6 +211,8 @@ class SequenceController(RunController):
|
|
207
211
|
def edit_injection_source(
|
208
212
|
self, inj_source: InjectionSource, row_num: int, save: bool = True
|
209
213
|
):
|
214
|
+
if not isinstance(inj_source, InjectionSource):
|
215
|
+
raise ValueError("`inj_source` should be of type `InjectionSource`")
|
210
216
|
self._edit_row_text(
|
211
217
|
row=row_num, col_name=RegisterFlag.INJ_SOR, val=inj_source.value
|
212
218
|
)
|
@@ -224,25 +230,65 @@ class SequenceController(RunController):
|
|
224
230
|
|
225
231
|
def edit_num_injections(self, num_inj: int, row_num: int, save: bool = True):
|
226
232
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=num_inj)
|
233
|
+
if save:
|
234
|
+
self.save()
|
227
235
|
|
228
|
-
def edit_method_name(
|
236
|
+
def edit_method_name(
|
237
|
+
self, method: str, row_num: int, save: bool = True, override_check: bool = False
|
238
|
+
):
|
229
239
|
method_dir = self.method_controller.src
|
230
240
|
possible_path = os.path.join(method_dir, method) + ".M\\"
|
231
241
|
if os.path.exists(possible_path):
|
232
242
|
method = os.path.join(method_dir, method)
|
243
|
+
elif not override_check:
|
244
|
+
raise ValueError(
|
245
|
+
"Method may not exist. If you would still like to use this method, set the `override_check` flag to `True`"
|
246
|
+
)
|
233
247
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
|
234
248
|
if save:
|
235
249
|
self.save()
|
236
250
|
|
237
251
|
def edit_vial_location(self, loc: Tray, row_num: int, save: bool = True):
|
238
252
|
loc_num = -1
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
253
|
+
try:
|
254
|
+
previous_contents = self.get_row(row_num)
|
255
|
+
if (
|
256
|
+
isinstance(loc, VialBar)
|
257
|
+
and isinstance(previous_contents.vial_location, VialBar)
|
258
|
+
or isinstance(loc, FiftyFourVialPlate)
|
259
|
+
and isinstance(previous_contents.vial_location, FiftyFourVialPlate)
|
260
|
+
):
|
261
|
+
if isinstance(loc, VialBar):
|
262
|
+
loc_num = loc.value
|
263
|
+
elif isinstance(loc, FiftyFourVialPlate):
|
264
|
+
loc_num = loc.value()
|
265
|
+
self._edit_row_num(
|
266
|
+
row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
|
267
|
+
)
|
268
|
+
elif isinstance(loc, VialBar) or isinstance(loc, FiftyFourVialPlate):
|
269
|
+
self.add_row()
|
270
|
+
previous_contents.vial_location = loc
|
271
|
+
num_rows = self.get_num_rows().ok_value.num_response
|
272
|
+
self._edit_row(previous_contents, num_rows)
|
273
|
+
self.move_row(int(num_rows), row_num)
|
274
|
+
self.delete_row(row_num + 1)
|
275
|
+
self.save()
|
276
|
+
else:
|
277
|
+
raise ValueError(
|
278
|
+
"`loc` should be of type `VialBar`, `FiftyFourVialPlate`"
|
279
|
+
)
|
280
|
+
except Exception:
|
281
|
+
if not (isinstance(loc, VialBar) or isinstance(loc, FiftyFourVialPlate)):
|
282
|
+
raise ValueError(
|
283
|
+
"`loc` should be of type `VialBar`, `FiftyFourVialPlate`"
|
284
|
+
)
|
285
|
+
if isinstance(loc, VialBar):
|
286
|
+
loc_num = loc.value
|
287
|
+
elif isinstance(loc, FiftyFourVialPlate):
|
288
|
+
loc_num = loc.value()
|
289
|
+
self._edit_row_num(
|
290
|
+
row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
|
291
|
+
)
|
246
292
|
if save:
|
247
293
|
self.save()
|
248
294
|
|
@@ -255,11 +301,6 @@ class SequenceController(RunController):
|
|
255
301
|
under the <data_dir>/<sequence table name> folder.
|
256
302
|
Device must be ready.
|
257
303
|
"""
|
258
|
-
if self.controller:
|
259
|
-
self.controller.send(Command.SAVE_METHOD_CMD)
|
260
|
-
self.controller.send(Command.SAVE_SEQUENCE_CMD)
|
261
|
-
else:
|
262
|
-
raise ValueError("Controller is offline!")
|
263
304
|
|
264
305
|
current_sequence_name = self.get_current_sequence_name()
|
265
306
|
if not self.table_state or self.table_state.name not in current_sequence_name:
|
@@ -284,6 +325,11 @@ class SequenceController(RunController):
|
|
284
325
|
curr_method_runtime = self.method_controller.get_total_runtime()
|
285
326
|
total_runtime += curr_method_runtime
|
286
327
|
|
328
|
+
timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
|
329
|
+
folder_name = f"{self.table_state.name} {timestamp}"
|
330
|
+
|
331
|
+
self.send(Command.SAVE_METHOD_CMD)
|
332
|
+
self.send(Command.SAVE_SEQUENCE_CMD)
|
287
333
|
self.send(Command.RUN_SEQUENCE_CMD.value)
|
288
334
|
self.timeout = total_runtime * 60
|
289
335
|
|
@@ -295,20 +341,11 @@ class SequenceController(RunController):
|
|
295
341
|
break
|
296
342
|
|
297
343
|
if hplc_running:
|
298
|
-
full_path_name, current_sample_file =
|
299
|
-
for _ in range(5):
|
300
|
-
try:
|
301
|
-
full_path_name, current_sample_file = (
|
302
|
-
self.get_current_run_data_dir_file()
|
303
|
-
)
|
304
|
-
break
|
305
|
-
except ValueError:
|
306
|
-
pass
|
344
|
+
full_path_name, current_sample_file = self.try_getting_run_info(folder_name)
|
307
345
|
if full_path_name and current_sample_file:
|
308
346
|
data_file = SequenceDataFiles(
|
309
347
|
sequence_name=self.table_state.name,
|
310
348
|
dir=full_path_name,
|
311
|
-
_data_files=[r.data_file for r in self.table_state.rows],
|
312
349
|
child_dirs=[os.path.join(full_path_name, current_sample_file)],
|
313
350
|
)
|
314
351
|
self.data_files.append(data_file)
|
@@ -324,40 +361,34 @@ class SequenceController(RunController):
|
|
324
361
|
else:
|
325
362
|
raise RuntimeError("Sequence run may not have started.")
|
326
363
|
|
364
|
+
def try_getting_run_info(self, folder_name: str) -> Tuple[str, str | None]:
|
365
|
+
full_path_name, current_sample_file = None, None
|
366
|
+
for _ in range(5):
|
367
|
+
try:
|
368
|
+
full_path_name, current_sample_file = (
|
369
|
+
self.get_current_run_data_dir_file()
|
370
|
+
)
|
371
|
+
except ValueError:
|
372
|
+
pass
|
373
|
+
if current_sample_file and full_path_name:
|
374
|
+
return full_path_name, current_sample_file
|
375
|
+
elif full_path_name:
|
376
|
+
return full_path_name, None
|
377
|
+
raise ValueError("Could not get sequence data folder")
|
378
|
+
|
327
379
|
@override
|
328
380
|
def _fuzzy_match_most_recent_folder(
|
329
|
-
self, most_recent_folder: T
|
381
|
+
self, most_recent_folder: T
|
330
382
|
) -> Result[SequenceDataFiles, str]:
|
331
383
|
if isinstance(most_recent_folder, SequenceDataFiles):
|
332
384
|
try:
|
333
385
|
if most_recent_folder.dir and os.path.isdir(most_recent_folder.dir):
|
334
386
|
subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
most_recent_folder.child_dirs = [
|
341
|
-
os.path.join(most_recent_folder.dir, f) if not os.path.isdir(f) else f for f in child_dirs
|
342
|
-
]
|
343
|
-
for d in most_recent_folder.child_dirs:
|
344
|
-
assert os.path.isdir(d)
|
345
|
-
else:
|
346
|
-
potential_folders: List[str] = sorted(
|
347
|
-
list(
|
348
|
-
filter(
|
349
|
-
lambda d: most_recent_folder.dir in d,
|
350
|
-
subdirs,
|
351
|
-
)
|
352
|
-
)
|
353
|
-
)
|
354
|
-
most_recent_folder.child_dirs = [
|
355
|
-
f
|
356
|
-
for f in potential_folders
|
357
|
-
if most_recent_folder.dir in f
|
358
|
-
and ".M" not in f
|
359
|
-
and ".D" in f
|
360
|
-
]
|
387
|
+
most_recent_folder.child_dirs = [
|
388
|
+
f
|
389
|
+
for f in subdirs
|
390
|
+
if most_recent_folder.dir in f and ".D" in f and f[-1] == "D"
|
391
|
+
]
|
361
392
|
return Ok(most_recent_folder)
|
362
393
|
else:
|
363
394
|
return Err("No sequence folder found, please give the full path.")
|
@@ -372,14 +403,11 @@ class SequenceController(RunController):
|
|
372
403
|
if custom_path
|
373
404
|
else self.data_files[-1]
|
374
405
|
)
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
seq_data_dir = search_folder.ok_value
|
381
|
-
else:
|
382
|
-
raise FileNotFoundError(search_folder.err_value)
|
406
|
+
search_folder = self._fuzzy_match_most_recent_folder(seq_data_dir)
|
407
|
+
if search_folder.is_ok():
|
408
|
+
seq_data_dir = search_folder.ok_value
|
409
|
+
else:
|
410
|
+
raise FileNotFoundError(search_folder.err_value)
|
383
411
|
all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
|
384
412
|
for row in seq_data_dir.child_dirs:
|
385
413
|
all_w_spectra.append(self.get_data_uv(custom_path=row))
|
@@ -403,10 +431,9 @@ class SequenceController(RunController):
|
|
403
431
|
if custom_path
|
404
432
|
else self.data_files[-1]
|
405
433
|
)
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
).ok_value
|
434
|
+
self.data_files[-1] = self._fuzzy_match_most_recent_folder(
|
435
|
+
seq_file_dir
|
436
|
+
).ok_value
|
410
437
|
spectra: List[AgilentChannelChromatogramData] = []
|
411
438
|
for row in self.data_files[-1].child_dirs:
|
412
439
|
self.get_spectrum_at_channels(row)
|
@@ -425,12 +452,13 @@ class SequenceController(RunController):
|
|
425
452
|
dir=custom_path,
|
426
453
|
sequence_name="NA",
|
427
454
|
),
|
428
|
-
child_dirs=None,
|
429
455
|
).ok_value
|
430
456
|
)
|
431
457
|
parent_dir = self.data_files[-1]
|
432
|
-
|
433
|
-
|
458
|
+
parent_dir = self._fuzzy_match_most_recent_folder(
|
459
|
+
most_recent_folder=parent_dir,
|
460
|
+
).ok_value
|
461
|
+
assert len(parent_dir.child_dirs) != 0
|
434
462
|
spectra = self.get_data()
|
435
463
|
reports = []
|
436
464
|
for i, child_dir in enumerate(parent_dir.child_dirs):
|
pychemstation/control/hplc.py
CHANGED
@@ -20,7 +20,7 @@ from ..control.controllers import CommunicationController
|
|
20
20
|
from ..utils.injector_types import InjectorTable
|
21
21
|
from ..utils.macro import Command, Response, Status
|
22
22
|
from ..utils.method_types import MethodDetails
|
23
|
-
from ..utils.sequence_types import SequenceTable
|
23
|
+
from ..utils.sequence_types import SequenceTable, SequenceDataFiles
|
24
24
|
from ..utils.table_types import Table
|
25
25
|
|
26
26
|
|
@@ -107,7 +107,11 @@ class HPLCController:
|
|
107
107
|
raise RuntimeError(
|
108
108
|
"Communication controller must be initialized before sending command. It is currently in offline mode."
|
109
109
|
)
|
110
|
-
|
110
|
+
res = self.comm.receive()
|
111
|
+
if res.is_ok():
|
112
|
+
return res.ok_value
|
113
|
+
else:
|
114
|
+
return res.err_value
|
111
115
|
|
112
116
|
def status(self) -> Status:
|
113
117
|
"""Get the current status of the HPLC machine.
|
@@ -202,6 +206,16 @@ class HPLCController:
|
|
202
206
|
"""
|
203
207
|
self.sequence_controller.edit(updated_sequence)
|
204
208
|
|
209
|
+
def get_last_run_method_file_path(self) -> str:
|
210
|
+
"""Get the folder (ending with .D) for last run method.
|
211
|
+
|
212
|
+
:return: Complete path for method run.
|
213
|
+
"""
|
214
|
+
if len(self.method_controller.data_files) > 0:
|
215
|
+
return self.method_controller.data_files[-1]
|
216
|
+
else:
|
217
|
+
raise UserWarning("No data yet!")
|
218
|
+
|
205
219
|
def get_last_run_method_report(
|
206
220
|
self,
|
207
221
|
custom_path: Optional[str] = None,
|
@@ -230,6 +244,19 @@ class HPLCController:
|
|
230
244
|
else:
|
231
245
|
return self.method_controller.get_data(custom_path=custom_path)
|
232
246
|
|
247
|
+
def get_last_run_sequence_file_paths(self) -> SequenceDataFiles:
|
248
|
+
"""Get the sequence folder and all run folders (ending with .D).
|
249
|
+
|
250
|
+
:return: `SequenceDataFiles` containing complete path locations for sequence folder and all runs.
|
251
|
+
"""
|
252
|
+
if len(self.sequence_controller.data_files):
|
253
|
+
self.sequence_controller._fuzzy_match_most_recent_folder(
|
254
|
+
most_recent_folder=self.sequence_controller.data_files[-1],
|
255
|
+
)
|
256
|
+
return self.sequence_controller.data_files[-1]
|
257
|
+
else:
|
258
|
+
raise UserWarning("No data files yet!")
|
259
|
+
|
233
260
|
def get_last_run_sequence_reports(
|
234
261
|
self,
|
235
262
|
custom_path: Optional[str] = None,
|
@@ -57,7 +57,6 @@ class ABCCommunicationController(abc.ABC):
|
|
57
57
|
|
58
58
|
self.reset_cmd_counter()
|
59
59
|
self._most_recent_hplc_status: Status = self.get_status()
|
60
|
-
self.send("Local Rows")
|
61
60
|
|
62
61
|
@abstractmethod
|
63
62
|
def get_num_val(self, cmd: str) -> Union[int, float]:
|
@@ -89,10 +88,15 @@ class ABCCommunicationController(abc.ABC):
|
|
89
88
|
:return: whether the HPLC machine is in a safe state to retrieve data back."""
|
90
89
|
self.set_status()
|
91
90
|
hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
92
|
-
time.sleep(
|
91
|
+
time.sleep(10)
|
93
92
|
self.set_status()
|
94
93
|
hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
95
|
-
|
94
|
+
time.sleep(10)
|
95
|
+
self.set_status()
|
96
|
+
hplc_final_check_avail = isinstance(
|
97
|
+
self._most_recent_hplc_status, HPLCAvailStatus
|
98
|
+
)
|
99
|
+
return hplc_avail and hplc_actually_avail and hplc_final_check_avail
|
96
100
|
|
97
101
|
def sleepy_send(self, cmd: Union[Command, str]):
|
98
102
|
self.send("Sleep 0.1")
|
@@ -94,9 +94,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
94
94
|
return object.__new__(cls)
|
95
95
|
|
96
96
|
@abc.abstractmethod
|
97
|
-
def _fuzzy_match_most_recent_folder(
|
98
|
-
self, most_recent_folder: T, child_dirs: Set[str]
|
99
|
-
) -> Result[T, str]:
|
97
|
+
def _fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
100
98
|
pass
|
101
99
|
|
102
100
|
@abc.abstractmethod
|
@@ -192,9 +190,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
192
190
|
else:
|
193
191
|
raise ValueError("Timeout value is None, no comparison can be made.")
|
194
192
|
|
195
|
-
check_folder = self._fuzzy_match_most_recent_folder(
|
196
|
-
self.data_files[-1], self.current_run_child_files
|
197
|
-
)
|
193
|
+
check_folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
|
198
194
|
if check_folder.is_ok() and finished_run:
|
199
195
|
return check_folder
|
200
196
|
elif check_folder.is_ok():
|
@@ -255,9 +251,14 @@ class RunController(ABCTableController, abc.ABC):
|
|
255
251
|
self.send(Command.GET_CURRENT_RUN_DATA_FILE)
|
256
252
|
current_sample_file = self.receive()
|
257
253
|
if full_path_name.is_ok() and current_sample_file.is_ok():
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
254
|
+
if os.path.isdir(full_path_name.ok_value.string_response) and os.path.isdir(
|
255
|
+
os.path.join(
|
256
|
+
full_path_name.ok_value.string_response,
|
257
|
+
current_sample_file.ok_value.string_response,
|
258
|
+
)
|
259
|
+
):
|
260
|
+
return (
|
261
|
+
full_path_name.ok_value.string_response,
|
262
|
+
current_sample_file.ok_value.string_response,
|
263
|
+
)
|
264
|
+
raise ValueError("Couldn't read data dir and file or doesn't exist yet.")
|
@@ -126,8 +126,8 @@ class ABCTableController(abc.ABC):
|
|
126
126
|
):
|
127
127
|
if not (isinstance(val, int) or isinstance(val, float)):
|
128
128
|
raise ValueError(f"{val} must be an int or float.")
|
129
|
+
num_rows = self.get_num_rows()
|
129
130
|
if row:
|
130
|
-
num_rows = self.get_num_rows()
|
131
131
|
if num_rows.is_ok():
|
132
132
|
if num_rows.ok_value.num_response < row:
|
133
133
|
raise ValueError("Not enough rows to edit!")
|
@@ -136,7 +136,7 @@ class ABCTableController(abc.ABC):
|
|
136
136
|
TableOperation.EDIT_ROW_VAL.value.format(
|
137
137
|
register=self.table_locator.register,
|
138
138
|
table_name=self.table_locator.name,
|
139
|
-
row=row if row is not None else "
|
139
|
+
row=row if row is not None else "response_num",
|
140
140
|
col_name=col_name,
|
141
141
|
val=val,
|
142
142
|
)
|
@@ -147,8 +147,8 @@ class ABCTableController(abc.ABC):
|
|
147
147
|
):
|
148
148
|
if not isinstance(val, str):
|
149
149
|
raise ValueError(f"{val} must be a str.")
|
150
|
+
num_rows = self.get_num_rows()
|
150
151
|
if row:
|
151
|
-
num_rows = self.get_num_rows()
|
152
152
|
if num_rows.is_ok():
|
153
153
|
if num_rows.ok_value.num_response < row:
|
154
154
|
raise ValueError("Not enough rows to edit!")
|
@@ -157,7 +157,7 @@ class ABCTableController(abc.ABC):
|
|
157
157
|
TableOperation.EDIT_ROW_TEXT.value.format(
|
158
158
|
register=self.table_locator.register,
|
159
159
|
table_name=self.table_locator.name,
|
160
|
-
row=row if row is not None else "
|
160
|
+
row=row if row is not None else "response_num",
|
161
161
|
col_name=col_name,
|
162
162
|
val=val,
|
163
163
|
)
|
@@ -178,11 +178,15 @@ class ABCTableController(abc.ABC):
|
|
178
178
|
|
179
179
|
def add_row(self):
|
180
180
|
"""Adds a row to the provided table for currently loaded method or sequence."""
|
181
|
+
previous_row_count = self.get_num_rows().ok_value.num_response
|
181
182
|
self.sleepy_send(
|
182
183
|
TableOperation.NEW_ROW.value.format(
|
183
184
|
register=self.table_locator.register, table_name=self.table_locator.name
|
184
185
|
)
|
185
186
|
)
|
187
|
+
new_row_count = self.get_num_rows().ok_value.num_response
|
188
|
+
if previous_row_count + 1 != new_row_count:
|
189
|
+
raise ValueError("Row could not be added.")
|
186
190
|
|
187
191
|
def delete_table(self):
|
188
192
|
"""Deletes the table."""
|
@@ -201,13 +205,6 @@ class ABCTableController(abc.ABC):
|
|
201
205
|
)
|
202
206
|
|
203
207
|
def get_num_rows(self) -> Result[Response, str]:
|
204
|
-
self.send(
|
205
|
-
TableOperation.GET_NUM_ROWS.value.format(
|
206
|
-
register=self.table_locator.register,
|
207
|
-
table_name=self.table_locator.name,
|
208
|
-
col_name=RegisterFlag.NUM_ROWS,
|
209
|
-
)
|
210
|
-
)
|
211
208
|
self.send(
|
212
209
|
Command.GET_ROWS_CMD.value.format(
|
213
210
|
register=self.table_locator.register,
|
@@ -221,8 +218,16 @@ class ABCTableController(abc.ABC):
|
|
221
218
|
raise ValueError("Controller is offline")
|
222
219
|
|
223
220
|
if res.is_ok():
|
224
|
-
self.send("Sleep 0.1")
|
225
|
-
self.send("Print Rows")
|
226
221
|
return res
|
227
222
|
else:
|
228
223
|
return Err("No rows could be read.")
|
224
|
+
|
225
|
+
def move_row(self, from_row: int, to_row: int):
|
226
|
+
self.send(
|
227
|
+
TableOperation.MOVE_ROW.value.format(
|
228
|
+
register=self.table_locator.register,
|
229
|
+
table_name=self.table_locator.name,
|
230
|
+
from_row=from_row,
|
231
|
+
to_row=to_row,
|
232
|
+
)
|
233
|
+
)
|
pychemstation/utils/macro.py
CHANGED
@@ -55,12 +55,15 @@ class Command(Enum):
|
|
55
55
|
# Get directories
|
56
56
|
GET_METHOD_DIR = "response$ = _METHPATH$"
|
57
57
|
GET_SEQUENCE_DIR = "response$ = _SEQUENCEPATHS$"
|
58
|
+
CONFIG_MET_PATH = "_CONFIGMETPATH"
|
59
|
+
CONFIG_SEQ_PATH = "_CONFIGSEQPATH"
|
60
|
+
GET_RUNNING_SEQUENCE_DIR = "response$ = _SEQPATHS$"
|
58
61
|
GET_DATA_DIRS = "response$ = _DATAPATHS$"
|
59
62
|
GET_CURRENT_RUN_DATA_DIR = "response$ = _DATAPath$"
|
60
63
|
GET_CURRENT_RUN_DATA_FILE = "response$ = _DATAFILE1$"
|
61
64
|
|
62
|
-
#
|
63
|
-
ERROR = "response$ =
|
65
|
+
# Errors
|
66
|
+
ERROR = "response$ = _ERRMSG$"
|
64
67
|
|
65
68
|
|
66
69
|
class HPLCRunningStatus(Enum):
|
@@ -1,5 +1,28 @@
|
|
1
|
-
from
|
1
|
+
from typing import Union
|
2
|
+
|
3
|
+
from result import Result
|
4
|
+
|
5
|
+
from .mock_hplc import MockHPLC
|
6
|
+
from ..abc_tables.abc_comm import ABCCommunicationController
|
7
|
+
from ..macro import Status
|
2
8
|
|
3
9
|
|
4
10
|
class MockCommunicationController(ABCCommunicationController):
|
5
|
-
|
11
|
+
def __init__(self, comm_dir: str):
|
12
|
+
super().__init__(comm_dir)
|
13
|
+
self.hplc = MockHPLC()
|
14
|
+
|
15
|
+
def get_num_val(self, cmd: str) -> Union[int, float]:
|
16
|
+
raise NotImplementedError
|
17
|
+
|
18
|
+
def get_text_val(self, cmd: str) -> str:
|
19
|
+
raise NotImplementedError
|
20
|
+
|
21
|
+
def get_status(self) -> Status:
|
22
|
+
raise NotImplementedError
|
23
|
+
|
24
|
+
def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
|
25
|
+
raise NotImplementedError
|
26
|
+
|
27
|
+
def _receive(self, cmd_no: int, num_attempts=100) -> Result[str, str]:
|
28
|
+
raise NotImplementedError
|
@@ -1,2 +1,30 @@
|
|
1
|
+
from ..macro import HPLCAvailStatus, Status
|
2
|
+
from ..method_types import MethodDetails, HPLCMethodParams, TimeTableEntry
|
3
|
+
from ..sequence_types import SequenceTable, SequenceEntry, InjectionSource
|
4
|
+
from ..tray_types import FiftyFourVialPlate
|
5
|
+
|
6
|
+
|
1
7
|
class MockHPLC:
|
2
|
-
|
8
|
+
def __init__(self):
|
9
|
+
self.current_method: MethodDetails = MethodDetails(
|
10
|
+
name="General-Poroshell",
|
11
|
+
params=HPLCMethodParams(organic_modifier=5, flow=0.65),
|
12
|
+
timetable=[TimeTableEntry(start_time=3, organic_modifer=99, flow=0.65)],
|
13
|
+
stop_time=5,
|
14
|
+
post_time=2,
|
15
|
+
)
|
16
|
+
self.current_sequence: SequenceTable = SequenceTable(
|
17
|
+
name="hplc_testing",
|
18
|
+
rows=[
|
19
|
+
SequenceEntry(
|
20
|
+
vial_location=FiftyFourVialPlate.from_str("P1-A2"),
|
21
|
+
sample_name="sample1",
|
22
|
+
data_file="sample1",
|
23
|
+
method="General-Poroshell",
|
24
|
+
num_inj=1,
|
25
|
+
inj_vol=1,
|
26
|
+
inj_source=InjectionSource.HIP_ALS,
|
27
|
+
)
|
28
|
+
],
|
29
|
+
)
|
30
|
+
self.current_status: Status = HPLCAvailStatus.STANDBY
|
@@ -13,13 +13,11 @@ class SequenceDataFiles:
|
|
13
13
|
|
14
14
|
:param sequence_name: the name of the sequence that is running
|
15
15
|
:param dir: the complete path of the directory generated for the sequence
|
16
|
-
:param _data_files: the names of the files of the sequence runs, ends with ".D"
|
17
16
|
:param child_dirs: the complete path of the files for sequence runs, contains the Chemstation data, `dir` and the sample run file.
|
18
17
|
"""
|
19
18
|
|
20
19
|
sequence_name: str
|
21
20
|
dir: str
|
22
|
-
_data_files: List[str] = field(default_factory=list)
|
23
21
|
child_dirs: List[str] = field(default_factory=list)
|
24
22
|
|
25
23
|
|
@@ -16,6 +16,8 @@ class TableOperation(Enum):
|
|
16
16
|
DELETE_TABLE = 'DelTab {register}, "{table_name}"'
|
17
17
|
CREATE_TABLE = 'NewTab {register}, "{table_name}"'
|
18
18
|
NEW_ROW = 'InsTabRow {register}, "{table_name}"'
|
19
|
+
NEW_ROW_SPECIFIC = 'InsTabRow {register}, "{table_name}"'
|
20
|
+
MOVE_ROW = 'MoveTabRow {register}, "{table_name}", {from_row}, {to_row}'
|
19
21
|
DELETE_ROW = 'DelTabRow {register}, "{table_name}", {row}'
|
20
22
|
EDIT_ROW_VAL = 'SetTabVal "{register}", "{table_name}", {row}, "{col_name}", {val}'
|
21
23
|
EDIT_ROW_TEXT = (
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pychemstation
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.9
|
4
4
|
Summary: Library to interact with Chemstation software, primarily used in Hein lab
|
5
5
|
Project-URL: Documentation, https://pychemstation-e5a086.gitlab.io/pychemstation.html
|
6
6
|
Project-URL: Repository, https://gitlab.com/heingroup/device-api/pychemstation
|
@@ -30,8 +30,7 @@ Description-Content-Type: text/markdown
|
|
30
30
|
[](https://pypi.org/project/pychemstation/)
|
31
31
|
|
32
32
|
> **_NOTE:_** If you are running Python **3.8**, use versions 0.**8**.x. If you are running Python **3.9** use versions 0.**9**.x.
|
33
|
-
> If you are running Python **>=3.10**, use version 0.**10**.x.
|
34
|
-
> is not guaranteed!
|
33
|
+
> If you are running Python **>=3.10**, use version 0.**10**.x. Older versions of pychemstation are not the most feature-rich and bug-free. Please consider upgrading to using Python 3.10!
|
35
34
|
|
36
35
|
Unofficial Python package to control Agilent Chemstation; we are not affiliated with Agilent.
|
37
36
|
Check out the [docs](https://pychemstation-e5a086.gitlab.io/pychemstation.html) for usage instructions. This project is under
|
@@ -73,21 +72,17 @@ HPLCTalk_Run
|
|
73
72
|
## Example Usage
|
74
73
|
|
75
74
|
```python
|
75
|
+
import time
|
76
76
|
from pychemstation.control import HPLCController
|
77
77
|
from pychemstation.utils.method_types import *
|
78
78
|
import pandas as pd
|
79
79
|
|
80
|
-
DEFAULT_METHOD_DIR = "C:\\ChemStation\\1\\Methods\\"
|
81
|
-
SEQUENCE_DIR = "C:\\USERS\\PUBLIC\\DOCUMENTS\\CHEMSTATION\\3\\Sequence"
|
82
80
|
DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
|
83
|
-
|
84
|
-
DATA_DIR_3 = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data"
|
81
|
+
CUSTOM_DATA_DIR = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data\\MyData"
|
85
82
|
|
86
83
|
# Initialize HPLC Controller
|
87
|
-
hplc_controller = HPLCController(extra_data_dirs=[
|
88
|
-
comm_dir=DEFAULT_COMMAND_PATH
|
89
|
-
method_dir=DEFAULT_METHOD_DIR,
|
90
|
-
sequence_dir=SEQUENCE_DIR)
|
84
|
+
hplc_controller = HPLCController(extra_data_dirs=[CUSTOM_DATA_DIR],
|
85
|
+
comm_dir=DEFAULT_COMMAND_PATH)
|
91
86
|
|
92
87
|
# Switching a method
|
93
88
|
hplc_controller.switch_method("General-Poroshell")
|
@@ -116,7 +111,17 @@ new_method = MethodDetails(
|
|
116
111
|
hplc_controller.edit_method(new_method)
|
117
112
|
|
118
113
|
# Run a method and get a report or data from last run method
|
119
|
-
hplc_controller.run_method(experiment_name="test_experiment")
|
114
|
+
hplc_controller.run_method(experiment_name="test_experiment", stall_while_running=False)
|
115
|
+
time_left, done = hplc_controller.check_method_complete()
|
116
|
+
while not done:
|
117
|
+
print(time_left)
|
118
|
+
time.sleep(time_left/2)
|
119
|
+
time_left, done = hplc_controller.check_method_complete()
|
120
|
+
|
121
|
+
# Save the path the HPLC data for later!
|
122
|
+
file_path = hplc_controller.get_last_run_method_file_path()
|
123
|
+
|
124
|
+
# Make sure CSV reports are being generated in the post-run MACRO!
|
120
125
|
report = hplc_controller.get_last_run_method_report()
|
121
126
|
vial_location = report.vial_location
|
122
127
|
|
@@ -2,40 +2,41 @@ pychemstation/__init__.py,sha256=Sc4z8LRVFMwJUoc_DPVUriSXTZ6PO9MaJ80PhRbKyB8,34
|
|
2
2
|
pychemstation/analysis/__init__.py,sha256=mPNnp0TmkoUxrTGcT6wNKMyCiOar5vC0cTPmFLrDU1Q,313
|
3
3
|
pychemstation/analysis/base_spectrum.py,sha256=HjbTPoueR7XB1_puCcmx7zbQmeT5SH6_HVwHrYhNXhA,16537
|
4
4
|
pychemstation/analysis/chromatogram.py,sha256=Jk6xOMHA6kSV597fJDZgrFRlvVorldvK4zerPASZrug,3969
|
5
|
-
pychemstation/analysis/process_report.py,sha256=
|
5
|
+
pychemstation/analysis/process_report.py,sha256=GEazFm1asY1FZO87siiV9kWu2Wnbu7xwx86nlPeRexk,14728
|
6
6
|
pychemstation/control/README.md,sha256=ohPn3xhgjFyPraQqR4x9aZhurQVAOYuYHv-E8sj0eK4,3124
|
7
7
|
pychemstation/control/__init__.py,sha256=7lSkY7Qa7Ikdz82-2FESc_oqktv7JndsjltCkiMqnMI,147
|
8
|
-
pychemstation/control/hplc.py,sha256=
|
8
|
+
pychemstation/control/hplc.py,sha256=Nw5rJAOsmW1W6faq4jRuMJlEoGda91kD9GqkRX2jcgk,14210
|
9
9
|
pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
|
10
10
|
pychemstation/control/controllers/__init__.py,sha256=q2TUEie3J-OLlxcGLkG7vIy8fazCEhHm61OGzJPbhD0,179
|
11
11
|
pychemstation/control/controllers/comm.py,sha256=ySjgMBIfJ11sygXiZSPp9Rf6ABM4t6JhZRONRj1u2Cc,6652
|
12
12
|
pychemstation/control/controllers/data_aq/__init__.py,sha256=w-Zgbit10niOQfz780ZmRHjUFxV1hMkdui7fOMPqeLA,132
|
13
|
-
pychemstation/control/controllers/data_aq/method.py,sha256=
|
14
|
-
pychemstation/control/controllers/data_aq/sequence.py,sha256=
|
13
|
+
pychemstation/control/controllers/data_aq/method.py,sha256=sWtMjmBV-oY8dCxQbnksKvTVUtiH7rcq9cF1t-yoAKY,24787
|
14
|
+
pychemstation/control/controllers/data_aq/sequence.py,sha256=5JpXDWpS3JR2Cawn9RdUwOIYSipQPeB3OR7KW6muO6U,18623
|
15
15
|
pychemstation/control/controllers/devices/__init__.py,sha256=QpgGnLXyWiB96KIB98wMccEi8oOUUaLxvBCyevJzcOg,75
|
16
16
|
pychemstation/control/controllers/devices/injector.py,sha256=LyubM-fqf5ruseGx32deTDK-yevmaTOvdo6YKg2PF7I,4029
|
17
17
|
pychemstation/generated/__init__.py,sha256=xnEs0QTjeuGYO3tVUIy8GDo95GqTV1peEjosGckpOu0,977
|
18
18
|
pychemstation/generated/dad_method.py,sha256=xTUiSCvkXcxBUhjVm1YZKu-tHs16k23pF-0xYrQSwWA,8408
|
19
19
|
pychemstation/generated/pump_method.py,sha256=s3MckKDw2-nZUC5lHrJVvXYdneWP8-9UvblNuGryPHY,12092
|
20
20
|
pychemstation/utils/__init__.py,sha256=GZJyDzkhzrlMguxZTUpgthq72pA3YV23DJIR2Q63PCk,449
|
21
|
+
pychemstation/utils/device_types.py,sha256=X8LFRdZpOK3bmdLgKKpsi8N9T27bI0Tg6_qEG9QJEBs,98
|
21
22
|
pychemstation/utils/injector_types.py,sha256=z2iWwTklGm0GRDCL9pnPCovQrwyRwxv8w5w5Xh7Pj3U,1152
|
22
|
-
pychemstation/utils/macro.py,sha256=
|
23
|
-
pychemstation/utils/method_types.py,sha256=
|
23
|
+
pychemstation/utils/macro.py,sha256=VGOU380ruJSKQhXJFI1g--qg4Xgx-e0D8z5FDPGe0cA,3433
|
24
|
+
pychemstation/utils/method_types.py,sha256=ck8I4dRGhHXCUfBf3AT1OU1eCcSSZPgnlhvlLTfezEM,1562
|
24
25
|
pychemstation/utils/num_utils.py,sha256=OpqZwMPoxTYkpjpinA1CcoQAXDY_0sie6d-hTU547uo,2087
|
25
26
|
pychemstation/utils/parsing.py,sha256=mzdpxrH5ux4-_i4CwZvnIYnIwAnRnOptKb3fZyYJcx0,9307
|
26
|
-
pychemstation/utils/sequence_types.py,sha256=
|
27
|
+
pychemstation/utils/sequence_types.py,sha256=H9htO2thyU9_KYOFBsGjlmA-4Nyd6aLd4D0MDLaXNCQ,2583
|
27
28
|
pychemstation/utils/spec_utils.py,sha256=BtXgQndZy4kVKsrgEDxwNd0HctypnAt5upB9SOk1D9w,9700
|
28
|
-
pychemstation/utils/table_types.py,sha256=
|
29
|
+
pychemstation/utils/table_types.py,sha256=ZmV52Vl1cYG_C1PEmVGB02mC3Dhfi7QMkUZwe0ujzAg,3748
|
29
30
|
pychemstation/utils/tray_types.py,sha256=UeHM0hUYaNc9giT96ZiGpyWBPQwG-SyLA0rVGzDDAJk,6618
|
30
31
|
pychemstation/utils/abc_tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
-
pychemstation/utils/abc_tables/abc_comm.py,sha256=
|
32
|
+
pychemstation/utils/abc_tables/abc_comm.py,sha256=7ywcufQZ7UWfNQdEEsfHqkA2CpRkt6ZlKYuLt9MXjvs,5656
|
32
33
|
pychemstation/utils/abc_tables/device.py,sha256=v8MNFvymbKe4hWsqzO6Z_qsVvju_rISYtnPgaftecyE,919
|
33
|
-
pychemstation/utils/abc_tables/run.py,sha256=
|
34
|
-
pychemstation/utils/abc_tables/table.py,sha256=
|
34
|
+
pychemstation/utils/abc_tables/run.py,sha256=uURDl66Mga8NAMffOUsG6prKhjpLo1-p2Y2PUTY1hY0,10052
|
35
|
+
pychemstation/utils/abc_tables/table.py,sha256=AbuitDcBHODV-KpxjL7ZPoE-TSa6gyG2uqXXY90Mv2o,7992
|
35
36
|
pychemstation/utils/mocking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
|
-
pychemstation/utils/mocking/mock_comm.py,sha256=
|
37
|
-
pychemstation/utils/mocking/mock_hplc.py,sha256=
|
38
|
-
pychemstation-0.10.
|
39
|
-
pychemstation-0.10.
|
40
|
-
pychemstation-0.10.
|
41
|
-
pychemstation-0.10.
|
37
|
+
pychemstation/utils/mocking/mock_comm.py,sha256=vZeYBXaKBZOlJmhn4TSbkov62gqlkfztqf3MSFU9kLE,800
|
38
|
+
pychemstation/utils/mocking/mock_hplc.py,sha256=esVIlU4oqEsYLPOQs0AeVnKv9l52xBGT6UY862l9RQE,1163
|
39
|
+
pychemstation-0.10.9.dist-info/METADATA,sha256=MecxkFDPopIv07Hrp-6togcH6zo6AbBswERVRw2sbvU,5848
|
40
|
+
pychemstation-0.10.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
41
|
+
pychemstation-0.10.9.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
|
42
|
+
pychemstation-0.10.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|