pychemstation 0.7.0.dev2__py3-none-any.whl → 0.8.0__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 +3 -6
- pychemstation/analysis/process_report.py +248 -225
- pychemstation/analysis/utils.py +3 -1
- pychemstation/control/README.md +124 -0
- pychemstation/control/controllers/README.md +1 -0
- pychemstation/control/controllers/__init__.py +0 -2
- pychemstation/control/controllers/comm.py +27 -20
- pychemstation/control/controllers/devices/device.py +17 -4
- pychemstation/control/controllers/tables/method.py +57 -39
- pychemstation/control/controllers/tables/sequence.py +98 -28
- pychemstation/control/controllers/tables/table.py +121 -126
- pychemstation/control/hplc.py +82 -37
- pychemstation/generated/dad_method.py +3 -3
- pychemstation/generated/pump_method.py +7 -7
- pychemstation/out.txt +145 -0
- pychemstation/tests.ipynb +310 -0
- pychemstation/utils/chromatogram.py +5 -1
- pychemstation/utils/injector_types.py +2 -2
- pychemstation/utils/macro.py +1 -1
- pychemstation/utils/table_types.py +3 -0
- pychemstation/utils/tray_types.py +59 -39
- {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.0.dist-info}/METADATA +25 -21
- pychemstation-0.8.0.dist-info/RECORD +39 -0
- {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.0.dist-info}/WHEEL +1 -2
- pychemstation/control/comm.py +0 -206
- pychemstation/control/controllers/devices/column.py +0 -12
- pychemstation/control/controllers/devices/dad.py +0 -0
- pychemstation/control/controllers/devices/pump.py +0 -43
- pychemstation/control/controllers/method.py +0 -338
- pychemstation/control/controllers/sequence.py +0 -190
- pychemstation/control/controllers/table_controller.py +0 -266
- pychemstation/control/table/__init__.py +0 -3
- pychemstation/control/table/method.py +0 -274
- pychemstation/control/table/sequence.py +0 -210
- pychemstation/control/table/table_controller.py +0 -201
- pychemstation-0.7.0.dev2.dist-info/RECORD +0 -58
- pychemstation-0.7.0.dev2.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/constants.py +0 -88
- tests/test_comb.py +0 -136
- tests/test_comm.py +0 -65
- tests/test_inj.py +0 -39
- tests/test_method.py +0 -99
- tests/test_nightly.py +0 -80
- tests/test_proc_rep.py +0 -52
- tests/test_runs_stable.py +0 -125
- tests/test_sequence.py +0 -125
- tests/test_stable.py +0 -276
- {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.0.dist-info/licenses}/LICENSE +0 -0
@@ -9,7 +9,6 @@ been processed.
|
|
9
9
|
|
10
10
|
Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
|
11
11
|
"""
|
12
|
-
import logging
|
13
12
|
import os
|
14
13
|
import time
|
15
14
|
from typing import Optional
|
@@ -32,12 +31,15 @@ class CommunicationController:
|
|
32
31
|
comm_dir: str,
|
33
32
|
cmd_file: str = "cmd",
|
34
33
|
reply_file: str = "reply",
|
34
|
+
debug: bool = False
|
35
35
|
):
|
36
36
|
"""
|
37
37
|
:param comm_dir:
|
38
38
|
:param cmd_file: Name of command file
|
39
39
|
:param reply_file: Name of reply file
|
40
|
+
:param debug: whether to save log of sent commands
|
40
41
|
"""
|
42
|
+
self.debug = debug
|
41
43
|
if os.path.isdir(comm_dir):
|
42
44
|
self.cmd_file = os.path.join(comm_dir, cmd_file)
|
43
45
|
self.reply_file = os.path.join(comm_dir, reply_file)
|
@@ -52,21 +54,26 @@ class CommunicationController:
|
|
52
54
|
|
53
55
|
self.reset_cmd_counter()
|
54
56
|
|
55
|
-
|
56
|
-
self.send(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
# Initialize row counter for table operations
|
58
|
+
self.send('Local Rows')
|
59
|
+
|
60
|
+
def get_num_val(self, cmd: str) -> Union[int, float]:
|
61
|
+
tries = 5
|
62
|
+
for _ in range(tries):
|
63
|
+
self.send(Command.GET_NUM_VAL_CMD.value.format(cmd=cmd))
|
64
|
+
res = self.receive()
|
65
|
+
if res.is_ok():
|
66
|
+
return res.ok_value.num_response
|
67
|
+
raise RuntimeError("Failed to get number.")
|
62
68
|
|
63
69
|
def get_text_val(self, cmd: str) -> str:
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
tries = 5
|
71
|
+
for _ in range(tries):
|
72
|
+
self.send(Command.GET_TEXT_VAL_CMD.value.format(cmd=cmd))
|
73
|
+
res = self.receive()
|
74
|
+
if res.is_ok():
|
75
|
+
return res.ok_value.string_response
|
76
|
+
raise RuntimeError("Failed to get string")
|
70
77
|
|
71
78
|
def get_status(self) -> Status:
|
72
79
|
"""Get device status(es).
|
@@ -89,16 +96,15 @@ class CommunicationController:
|
|
89
96
|
"""Updates current status of HPLC machine"""
|
90
97
|
self._most_recent_hplc_status = self.get_status()
|
91
98
|
|
92
|
-
def
|
99
|
+
def check_if_not_running(self) -> bool:
|
93
100
|
"""Checks if HPLC machine is in an available state, meaning a state that data is not being written.
|
94
101
|
|
95
102
|
:return: whether the HPLC machine is in a safe state to retrieve data back."""
|
96
103
|
self.set_status()
|
97
104
|
hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
98
|
-
time.sleep(
|
105
|
+
time.sleep(5)
|
99
106
|
self.set_status()
|
100
107
|
hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
101
|
-
logging.info("Still running")
|
102
108
|
return hplc_avail and hplc_actually_avail
|
103
109
|
|
104
110
|
def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
|
@@ -173,9 +179,10 @@ class CommunicationController:
|
|
173
179
|
cmd_to_send: str = cmd.value if isinstance(cmd, Command) else cmd
|
174
180
|
self.cmd_no += 1
|
175
181
|
self._send(cmd_to_send, self.cmd_no)
|
176
|
-
|
177
|
-
|
178
|
-
|
182
|
+
if self.debug:
|
183
|
+
f = open("out.txt", "a")
|
184
|
+
f.write(cmd_to_send + "\n")
|
185
|
+
f.close()
|
179
186
|
|
180
187
|
def receive(self) -> Result[Response, str]:
|
181
188
|
"""Returns messages received in reply file.
|
@@ -1,16 +1,23 @@
|
|
1
1
|
import abc
|
2
|
-
from typing import Union
|
2
|
+
from typing import Union, List
|
3
3
|
|
4
|
+
from result import Result
|
5
|
+
|
6
|
+
from ....analysis.process_report import ReportType, AgilentReport
|
4
7
|
from ....control.controllers import CommunicationController
|
5
8
|
from ....control.controllers.tables.table import TableController
|
6
9
|
from ....utils.chromatogram import AgilentChannelChromatogramData
|
7
|
-
from ....utils.table_types import Table
|
10
|
+
from ....utils.table_types import Table, T
|
8
11
|
|
9
12
|
|
10
13
|
class DeviceController(TableController, abc.ABC):
|
11
14
|
|
12
15
|
def __init__(self, controller: CommunicationController, table: Table, offline: bool):
|
13
|
-
super().__init__(controller,
|
16
|
+
super().__init__(controller=controller,
|
17
|
+
src=None,
|
18
|
+
data_dirs=[],
|
19
|
+
table=table,
|
20
|
+
offline=offline)
|
14
21
|
|
15
22
|
@abc.abstractmethod
|
16
23
|
def get_row(self, row: int):
|
@@ -19,5 +26,11 @@ class DeviceController(TableController, abc.ABC):
|
|
19
26
|
def retrieve_recent_data_files(self):
|
20
27
|
raise NotImplementedError
|
21
28
|
|
22
|
-
def get_data(self) -> Union[
|
29
|
+
def get_data(self) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
30
|
+
raise NotImplementedError
|
31
|
+
|
32
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
33
|
+
raise NotImplementedError
|
34
|
+
|
35
|
+
def get_report(self, report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
23
36
|
raise NotImplementedError
|
@@ -2,13 +2,15 @@ import os
|
|
2
2
|
import time
|
3
3
|
from typing import List
|
4
4
|
|
5
|
+
from result import Result, Ok, Err
|
5
6
|
from xsdata.formats.dataclass.parsers import XmlParser
|
6
7
|
|
7
8
|
from .table import TableController
|
8
9
|
from ..devices.injector import InjectorController
|
10
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
9
11
|
from ....control.controllers import CommunicationController
|
10
12
|
from ....generated import PumpMethod, DadMethod, SolventElement
|
11
|
-
from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
|
13
|
+
from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData, AgilentHPLCChromatogram
|
12
14
|
from ....utils.macro import *
|
13
15
|
from ....utils.method_types import *
|
14
16
|
from ....utils.table_types import *
|
@@ -26,7 +28,8 @@ class MethodController(TableController):
|
|
26
28
|
offline: bool,
|
27
29
|
injector_controller: InjectorController):
|
28
30
|
self.injector_controller = injector_controller
|
29
|
-
|
31
|
+
self.data_files: List[str] = []
|
32
|
+
super().__init__(controller=controller, src=src, data_dirs=data_dirs, table=table, offline=offline)
|
30
33
|
|
31
34
|
def check(self) -> str:
|
32
35
|
time.sleep(2)
|
@@ -40,13 +43,13 @@ class MethodController(TableController):
|
|
40
43
|
def get_method_params(self) -> HPLCMethodParams:
|
41
44
|
return HPLCMethodParams(organic_modifier=self.controller.get_num_val(
|
42
45
|
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
43
|
-
register=self.
|
46
|
+
register=self.table_locator.register,
|
44
47
|
register_flag=RegisterFlag.SOLVENT_B_COMPOSITION
|
45
48
|
)
|
46
49
|
),
|
47
50
|
flow=self.controller.get_num_val(
|
48
51
|
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
49
|
-
register=self.
|
52
|
+
register=self.table_locator.register,
|
50
53
|
register_flag=RegisterFlag.FLOW
|
51
54
|
)
|
52
55
|
),
|
@@ -90,19 +93,11 @@ class MethodController(TableController):
|
|
90
93
|
def load(self) -> MethodDetails:
|
91
94
|
rows = self.get_num_rows()
|
92
95
|
if rows.is_ok():
|
93
|
-
self.
|
94
|
-
res = self.receive()
|
95
|
-
method_name = res.ok_value.string_response
|
96
|
+
method_name = self.get_method_name()
|
96
97
|
timetable_rows = self.get_timetable(int(rows.ok_value.num_response))
|
97
98
|
params = self.get_method_params()
|
98
|
-
stop_time = self.
|
99
|
-
|
100
|
-
register=self.table.register,
|
101
|
-
register_flag=RegisterFlag.MAX_TIME))
|
102
|
-
post_time = self.controller.get_num_val(
|
103
|
-
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
104
|
-
register=self.table.register,
|
105
|
-
register_flag=RegisterFlag.POST_TIME))
|
99
|
+
stop_time = self.get_stop_time()
|
100
|
+
post_time = self.get_post_time()
|
106
101
|
self.table_state = MethodDetails(name=method_name,
|
107
102
|
timetable=timetable_rows,
|
108
103
|
stop_time=stop_time,
|
@@ -112,6 +107,24 @@ class MethodController(TableController):
|
|
112
107
|
else:
|
113
108
|
raise RuntimeError(rows.err_value)
|
114
109
|
|
110
|
+
def get_method_name(self):
|
111
|
+
self.send(Command.GET_METHOD_CMD)
|
112
|
+
res = self.receive()
|
113
|
+
method_name = res.ok_value.string_response
|
114
|
+
return method_name
|
115
|
+
|
116
|
+
def get_post_time(self) -> Union[int, float]:
|
117
|
+
return self.controller.get_num_val(
|
118
|
+
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
119
|
+
register=self.table_locator.register,
|
120
|
+
register_flag=RegisterFlag.POST_TIME))
|
121
|
+
|
122
|
+
def get_stop_time(self) -> Union[int, float]:
|
123
|
+
return self.controller.get_num_val(
|
124
|
+
cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
|
125
|
+
register=self.table_locator.register,
|
126
|
+
register_flag=RegisterFlag.MAX_TIME))
|
127
|
+
|
115
128
|
def current_method(self, method_name: str):
|
116
129
|
"""
|
117
130
|
Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
|
@@ -123,18 +136,19 @@ class MethodController(TableController):
|
|
123
136
|
parsed_response = self.receive()
|
124
137
|
return method_name in parsed_response
|
125
138
|
|
126
|
-
def switch(self, method_name: str):
|
139
|
+
def switch(self, method_name: str, alt_method_dir: Optional[str] = None):
|
127
140
|
"""
|
128
141
|
Allows the user to switch between pre-programmed methods. No need to append '.M'
|
129
142
|
to the end of the method name. For example. for the method named 'General-Poroshell.M',
|
130
143
|
only 'General-Poroshell' is needed.
|
131
144
|
|
132
145
|
:param method_name: any available method in Chemstation method directory
|
146
|
+
:param alt_method_dir: directory where the method resides
|
133
147
|
:raise IndexError: Response did not have expected format. Try again.
|
134
148
|
:raise AssertionError: The desired method is not selected. Try again.
|
135
149
|
"""
|
136
|
-
|
137
|
-
|
150
|
+
method_dir = self.src if not alt_method_dir else alt_method_dir
|
151
|
+
self.send(Command.SWITCH_METHOD_CMD_SPECIFIC.value.format(method_dir=method_dir, method_name=method_name))
|
138
152
|
|
139
153
|
time.sleep(2)
|
140
154
|
self.send(Command.GET_METHOD_CMD)
|
@@ -239,7 +253,7 @@ class MethodController(TableController):
|
|
239
253
|
|
240
254
|
:param method_param: a parameter to update for currently loaded method.
|
241
255
|
"""
|
242
|
-
register = self.
|
256
|
+
register = self.table_locator.register
|
243
257
|
setting_command = TableOperation.UPDATE_OBJ_HDR_VAL if method_param.ptype == PType.NUM else TableOperation.UPDATE_OBJ_HDR_TEXT
|
244
258
|
if isinstance(method_param.chemstation_key, list):
|
245
259
|
for register_flag in method_param.chemstation_key:
|
@@ -287,9 +301,8 @@ class MethodController(TableController):
|
|
287
301
|
self._edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
|
288
302
|
self.download()
|
289
303
|
|
290
|
-
def _update_method_timetable(self, timetable_rows:
|
304
|
+
def _update_method_timetable(self, timetable_rows: List[TimeTableEntry]):
|
291
305
|
self.get_num_rows()
|
292
|
-
|
293
306
|
self.delete_table()
|
294
307
|
res = self.get_num_rows()
|
295
308
|
while not res.is_err():
|
@@ -304,19 +317,15 @@ class MethodController(TableController):
|
|
304
317
|
|
305
318
|
def stop(self):
|
306
319
|
"""
|
307
|
-
Stops the method run. A dialog window will pop up and manual intervention may be required
|
320
|
+
Stops the method run. A dialog window will pop up and manual intervention may be required.
|
308
321
|
"""
|
309
322
|
self.send(Command.STOP_METHOD_CMD)
|
310
323
|
|
311
|
-
def run(self, experiment_name: str, stall_while_running: bool = True):
|
324
|
+
def run(self, experiment_name: str, add_timestamp: bool = True, stall_while_running: bool = True):
|
312
325
|
"""
|
313
|
-
This is the preferred method to trigger a run.
|
314
|
-
Starts the currently selected method, storing data
|
315
|
-
under the <data_dir>/<experiment_name>.D folder.
|
316
|
-
The <experiment_name> will be appended with a timestamp in the '%Y-%m-%d-%H-%M' format.
|
317
|
-
Device must be ready.
|
318
|
-
|
319
326
|
:param experiment_name: Name of the experiment
|
327
|
+
:param stall_while_running: whether to stall or immediately return
|
328
|
+
:param add_timestamp: if should add timestamp to experiment name
|
320
329
|
"""
|
321
330
|
|
322
331
|
folder_name = ""
|
@@ -325,9 +334,8 @@ class MethodController(TableController):
|
|
325
334
|
while tries < 10 and not hplc_is_running:
|
326
335
|
timestamp = time.strftime(TIME_FORMAT)
|
327
336
|
self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dirs[0],
|
328
|
-
experiment_name=experiment_name
|
329
|
-
|
330
|
-
folder_name = f"{experiment_name}_{timestamp}.D"
|
337
|
+
experiment_name=f"{experiment_name}_{timestamp}" if add_timestamp else experiment_name))
|
338
|
+
folder_name = f"{experiment_name}_{timestamp}.D" if add_timestamp else f"{experiment_name}.D"
|
331
339
|
hplc_is_running = self.check_hplc_is_running()
|
332
340
|
tries += 1
|
333
341
|
|
@@ -337,11 +345,8 @@ class MethodController(TableController):
|
|
337
345
|
self.data_files.append(os.path.join(self.data_dirs[0], folder_name))
|
338
346
|
|
339
347
|
if stall_while_running:
|
340
|
-
|
341
|
-
|
342
|
-
self.table_state = self.load()
|
343
|
-
|
344
|
-
run_completed = self.check_hplc_done_running(method=self.table_state)
|
348
|
+
self.timeout = (self.get_stop_time() + self.get_post_time()) * 60
|
349
|
+
run_completed = self.check_hplc_done_running()
|
345
350
|
if run_completed.is_ok():
|
346
351
|
self.data_files[-1] = run_completed.ok_value
|
347
352
|
else:
|
@@ -349,8 +354,10 @@ class MethodController(TableController):
|
|
349
354
|
else:
|
350
355
|
self.data_files[-1] = self.fuzzy_match_most_recent_folder(folder_name).ok_value
|
351
356
|
|
352
|
-
def
|
353
|
-
|
357
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
358
|
+
if os.path.exists(most_recent_folder):
|
359
|
+
return Ok(most_recent_folder)
|
360
|
+
return Err("Folder not found!")
|
354
361
|
|
355
362
|
def get_data(self, custom_path: Optional[str] = None,
|
356
363
|
read_uv: bool = False) -> AgilentChannelChromatogramData:
|
@@ -359,3 +366,14 @@ class MethodController(TableController):
|
|
359
366
|
else:
|
360
367
|
self.get_spectrum(custom_path, read_uv)
|
361
368
|
return AgilentChannelChromatogramData(**self.spectra) if not read_uv else self.uv
|
369
|
+
|
370
|
+
def get_report(self, custom_path: Optional[str] = None,
|
371
|
+
report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
372
|
+
metd_report = self.get_report_details(self.data_files[-1] if not custom_path else custom_path,
|
373
|
+
report_type)
|
374
|
+
chrom_data: List[AgilentHPLCChromatogram] = list(self.get_data(custom_path).__dict__.values())
|
375
|
+
for i, signal in enumerate(metd_report.signals):
|
376
|
+
possible_data = chrom_data[i]
|
377
|
+
if len(possible_data.x) > 0:
|
378
|
+
signal.data = possible_data
|
379
|
+
return [metd_report]
|
@@ -1,10 +1,15 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
from typing import Optional,
|
3
|
+
from typing import Optional, List, Dict
|
4
4
|
|
5
|
-
from
|
5
|
+
from result import Result, Ok, Err
|
6
|
+
from typing_extensions import override
|
7
|
+
|
8
|
+
from .table import TableController
|
9
|
+
from .. import MethodController
|
10
|
+
from ....analysis.process_report import ReportType, AgilentReport
|
6
11
|
from ....control.controllers.comm import CommunicationController
|
7
|
-
from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData
|
12
|
+
from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData, AgilentHPLCChromatogram
|
8
13
|
from ....utils.macro import Command
|
9
14
|
from ....utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
|
10
15
|
from ....utils.table_types import RegisterFlag, Table
|
@@ -17,13 +22,14 @@ class SequenceController(TableController):
|
|
17
22
|
"""
|
18
23
|
|
19
24
|
def __init__(self, controller: CommunicationController,
|
25
|
+
method_controller: MethodController,
|
20
26
|
src: str,
|
21
27
|
data_dirs: List[str],
|
22
28
|
table: Table,
|
23
|
-
method_dir: str,
|
24
29
|
offline: bool):
|
25
|
-
self.
|
26
|
-
|
30
|
+
self.method_controller = method_controller
|
31
|
+
self.data_files: List[SequenceDataFiles] = []
|
32
|
+
super().__init__(controller=controller, src=src, data_dirs=data_dirs, table=table, offline=offline)
|
27
33
|
|
28
34
|
def load(self) -> SequenceTable:
|
29
35
|
rows = self.get_num_rows()
|
@@ -42,7 +48,7 @@ class SequenceController(TableController):
|
|
42
48
|
vial_location = int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
|
43
49
|
method = self.get_text(row, RegisterFlag.METHOD)
|
44
50
|
num_inj = int(self.get_num(row, RegisterFlag.NUM_INJ))
|
45
|
-
inj_vol =
|
51
|
+
inj_vol = float(self.get_text(row, RegisterFlag.INJ_VOL))
|
46
52
|
inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
|
47
53
|
sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
|
48
54
|
vial_enum = TenVialColumn(vial_location) if vial_location <= 10 else FiftyFourVialPlate.from_int(
|
@@ -131,10 +137,11 @@ class SequenceController(TableController):
|
|
131
137
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
|
132
138
|
|
133
139
|
if row.method:
|
134
|
-
|
140
|
+
method_dir = self.method_controller.src
|
141
|
+
possible_path = os.path.join(method_dir, row.method) + ".M\\"
|
135
142
|
method = row.method
|
136
143
|
if os.path.exists(possible_path):
|
137
|
-
method = os.path.join(
|
144
|
+
method = os.path.join(method_dir, row.method)
|
138
145
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
|
139
146
|
|
140
147
|
if row.num_inj:
|
@@ -165,11 +172,27 @@ class SequenceController(TableController):
|
|
165
172
|
Device must be ready.
|
166
173
|
"""
|
167
174
|
self.controller.send(Command.SAVE_METHOD_CMD)
|
175
|
+
self.controller.send(Command.SAVE_SEQUENCE_CMD)
|
176
|
+
|
168
177
|
if not self.table_state:
|
169
178
|
self.table_state = self.load()
|
170
179
|
|
180
|
+
total_runtime = 0
|
181
|
+
for entry in self.table_state.rows:
|
182
|
+
curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
|
183
|
+
loaded_method = self.method_controller.get_method_name()[:-2]
|
184
|
+
method_path = entry.method.split(sep="\\")
|
185
|
+
method_name = method_path[-1]
|
186
|
+
if loaded_method != method_name:
|
187
|
+
method_dir = os.path.join(*method_path[:-1]) if len(method_path) > 1 else None
|
188
|
+
self.method_controller.switch(method_name=method_name,
|
189
|
+
alt_method_dir=method_dir)
|
190
|
+
curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
|
191
|
+
total_runtime += curr_method_runtime
|
192
|
+
|
171
193
|
timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
|
172
194
|
self.send(Command.RUN_SEQUENCE_CMD.value)
|
195
|
+
self.timeout = total_runtime * 60
|
173
196
|
|
174
197
|
if self.check_hplc_is_running():
|
175
198
|
folder_name = f"{self.table_state.name} {timestamp}"
|
@@ -177,35 +200,82 @@ class SequenceController(TableController):
|
|
177
200
|
sequence_name=self.table_state.name))
|
178
201
|
|
179
202
|
if stall_while_running:
|
180
|
-
run_completed = self.check_hplc_done_running(
|
203
|
+
run_completed = self.check_hplc_done_running()
|
181
204
|
if run_completed.is_ok():
|
182
|
-
self.data_files[-1].dir = run_completed.
|
205
|
+
self.data_files[-1].dir = run_completed.ok_value
|
183
206
|
else:
|
184
207
|
raise RuntimeError("Run error has occurred.")
|
185
208
|
else:
|
186
|
-
self.data_files[-1]
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
209
|
+
self.data_files[-1] = SequenceDataFiles(dir=folder_name,
|
210
|
+
child_dirs=[],
|
211
|
+
sequence_name=self.table_state.name)
|
212
|
+
|
213
|
+
@override
|
214
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder: SequenceDataFiles) -> Result[SequenceDataFiles, str]:
|
215
|
+
if os.path.isdir(most_recent_folder.dir):
|
216
|
+
subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
|
217
|
+
potential_folders = sorted(list(filter(lambda d: most_recent_folder.dir in d, subdirs)))
|
218
|
+
most_recent_folder.child_dirs = [f for f in potential_folders if
|
219
|
+
most_recent_folder.dir in f and ".M" not in f and ".D" in f]
|
220
|
+
return Ok(most_recent_folder)
|
221
|
+
|
222
|
+
try:
|
223
|
+
potential_folders = []
|
224
|
+
for d in self.data_dirs:
|
225
|
+
subdirs = [x[0] for x in os.walk(d)]
|
226
|
+
potential_folders = sorted(list(filter(lambda d: most_recent_folder.dir in d, subdirs)))
|
227
|
+
if len(potential_folders) > 0:
|
228
|
+
break
|
229
|
+
assert len(potential_folders) > 0
|
230
|
+
parent_dirs = []
|
231
|
+
for folder in potential_folders:
|
232
|
+
path = os.path.normpath(folder)
|
233
|
+
split_folder = path.split(os.sep)
|
234
|
+
if most_recent_folder.dir in split_folder[-1]:
|
235
|
+
parent_dirs.append(folder)
|
236
|
+
parent_dir = sorted(parent_dirs, reverse=True)[0]
|
237
|
+
|
238
|
+
potential_folders = []
|
239
|
+
for d in self.data_dirs:
|
198
240
|
subdirs = [x[0] for x in os.walk(d)]
|
199
241
|
potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
|
200
242
|
if len(potential_folders) > 0:
|
201
243
|
break
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
244
|
+
assert len(potential_folders) > 0
|
245
|
+
most_recent_folder.child_dirs = [f for f in potential_folders if
|
246
|
+
parent_dir in f and ".M" not in f and ".D" in f]
|
247
|
+
return Ok(most_recent_folder)
|
248
|
+
except Exception:
|
249
|
+
return Err("Failed to get sequence folder")
|
250
|
+
|
251
|
+
def get_data(self, custom_path: Optional[str] = None,
|
252
|
+
read_uv: bool = False) -> List[AgilentChannelChromatogramData]:
|
253
|
+
if len(self.data_files[-1].child_dirs) == 0:
|
254
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(self.data_files[-1]).ok_value
|
255
|
+
spectra: list[AgilentChannelChromatogramData] = []
|
256
|
+
all_w_spectra: list[Dict[str, AgilentHPLCChromatogram]] = []
|
206
257
|
for row in self.data_files[-1].child_dirs:
|
207
258
|
self.get_spectrum(row, read_uv)
|
208
259
|
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
209
260
|
all_w_spectra.append(self.uv)
|
210
|
-
|
211
261
|
return spectra if not read_uv else all_w_spectra
|
262
|
+
|
263
|
+
def get_report(self, custom_path: Optional[str] = None,
|
264
|
+
report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
265
|
+
if custom_path:
|
266
|
+
self.data_files.append(
|
267
|
+
self.fuzzy_match_most_recent_folder(most_recent_folder=SequenceDataFiles(dir=custom_path,
|
268
|
+
child_dirs=[],
|
269
|
+
sequence_name="NA")).ok_value)
|
270
|
+
parent_dir = self.data_files[-1]
|
271
|
+
spectra = self.get_data(custom_path=custom_path)
|
272
|
+
reports = []
|
273
|
+
for i, child_dir in enumerate(parent_dir.child_dirs):
|
274
|
+
metd_report = self.get_report_details(child_dir, report_type)
|
275
|
+
child_spectra: List[AgilentHPLCChromatogram] = list(spectra[i].__dict__.values())
|
276
|
+
for j, signal in enumerate(metd_report.signals):
|
277
|
+
possible_data = child_spectra[j]
|
278
|
+
if len(possible_data.x) > 0:
|
279
|
+
signal.data = possible_data
|
280
|
+
reports.append(metd_report)
|
281
|
+
return reports
|