pychemstation 0.10.2__py3-none-any.whl → 0.10.4__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 +3 -4
- pychemstation/analysis/base_spectrum.py +7 -7
- pychemstation/{utils → analysis}/chromatogram.py +4 -4
- pychemstation/analysis/process_report.py +106 -70
- pychemstation/control/README.md +21 -53
- pychemstation/control/__init__.py +3 -2
- pychemstation/control/controllers/__init__.py +1 -5
- pychemstation/control/controllers/comm.py +20 -11
- pychemstation/control/controllers/devices/device.py +22 -12
- pychemstation/control/controllers/devices/injector.py +24 -14
- pychemstation/control/controllers/tables/method.py +233 -100
- pychemstation/control/controllers/tables/ms.py +7 -4
- pychemstation/control/controllers/tables/sequence.py +134 -54
- pychemstation/control/controllers/tables/table.py +152 -92
- pychemstation/control/hplc.py +96 -78
- pychemstation/generated/__init__.py +0 -2
- pychemstation/generated/pump_method.py +15 -19
- pychemstation/utils/macro.py +10 -9
- pychemstation/utils/method_types.py +1 -0
- pychemstation/utils/num_utils.py +2 -2
- pychemstation/utils/parsing.py +0 -11
- pychemstation/utils/sequence_types.py +2 -3
- pychemstation/utils/spec_utils.py +2 -3
- pychemstation/utils/table_types.py +10 -9
- pychemstation/utils/tray_types.py +48 -38
- {pychemstation-0.10.2.dist-info → pychemstation-0.10.4.dist-info}/METADATA +46 -20
- pychemstation-0.10.4.dist-info/RECORD +37 -0
- pychemstation-0.10.2.dist-info/RECORD +0 -37
- {pychemstation-0.10.2.dist-info → pychemstation-0.10.4.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.2.dist-info → pychemstation-0.10.4.dist-info}/licenses/LICENSE +0 -0
pychemstation/__init__.py
CHANGED
@@ -197,10 +197,10 @@ class AbstractSpectrum(ABC):
|
|
197
197
|
return (self.x.copy()[full_mask], self.y.copy()[full_mask])
|
198
198
|
|
199
199
|
def show_spectrum(
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
200
|
+
self,
|
201
|
+
filename=None,
|
202
|
+
title=None,
|
203
|
+
label=None,
|
204
204
|
):
|
205
205
|
"""Plots the spectral data using matplotlib.pyplot module.
|
206
206
|
|
@@ -249,7 +249,7 @@ class AbstractSpectrum(ABC):
|
|
249
249
|
os.makedirs(path, exist_ok=True)
|
250
250
|
fig.savefig(os.path.join(path, f"{filename}.png"), dpi=150)
|
251
251
|
|
252
|
-
def find_peaks(self, threshold=1, min_width
|
252
|
+
def find_peaks(self, threshold=1, min_width=0.1, min_dist=None, area=None):
|
253
253
|
"""Finds all peaks above the threshold with at least min_width width.
|
254
254
|
|
255
255
|
Args:
|
@@ -385,12 +385,12 @@ class AbstractSpectrum(ABC):
|
|
385
385
|
|
386
386
|
if rule == "trapz":
|
387
387
|
return integrate.trapz(
|
388
|
-
self.y[left_idx: right_idx + 1], self.x[left_idx: right_idx + 1]
|
388
|
+
self.y[left_idx : right_idx + 1], self.x[left_idx : right_idx + 1]
|
389
389
|
)
|
390
390
|
|
391
391
|
elif rule == "simps":
|
392
392
|
return integrate.simps(
|
393
|
-
self.y[left_idx: right_idx + 1], self.x[left_idx: right_idx + 1]
|
393
|
+
self.y[left_idx : right_idx + 1], self.x[left_idx : right_idx + 1]
|
394
394
|
)
|
395
395
|
|
396
396
|
else:
|
@@ -6,8 +6,9 @@ from dataclasses import dataclass
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
|
-
|
10
|
-
from .parsing import CHFile
|
9
|
+
|
10
|
+
from ..utils.parsing import CHFile
|
11
|
+
from ..analysis.base_spectrum import AbstractSpectrum
|
11
12
|
|
12
13
|
ACQUISITION_PARAMETERS = "acq.txt"
|
13
14
|
|
@@ -36,12 +37,11 @@ class AgilentHPLCChromatogram(AbstractSpectrum):
|
|
36
37
|
}
|
37
38
|
|
38
39
|
def __init__(self, path=None, autosaving=False):
|
39
|
-
|
40
40
|
if path is not None:
|
41
41
|
os.makedirs(path, exist_ok=True)
|
42
42
|
self.path = path
|
43
43
|
else:
|
44
|
-
self.path = os.path.join("
|
44
|
+
self.path = os.path.join("../utils", "hplc_data")
|
45
45
|
os.makedirs(self.path, exist_ok=True)
|
46
46
|
|
47
47
|
super().__init__(path=path, autosaving=autosaving)
|
@@ -17,19 +17,19 @@ from aghplctools.ingestion.text import (
|
|
17
17
|
)
|
18
18
|
from result import Err, Ok, Result
|
19
19
|
|
20
|
-
from
|
21
|
-
from
|
20
|
+
from ..analysis.chromatogram import AgilentHPLCChromatogram
|
21
|
+
from ..utils.tray_types import FiftyFourVialPlate, Tray
|
22
22
|
|
23
23
|
|
24
24
|
@dataclass
|
25
25
|
class AgilentPeak:
|
26
|
+
peak_number: Optional[int]
|
26
27
|
retention_time: float
|
28
|
+
peak_type: Optional[str]
|
27
29
|
width: float
|
28
30
|
area: float
|
29
31
|
height: float
|
30
|
-
|
31
|
-
peak_type: Optional[str]
|
32
|
-
height_percent: Optional[float]
|
32
|
+
area_percent: Optional[float]
|
33
33
|
|
34
34
|
|
35
35
|
@dataclass
|
@@ -73,12 +73,14 @@ class CSVProcessor(ReportProcessor):
|
|
73
73
|
"""
|
74
74
|
Method to parse details from CSV report.
|
75
75
|
|
76
|
-
:
|
76
|
+
:return: subset of complete report details, specifically the sample location, solvents in pumps,
|
77
77
|
and list of peaks at each wavelength channel.
|
78
78
|
"""
|
79
|
-
labels = os.path.join(self.path,
|
79
|
+
labels = os.path.join(self.path, "REPORT00.CSV")
|
80
80
|
if os.path.exists(labels):
|
81
|
-
df_labels: Dict[int, Dict[int:
|
81
|
+
df_labels: Dict[int, Dict[int:AnyStr]] = pd.read_csv(
|
82
|
+
labels, encoding="utf-16", header=None
|
83
|
+
).to_dict()
|
82
84
|
vial_location = []
|
83
85
|
signals = {}
|
84
86
|
solvents = {}
|
@@ -91,18 +93,28 @@ class CSVProcessor(ReportProcessor):
|
|
91
93
|
elif val == "Number of Signals":
|
92
94
|
num_signals = int(df_labels[1][pos])
|
93
95
|
for s in range(1, num_signals + 1):
|
94
|
-
df = pd.read_csv(
|
95
|
-
|
96
|
+
df = pd.read_csv(
|
97
|
+
os.path.join(self.path, f"REPORT0{s}.CSV"),
|
98
|
+
encoding="utf-16",
|
99
|
+
header=None,
|
100
|
+
)
|
96
101
|
peaks = df.apply(lambda row: AgilentPeak(*row), axis=1)
|
97
|
-
wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[0][
|
98
|
-
|
102
|
+
wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[0][
|
103
|
+
-3:
|
104
|
+
]
|
105
|
+
signals[wavelength] = list(peaks)
|
99
106
|
break
|
100
107
|
|
101
|
-
return Ok(
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
108
|
+
return Ok(
|
109
|
+
AgilentReport(
|
110
|
+
signals=[
|
111
|
+
Signals(wavelength=int(w), peaks=s, data=None)
|
112
|
+
for w, s in signals.items()
|
113
|
+
],
|
114
|
+
vial_location=FiftyFourVialPlate.from_int(int(vial_location)),
|
115
|
+
solvents=solvents,
|
116
|
+
)
|
117
|
+
)
|
106
118
|
|
107
119
|
return Err("No report found")
|
108
120
|
|
@@ -111,34 +123,39 @@ class TXTProcessor(ReportProcessor):
|
|
111
123
|
"""
|
112
124
|
Regex matches for column and unit combinations, courtesy of Veronica Lai.
|
113
125
|
"""
|
126
|
+
|
114
127
|
_column_re_dictionary = {
|
115
|
-
|
116
|
-
|
128
|
+
"Peak": { # peak index
|
129
|
+
"#": "[ ]+(?P<Peak>[\d]+)", # number
|
117
130
|
},
|
118
|
-
|
119
|
-
|
131
|
+
"RetTime": { # retention time
|
132
|
+
"[min]": "(?P<RetTime>[\d]+.[\d]+)", # minutes
|
120
133
|
},
|
121
|
-
|
122
|
-
|
134
|
+
"Type": { # peak type
|
135
|
+
"": "(?P<Type>[A-Z]{1,3}(?: [A-Z]{1,2})*)", # todo this is different from <4.8.8 aghplc tools
|
123
136
|
},
|
124
|
-
|
125
|
-
|
137
|
+
"Width": { # peak width
|
138
|
+
"[min]": "(?P<Width>[\d]+.[\d]+[e+-]*[\d]+)",
|
126
139
|
},
|
127
|
-
|
128
|
-
|
129
|
-
|
140
|
+
"Area": { # peak area
|
141
|
+
"[mAU*s]": "(?P<Area>[\d]+.[\d]+[e+-]*[\d]+)", # area units
|
142
|
+
"%": "(?P<percent>[\d]+.[\d]+[e+-]*[\d]+)", # percent
|
130
143
|
},
|
131
|
-
|
132
|
-
|
144
|
+
"Height": { # peak height
|
145
|
+
"[mAU]": "(?P<Height>[\d]+.[\d]+[e+-]*[\d]+)",
|
133
146
|
},
|
134
|
-
|
135
|
-
|
147
|
+
"Name": {
|
148
|
+
"": "(?P<Name>[^\s]+(?:\s[^\s]+)*)", # peak name
|
136
149
|
},
|
137
150
|
}
|
138
151
|
|
139
|
-
def __init__(
|
140
|
-
|
141
|
-
|
152
|
+
def __init__(
|
153
|
+
self,
|
154
|
+
path: str,
|
155
|
+
min_ret_time: int = 0,
|
156
|
+
max_ret_time: int = 999,
|
157
|
+
target_wavelength_range: List[int] = range(200, 300),
|
158
|
+
):
|
142
159
|
"""
|
143
160
|
Class to process reports in CSV form.
|
144
161
|
|
@@ -155,16 +172,17 @@ class TXTProcessor(ReportProcessor):
|
|
155
172
|
def process_report(self) -> Result[AgilentReport, AnyStr]:
|
156
173
|
"""
|
157
174
|
Method to parse details from CSV report.
|
158
|
-
|
159
|
-
:returns: subset of complete report details, specifically the sample location, solvents in pumps,
|
160
|
-
and list of peaks at each wavelength channel.
|
161
|
-
|
162
175
|
If you want more functionality, use `aghplctools`.
|
163
176
|
`from aghplctools.ingestion.text import pull_hplc_area_from_txt`
|
164
177
|
`signals = pull_hplc_area_from_txt(file_path)`
|
178
|
+
|
179
|
+
:return: subset of complete report details, specifically the sample location, solvents in pumps,
|
180
|
+
and list of peaks at each wavelength channel.
|
165
181
|
"""
|
166
182
|
|
167
|
-
with open(
|
183
|
+
with open(
|
184
|
+
os.path.join(self.path, "REPORT.TXT"), "r", encoding="utf-16"
|
185
|
+
) as openfile:
|
168
186
|
text = openfile.read()
|
169
187
|
|
170
188
|
try:
|
@@ -172,25 +190,33 @@ class TXTProcessor(ReportProcessor):
|
|
172
190
|
except ValueError as e:
|
173
191
|
return Err("No peaks found: " + str(e))
|
174
192
|
|
175
|
-
signals = {
|
193
|
+
signals = {
|
194
|
+
key: signals[key] for key in self.target_wavelength_range if key in signals
|
195
|
+
}
|
176
196
|
|
177
197
|
parsed_signals = []
|
178
198
|
for wavelength, wavelength_dict in signals.items():
|
179
|
-
current_wavelength_signals = Signals(
|
199
|
+
current_wavelength_signals = Signals(
|
200
|
+
wavelength=int(wavelength), peaks=[], data=None
|
201
|
+
)
|
180
202
|
for ret_time, ret_time_dict in wavelength_dict.items():
|
181
203
|
if self.min_ret_time <= ret_time <= self.max_ret_time:
|
182
|
-
current_wavelength_signals.peaks.append(
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
204
|
+
current_wavelength_signals.peaks.append(
|
205
|
+
AgilentPeak(
|
206
|
+
retention_time=ret_time,
|
207
|
+
area=ret_time_dict["Area"],
|
208
|
+
width=ret_time_dict["Width"],
|
209
|
+
height=ret_time_dict["Height"],
|
210
|
+
peak_number=None,
|
211
|
+
peak_type=ret_time_dict["Type"],
|
212
|
+
area_percent=None,
|
213
|
+
)
|
214
|
+
)
|
189
215
|
parsed_signals.append(current_wavelength_signals)
|
190
216
|
|
191
|
-
return Ok(
|
192
|
-
|
193
|
-
|
217
|
+
return Ok(
|
218
|
+
AgilentReport(vial_location=None, solvents=None, signals=parsed_signals)
|
219
|
+
)
|
194
220
|
|
195
221
|
def parse_area_report(self, report_text: str) -> Dict:
|
196
222
|
"""
|
@@ -206,7 +232,7 @@ class TXTProcessor(ReportProcessor):
|
|
206
232
|
should be able to use the `parse_area_report` method of aghplctools v4.8.8
|
207
233
|
"""
|
208
234
|
if re.search(_no_peaks_re, report_text): # There are no peaks in Report.txt
|
209
|
-
raise ValueError(
|
235
|
+
raise ValueError("No peaks found in Report.txt")
|
210
236
|
blocks = _header_block_re.split(report_text)
|
211
237
|
signals = {} # output dictionary
|
212
238
|
for ind, block in enumerate(blocks):
|
@@ -219,23 +245,28 @@ class TXTProcessor(ReportProcessor):
|
|
219
245
|
si = _signal_info_re.match(table)
|
220
246
|
if si is not None:
|
221
247
|
# some error state (e.g. 'not found')
|
222
|
-
if si.group(
|
248
|
+
if si.group("error") != "":
|
223
249
|
continue
|
224
|
-
wavelength = float(si.group(
|
250
|
+
wavelength = float(si.group("wavelength"))
|
225
251
|
if wavelength in signals:
|
226
252
|
# placeholder error raise just in case (this probably won't happen)
|
227
253
|
raise KeyError(
|
228
|
-
f
|
254
|
+
f"The wavelength {float(si.group('wavelength'))} is already in the signals dictionary"
|
255
|
+
)
|
229
256
|
signals[wavelength] = {}
|
230
257
|
# build peak regex
|
231
258
|
peak_re = self.build_peak_regex(table)
|
232
|
-
if
|
259
|
+
if (
|
260
|
+
peak_re is None
|
261
|
+
): # if there are no columns (empty table), continue
|
233
262
|
continue
|
234
|
-
for line in table.split(
|
263
|
+
for line in table.split("\n"):
|
235
264
|
peak = peak_re.match(line)
|
236
265
|
if peak is not None:
|
237
|
-
signals[wavelength][float(peak.group(
|
238
|
-
current = signals[wavelength][
|
266
|
+
signals[wavelength][float(peak.group("RetTime"))] = {}
|
267
|
+
current = signals[wavelength][
|
268
|
+
float(peak.group("RetTime"))
|
269
|
+
]
|
239
270
|
for key in self._column_re_dictionary:
|
240
271
|
if key in peak.re.groupindex:
|
241
272
|
try: # try float conversion, otherwise continue
|
@@ -254,30 +285,35 @@ class TXTProcessor(ReportProcessor):
|
|
254
285
|
:param signal_table: block of lines associated with an area table
|
255
286
|
:return: peak line regex object (<=3.6 _sre.SRE_PATTERN, >=3.7 re.Pattern)
|
256
287
|
"""
|
257
|
-
split_table = signal_table.split(
|
288
|
+
split_table = signal_table.split("\n")
|
258
289
|
if len(split_table) <= 4: # catch peak table with no values
|
259
290
|
return None
|
260
291
|
# todo verify that these indicies are always true
|
261
292
|
column_line = split_table[2] # table column line
|
262
293
|
unit_line = split_table[3] # column unit line
|
263
|
-
length_line = [len(val) + 1 for val in split_table[4].split(
|
294
|
+
length_line = [len(val) + 1 for val in split_table[4].split("|")] # length line
|
264
295
|
|
265
296
|
# iterate over header values and units to build peak table regex
|
266
297
|
peak_re_string = []
|
267
298
|
for header, unit in zip(
|
268
|
-
|
269
|
-
chunk_string(unit_line, length_line)
|
299
|
+
chunk_string(column_line, length_line), chunk_string(unit_line, length_line)
|
270
300
|
):
|
271
|
-
if header ==
|
301
|
+
if header == "": # todo create a better catch for an undefined header
|
272
302
|
continue
|
273
303
|
try:
|
274
304
|
peak_re_string.append(
|
275
|
-
self._column_re_dictionary[header][
|
305
|
+
self._column_re_dictionary[header][
|
306
|
+
unit
|
307
|
+
] # append the appropriate regex
|
276
308
|
)
|
277
309
|
except KeyError: # catch for undefined regexes (need to be built)
|
278
|
-
raise KeyError(
|
279
|
-
|
310
|
+
raise KeyError(
|
311
|
+
f'The header/unit combination "{header}" "{unit}" is not defined in the peak regex '
|
312
|
+
f"dictionary. Let Lars know."
|
313
|
+
)
|
280
314
|
return re.compile(
|
281
|
-
|
282
|
-
|
315
|
+
"[ ]+".join(
|
316
|
+
peak_re_string
|
317
|
+
) # constructed string delimited by 1 or more spaces
|
318
|
+
+ "[\s]*" # and any remaining white space
|
283
319
|
)
|
pychemstation/control/README.md
CHANGED
@@ -1,30 +1,24 @@
|
|
1
1
|
# Examples of usecases
|
2
2
|
|
3
|
-
## Initialization
|
4
|
-
|
5
3
|
```python
|
6
4
|
from pychemstation.control import HPLCController
|
7
5
|
|
8
6
|
DEFAULT_METHOD_DIR = "C:\\ChemStation\\1\\Methods\\"
|
9
|
-
DATA_DIR = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data"
|
10
7
|
SEQUENCE_DIR = "C:\\USERS\\PUBLIC\\DOCUMENTS\\CHEMSTATION\\3\\Sequence"
|
11
8
|
DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
|
9
|
+
DATA_DIR_2 = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data"
|
10
|
+
DATA_DIR_3 = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data"
|
12
11
|
|
13
|
-
|
12
|
+
# Initialize HPLC Controller
|
13
|
+
hplc_controller = HPLCController(data_dirs=[DATA_DIR_2, DATA_DIR_3],
|
14
14
|
comm_dir=DEFAULT_COMMAND_PATH,
|
15
15
|
method_dir=DEFAULT_METHOD_DIR,
|
16
16
|
sequence_dir=SEQUENCE_DIR)
|
17
|
-
```
|
18
|
-
|
19
|
-
## Switching a method
|
20
17
|
|
21
|
-
|
18
|
+
# Switching a method
|
22
19
|
hplc_controller.switch_method("General-Poroshell")
|
23
|
-
```
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
```python
|
21
|
+
# Editing a method
|
28
22
|
from pychemstation.utils.method_types import *
|
29
23
|
|
30
24
|
new_method = MethodDetails(
|
@@ -47,52 +41,27 @@ new_method = MethodDetails(
|
|
47
41
|
stop_time=5,
|
48
42
|
post_time=2
|
49
43
|
)
|
50
|
-
|
51
44
|
hplc_controller.edit_method(new_method)
|
52
|
-
```
|
53
|
-
|
54
|
-
## Running a method and get data from last run method
|
55
45
|
|
56
|
-
|
46
|
+
# Run a method and get a report or data from last run method
|
57
47
|
hplc_controller.run_method(experiment_name="test_experiment")
|
58
48
|
chrom = hplc_controller.get_last_run_method_data()
|
59
49
|
channel_a_time = chrom.A.x
|
60
|
-
|
50
|
+
report = hplc_controller.get_last_run_method_report()
|
51
|
+
vial_location = report.vial_location
|
61
52
|
|
62
|
-
|
63
|
-
|
64
|
-
```python
|
53
|
+
# switch the currently loaded sequence
|
65
54
|
hplc_controller.switch_sequence(sequence_name="hplc_testing")
|
66
|
-
```
|
67
55
|
|
68
|
-
|
69
|
-
|
70
|
-
```python
|
71
|
-
from pychemstation.utils.sequence_types import *
|
72
|
-
from pychemstation.utils.tray_types import *
|
73
|
-
|
74
|
-
hplc_controller.edit_sequence_row(SequenceEntry(
|
75
|
-
vial_location=FiftyFourVialPlate(plate=Plate.TWO, letter=Letter.A, num=Num.SEVEN).value(),
|
76
|
-
method="General-Poroshell",
|
77
|
-
num_inj=3,
|
78
|
-
inj_vol=4,
|
79
|
-
sample_name="Blank",
|
80
|
-
sample_type=SampleType.BLANK,
|
81
|
-
inj_source=InjectionSource.HIP_ALS
|
82
|
-
), 1)
|
83
|
-
```
|
84
|
-
|
85
|
-
## Editing entire Sequence Table
|
86
|
-
|
87
|
-
```python
|
56
|
+
# edit the sequence table
|
88
57
|
from pychemstation.utils.tray_types import *
|
89
58
|
from pychemstation.utils.sequence_types import *
|
90
59
|
|
91
60
|
seq_table = SequenceTable(
|
92
|
-
name=
|
61
|
+
name="hplc_testing",
|
93
62
|
rows=[
|
94
63
|
SequenceEntry(
|
95
|
-
vial_location=FiftyFourVialPlate
|
64
|
+
vial_location=FiftyFourVialPlate.from_str("P1-A1"),
|
96
65
|
method="General-Poroshell",
|
97
66
|
num_inj=3,
|
98
67
|
inj_vol=4,
|
@@ -101,7 +70,7 @@ seq_table = SequenceTable(
|
|
101
70
|
inj_source=InjectionSource.MANUAL
|
102
71
|
),
|
103
72
|
SequenceEntry(
|
104
|
-
vial_location=TenVialColumn.ONE
|
73
|
+
vial_location=TenVialColumn.ONE,
|
105
74
|
method="General-Poroshell",
|
106
75
|
num_inj=1,
|
107
76
|
inj_vol=1,
|
@@ -110,7 +79,7 @@ seq_table = SequenceTable(
|
|
110
79
|
inj_source=InjectionSource.AS_METHOD
|
111
80
|
),
|
112
81
|
SequenceEntry(
|
113
|
-
vial_location=
|
82
|
+
vial_location=FiftyFourVialPlate.from_str("P2-B4"),
|
114
83
|
method="General-Poroshell",
|
115
84
|
num_inj=3,
|
116
85
|
inj_vol=4,
|
@@ -121,12 +90,11 @@ seq_table = SequenceTable(
|
|
121
90
|
]
|
122
91
|
)
|
123
92
|
hplc_controller.edit_sequence(seq_table)
|
124
|
-
```
|
125
93
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
94
|
+
# Run a sequence and get data or report from last run sequence
|
95
|
+
hplc_controller.run_sequence()
|
96
|
+
chroms = hplc_controller.get_last_run_sequence_data(read_uv=True)
|
97
|
+
row_1_channel_A_abs = chroms[0][210].y
|
98
|
+
report = hplc_controller.get_last_run_sequence_reports()
|
99
|
+
vial_location_row_1 = report[0].vial_location
|
132
100
|
```
|
@@ -6,8 +6,4 @@ from .comm import CommunicationController
|
|
6
6
|
from .tables.method import MethodController
|
7
7
|
from .tables.sequence import SequenceController
|
8
8
|
|
9
|
-
__all__ = [
|
10
|
-
'CommunicationController',
|
11
|
-
'MethodController',
|
12
|
-
'SequenceController'
|
13
|
-
]
|
9
|
+
__all__ = ["CommunicationController", "MethodController", "SequenceController"]
|
@@ -9,6 +9,7 @@ been processed.
|
|
9
9
|
|
10
10
|
Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
|
11
11
|
"""
|
12
|
+
|
12
13
|
import os
|
13
14
|
import time
|
14
15
|
from typing import Optional, Union
|
@@ -34,11 +35,11 @@ class CommunicationController:
|
|
34
35
|
MAX_CMD_NO = 255
|
35
36
|
|
36
37
|
def __init__(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
self,
|
39
|
+
comm_dir: str,
|
40
|
+
cmd_file: str = "cmd",
|
41
|
+
reply_file: str = "reply",
|
42
|
+
debug: bool = False,
|
42
43
|
):
|
43
44
|
"""
|
44
45
|
:param comm_dir:
|
@@ -62,7 +63,7 @@ class CommunicationController:
|
|
62
63
|
self.reset_cmd_counter()
|
63
64
|
|
64
65
|
# Initialize row counter for table operations
|
65
|
-
self.send(
|
66
|
+
self.send("Local Rows")
|
66
67
|
|
67
68
|
def get_num_val(self, cmd: str) -> Union[int, float]:
|
68
69
|
tries = 5
|
@@ -194,7 +195,7 @@ class CommunicationController:
|
|
194
195
|
def receive(self) -> Result[Response, str]:
|
195
196
|
"""Returns messages received in reply file.
|
196
197
|
|
197
|
-
:return: ChemStation response
|
198
|
+
:return: ChemStation response
|
198
199
|
"""
|
199
200
|
num_response_prefix = "Numerical Responses:"
|
200
201
|
str_response_prefix = "String Responses:"
|
@@ -203,10 +204,18 @@ class CommunicationController:
|
|
203
204
|
lines = possible_response.ok_value.splitlines()
|
204
205
|
for line in lines:
|
205
206
|
if str_response_prefix in line and num_response_prefix in line:
|
206
|
-
string_responses_dirty, _, numerical_responses = line.partition(
|
207
|
-
|
208
|
-
|
209
|
-
|
207
|
+
string_responses_dirty, _, numerical_responses = line.partition(
|
208
|
+
num_response_prefix
|
209
|
+
)
|
210
|
+
_, _, string_responses = string_responses_dirty.partition(
|
211
|
+
str_response_prefix
|
212
|
+
)
|
213
|
+
return Ok(
|
214
|
+
Response(
|
215
|
+
string_response=string_responses.strip(),
|
216
|
+
num_response=float(numerical_responses.strip()),
|
217
|
+
)
|
218
|
+
)
|
210
219
|
return Err("Could not retrieve HPLC response")
|
211
220
|
else:
|
212
221
|
return Err(f"Could not establish response to HPLC: {possible_response}")
|
@@ -6,18 +6,20 @@ from result import Result
|
|
6
6
|
from ....analysis.process_report import AgilentReport, ReportType
|
7
7
|
from ....control.controllers import CommunicationController
|
8
8
|
from ....control.controllers.tables.table import TableController
|
9
|
-
from
|
9
|
+
from pychemstation.analysis.chromatogram import (
|
10
|
+
AgilentChannelChromatogramData,
|
11
|
+
AgilentHPLCChromatogram,
|
12
|
+
)
|
10
13
|
from ....utils.table_types import T, Table
|
11
14
|
|
12
15
|
|
13
16
|
class DeviceController(TableController, abc.ABC):
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
offline=offline)
|
17
|
+
def __init__(
|
18
|
+
self, controller: CommunicationController, table: Table, offline: bool
|
19
|
+
):
|
20
|
+
super().__init__(
|
21
|
+
controller=controller, src=None, data_dirs=[], table=table, offline=offline
|
22
|
+
)
|
21
23
|
|
22
24
|
@abc.abstractmethod
|
23
25
|
def get_row(self, row: int):
|
@@ -29,11 +31,19 @@ class DeviceController(TableController, abc.ABC):
|
|
29
31
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
30
32
|
raise NotImplementedError
|
31
33
|
|
32
|
-
def get_report(
|
34
|
+
def get_report(
|
35
|
+
self, report_type: ReportType = ReportType.TXT
|
36
|
+
) -> List[AgilentReport]:
|
33
37
|
raise NotImplementedError
|
34
38
|
|
35
|
-
def get_data_uv(
|
39
|
+
def get_data_uv(
|
40
|
+
self,
|
41
|
+
) -> Union[
|
42
|
+
List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]
|
43
|
+
]:
|
36
44
|
raise NotImplementedError
|
37
45
|
|
38
|
-
def get_data(
|
39
|
-
|
46
|
+
def get_data(
|
47
|
+
self, custom_path: Optional[str] = None
|
48
|
+
) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
49
|
+
raise NotImplementedError
|