pychemstation 0.8.4__py3-none-any.whl → 0.8.7__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 +173 -77
- 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.4.dist-info → pychemstation-0.8.7.dist-info}/METADATA +64 -23
- pychemstation-0.8.7.dist-info/RECORD +37 -0
- pychemstation-0.8.4.dist-info/RECORD +0 -37
- {pychemstation-0.8.4.dist-info → pychemstation-0.8.7.dist-info}/WHEEL +0 -0
- {pychemstation-0.8.4.dist-info → pychemstation-0.8.7.dist-info}/licenses/LICENSE +0 -0
@@ -9,13 +9,21 @@ 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
|
-
from typing import Optional
|
15
|
+
from typing import Optional, Union
|
15
16
|
|
16
|
-
from result import
|
17
|
+
from result import Err, Ok, Result
|
17
18
|
|
18
|
-
from ...utils.macro import
|
19
|
+
from ...utils.macro import (
|
20
|
+
str_to_status,
|
21
|
+
HPLCAvailStatus,
|
22
|
+
HPLCErrorStatus,
|
23
|
+
Command,
|
24
|
+
Status,
|
25
|
+
Response,
|
26
|
+
)
|
19
27
|
|
20
28
|
|
21
29
|
class CommunicationController:
|
@@ -27,11 +35,11 @@ class CommunicationController:
|
|
27
35
|
MAX_CMD_NO = 255
|
28
36
|
|
29
37
|
def __init__(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
self,
|
39
|
+
comm_dir: str,
|
40
|
+
cmd_file: str = "cmd",
|
41
|
+
reply_file: str = "reply",
|
42
|
+
debug: bool = False,
|
35
43
|
):
|
36
44
|
"""
|
37
45
|
:param comm_dir:
|
@@ -55,7 +63,7 @@ class CommunicationController:
|
|
55
63
|
self.reset_cmd_counter()
|
56
64
|
|
57
65
|
# Initialize row counter for table operations
|
58
|
-
self.send(
|
66
|
+
self.send("Local Rows")
|
59
67
|
|
60
68
|
def get_num_val(self, cmd: str) -> Union[int, float]:
|
61
69
|
tries = 5
|
@@ -138,6 +146,7 @@ class CommunicationController:
|
|
138
146
|
:return: Potential ChemStation response
|
139
147
|
"""
|
140
148
|
err: Optional[Union[OSError, IndexError]] = None
|
149
|
+
err_msg = ""
|
141
150
|
for _ in range(num_attempts):
|
142
151
|
time.sleep(1)
|
143
152
|
|
@@ -150,7 +159,11 @@ class CommunicationController:
|
|
150
159
|
|
151
160
|
try:
|
152
161
|
first_line = response.splitlines()[0]
|
153
|
-
|
162
|
+
try:
|
163
|
+
response_no = int(first_line.split()[0])
|
164
|
+
except ValueError as e:
|
165
|
+
err = e
|
166
|
+
err_msg = f"Caused by {first_line}"
|
154
167
|
except IndexError as e:
|
155
168
|
err = e
|
156
169
|
continue
|
@@ -161,7 +174,9 @@ class CommunicationController:
|
|
161
174
|
else:
|
162
175
|
continue
|
163
176
|
else:
|
164
|
-
return Err(
|
177
|
+
return Err(
|
178
|
+
f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
|
179
|
+
)
|
165
180
|
|
166
181
|
def sleepy_send(self, cmd: Union[Command, str]):
|
167
182
|
self.send("Sleep 0.1")
|
@@ -187,20 +202,28 @@ class CommunicationController:
|
|
187
202
|
def receive(self) -> Result[Response, str]:
|
188
203
|
"""Returns messages received in reply file.
|
189
204
|
|
190
|
-
:return: ChemStation response
|
205
|
+
:return: ChemStation response
|
191
206
|
"""
|
192
207
|
num_response_prefix = "Numerical Responses:"
|
193
208
|
str_response_prefix = "String Responses:"
|
194
209
|
possible_response = self._receive(self.cmd_no)
|
195
210
|
if possible_response.is_ok():
|
196
|
-
lines = possible_response.
|
211
|
+
lines = possible_response.ok_value.splitlines()
|
197
212
|
for line in lines:
|
198
213
|
if str_response_prefix in line and num_response_prefix in line:
|
199
|
-
string_responses_dirty, _, numerical_responses = line.partition(
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
214
|
+
string_responses_dirty, _, numerical_responses = line.partition(
|
215
|
+
num_response_prefix
|
216
|
+
)
|
217
|
+
_, _, string_responses = string_responses_dirty.partition(
|
218
|
+
str_response_prefix
|
219
|
+
)
|
220
|
+
return Ok(
|
221
|
+
Response(
|
222
|
+
string_response=string_responses.strip(),
|
223
|
+
num_response=float(numerical_responses.strip()),
|
224
|
+
)
|
225
|
+
)
|
226
|
+
return Err("Could not retrieve HPLC response")
|
204
227
|
else:
|
205
228
|
return Err(f"Could not establish response to HPLC: {possible_response}")
|
206
229
|
|
@@ -1,23 +1,25 @@
|
|
1
1
|
import abc
|
2
|
-
from typing import Union,
|
2
|
+
from typing import List, Union, Dict, Optional
|
3
3
|
|
4
4
|
from result import Result
|
5
5
|
|
6
|
-
from ....analysis.process_report import
|
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
|
10
|
-
|
9
|
+
from pychemstation.analysis.chromatogram import (
|
10
|
+
AgilentChannelChromatogramData,
|
11
|
+
AgilentHPLCChromatogram,
|
12
|
+
)
|
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):
|
@@ -26,11 +28,22 @@ class DeviceController(TableController, abc.ABC):
|
|
26
28
|
def retrieve_recent_data_files(self):
|
27
29
|
raise NotImplementedError
|
28
30
|
|
29
|
-
def
|
31
|
+
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
30
32
|
raise NotImplementedError
|
31
33
|
|
32
|
-
def
|
34
|
+
def get_report(
|
35
|
+
self, report_type: ReportType = ReportType.TXT
|
36
|
+
) -> List[AgilentReport]:
|
37
|
+
raise NotImplementedError
|
38
|
+
|
39
|
+
def get_data_uv(
|
40
|
+
self,
|
41
|
+
) -> Union[
|
42
|
+
List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]
|
43
|
+
]:
|
33
44
|
raise NotImplementedError
|
34
45
|
|
35
|
-
def
|
46
|
+
def get_data(
|
47
|
+
self, custom_path: Optional[str] = None
|
48
|
+
) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
36
49
|
raise NotImplementedError
|
@@ -1,13 +1,24 @@
|
|
1
1
|
from ....control.controllers import CommunicationController
|
2
|
+
from ....utils.injector_types import (
|
3
|
+
Draw,
|
4
|
+
Inject,
|
5
|
+
InjectorFunction,
|
6
|
+
InjectorTable,
|
7
|
+
Mode,
|
8
|
+
Remote,
|
9
|
+
RemoteCommand,
|
10
|
+
SourceType,
|
11
|
+
Wait,
|
12
|
+
)
|
13
|
+
from ....utils.table_types import RegisterFlag, Table
|
14
|
+
from ....utils.tray_types import Tray
|
2
15
|
from .device import DeviceController
|
3
|
-
from ....utils.injector_types import *
|
4
|
-
from ....utils.macro import Command
|
5
|
-
from ....utils.table_types import Table, RegisterFlag
|
6
16
|
|
7
17
|
|
8
18
|
class InjectorController(DeviceController):
|
9
|
-
|
10
|
-
|
19
|
+
def __init__(
|
20
|
+
self, controller: CommunicationController, table: Table, offline: bool
|
21
|
+
):
|
11
22
|
super().__init__(controller, table, offline)
|
12
23
|
|
13
24
|
def get_row(self, row: int) -> InjectorFunction:
|
@@ -23,95 +34,28 @@ class InjectorController(DeviceController):
|
|
23
34
|
# TODO: better error handling
|
24
35
|
is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
|
25
36
|
is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
|
26
|
-
vol =
|
37
|
+
vol = (
|
38
|
+
self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE)
|
39
|
+
if is_volume == Mode.SET
|
40
|
+
else None
|
41
|
+
)
|
27
42
|
if is_source is SourceType.SPECIFIC_LOCATION:
|
28
43
|
return Draw(amount=vol, source=return_tray_loc())
|
29
44
|
elif is_source is SourceType.LOCATION:
|
30
|
-
return Draw(
|
45
|
+
return Draw(
|
46
|
+
amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION)
|
47
|
+
)
|
31
48
|
elif function == "Remote":
|
32
|
-
return Remote(
|
33
|
-
|
49
|
+
return Remote(
|
50
|
+
command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
|
51
|
+
duration=self.get_num(row, RegisterFlag.REMOTE_DUR),
|
52
|
+
)
|
34
53
|
|
35
54
|
def load(self) -> InjectorTable:
|
36
55
|
rows = self.get_num_rows()
|
37
56
|
if rows.is_ok():
|
38
|
-
return InjectorTable(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
def add_table_val(col_name: RegisterFlag, val: Union[str, int, float]):
|
44
|
-
nonlocal columns_added
|
45
|
-
if True:
|
46
|
-
if isinstance(val, str):
|
47
|
-
self._edit_row_text(col_name=col_name, val=val)
|
48
|
-
else:
|
49
|
-
self._edit_row_num(col_name=col_name, val=val)
|
50
|
-
else:
|
51
|
-
if isinstance(val, str):
|
52
|
-
self.add_new_col_text(col_name=col_name, val=val)
|
53
|
-
else:
|
54
|
-
self.add_new_col_num(col_name=col_name, val=val)
|
55
|
-
columns_added.add(col_name)
|
56
|
-
|
57
|
-
def add_inject(inject: Inject):
|
58
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=inject.__class__.__name__)
|
59
|
-
|
60
|
-
def add_draw(draw: Draw):
|
61
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=draw.__class__.__name__)
|
62
|
-
add_table_val(col_name=RegisterFlag.DRAW_SPEED, val=SourceType.DEFAULT.value)
|
63
|
-
add_table_val(col_name=RegisterFlag.DRAW_OFFSET, val=SourceType.DEFAULT.value)
|
64
|
-
|
65
|
-
if draw.amount:
|
66
|
-
add_table_val(col_name=RegisterFlag.DRAW_VOLUME, val=Mode.SET.value)
|
67
|
-
add_table_val(col_name=RegisterFlag.DRAW_VOLUME_VALUE, val=draw.amount)
|
68
|
-
else:
|
69
|
-
add_table_val(col_name=RegisterFlag.DRAW_VOLUME, val=Mode.DEFAULT.value)
|
70
|
-
|
71
|
-
if draw.location:
|
72
|
-
add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.LOCATION.value)
|
73
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION, val=draw.location)
|
74
|
-
elif draw.source:
|
75
|
-
add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.SPECIFIC_LOCATION.value)
|
76
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_UNIT, val=1)
|
77
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_TRAY, val=1)
|
78
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_ROW, val=1)
|
79
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_COLUMN, val=1)
|
80
|
-
else:
|
81
|
-
add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.DEFAULT.value)
|
82
|
-
|
83
|
-
def add_wait(wait: Wait):
|
84
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=wait.__class__.__name__)
|
85
|
-
add_table_val(col_name=RegisterFlag.TIME, val=wait.duration)
|
86
|
-
|
87
|
-
def add_remote(remote: Remote):
|
88
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=remote.__class__.__name__)
|
89
|
-
add_table_val(col_name=RegisterFlag.REMOTE, val=remote.command.value)
|
90
|
-
add_table_val(col_name=RegisterFlag.REMOTE_DUR, val=remote.duration)
|
91
|
-
|
92
|
-
self.send(Command.SAVE_METHOD_CMD)
|
93
|
-
rows = self.get_num_rows()
|
94
|
-
if rows.is_ok():
|
95
|
-
existing_row_num = rows.value.num_response
|
96
|
-
for i, function in enumerate(injector_table.functions):
|
97
|
-
if (i+1) > existing_row_num:
|
98
|
-
self.add_row()
|
99
|
-
if isinstance(function, Inject):
|
100
|
-
add_inject(function)
|
101
|
-
elif isinstance(function, Draw):
|
102
|
-
add_draw(function)
|
103
|
-
elif isinstance(function, Wait):
|
104
|
-
add_wait(function)
|
105
|
-
elif isinstance(function, Remote):
|
106
|
-
add_remote(function)
|
107
|
-
self.download()
|
108
|
-
self.send(Command.SAVE_METHOD_CMD)
|
109
|
-
self.send(Command.SWITCH_METHOD_CMD)
|
110
|
-
existing_row_num = self.get_num_rows().ok_value.num_response
|
111
|
-
|
112
|
-
def download(self):
|
113
|
-
self.send('Sleep 1')
|
114
|
-
self.sleepy_send("DownloadRCMethod WLS1")
|
115
|
-
self.send('Sleep 1')
|
116
|
-
self.sleepy_send("DownloadLWls 1")
|
117
|
-
self.send('Sleep 1')
|
57
|
+
return InjectorTable(
|
58
|
+
functions=[
|
59
|
+
self.get_row(i) for i in range(int(rows.ok_value.num_response))
|
60
|
+
]
|
61
|
+
)
|