pychemstation 0.10.7__py3-none-any.whl → 0.10.8__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 +14 -15
- pychemstation/analysis/chromatogram.py +7 -8
- pychemstation/analysis/process_report.py +10 -16
- pychemstation/control/README.md +1 -1
- pychemstation/control/controllers/__init__.py +2 -1
- pychemstation/control/controllers/comm.py +33 -27
- pychemstation/control/controllers/data_aq/method.py +233 -79
- pychemstation/control/controllers/data_aq/sequence.py +104 -84
- pychemstation/control/controllers/devices/injector.py +3 -3
- pychemstation/control/hplc.py +53 -41
- pychemstation/utils/__init__.py +23 -0
- pychemstation/{control/controllers → utils}/abc_tables/abc_comm.py +15 -13
- pychemstation/{control/controllers → utils}/abc_tables/device.py +9 -2
- pychemstation/{control/controllers → utils}/abc_tables/run.py +45 -37
- pychemstation/{control/controllers → utils}/abc_tables/table.py +32 -29
- pychemstation/utils/macro.py +7 -2
- pychemstation/utils/method_types.py +13 -14
- pychemstation/utils/mocking/mock_comm.py +25 -2
- pychemstation/utils/mocking/mock_hplc.py +29 -1
- pychemstation/utils/num_utils.py +3 -3
- pychemstation/utils/sequence_types.py +30 -14
- pychemstation/utils/spec_utils.py +42 -66
- pychemstation/utils/table_types.py +15 -2
- pychemstation/utils/tray_types.py +28 -16
- {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/METADATA +1 -7
- pychemstation-0.10.8.dist-info/RECORD +41 -0
- pychemstation/utils/pump_types.py +0 -7
- pychemstation-0.10.7.dist-info/RECORD +0 -42
- /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
- {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.7.dist-info → pychemstation-0.10.8.dist-info}/licenses/LICENSE +0 -0
@@ -18,12 +18,17 @@ from typing import Union
|
|
18
18
|
|
19
19
|
from result import Err, Ok, Result
|
20
20
|
|
21
|
-
from
|
21
|
+
from ..macro import HPLCAvailStatus, Command, Response, Status
|
22
22
|
|
23
23
|
|
24
24
|
class ABCCommunicationController(abc.ABC):
|
25
|
-
"""
|
26
|
-
|
25
|
+
"""Abstract class representing the communication controller.
|
26
|
+
|
27
|
+
:param comm_dir: the complete directory path that was used in the MACRO file, common file that pychemstation and Chemstation use to communicate.
|
28
|
+
:param cmd_file: name of the write file that pychemstation writes MACROs to, in `comm_dir`
|
29
|
+
:param reply_file: name of the read file that Chemstation replies to, in `comm_dir
|
30
|
+
:param offline: whether or not communication with Chemstation is to be established
|
31
|
+
:param debug: if True, prints all send MACROs to an out.txt file
|
27
32
|
"""
|
28
33
|
|
29
34
|
# maximum command number
|
@@ -37,12 +42,6 @@ class ABCCommunicationController(abc.ABC):
|
|
37
42
|
offline: bool = False,
|
38
43
|
debug: bool = False,
|
39
44
|
):
|
40
|
-
"""
|
41
|
-
:param comm_dir:
|
42
|
-
:param cmd_file: Name of command file
|
43
|
-
:param reply_file: Name of reply file
|
44
|
-
:param debug: whether to save log of sent commands
|
45
|
-
"""
|
46
45
|
if not offline:
|
47
46
|
self.debug = debug
|
48
47
|
if os.path.isdir(comm_dir):
|
@@ -58,7 +57,6 @@ class ABCCommunicationController(abc.ABC):
|
|
58
57
|
|
59
58
|
self.reset_cmd_counter()
|
60
59
|
self._most_recent_hplc_status: Status = self.get_status()
|
61
|
-
self.send("Local Rows")
|
62
60
|
|
63
61
|
@abstractmethod
|
64
62
|
def get_num_val(self, cmd: str) -> Union[int, float]:
|
@@ -90,15 +88,19 @@ class ABCCommunicationController(abc.ABC):
|
|
90
88
|
:return: whether the HPLC machine is in a safe state to retrieve data back."""
|
91
89
|
self.set_status()
|
92
90
|
hplc_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
93
|
-
time.sleep(
|
91
|
+
time.sleep(10)
|
94
92
|
self.set_status()
|
95
93
|
hplc_actually_avail = isinstance(self._most_recent_hplc_status, HPLCAvailStatus)
|
96
|
-
|
94
|
+
time.sleep(10)
|
95
|
+
self.set_status()
|
96
|
+
hplc_final_check_avail = isinstance(
|
97
|
+
self._most_recent_hplc_status, HPLCAvailStatus
|
98
|
+
)
|
99
|
+
return hplc_avail and hplc_actually_avail and hplc_final_check_avail
|
97
100
|
|
98
101
|
def sleepy_send(self, cmd: Union[Command, str]):
|
99
102
|
self.send("Sleep 0.1")
|
100
103
|
self.send(cmd)
|
101
|
-
self.send("Sleep 0.1")
|
102
104
|
|
103
105
|
def send(self, cmd: Union[Command, str]):
|
104
106
|
"""Sends a command to Chemstation.
|
@@ -2,12 +2,19 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from abc import ABC
|
4
4
|
|
5
|
-
from ....control.controllers import CommunicationController
|
6
|
-
from ....utils.table_types import Table
|
7
5
|
from .table import ABCTableController
|
6
|
+
from ..table_types import Table
|
7
|
+
from ...control.controllers import CommunicationController
|
8
8
|
|
9
9
|
|
10
10
|
class DeviceController(ABCTableController, ABC):
|
11
|
+
"""Abstract controller representing tables that contain device information.
|
12
|
+
|
13
|
+
:param controller: controller for sending MACROs
|
14
|
+
:param table: contains register keys for accessing table in Chemstation
|
15
|
+
:param offline: whether the communication controller is online.
|
16
|
+
"""
|
17
|
+
|
11
18
|
def __init__(
|
12
19
|
self, controller: CommunicationController, table: Table, offline: bool
|
13
20
|
):
|
@@ -17,33 +17,42 @@ import polling
|
|
17
17
|
import rainbow as rb
|
18
18
|
from result import Err, Ok, Result
|
19
19
|
|
20
|
-
from
|
20
|
+
from ..macro import HPLCRunningStatus, Command
|
21
|
+
from ..method_types import MethodDetails
|
22
|
+
from ..sequence_types import SequenceTable
|
23
|
+
from ..table_types import Table, T
|
24
|
+
from ...analysis.chromatogram import (
|
21
25
|
AgilentChannelChromatogramData,
|
22
26
|
AgilentHPLCChromatogram,
|
23
27
|
)
|
24
28
|
|
25
|
-
from
|
29
|
+
from .table import ABCTableController
|
30
|
+
from ...analysis.process_report import (
|
31
|
+
ReportType,
|
26
32
|
AgilentReport,
|
27
33
|
CSVProcessor,
|
28
|
-
ReportType,
|
29
34
|
TXTProcessor,
|
30
35
|
)
|
31
|
-
from
|
32
|
-
from ....utils.macro import Command, HPLCRunningStatus
|
33
|
-
from ....utils.method_types import MethodDetails
|
34
|
-
from ....utils.sequence_types import SequenceTable
|
35
|
-
from ....utils.table_types import T, Table
|
36
|
-
from .table import ABCTableController
|
36
|
+
from ...control.controllers import CommunicationController
|
37
37
|
|
38
38
|
TableType = Union[MethodDetails, SequenceTable]
|
39
39
|
|
40
40
|
|
41
41
|
class RunController(ABCTableController, abc.ABC):
|
42
|
+
"""Abstract controller for all tables that can trigger runs on Chemstation.
|
43
|
+
|
44
|
+
:param controller: the controller for sending MACROs, must be initialized for a run to be triggered.
|
45
|
+
:param src: complete directory path where files containing run parameters are stored.
|
46
|
+
:param data_dirs: list of complete directories that Chemstation will write data to.
|
47
|
+
:param table: contains register keys for accessing table in Chemstation.
|
48
|
+
:param offline: whether the communication controller is online.
|
49
|
+
"""
|
50
|
+
|
42
51
|
def __init__(
|
43
52
|
self,
|
44
53
|
controller: Optional[CommunicationController],
|
45
|
-
src: str,
|
46
|
-
data_dirs: List[str],
|
54
|
+
src: Optional[str],
|
55
|
+
data_dirs: Optional[List[str]],
|
47
56
|
table: Table,
|
48
57
|
offline: bool = False,
|
49
58
|
):
|
@@ -56,14 +65,15 @@ class RunController(ABCTableController, abc.ABC):
|
|
56
65
|
if not offline:
|
57
66
|
if src and not os.path.isdir(src):
|
58
67
|
raise FileNotFoundError(f"dir: {src} not found.")
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
68
|
+
if data_dirs:
|
69
|
+
for d in data_dirs:
|
70
|
+
if not os.path.isdir(d):
|
71
|
+
raise FileNotFoundError(f"dir: {d} not found.")
|
72
|
+
if r"\\" in d:
|
73
|
+
raise ValueError("Data directories should not be raw strings!")
|
74
|
+
if src and data_dirs:
|
75
|
+
self.src: str = src
|
76
|
+
self.data_dirs: List[str] = data_dirs
|
67
77
|
|
68
78
|
self.spectra: dict[str, AgilentHPLCChromatogram] = {
|
69
79
|
"A": AgilentHPLCChromatogram(),
|
@@ -84,9 +94,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
84
94
|
return object.__new__(cls)
|
85
95
|
|
86
96
|
@abc.abstractmethod
|
87
|
-
def
|
88
|
-
self, most_recent_folder: T, child_files: Set[str]
|
89
|
-
) -> Result[T, str]:
|
97
|
+
def _fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
90
98
|
pass
|
91
99
|
|
92
100
|
@abc.abstractmethod
|
@@ -148,8 +156,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
148
156
|
raise ValueError("Controller is offline!")
|
149
157
|
|
150
158
|
def check_hplc_done_running(self) -> Ok[T] | Err[str]:
|
151
|
-
"""
|
152
|
-
Checks if ChemStation has finished running and can read data back
|
159
|
+
"""Checks if ChemStation has finished running and can read data back
|
153
160
|
|
154
161
|
:return: Data file object containing most recent run file information.
|
155
162
|
"""
|
@@ -183,9 +190,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
183
190
|
else:
|
184
191
|
raise ValueError("Timeout value is None, no comparison can be made.")
|
185
192
|
|
186
|
-
check_folder = self.
|
187
|
-
self.data_files[-1], self.current_run_child_files
|
188
|
-
)
|
193
|
+
check_folder = self._fuzzy_match_most_recent_folder(self.data_files[-1])
|
189
194
|
if check_folder.is_ok() and finished_run:
|
190
195
|
return check_folder
|
191
196
|
elif check_folder.is_ok():
|
@@ -198,7 +203,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
198
203
|
except Exception:
|
199
204
|
self._reset_time()
|
200
205
|
return self.data_files[-1]
|
201
|
-
return Err("Run
|
206
|
+
return Err("Run not may not have completed.")
|
202
207
|
|
203
208
|
def get_uv_spectrum(self, path: str):
|
204
209
|
data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
|
@@ -227,9 +232,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
227
232
|
raise ValueError("Expected one of ReportType.TXT or ReportType.CSV")
|
228
233
|
|
229
234
|
def get_spectrum_at_channels(self, data_path: str):
|
230
|
-
"""
|
231
|
-
Load chromatogram for any channel in spectra dictionary.
|
232
|
-
"""
|
235
|
+
"""Load chromatogram for any channel in spectra dictionary."""
|
233
236
|
for channel, spec in self.spectra.items():
|
234
237
|
try:
|
235
238
|
spec.load_spectrum(data_path=data_path, channel=channel)
|
@@ -248,9 +251,14 @@ class RunController(ABCTableController, abc.ABC):
|
|
248
251
|
self.send(Command.GET_CURRENT_RUN_DATA_FILE)
|
249
252
|
current_sample_file = self.receive()
|
250
253
|
if full_path_name.is_ok() and current_sample_file.is_ok():
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
254
|
+
if os.path.isdir(full_path_name.ok_value.string_response) and os.path.isdir(
|
255
|
+
os.path.join(
|
256
|
+
full_path_name.ok_value.string_response,
|
257
|
+
current_sample_file.ok_value.string_response,
|
258
|
+
)
|
259
|
+
):
|
260
|
+
return (
|
261
|
+
full_path_name.ok_value.string_response,
|
262
|
+
current_sample_file.ok_value.string_response,
|
263
|
+
)
|
264
|
+
raise ValueError("Couldn't read data dir and file or doesn't exist yet.")
|
@@ -11,16 +11,21 @@ from typing import Optional, Union
|
|
11
11
|
|
12
12
|
from result import Err, Result
|
13
13
|
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
14
|
+
from ..macro import Command, Response
|
15
|
+
from ..method_types import MethodDetails
|
16
|
+
from ..sequence_types import SequenceTable
|
17
|
+
from ..table_types import Table, RegisterFlag, TableOperation
|
18
|
+
from ...control.controllers import CommunicationController
|
19
19
|
|
20
20
|
TableType = Union[MethodDetails, SequenceTable]
|
21
21
|
|
22
22
|
|
23
23
|
class ABCTableController(abc.ABC):
|
24
|
+
"""Abstract controller for all table-like objects in Chemstation.
|
25
|
+
:param controller: controller for sending MACROs to Chemstation
|
26
|
+
:param table: contains register keys needed for accessing table in Chemstation.
|
27
|
+
"""
|
28
|
+
|
24
29
|
def __init__(
|
25
30
|
self,
|
26
31
|
controller: Optional[CommunicationController],
|
@@ -60,8 +65,7 @@ class ABCTableController(abc.ABC):
|
|
60
65
|
raise ValueError("Controller is offline")
|
61
66
|
|
62
67
|
def sleep(self, seconds: int):
|
63
|
-
"""
|
64
|
-
Tells the HPLC to wait for a specified number of seconds.
|
68
|
+
"""Tells the HPLC to wait for a specified number of seconds.
|
65
69
|
|
66
70
|
:param seconds: number of seconds to wait
|
67
71
|
"""
|
@@ -122,8 +126,8 @@ class ABCTableController(abc.ABC):
|
|
122
126
|
):
|
123
127
|
if not (isinstance(val, int) or isinstance(val, float)):
|
124
128
|
raise ValueError(f"{val} must be an int or float.")
|
129
|
+
num_rows = self.get_num_rows()
|
125
130
|
if row:
|
126
|
-
num_rows = self.get_num_rows()
|
127
131
|
if num_rows.is_ok():
|
128
132
|
if num_rows.ok_value.num_response < row:
|
129
133
|
raise ValueError("Not enough rows to edit!")
|
@@ -132,7 +136,7 @@ class ABCTableController(abc.ABC):
|
|
132
136
|
TableOperation.EDIT_ROW_VAL.value.format(
|
133
137
|
register=self.table_locator.register,
|
134
138
|
table_name=self.table_locator.name,
|
135
|
-
row=row if row is not None else "
|
139
|
+
row=row if row is not None else "response_num",
|
136
140
|
col_name=col_name,
|
137
141
|
val=val,
|
138
142
|
)
|
@@ -143,8 +147,8 @@ class ABCTableController(abc.ABC):
|
|
143
147
|
):
|
144
148
|
if not isinstance(val, str):
|
145
149
|
raise ValueError(f"{val} must be a str.")
|
150
|
+
num_rows = self.get_num_rows()
|
146
151
|
if row:
|
147
|
-
num_rows = self.get_num_rows()
|
148
152
|
if num_rows.is_ok():
|
149
153
|
if num_rows.ok_value.num_response < row:
|
150
154
|
raise ValueError("Not enough rows to edit!")
|
@@ -153,7 +157,7 @@ class ABCTableController(abc.ABC):
|
|
153
157
|
TableOperation.EDIT_ROW_TEXT.value.format(
|
154
158
|
register=self.table_locator.register,
|
155
159
|
table_name=self.table_locator.name,
|
156
|
-
row=row if row is not None else "
|
160
|
+
row=row if row is not None else "response_num",
|
157
161
|
col_name=col_name,
|
158
162
|
val=val,
|
159
163
|
)
|
@@ -173,19 +177,19 @@ class ABCTableController(abc.ABC):
|
|
173
177
|
)
|
174
178
|
|
175
179
|
def add_row(self):
|
176
|
-
"""
|
177
|
-
|
178
|
-
"""
|
180
|
+
"""Adds a row to the provided table for currently loaded method or sequence."""
|
181
|
+
previous_row_count = self.get_num_rows().ok_value.num_response
|
179
182
|
self.sleepy_send(
|
180
183
|
TableOperation.NEW_ROW.value.format(
|
181
184
|
register=self.table_locator.register, table_name=self.table_locator.name
|
182
185
|
)
|
183
186
|
)
|
187
|
+
new_row_count = self.get_num_rows().ok_value.num_response
|
188
|
+
if previous_row_count + 1 != new_row_count:
|
189
|
+
raise ValueError("Row could not be added.")
|
184
190
|
|
185
191
|
def delete_table(self):
|
186
|
-
"""
|
187
|
-
Deletes the table for the current loaded method or sequence.
|
188
|
-
"""
|
192
|
+
"""Deletes the table."""
|
189
193
|
self.sleepy_send(
|
190
194
|
TableOperation.DELETE_TABLE.value.format(
|
191
195
|
register=self.table_locator.register, table_name=self.table_locator.name
|
@@ -193,9 +197,7 @@ class ABCTableController(abc.ABC):
|
|
193
197
|
)
|
194
198
|
|
195
199
|
def new_table(self):
|
196
|
-
"""
|
197
|
-
Creates the table for the currently loaded method or sequence.
|
198
|
-
"""
|
200
|
+
"""Creates the table."""
|
199
201
|
self.send(
|
200
202
|
TableOperation.CREATE_TABLE.value.format(
|
201
203
|
register=self.table_locator.register, table_name=self.table_locator.name
|
@@ -203,13 +205,6 @@ class ABCTableController(abc.ABC):
|
|
203
205
|
)
|
204
206
|
|
205
207
|
def get_num_rows(self) -> Result[Response, str]:
|
206
|
-
self.send(
|
207
|
-
TableOperation.GET_NUM_ROWS.value.format(
|
208
|
-
register=self.table_locator.register,
|
209
|
-
table_name=self.table_locator.name,
|
210
|
-
col_name=RegisterFlag.NUM_ROWS,
|
211
|
-
)
|
212
|
-
)
|
213
208
|
self.send(
|
214
209
|
Command.GET_ROWS_CMD.value.format(
|
215
210
|
register=self.table_locator.register,
|
@@ -223,8 +218,16 @@ class ABCTableController(abc.ABC):
|
|
223
218
|
raise ValueError("Controller is offline")
|
224
219
|
|
225
220
|
if res.is_ok():
|
226
|
-
self.send("Sleep 0.1")
|
227
|
-
self.send("Print Rows")
|
228
221
|
return res
|
229
222
|
else:
|
230
223
|
return Err("No rows could be read.")
|
224
|
+
|
225
|
+
def move_row(self, from_row: int, to_row: int):
|
226
|
+
self.send(
|
227
|
+
TableOperation.MOVE_ROW.value.format(
|
228
|
+
register=self.table_locator.register,
|
229
|
+
table_name=self.table_locator.name,
|
230
|
+
from_row=from_row,
|
231
|
+
to_row=to_row,
|
232
|
+
)
|
233
|
+
)
|
pychemstation/utils/macro.py
CHANGED
@@ -7,6 +7,8 @@ from typing import Union
|
|
7
7
|
|
8
8
|
@dataclass
|
9
9
|
class Response:
|
10
|
+
"""Class representing a response from Chemstation"""
|
11
|
+
|
10
12
|
string_response: str
|
11
13
|
num_response: Union[int, float]
|
12
14
|
|
@@ -53,12 +55,15 @@ class Command(Enum):
|
|
53
55
|
# Get directories
|
54
56
|
GET_METHOD_DIR = "response$ = _METHPATH$"
|
55
57
|
GET_SEQUENCE_DIR = "response$ = _SEQUENCEPATHS$"
|
58
|
+
CONFIG_MET_PATH = "_CONFIGMETPATH"
|
59
|
+
CONFIG_SEQ_PATH = "_CONFIGSEQPATH"
|
60
|
+
GET_RUNNING_SEQUENCE_DIR = "response$ = _SEQPATHS$"
|
56
61
|
GET_DATA_DIRS = "response$ = _DATAPATHS$"
|
57
62
|
GET_CURRENT_RUN_DATA_DIR = "response$ = _DATAPath$"
|
58
63
|
GET_CURRENT_RUN_DATA_FILE = "response$ = _DATAFILE1$"
|
59
64
|
|
60
|
-
#
|
61
|
-
ERROR = "response$ =
|
65
|
+
# Errors
|
66
|
+
ERROR = "response$ = _ERRMSG$"
|
62
67
|
|
63
68
|
|
64
69
|
class HPLCRunningStatus(Enum):
|
@@ -4,8 +4,6 @@ from dataclasses import dataclass
|
|
4
4
|
from enum import Enum
|
5
5
|
from typing import Any, Optional, Union
|
6
6
|
|
7
|
-
from ..generated import Signal
|
8
|
-
from .injector_types import InjectorTable
|
9
7
|
from .table_types import RegisterFlag
|
10
8
|
|
11
9
|
|
@@ -23,6 +21,8 @@ class Param:
|
|
23
21
|
|
24
22
|
@dataclass
|
25
23
|
class HPLCMethodParams:
|
24
|
+
"""The starting conditions of a run that are displayed in the Chemstation GUI for the pump."""
|
25
|
+
|
26
26
|
organic_modifier: float
|
27
27
|
flow: float
|
28
28
|
pressure: Optional[float] = None # TODO: find this
|
@@ -30,28 +30,27 @@ class HPLCMethodParams:
|
|
30
30
|
|
31
31
|
@dataclass
|
32
32
|
class TimeTableEntry:
|
33
|
+
"""Row in a method timetable."""
|
34
|
+
|
33
35
|
start_time: float
|
34
|
-
organic_modifer: Optional[float]
|
36
|
+
organic_modifer: Optional[float] = None
|
35
37
|
flow: Optional[float] = None
|
36
38
|
|
37
39
|
|
38
40
|
@dataclass
|
39
41
|
class MethodDetails:
|
40
|
-
"""An Agilent Chemstation method
|
41
|
-
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
based on settings in the first row of the timetable.
|
47
|
-
:
|
48
|
-
:attribute dad_wavelengthes:
|
42
|
+
"""An Agilent Chemstation method
|
43
|
+
|
44
|
+
:param name: the name of the method, should be the same as the Chemstation method name.
|
45
|
+
:param timetable: list of entries in the method timetable
|
46
|
+
:param stop_time: the time the method stops running after the last timetable entry. If `None`, won't be set.
|
47
|
+
:param post_time: the amount of time after the stoptime that the pumps keep running,
|
48
|
+
based on settings in the first row of the timetable. If `None`, won't be set.
|
49
|
+
:param params: the organic modifier (pump B) and flow rate displayed for the method (the time 0 settings)
|
49
50
|
"""
|
50
51
|
|
51
52
|
name: str
|
52
53
|
params: HPLCMethodParams
|
53
54
|
timetable: list[TimeTableEntry]
|
54
|
-
injector_program: Optional[InjectorTable] = None
|
55
55
|
stop_time: Optional[float] = None
|
56
56
|
post_time: Optional[float] = None
|
57
|
-
dad_wavelengthes: Optional[list[Signal]] = None
|
@@ -1,5 +1,28 @@
|
|
1
|
-
from
|
1
|
+
from typing import Union
|
2
|
+
|
3
|
+
from result import Result
|
4
|
+
|
5
|
+
from .mock_hplc import MockHPLC
|
6
|
+
from ..abc_tables.abc_comm import ABCCommunicationController
|
7
|
+
from ..macro import Status
|
2
8
|
|
3
9
|
|
4
10
|
class MockCommunicationController(ABCCommunicationController):
|
5
|
-
|
11
|
+
def __init__(self, comm_dir: str):
|
12
|
+
super().__init__(comm_dir)
|
13
|
+
self.hplc = MockHPLC()
|
14
|
+
|
15
|
+
def get_num_val(self, cmd: str) -> Union[int, float]:
|
16
|
+
raise NotImplementedError
|
17
|
+
|
18
|
+
def get_text_val(self, cmd: str) -> str:
|
19
|
+
raise NotImplementedError
|
20
|
+
|
21
|
+
def get_status(self) -> Status:
|
22
|
+
raise NotImplementedError
|
23
|
+
|
24
|
+
def _send(self, cmd: str, cmd_no: int, num_attempts=5) -> None:
|
25
|
+
raise NotImplementedError
|
26
|
+
|
27
|
+
def _receive(self, cmd_no: int, num_attempts=100) -> Result[str, str]:
|
28
|
+
raise NotImplementedError
|
@@ -1,2 +1,30 @@
|
|
1
|
+
from ..macro import HPLCAvailStatus, Status
|
2
|
+
from ..method_types import MethodDetails, HPLCMethodParams, TimeTableEntry
|
3
|
+
from ..sequence_types import SequenceTable, SequenceEntry, InjectionSource
|
4
|
+
from ..tray_types import FiftyFourVialPlate
|
5
|
+
|
6
|
+
|
1
7
|
class MockHPLC:
|
2
|
-
|
8
|
+
def __init__(self):
|
9
|
+
self.current_method: MethodDetails = MethodDetails(
|
10
|
+
name="General-Poroshell",
|
11
|
+
params=HPLCMethodParams(organic_modifier=5, flow=0.65),
|
12
|
+
timetable=[TimeTableEntry(start_time=3, organic_modifer=99, flow=0.65)],
|
13
|
+
stop_time=5,
|
14
|
+
post_time=2,
|
15
|
+
)
|
16
|
+
self.current_sequence: SequenceTable = SequenceTable(
|
17
|
+
name="hplc_testing",
|
18
|
+
rows=[
|
19
|
+
SequenceEntry(
|
20
|
+
vial_location=FiftyFourVialPlate.from_str("P1-A2"),
|
21
|
+
sample_name="sample1",
|
22
|
+
data_file="sample1",
|
23
|
+
method="General-Poroshell",
|
24
|
+
num_inj=1,
|
25
|
+
inj_vol=1,
|
26
|
+
inj_source=InjectionSource.HIP_ALS,
|
27
|
+
)
|
28
|
+
],
|
29
|
+
)
|
30
|
+
self.current_status: Status = HPLCAvailStatus.STANDBY
|
pychemstation/utils/num_utils.py
CHANGED
@@ -18,7 +18,7 @@ def find_nearest_value_index(array, value) -> Tuple[float, int]:
|
|
18
18
|
return array[index_], index_
|
19
19
|
|
20
20
|
|
21
|
-
def interpolate_to_index(array, ids, precision: int = 100) -> np.
|
21
|
+
def interpolate_to_index(array, ids, precision: int = 100) -> np.ndarray:
|
22
22
|
"""Find value in between arrays elements.
|
23
23
|
|
24
24
|
Constructs linspace of size "precision" between index+1 and index to
|
@@ -37,8 +37,8 @@ def interpolate_to_index(array, ids, precision: int = 100) -> np.array:
|
|
37
37
|
:return: New array with interpolated values according to provided indexes "ids".
|
38
38
|
|
39
39
|
Example:
|
40
|
-
>>> interpolate_to_index(np.array([1.5]), np.array([1,2,3], 100))
|
41
|
-
array([2.50505051])
|
40
|
+
>>> interpolate_to_index(np.array([1.5]), np.array([1, 2, 3], 100))
|
41
|
+
... array([2.50505051])
|
42
42
|
"""
|
43
43
|
|
44
44
|
# breaking ids into fractional and integral parts
|
@@ -9,9 +9,15 @@ from pychemstation.utils.tray_types import Tray
|
|
9
9
|
|
10
10
|
@dataclass
|
11
11
|
class SequenceDataFiles:
|
12
|
+
"""Class to represent files generated during a sequence.
|
13
|
+
|
14
|
+
:param sequence_name: the name of the sequence that is running
|
15
|
+
:param dir: the complete path of the directory generated for the sequence
|
16
|
+
:param child_dirs: the complete path of the files for sequence runs, contains the Chemstation data, `dir` and the sample run file.
|
17
|
+
"""
|
18
|
+
|
12
19
|
sequence_name: str
|
13
20
|
dir: str
|
14
|
-
_data_files: List[str] = field(default_factory=list)
|
15
21
|
child_dirs: List[str] = field(default_factory=list)
|
16
22
|
|
17
23
|
|
@@ -39,6 +45,8 @@ class InjectionSource(Enum):
|
|
39
45
|
|
40
46
|
@dataclass
|
41
47
|
class SequenceEntry:
|
48
|
+
"""Class to represent each row of a sequence file, maps one to one to Chemstation."""
|
49
|
+
|
42
50
|
data_file: str
|
43
51
|
vial_location: Tray
|
44
52
|
sample_name: Optional[str] = None
|
@@ -51,23 +59,31 @@ class SequenceEntry:
|
|
51
59
|
|
52
60
|
@dataclass
|
53
61
|
class SequenceTable:
|
62
|
+
"""Class to represent a sequence file.
|
63
|
+
|
64
|
+
:param name: name of the sequence
|
65
|
+
:param rows: the entries
|
66
|
+
"""
|
67
|
+
|
54
68
|
name: str
|
55
69
|
rows: list[SequenceEntry]
|
56
70
|
|
57
|
-
def __eq__(self, other):
|
58
|
-
equal = False
|
71
|
+
def __eq__(self, other) -> bool:
|
59
72
|
if not isinstance(other, SequenceTable):
|
60
73
|
return False
|
61
74
|
|
75
|
+
equal = True
|
62
76
|
for self_row, other_row in zip(self.rows, other.rows):
|
63
|
-
equal
|
64
|
-
equal
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
equal
|
71
|
-
equal
|
72
|
-
equal
|
73
|
-
equal
|
77
|
+
equal &= self_row.vial_location == other_row.vial_location
|
78
|
+
equal &= self_row.data_file == other_row.data_file
|
79
|
+
if self_row.method and other_row.method:
|
80
|
+
equal &= (
|
81
|
+
os.path.split(os.path.normpath(self_row.method))[-1]
|
82
|
+
== os.path.split(os.path.normpath(other_row.method))[-1]
|
83
|
+
)
|
84
|
+
equal &= self_row.num_inj == other_row.num_inj
|
85
|
+
equal &= self_row.inj_vol == other_row.inj_vol
|
86
|
+
equal &= self_row.inj_source == other_row.inj_source
|
87
|
+
equal &= self_row.sample_name == other_row.sample_name
|
88
|
+
equal &= self_row.sample_type == other_row.sample_type
|
89
|
+
return equal
|