pychemstation 0.8.3__py3-none-any.whl → 0.10.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/__init__.py +4 -0
- pychemstation/analysis/base_spectrum.py +9 -9
- pychemstation/analysis/process_report.py +13 -7
- pychemstation/analysis/utils.py +1 -3
- pychemstation/control/__init__.py +4 -0
- pychemstation/control/comm.py +206 -0
- pychemstation/control/controllers/__init__.py +6 -0
- pychemstation/control/controllers/comm.py +12 -5
- pychemstation/control/controllers/devices/column.py +12 -0
- pychemstation/control/controllers/devices/dad.py +0 -0
- pychemstation/control/controllers/devices/device.py +10 -7
- pychemstation/control/controllers/devices/injector.py +18 -84
- pychemstation/control/controllers/devices/pump.py +43 -0
- pychemstation/control/controllers/method.py +338 -0
- pychemstation/control/controllers/sequence.py +190 -0
- pychemstation/control/controllers/table_controller.py +266 -0
- pychemstation/control/controllers/tables/method.py +35 -13
- pychemstation/control/controllers/tables/sequence.py +46 -37
- pychemstation/control/controllers/tables/table.py +46 -30
- pychemstation/control/hplc.py +27 -11
- pychemstation/control/table/__init__.py +3 -0
- pychemstation/control/table/method.py +274 -0
- pychemstation/control/table/sequence.py +210 -0
- pychemstation/control/table/table_controller.py +201 -0
- pychemstation/generated/dad_method.py +1 -1
- pychemstation/generated/pump_method.py +1 -1
- pychemstation/utils/chromatogram.py +2 -5
- pychemstation/utils/injector_types.py +1 -1
- pychemstation/utils/macro.py +3 -3
- pychemstation/utils/method_types.py +2 -2
- pychemstation/utils/num_utils.py +65 -0
- pychemstation/utils/parsing.py +1 -0
- pychemstation/utils/sequence_types.py +3 -3
- pychemstation/utils/spec_utils.py +304 -0
- {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/METADATA +19 -8
- pychemstation-0.10.0.dist-info/RECORD +62 -0
- {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/WHEEL +2 -1
- pychemstation-0.10.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/constants.py +134 -0
- tests/test_comb.py +136 -0
- tests/test_comm.py +65 -0
- tests/test_inj.py +39 -0
- tests/test_method.py +99 -0
- tests/test_nightly.py +80 -0
- tests/test_offline_stable.py +69 -0
- tests/test_online_stable.py +275 -0
- tests/test_proc_rep.py +52 -0
- tests/test_runs_stable.py +225 -0
- tests/test_sequence.py +125 -0
- tests/test_stable.py +276 -0
- pychemstation/control/README.md +0 -124
- pychemstation/control/controllers/README.md +0 -1
- pychemstation-0.8.3.dist-info/RECORD +0 -37
- {pychemstation-0.8.3.dist-info → pychemstation-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,266 @@
|
|
1
|
+
"""
|
2
|
+
Abstract module containing shared logic for Method and Sequence tables.
|
3
|
+
|
4
|
+
Authors: Lucy Hao
|
5
|
+
"""
|
6
|
+
|
7
|
+
import abc
|
8
|
+
import os
|
9
|
+
from typing import Union, Optional
|
10
|
+
|
11
|
+
import polling
|
12
|
+
from result import Result, Ok, Err
|
13
|
+
|
14
|
+
from ...control.controllers.comm import CommunicationController
|
15
|
+
from ...utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
|
16
|
+
from ...utils.macro import Command, HPLCRunningStatus, Response
|
17
|
+
from ...utils.method_types import MethodDetails
|
18
|
+
from ...utils.sequence_types import SequenceDataFiles, SequenceTable
|
19
|
+
from ...utils.table_types import Table, TableOperation, RegisterFlag
|
20
|
+
|
21
|
+
TableType = Union[MethodDetails, SequenceTable]
|
22
|
+
|
23
|
+
|
24
|
+
class TableController(abc.ABC):
|
25
|
+
|
26
|
+
def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
|
27
|
+
self.controller = controller
|
28
|
+
self.table = table
|
29
|
+
self.table_state: Optional[TableType] = None
|
30
|
+
|
31
|
+
if os.path.isdir(src):
|
32
|
+
self.src: str = src
|
33
|
+
else:
|
34
|
+
raise FileNotFoundError(f"dir: {src} not found.")
|
35
|
+
|
36
|
+
if os.path.isdir(data_dir):
|
37
|
+
self.data_dir: str = data_dir
|
38
|
+
else:
|
39
|
+
raise FileNotFoundError(f"dir: {data_dir} not found.")
|
40
|
+
|
41
|
+
self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
|
42
|
+
"A": AgilentHPLCChromatogram(self.data_dir),
|
43
|
+
"B": AgilentHPLCChromatogram(self.data_dir),
|
44
|
+
"C": AgilentHPLCChromatogram(self.data_dir),
|
45
|
+
"D": AgilentHPLCChromatogram(self.data_dir),
|
46
|
+
"E": AgilentHPLCChromatogram(self.data_dir),
|
47
|
+
"F": AgilentHPLCChromatogram(self.data_dir),
|
48
|
+
"G": AgilentHPLCChromatogram(self.data_dir),
|
49
|
+
"H": AgilentHPLCChromatogram(self.data_dir),
|
50
|
+
}
|
51
|
+
|
52
|
+
self.data_files: Union[list[SequenceDataFiles], list[str]] = []
|
53
|
+
|
54
|
+
# Initialize row counter for table operations
|
55
|
+
self.send('Local Rows')
|
56
|
+
|
57
|
+
def receive(self) -> Result[Response, str]:
|
58
|
+
for _ in range(10):
|
59
|
+
try:
|
60
|
+
return self.controller.receive()
|
61
|
+
except IndexError:
|
62
|
+
continue
|
63
|
+
return Err("Could not parse response")
|
64
|
+
|
65
|
+
def send(self, cmd: Union[Command, str]):
|
66
|
+
if not self.controller:
|
67
|
+
raise RuntimeError(
|
68
|
+
"Communication controller must be initialized before sending command. It is currently in offline mode.")
|
69
|
+
self.controller.send(cmd)
|
70
|
+
|
71
|
+
def sleepy_send(self, cmd: Union[Command, str]):
|
72
|
+
self.controller.sleepy_send(cmd)
|
73
|
+
|
74
|
+
def sleep(self, seconds: int):
|
75
|
+
"""
|
76
|
+
Tells the HPLC to wait for a specified number of seconds.
|
77
|
+
|
78
|
+
:param seconds: number of seconds to wait
|
79
|
+
"""
|
80
|
+
self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
|
81
|
+
|
82
|
+
def get_num(self, row: int, col_name: RegisterFlag) -> float:
|
83
|
+
return self.controller.get_num_val(TableOperation.GET_ROW_VAL.value.format(register=self.table.register,
|
84
|
+
table_name=self.table.name,
|
85
|
+
row=row,
|
86
|
+
col_name=col_name.value))
|
87
|
+
|
88
|
+
def get_text(self, row: int, col_name: RegisterFlag) -> str:
|
89
|
+
return self.controller.get_text_val(TableOperation.GET_ROW_TEXT.value.format(register=self.table.register,
|
90
|
+
table_name=self.table.name,
|
91
|
+
row=row,
|
92
|
+
col_name=col_name.value))
|
93
|
+
|
94
|
+
def add_new_col_num(self,
|
95
|
+
col_name: RegisterFlag,
|
96
|
+
val: Union[int, float]):
|
97
|
+
self.sleepy_send(TableOperation.NEW_COL_VAL.value.format(
|
98
|
+
register=self.table.register,
|
99
|
+
table_name=self.table.name,
|
100
|
+
col_name=col_name,
|
101
|
+
val=val))
|
102
|
+
|
103
|
+
def add_new_col_text(self,
|
104
|
+
col_name: RegisterFlag,
|
105
|
+
val: str):
|
106
|
+
self.sleepy_send(TableOperation.NEW_COL_TEXT.value.format(
|
107
|
+
register=self.table.register,
|
108
|
+
table_name=self.table.name,
|
109
|
+
col_name=col_name,
|
110
|
+
val=val))
|
111
|
+
|
112
|
+
def edit_row_num(self,
|
113
|
+
col_name: RegisterFlag,
|
114
|
+
val: Union[int, float],
|
115
|
+
row: Optional[int] = None):
|
116
|
+
self.sleepy_send(TableOperation.EDIT_ROW_VAL.value.format(
|
117
|
+
register=self.table.register,
|
118
|
+
table_name=self.table.name,
|
119
|
+
row=row if row is not None else 'Rows',
|
120
|
+
col_name=col_name,
|
121
|
+
val=val))
|
122
|
+
|
123
|
+
def edit_row_text(self,
|
124
|
+
col_name: RegisterFlag,
|
125
|
+
val: str,
|
126
|
+
row: Optional[int] = None):
|
127
|
+
self.sleepy_send(TableOperation.EDIT_ROW_TEXT.value.format(
|
128
|
+
register=self.table.register,
|
129
|
+
table_name=self.table.name,
|
130
|
+
row=row if row is not None else 'Rows',
|
131
|
+
col_name=col_name,
|
132
|
+
val=val))
|
133
|
+
|
134
|
+
@abc.abstractmethod
|
135
|
+
def get_row(self, row: int):
|
136
|
+
pass
|
137
|
+
|
138
|
+
def delete_row(self, row: int):
|
139
|
+
self.sleepy_send(TableOperation.DELETE_ROW.value.format(register=self.table.register,
|
140
|
+
table_name=self.table.name,
|
141
|
+
row=row))
|
142
|
+
|
143
|
+
def add_row(self):
|
144
|
+
"""
|
145
|
+
Adds a row to the provided table for currently loaded method or sequence.
|
146
|
+
Import either the SEQUENCE_TABLE or METHOD_TIMETABLE from hein_analytical_control.constants.
|
147
|
+
You can also provide your own table.
|
148
|
+
|
149
|
+
:param table: the table to add a new row to
|
150
|
+
"""
|
151
|
+
self.sleepy_send(TableOperation.NEW_ROW.value.format(register=self.table.register,
|
152
|
+
table_name=self.table.name))
|
153
|
+
|
154
|
+
def delete_table(self):
|
155
|
+
"""
|
156
|
+
Deletes the table for the current loaded method or sequence.
|
157
|
+
Import either the SEQUENCE_TABLE or METHOD_TIMETABLE from hein_analytical_control.constants.
|
158
|
+
You can also provide your own table.
|
159
|
+
|
160
|
+
:param table: the table to delete
|
161
|
+
"""
|
162
|
+
self.sleepy_send(TableOperation.DELETE_TABLE.value.format(register=self.table.register,
|
163
|
+
table_name=self.table.name))
|
164
|
+
|
165
|
+
def new_table(self):
|
166
|
+
"""
|
167
|
+
Creates the table for the currently loaded method or sequence. Import either the SEQUENCE_TABLE or
|
168
|
+
METHOD_TIMETABLE from hein_analytical_control.constants. You can also provide your own table.
|
169
|
+
|
170
|
+
:param table: the table to create
|
171
|
+
"""
|
172
|
+
self.send(TableOperation.CREATE_TABLE.value.format(register=self.table.register,
|
173
|
+
table_name=self.table.name))
|
174
|
+
|
175
|
+
def get_num_rows(self) -> Result[Response, str]:
|
176
|
+
self.send(TableOperation.GET_NUM_ROWS.value.format(register=self.table.register,
|
177
|
+
table_name=self.table.name,
|
178
|
+
col_name=RegisterFlag.NUM_ROWS))
|
179
|
+
self.send(Command.GET_ROWS_CMD.value.format(register=self.table.register,
|
180
|
+
table_name=self.table.name,
|
181
|
+
col_name=RegisterFlag.NUM_ROWS))
|
182
|
+
res = self.controller.receive()
|
183
|
+
|
184
|
+
if res.is_ok():
|
185
|
+
self.send("Sleep 0.1")
|
186
|
+
self.send('Print Rows')
|
187
|
+
return res
|
188
|
+
else:
|
189
|
+
return Err("No rows could be read.")
|
190
|
+
|
191
|
+
def check_hplc_is_running(self) -> bool:
|
192
|
+
started_running = polling.poll(
|
193
|
+
lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
|
194
|
+
step=5,
|
195
|
+
max_tries=100)
|
196
|
+
return started_running
|
197
|
+
|
198
|
+
def check_hplc_done_running(self,
|
199
|
+
method: Optional[MethodDetails] = None,
|
200
|
+
sequence: Optional[SequenceTable] = None) -> Result[str, str]:
|
201
|
+
"""
|
202
|
+
Checks if ChemStation has finished running and can read data back
|
203
|
+
|
204
|
+
:param method: if you are running a method and want to read back data, the timeout period will be adjusted to be longer than the method's runtime
|
205
|
+
:param sequence: if you are running a sequence and want to read back data, the timeout period will be adjusted to be longer than the sequence's runtime
|
206
|
+
:return: Return True if data can be read back, else False.
|
207
|
+
"""
|
208
|
+
timeout = 10 * 60
|
209
|
+
if method:
|
210
|
+
timeout = ((method.stop_time + method.post_time + 3) * 60)
|
211
|
+
if sequence:
|
212
|
+
timeout *= len(sequence.rows)
|
213
|
+
|
214
|
+
most_recent_folder = self.retrieve_recent_data_files()
|
215
|
+
finished_run = polling.poll(
|
216
|
+
lambda: self.controller.check_if_running(),
|
217
|
+
timeout=timeout,
|
218
|
+
step=50)
|
219
|
+
|
220
|
+
check_folder = self.fuzzy_match_most_recent_folder(most_recent_folder)
|
221
|
+
if check_folder.is_ok() and finished_run:
|
222
|
+
return check_folder
|
223
|
+
elif check_folder.is_ok():
|
224
|
+
finished_run = polling.poll(
|
225
|
+
lambda: self.controller.check_if_running(),
|
226
|
+
timeout=timeout,
|
227
|
+
step=50)
|
228
|
+
if finished_run:
|
229
|
+
return check_folder
|
230
|
+
return check_folder
|
231
|
+
else:
|
232
|
+
return Err("Run did not complete as expected")
|
233
|
+
|
234
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder) -> Result[str, str]:
|
235
|
+
if os.path.exists(most_recent_folder):
|
236
|
+
return Ok(most_recent_folder)
|
237
|
+
|
238
|
+
subdirs = [x[0] for x in os.walk(self.data_dir)]
|
239
|
+
potential_folders = sorted(list(filter(lambda d: most_recent_folder in d, subdirs)))
|
240
|
+
parent_dirs = []
|
241
|
+
for folder in potential_folders:
|
242
|
+
path = os.path.normpath(folder)
|
243
|
+
split_folder = path.split(os.sep)
|
244
|
+
if most_recent_folder in split_folder[-1]:
|
245
|
+
parent_dirs.append(folder)
|
246
|
+
parent_dir = sorted(parent_dirs, reverse=True)[0]
|
247
|
+
return Ok(parent_dir)
|
248
|
+
|
249
|
+
@abc.abstractmethod
|
250
|
+
def retrieve_recent_data_files(self):
|
251
|
+
pass
|
252
|
+
|
253
|
+
@abc.abstractmethod
|
254
|
+
def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
255
|
+
pass
|
256
|
+
|
257
|
+
def get_spectrum(self, data_file: str):
|
258
|
+
"""
|
259
|
+
Load chromatogram for any channel in spectra dictionary.
|
260
|
+
"""
|
261
|
+
for channel, spec in self.spectra.items():
|
262
|
+
try:
|
263
|
+
spec.load_spectrum(data_path=data_file, channel=channel)
|
264
|
+
except FileNotFoundError:
|
265
|
+
self.spectra[channel] = None
|
266
|
+
print(f"No data at channel: {channel}")
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
|
3
|
+
import warnings
|
4
|
+
from typing import Dict, List, Optional, Union
|
4
5
|
|
5
6
|
from result import Err, Ok, Result
|
6
7
|
from xsdata.formats.dataclass.parsers import XmlParser
|
@@ -13,9 +14,15 @@ from ....utils.chromatogram import (
|
|
13
14
|
AgilentChannelChromatogramData,
|
14
15
|
AgilentHPLCChromatogram,
|
15
16
|
)
|
16
|
-
from ....utils.macro import
|
17
|
-
from ....utils.method_types import
|
18
|
-
|
17
|
+
from ....utils.macro import Command
|
18
|
+
from ....utils.method_types import (
|
19
|
+
HPLCMethodParams,
|
20
|
+
MethodDetails,
|
21
|
+
Param,
|
22
|
+
PType,
|
23
|
+
TimeTableEntry,
|
24
|
+
)
|
25
|
+
from ....utils.table_types import RegisterFlag, T, Table, TableOperation
|
19
26
|
from ..devices.injector import InjectorController
|
20
27
|
from .table import TableController
|
21
28
|
|
@@ -129,6 +136,10 @@ class MethodController(TableController):
|
|
129
136
|
register=self.table_locator.register,
|
130
137
|
register_flag=RegisterFlag.MAX_TIME))
|
131
138
|
|
139
|
+
def get_total_runtime(self) -> Union[int, float]:
|
140
|
+
"""Returns total method runtime in minutes."""
|
141
|
+
return self.get_post_time() + self.get_stop_time()
|
142
|
+
|
132
143
|
def current_method(self, method_name: str):
|
133
144
|
"""
|
134
145
|
Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
|
@@ -173,6 +184,7 @@ class MethodController(TableController):
|
|
173
184
|
:raises FileNotFoundError: Method does not exist
|
174
185
|
:return: method details
|
175
186
|
"""
|
187
|
+
warnings.warn("This method is not actively maintained.")
|
176
188
|
method_folder = f"{method_name}.M"
|
177
189
|
method_path = os.path.join(self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml")
|
178
190
|
dad_path = os.path.join(self.src, method_folder, "Agilent1200erDadDriver1.RapidControl.MethodXML.xml")
|
@@ -347,29 +359,39 @@ class MethodController(TableController):
|
|
347
359
|
raise RuntimeError("Method failed to start.")
|
348
360
|
|
349
361
|
self.data_files.append(os.path.join(self.data_dirs[0], folder_name))
|
362
|
+
self.timeout = (self.get_total_runtime()) * 60
|
350
363
|
|
351
364
|
if stall_while_running:
|
352
|
-
self.timeout = (self.get_stop_time() + self.get_post_time()) * 60
|
353
365
|
run_completed = self.check_hplc_done_running()
|
354
366
|
if run_completed.is_ok():
|
355
367
|
self.data_files[-1] = run_completed.ok_value
|
356
368
|
else:
|
357
369
|
raise RuntimeError("Run error has occurred.")
|
358
370
|
else:
|
359
|
-
self.data_files[-1]
|
371
|
+
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
|
372
|
+
while folder.is_err():
|
373
|
+
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
|
374
|
+
if folder.is_ok():
|
375
|
+
self.data_files[-1] = folder.ok_value
|
376
|
+
else:
|
377
|
+
warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
|
378
|
+
warnings.warn(warning)
|
379
|
+
|
360
380
|
|
361
381
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
362
382
|
if os.path.exists(most_recent_folder):
|
363
383
|
return Ok(most_recent_folder)
|
364
384
|
return Err("Folder not found!")
|
365
385
|
|
366
|
-
def get_data(self, custom_path: Optional[str] = None
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
386
|
+
def get_data(self, custom_path: Optional[str] = None) -> AgilentChannelChromatogramData:
|
387
|
+
custom_path = custom_path if custom_path else self.data_files[-1]
|
388
|
+
self.get_spectrum_at_channels(custom_path)
|
389
|
+
return AgilentChannelChromatogramData(**self.spectra)
|
390
|
+
|
391
|
+
def get_data_uv(self, custom_path: Optional[str] = None) -> Dict[str, AgilentHPLCChromatogram]:
|
392
|
+
custom_path = custom_path if custom_path else self.data_files[-1]
|
393
|
+
self.get_uv_spectrum(custom_path)
|
394
|
+
return self.uv
|
373
395
|
|
374
396
|
def get_report(self, custom_path: Optional[str] = None,
|
375
397
|
report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
@@ -1,19 +1,29 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
from typing import
|
3
|
+
from typing import Dict, List, Optional
|
4
4
|
|
5
|
-
from result import
|
5
|
+
from result import Err, Ok, Result
|
6
6
|
from typing_extensions import override
|
7
7
|
|
8
|
-
from .
|
9
|
-
from .. import MethodController
|
10
|
-
from ....analysis.process_report import ReportType, AgilentReport
|
8
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
11
9
|
from ....control.controllers.comm import CommunicationController
|
12
|
-
from ....utils.chromatogram import
|
10
|
+
from ....utils.chromatogram import (
|
11
|
+
SEQUENCE_TIME_FORMAT,
|
12
|
+
AgilentChannelChromatogramData,
|
13
|
+
AgilentHPLCChromatogram,
|
14
|
+
)
|
13
15
|
from ....utils.macro import Command
|
14
|
-
from ....utils.sequence_types import
|
16
|
+
from ....utils.sequence_types import (
|
17
|
+
InjectionSource,
|
18
|
+
SampleType,
|
19
|
+
SequenceDataFiles,
|
20
|
+
SequenceEntry,
|
21
|
+
SequenceTable,
|
22
|
+
)
|
15
23
|
from ....utils.table_types import RegisterFlag, Table
|
16
|
-
from ....utils.tray_types import
|
24
|
+
from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
|
25
|
+
from .. import MethodController
|
26
|
+
from .table import TableController
|
17
27
|
|
18
28
|
|
19
29
|
class SequenceController(TableController):
|
@@ -82,7 +92,7 @@ class SequenceController(TableController):
|
|
82
92
|
time.sleep(2)
|
83
93
|
self.send(Command.GET_SEQUENCE_CMD)
|
84
94
|
time.sleep(2)
|
85
|
-
parsed_response = self.receive().
|
95
|
+
parsed_response = self.receive().ok_value.string_response
|
86
96
|
|
87
97
|
assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
|
88
98
|
self.table_state = None
|
@@ -97,7 +107,7 @@ class SequenceController(TableController):
|
|
97
107
|
self.table_state = sequence_table
|
98
108
|
rows = self.get_num_rows()
|
99
109
|
if rows.is_ok():
|
100
|
-
existing_row_num = rows.
|
110
|
+
existing_row_num = rows.ok_value.num_response
|
101
111
|
wanted_row_num = len(sequence_table.rows)
|
102
112
|
while existing_row_num != wanted_row_num:
|
103
113
|
if wanted_row_num > existing_row_num:
|
@@ -127,7 +137,6 @@ class SequenceController(TableController):
|
|
127
137
|
self.add_row()
|
128
138
|
self.send(Command.SAVE_SEQUENCE_CMD)
|
129
139
|
num_rows = self.get_num_rows()
|
130
|
-
|
131
140
|
if row.vial_location:
|
132
141
|
loc = row.vial_location
|
133
142
|
if isinstance(loc, TenVialColumn):
|
@@ -135,7 +144,6 @@ class SequenceController(TableController):
|
|
135
144
|
elif isinstance(loc, FiftyFourVialPlate):
|
136
145
|
loc = row.vial_location.value()
|
137
146
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
|
138
|
-
|
139
147
|
if row.method:
|
140
148
|
method_dir = self.method_controller.src
|
141
149
|
possible_path = os.path.join(method_dir, row.method) + ".M\\"
|
@@ -143,23 +151,18 @@ class SequenceController(TableController):
|
|
143
151
|
if os.path.exists(possible_path):
|
144
152
|
method = os.path.join(method_dir, row.method)
|
145
153
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
|
146
|
-
|
147
154
|
if row.num_inj:
|
148
155
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj)
|
149
|
-
|
150
156
|
if row.inj_vol:
|
151
157
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol)
|
152
|
-
|
153
158
|
if row.inj_source:
|
154
159
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value)
|
155
|
-
|
156
160
|
if row.sample_name:
|
157
161
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
|
158
162
|
if row.data_file:
|
159
163
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file)
|
160
164
|
else:
|
161
165
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
|
162
|
-
|
163
166
|
if row.sample_type:
|
164
167
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
|
165
168
|
|
@@ -179,15 +182,15 @@ class SequenceController(TableController):
|
|
179
182
|
|
180
183
|
total_runtime = 0
|
181
184
|
for entry in self.table_state.rows:
|
182
|
-
curr_method_runtime = self.method_controller.
|
183
|
-
loaded_method = self.method_controller.get_method_name()
|
185
|
+
curr_method_runtime = self.method_controller.get_total_runtime()
|
186
|
+
loaded_method = self.method_controller.get_method_name().removesuffix(".M")
|
184
187
|
method_path = entry.method.split(sep="\\")
|
185
188
|
method_name = method_path[-1]
|
186
189
|
if loaded_method != method_name:
|
187
|
-
method_dir =
|
190
|
+
method_dir = "\\".join(method_path[:-1])+"\\" if len(method_path) > 1 else None
|
188
191
|
self.method_controller.switch(method_name=method_name,
|
189
192
|
alt_method_dir=method_dir)
|
190
|
-
curr_method_runtime = self.method_controller.
|
193
|
+
curr_method_runtime = self.method_controller.get_total_runtime()
|
191
194
|
total_runtime += curr_method_runtime
|
192
195
|
|
193
196
|
timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
|
@@ -196,8 +199,8 @@ class SequenceController(TableController):
|
|
196
199
|
|
197
200
|
if self.check_hplc_is_running():
|
198
201
|
folder_name = f"{self.table_state.name} {timestamp}"
|
199
|
-
|
200
|
-
|
202
|
+
data_file = SequenceDataFiles(dir=folder_name, sequence_name=self.table_state.name)
|
203
|
+
self.data_files.append(data_file)
|
201
204
|
|
202
205
|
if stall_while_running:
|
203
206
|
run_completed = self.check_hplc_done_running()
|
@@ -205,10 +208,8 @@ class SequenceController(TableController):
|
|
205
208
|
self.data_files[-1] = run_completed.ok_value
|
206
209
|
else:
|
207
210
|
raise RuntimeError("Run error has occurred.")
|
208
|
-
|
209
|
-
|
210
|
-
child_dirs=[],
|
211
|
-
sequence_name=self.table_state.name)
|
211
|
+
else:
|
212
|
+
raise RuntimeError("Sequence run did not start.")
|
212
213
|
|
213
214
|
@override
|
214
215
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: SequenceDataFiles) -> Result[SequenceDataFiles, str]:
|
@@ -248,17 +249,25 @@ class SequenceController(TableController):
|
|
248
249
|
except Exception:
|
249
250
|
return Err("Failed to get sequence folder")
|
250
251
|
|
251
|
-
def
|
252
|
-
|
253
|
-
if len(
|
254
|
-
self.data_files[-1] = self.fuzzy_match_most_recent_folder(
|
255
|
-
|
256
|
-
all_w_spectra: list[Dict[str, AgilentHPLCChromatogram]] = []
|
252
|
+
def get_data_uv(self,custom_path: Optional[str] = None) -> List[Dict[str, AgilentHPLCChromatogram]]:
|
253
|
+
custom_path = SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="") if custom_path else self.data_files[-1]
|
254
|
+
if len(custom_path.child_dirs) == 0:
|
255
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(custom_path).ok_value
|
256
|
+
all_w_spectra: List[Dict[str, AgilentHPLCChromatogram]] = []
|
257
257
|
for row in self.data_files[-1].child_dirs:
|
258
|
-
self.
|
259
|
-
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
258
|
+
self.get_uv_spectrum(row)
|
260
259
|
all_w_spectra.append(self.uv)
|
261
|
-
return
|
260
|
+
return all_w_spectra
|
261
|
+
|
262
|
+
def get_data(self, custom_path: Optional[str] = None) -> List[AgilentChannelChromatogramData]:
|
263
|
+
custom_path = SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="") if custom_path else self.data_files[-1]
|
264
|
+
if len(custom_path.child_dirs) == 0:
|
265
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(custom_path).ok_value
|
266
|
+
spectra: List[AgilentChannelChromatogramData] = []
|
267
|
+
for row in self.data_files[-1].child_dirs:
|
268
|
+
self.get_spectrum_at_channels(row)
|
269
|
+
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
270
|
+
return spectra
|
262
271
|
|
263
272
|
def get_report(self, custom_path: Optional[str] = None,
|
264
273
|
report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
@@ -268,7 +277,7 @@ class SequenceController(TableController):
|
|
268
277
|
child_dirs=[],
|
269
278
|
sequence_name="NA")).ok_value)
|
270
279
|
parent_dir = self.data_files[-1]
|
271
|
-
spectra = self.get_data(
|
280
|
+
spectra = self.get_data()
|
272
281
|
reports = []
|
273
282
|
for i, child_dir in enumerate(parent_dir.child_dirs):
|
274
283
|
metd_report = self.get_report_details(child_dir, report_type)
|