pychemstation 0.8.3__py3-none-any.whl → 0.8.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pychemstation/__init__.py +1 -1
- pychemstation/analysis/__init__.py +4 -1
- pychemstation/analysis/base_spectrum.py +4 -4
- pychemstation/{utils → analysis}/chromatogram.py +4 -7
- pychemstation/analysis/process_report.py +121 -74
- pychemstation/control/README.md +22 -46
- pychemstation/control/__init__.py +5 -0
- pychemstation/control/controllers/__init__.py +2 -0
- pychemstation/control/controllers/comm.py +41 -18
- pychemstation/control/controllers/devices/device.py +27 -14
- pychemstation/control/controllers/devices/injector.py +33 -89
- pychemstation/control/controllers/tables/method.py +266 -111
- pychemstation/control/controllers/tables/ms.py +7 -4
- pychemstation/control/controllers/tables/sequence.py +171 -82
- pychemstation/control/controllers/tables/table.py +192 -116
- pychemstation/control/hplc.py +117 -83
- pychemstation/generated/__init__.py +0 -2
- pychemstation/generated/dad_method.py +1 -1
- pychemstation/generated/pump_method.py +15 -19
- pychemstation/utils/injector_types.py +1 -1
- pychemstation/utils/macro.py +12 -11
- pychemstation/utils/method_types.py +3 -2
- pychemstation/{analysis/utils.py → utils/num_utils.py} +2 -2
- pychemstation/utils/parsing.py +1 -11
- pychemstation/utils/sequence_types.py +4 -5
- pychemstation/{analysis → utils}/spec_utils.py +1 -2
- pychemstation/utils/table_types.py +10 -9
- pychemstation/utils/tray_types.py +48 -38
- {pychemstation-0.8.3.dist-info → pychemstation-0.8.6.dist-info}/METADATA +64 -23
- pychemstation-0.8.6.dist-info/RECORD +37 -0
- pychemstation-0.8.3.dist-info/RECORD +0 -37
- {pychemstation-0.8.3.dist-info → pychemstation-0.8.6.dist-info}/WHEEL +0 -0
- {pychemstation-0.8.3.dist-info → pychemstation-0.8.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,30 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
|
3
|
+
import warnings
|
4
|
+
from typing import Dict, List, Optional
|
4
5
|
|
5
|
-
from result import
|
6
|
+
from result import Err, Ok, Result
|
6
7
|
from typing_extensions import override
|
7
8
|
|
8
|
-
from .
|
9
|
-
from .. import MethodController
|
10
|
-
from ....analysis.process_report import ReportType, AgilentReport
|
9
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
11
10
|
from ....control.controllers.comm import CommunicationController
|
12
|
-
from
|
11
|
+
from pychemstation.analysis.chromatogram import (
|
12
|
+
SEQUENCE_TIME_FORMAT,
|
13
|
+
AgilentChannelChromatogramData,
|
14
|
+
AgilentHPLCChromatogram,
|
15
|
+
)
|
13
16
|
from ....utils.macro import Command
|
14
|
-
from ....utils.sequence_types import
|
17
|
+
from ....utils.sequence_types import (
|
18
|
+
InjectionSource,
|
19
|
+
SampleType,
|
20
|
+
SequenceDataFiles,
|
21
|
+
SequenceEntry,
|
22
|
+
SequenceTable,
|
23
|
+
)
|
15
24
|
from ....utils.table_types import RegisterFlag, Table
|
16
|
-
from ....utils.tray_types import
|
25
|
+
from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
|
26
|
+
from .. import MethodController
|
27
|
+
from .table import TableController
|
17
28
|
|
18
29
|
|
19
30
|
class SequenceController(TableController):
|
@@ -21,15 +32,24 @@ class SequenceController(TableController):
|
|
21
32
|
Class containing sequence related logic
|
22
33
|
"""
|
23
34
|
|
24
|
-
def __init__(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
controller: CommunicationController,
|
38
|
+
method_controller: MethodController,
|
39
|
+
src: str,
|
40
|
+
data_dirs: List[str],
|
41
|
+
table: Table,
|
42
|
+
offline: bool,
|
43
|
+
):
|
30
44
|
self.method_controller = method_controller
|
31
45
|
self.data_files: List[SequenceDataFiles] = []
|
32
|
-
super().__init__(
|
46
|
+
super().__init__(
|
47
|
+
controller=controller,
|
48
|
+
src=src,
|
49
|
+
data_dirs=data_dirs,
|
50
|
+
table=table,
|
51
|
+
offline=offline,
|
52
|
+
)
|
33
53
|
|
34
54
|
def load(self) -> SequenceTable:
|
35
55
|
rows = self.get_num_rows()
|
@@ -39,7 +59,10 @@ class SequenceController(TableController):
|
|
39
59
|
if rows.is_ok() and seq_name.is_ok():
|
40
60
|
self.table_state = SequenceTable(
|
41
61
|
name=seq_name.ok_value.string_response.partition(".S")[0],
|
42
|
-
rows=[
|
62
|
+
rows=[
|
63
|
+
self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))
|
64
|
+
],
|
65
|
+
)
|
43
66
|
return self.table_state
|
44
67
|
raise RuntimeError(rows.err_value)
|
45
68
|
|
@@ -51,15 +74,20 @@ class SequenceController(TableController):
|
|
51
74
|
inj_vol = float(self.get_text(row, RegisterFlag.INJ_VOL))
|
52
75
|
inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
|
53
76
|
sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
|
54
|
-
vial_enum =
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
vial_enum = (
|
78
|
+
TenVialColumn(vial_location)
|
79
|
+
if vial_location <= 10
|
80
|
+
else FiftyFourVialPlate.from_int(num=vial_location)
|
81
|
+
)
|
82
|
+
return SequenceEntry(
|
83
|
+
sample_name=sample_name,
|
84
|
+
vial_location=vial_enum,
|
85
|
+
method=None if len(method) == 0 else method,
|
86
|
+
num_inj=num_inj,
|
87
|
+
inj_vol=inj_vol,
|
88
|
+
inj_source=inj_source,
|
89
|
+
sample_type=sample_type,
|
90
|
+
)
|
63
91
|
|
64
92
|
def check(self) -> str:
|
65
93
|
time.sleep(2)
|
@@ -82,7 +110,7 @@ class SequenceController(TableController):
|
|
82
110
|
time.sleep(2)
|
83
111
|
self.send(Command.GET_SEQUENCE_CMD)
|
84
112
|
time.sleep(2)
|
85
|
-
parsed_response = self.receive().
|
113
|
+
parsed_response = self.receive().ok_value.string_response
|
86
114
|
|
87
115
|
assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
|
88
116
|
self.table_state = None
|
@@ -97,7 +125,7 @@ class SequenceController(TableController):
|
|
97
125
|
self.table_state = sequence_table
|
98
126
|
rows = self.get_num_rows()
|
99
127
|
if rows.is_ok():
|
100
|
-
existing_row_num = rows.
|
128
|
+
existing_row_num = rows.ok_value.num_response
|
101
129
|
wanted_row_num = len(sequence_table.rows)
|
102
130
|
while existing_row_num != wanted_row_num:
|
103
131
|
if wanted_row_num > existing_row_num:
|
@@ -127,15 +155,15 @@ class SequenceController(TableController):
|
|
127
155
|
self.add_row()
|
128
156
|
self.send(Command.SAVE_SEQUENCE_CMD)
|
129
157
|
num_rows = self.get_num_rows()
|
130
|
-
|
131
158
|
if row.vial_location:
|
132
159
|
loc = row.vial_location
|
133
160
|
if isinstance(loc, TenVialColumn):
|
134
161
|
loc = row.vial_location.value
|
135
162
|
elif isinstance(loc, FiftyFourVialPlate):
|
136
163
|
loc = row.vial_location.value()
|
137
|
-
self._edit_row_num(
|
138
|
-
|
164
|
+
self._edit_row_num(
|
165
|
+
row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc
|
166
|
+
)
|
139
167
|
if row.method:
|
140
168
|
method_dir = self.method_controller.src
|
141
169
|
possible_path = os.path.join(method_dir, row.method) + ".M\\"
|
@@ -143,25 +171,36 @@ class SequenceController(TableController):
|
|
143
171
|
if os.path.exists(possible_path):
|
144
172
|
method = os.path.join(method_dir, row.method)
|
145
173
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
|
146
|
-
|
147
174
|
if row.num_inj:
|
148
|
-
self._edit_row_num(
|
149
|
-
|
175
|
+
self._edit_row_num(
|
176
|
+
row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj
|
177
|
+
)
|
150
178
|
if row.inj_vol:
|
151
|
-
self._edit_row_text(
|
152
|
-
|
179
|
+
self._edit_row_text(
|
180
|
+
row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol
|
181
|
+
)
|
153
182
|
if row.inj_source:
|
154
|
-
self._edit_row_text(
|
155
|
-
|
183
|
+
self._edit_row_text(
|
184
|
+
row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value
|
185
|
+
)
|
156
186
|
if row.sample_name:
|
157
|
-
self._edit_row_text(
|
187
|
+
self._edit_row_text(
|
188
|
+
row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name
|
189
|
+
)
|
158
190
|
if row.data_file:
|
159
|
-
self._edit_row_text(
|
191
|
+
self._edit_row_text(
|
192
|
+
row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file
|
193
|
+
)
|
160
194
|
else:
|
161
|
-
self._edit_row_text(
|
162
|
-
|
195
|
+
self._edit_row_text(
|
196
|
+
row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name
|
197
|
+
)
|
163
198
|
if row.sample_type:
|
164
|
-
self._edit_row_num(
|
199
|
+
self._edit_row_num(
|
200
|
+
row=row_num,
|
201
|
+
col_name=RegisterFlag.SAMPLE_TYPE,
|
202
|
+
val=row.sample_type.value,
|
203
|
+
)
|
165
204
|
|
166
205
|
self.send(Command.SAVE_SEQUENCE_CMD)
|
167
206
|
|
@@ -179,15 +218,18 @@ class SequenceController(TableController):
|
|
179
218
|
|
180
219
|
total_runtime = 0
|
181
220
|
for entry in self.table_state.rows:
|
182
|
-
curr_method_runtime = self.method_controller.
|
183
|
-
loaded_method = self.method_controller.get_method_name()
|
221
|
+
curr_method_runtime = self.method_controller.get_total_runtime()
|
222
|
+
loaded_method = self.method_controller.get_method_name().removesuffix(".M")
|
184
223
|
method_path = entry.method.split(sep="\\")
|
185
224
|
method_name = method_path[-1]
|
186
225
|
if loaded_method != method_name:
|
187
|
-
method_dir =
|
188
|
-
|
189
|
-
|
190
|
-
|
226
|
+
method_dir = (
|
227
|
+
"\\".join(method_path[:-1]) + "\\" if len(method_path) > 1 else None
|
228
|
+
)
|
229
|
+
self.method_controller.switch(
|
230
|
+
method_name=method_name, alt_method_dir=method_dir
|
231
|
+
)
|
232
|
+
curr_method_runtime = self.method_controller.get_total_runtime()
|
191
233
|
total_runtime += curr_method_runtime
|
192
234
|
|
193
235
|
timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
|
@@ -196,34 +238,43 @@ class SequenceController(TableController):
|
|
196
238
|
|
197
239
|
if self.check_hplc_is_running():
|
198
240
|
folder_name = f"{self.table_state.name} {timestamp}"
|
199
|
-
|
200
|
-
|
241
|
+
data_file = SequenceDataFiles(
|
242
|
+
dir=folder_name, sequence_name=self.table_state.name
|
243
|
+
)
|
244
|
+
self.data_files.append(data_file)
|
201
245
|
|
202
246
|
if stall_while_running:
|
203
247
|
run_completed = self.check_hplc_done_running()
|
204
248
|
if run_completed.is_ok():
|
205
249
|
self.data_files[-1] = run_completed.ok_value
|
206
250
|
else:
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
child_dirs=[],
|
211
|
-
sequence_name=self.table_state.name)
|
251
|
+
warnings.warn("Run may have not completed.")
|
252
|
+
else:
|
253
|
+
raise RuntimeError("Sequence run did not start.")
|
212
254
|
|
213
255
|
@override
|
214
|
-
def fuzzy_match_most_recent_folder(
|
256
|
+
def fuzzy_match_most_recent_folder(
|
257
|
+
self, most_recent_folder: SequenceDataFiles
|
258
|
+
) -> Result[SequenceDataFiles, str]:
|
215
259
|
if os.path.isdir(most_recent_folder.dir):
|
216
260
|
subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
|
217
|
-
potential_folders = sorted(
|
218
|
-
|
219
|
-
|
261
|
+
potential_folders = sorted(
|
262
|
+
list(filter(lambda d: most_recent_folder.dir in d, subdirs))
|
263
|
+
)
|
264
|
+
most_recent_folder.child_dirs = [
|
265
|
+
f
|
266
|
+
for f in potential_folders
|
267
|
+
if most_recent_folder.dir in f and ".M" not in f and ".D" in f
|
268
|
+
]
|
220
269
|
return Ok(most_recent_folder)
|
221
270
|
|
222
271
|
try:
|
223
272
|
potential_folders = []
|
224
273
|
for d in self.data_dirs:
|
225
274
|
subdirs = [x[0] for x in os.walk(d)]
|
226
|
-
potential_folders = sorted(
|
275
|
+
potential_folders = sorted(
|
276
|
+
list(filter(lambda d: most_recent_folder.dir in d, subdirs))
|
277
|
+
)
|
227
278
|
if len(potential_folders) > 0:
|
228
279
|
break
|
229
280
|
assert len(potential_folders) > 0
|
@@ -238,41 +289,79 @@ class SequenceController(TableController):
|
|
238
289
|
potential_folders = []
|
239
290
|
for d in self.data_dirs:
|
240
291
|
subdirs = [x[0] for x in os.walk(d)]
|
241
|
-
potential_folders = sorted(
|
292
|
+
potential_folders = sorted(
|
293
|
+
list(filter(lambda d: parent_dir in d, subdirs))
|
294
|
+
)
|
242
295
|
if len(potential_folders) > 0:
|
243
296
|
break
|
244
297
|
assert len(potential_folders) > 0
|
245
|
-
most_recent_folder.child_dirs = [
|
246
|
-
|
298
|
+
most_recent_folder.child_dirs = [
|
299
|
+
f
|
300
|
+
for f in potential_folders
|
301
|
+
if parent_dir in f and ".M" not in f and ".D" in f
|
302
|
+
]
|
247
303
|
return Ok(most_recent_folder)
|
248
|
-
except Exception:
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
304
|
+
except Exception as e:
|
305
|
+
error = f"Failed to get sequence folder: {e}"
|
306
|
+
return Err(error)
|
307
|
+
|
308
|
+
def get_data_uv(
|
309
|
+
self, custom_path: Optional[str] = None
|
310
|
+
) -> List[Dict[int, AgilentHPLCChromatogram]]:
|
311
|
+
custom_path = (
|
312
|
+
SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
|
313
|
+
if custom_path
|
314
|
+
else self.data_files[-1]
|
315
|
+
)
|
316
|
+
if len(custom_path.child_dirs) == 0:
|
317
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(
|
318
|
+
custom_path
|
319
|
+
).ok_value
|
320
|
+
all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
|
257
321
|
for row in self.data_files[-1].child_dirs:
|
258
|
-
self.
|
259
|
-
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
322
|
+
self.get_uv_spectrum(row)
|
260
323
|
all_w_spectra.append(self.uv)
|
261
|
-
return
|
324
|
+
return all_w_spectra
|
325
|
+
|
326
|
+
def get_data(
|
327
|
+
self, custom_path: Optional[str] = None
|
328
|
+
) -> List[AgilentChannelChromatogramData]:
|
329
|
+
custom_path = (
|
330
|
+
SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
|
331
|
+
if custom_path
|
332
|
+
else self.data_files[-1]
|
333
|
+
)
|
334
|
+
if len(custom_path.child_dirs) == 0:
|
335
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(
|
336
|
+
custom_path
|
337
|
+
).ok_value
|
338
|
+
spectra: List[AgilentChannelChromatogramData] = []
|
339
|
+
for row in self.data_files[-1].child_dirs:
|
340
|
+
self.get_spectrum_at_channels(row)
|
341
|
+
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
342
|
+
return spectra
|
262
343
|
|
263
|
-
def get_report(
|
264
|
-
|
344
|
+
def get_report(
|
345
|
+
self,
|
346
|
+
custom_path: Optional[str] = None,
|
347
|
+
report_type: ReportType = ReportType.TXT,
|
348
|
+
) -> List[AgilentReport]:
|
265
349
|
if custom_path:
|
266
350
|
self.data_files.append(
|
267
|
-
self.fuzzy_match_most_recent_folder(
|
268
|
-
|
269
|
-
|
351
|
+
self.fuzzy_match_most_recent_folder(
|
352
|
+
most_recent_folder=SequenceDataFiles(
|
353
|
+
dir=custom_path, child_dirs=[], sequence_name="NA"
|
354
|
+
)
|
355
|
+
).ok_value
|
356
|
+
)
|
270
357
|
parent_dir = self.data_files[-1]
|
271
|
-
spectra = self.get_data(
|
358
|
+
spectra = self.get_data()
|
272
359
|
reports = []
|
273
360
|
for i, child_dir in enumerate(parent_dir.child_dirs):
|
274
361
|
metd_report = self.get_report_details(child_dir, report_type)
|
275
|
-
child_spectra: List[AgilentHPLCChromatogram] = list(
|
362
|
+
child_spectra: List[AgilentHPLCChromatogram] = list(
|
363
|
+
spectra[i].__dict__.values()
|
364
|
+
)
|
276
365
|
for j, signal in enumerate(metd_report.signals):
|
277
366
|
possible_data = child_spectra[j]
|
278
367
|
if len(possible_data.x) > 0:
|