pychemstation 0.10.6__py3-none-any.whl → 0.10.7.dev1__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 +7 -15
- pychemstation/control/README.md +2 -2
- pychemstation/control/controllers/__init__.py +2 -1
- pychemstation/control/controllers/comm.py +40 -13
- pychemstation/control/controllers/data_aq/method.py +19 -22
- pychemstation/control/controllers/data_aq/sequence.py +129 -111
- pychemstation/control/controllers/devices/injector.py +7 -7
- pychemstation/control/hplc.py +57 -60
- pychemstation/utils/__init__.py +23 -0
- pychemstation/utils/{mocking → abc_tables}/abc_comm.py +8 -14
- pychemstation/utils/abc_tables/device.py +27 -0
- pychemstation/{control/controllers → utils}/abc_tables/run.py +69 -34
- pychemstation/{control/controllers → utils}/abc_tables/table.py +29 -22
- pychemstation/utils/macro.py +13 -0
- pychemstation/utils/method_types.py +12 -13
- pychemstation/utils/mocking/mock_comm.py +1 -1
- pychemstation/utils/num_utils.py +3 -3
- pychemstation/utils/sequence_types.py +30 -12
- pychemstation/utils/spec_utils.py +42 -66
- pychemstation/utils/table_types.py +13 -2
- pychemstation/utils/tray_types.py +28 -16
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dev1.dist-info}/METADATA +2 -8
- pychemstation-0.10.7.dev1.dist-info/RECORD +41 -0
- pychemstation/control/controllers/abc_tables/device.py +0 -15
- pychemstation/utils/pump_types.py +0 -7
- pychemstation-0.10.6.dist-info/RECORD +0 -42
- /pychemstation/{control/controllers → utils}/abc_tables/__init__.py +0 -0
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dev1.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dev1.dist-info}/licenses/LICENSE +0 -0
pychemstation/control/hplc.py
CHANGED
@@ -6,6 +6,7 @@ Authors: Lucy Hao
|
|
6
6
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
|
+
import os.path
|
9
10
|
from typing import Dict, List, Optional, Tuple, Union
|
10
11
|
|
11
12
|
from pychemstation.analysis.chromatogram import AgilentChannelChromatogramData
|
@@ -36,9 +37,9 @@ class HPLCController:
|
|
36
37
|
def __init__(
|
37
38
|
self,
|
38
39
|
comm_dir: str,
|
39
|
-
method_dir: str,
|
40
|
-
sequence_dir: str,
|
41
|
-
|
40
|
+
method_dir: Optional[str] = None,
|
41
|
+
sequence_dir: Optional[str] = None,
|
42
|
+
extra_data_dirs: Optional[List[str]] = None,
|
42
43
|
offline: bool = False,
|
43
44
|
debug: bool = False,
|
44
45
|
):
|
@@ -47,37 +48,47 @@ class HPLCController:
|
|
47
48
|
double escaped: "C:\\my_folder\\"
|
48
49
|
|
49
50
|
:param comm_dir: Name of directory for communication, where ChemStation will read and write from. Can be any existing directory.
|
50
|
-
:param data_dirs: Name of directories for storing data after method or sequence runs. Method data dir is default
|
51
51
|
the first one in the list. In other words, the first dir in the list is highest prio. Must be "normal" strings and not r-strings.
|
52
|
-
:param method_dir: Name of directory where method files are stored.
|
53
|
-
:param sequence_dir: Name of directory where sequence files are stored.
|
54
52
|
:raises FileNotFoundError: If either `data_dir`, `method_dir`, `sequence_dir`, `sequence_data_dir`or `comm_dir` is not a valid directory.
|
55
53
|
"""
|
56
54
|
self.comm: CommunicationController = CommunicationController(
|
57
|
-
comm_dir=comm_dir, debug=debug
|
58
|
-
)
|
59
|
-
self.method_controller: MethodController = MethodController(
|
60
|
-
controller=self.comm,
|
61
|
-
src=method_dir,
|
62
|
-
data_dirs=data_dirs,
|
63
|
-
table=self.METHOD_TIMETABLE,
|
64
|
-
offline=offline,
|
65
|
-
injector_controller=InjectorController(
|
66
|
-
controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
|
67
|
-
),
|
68
|
-
)
|
69
|
-
self.sequence_controller: SequenceController = SequenceController(
|
70
|
-
controller=self.comm,
|
71
|
-
src=sequence_dir,
|
72
|
-
data_dirs=data_dirs,
|
73
|
-
table=self.SEQUENCE_TABLE,
|
74
|
-
method_controller=self.method_controller,
|
75
|
-
offline=offline,
|
55
|
+
comm_dir=comm_dir, debug=debug, offline=offline
|
76
56
|
)
|
57
|
+
data_dirs: List[str] = []
|
58
|
+
if not offline:
|
59
|
+
if not method_dir or not sequence_dir or not extra_data_dirs:
|
60
|
+
method_dir, sequence_dir, data_dirs = self.comm.get_chemstation_dirs()
|
61
|
+
if extra_data_dirs:
|
62
|
+
data_dirs.extend(extra_data_dirs)
|
63
|
+
data_dirs = list(set([os.path.normpath(p) for p in data_dirs]))
|
64
|
+
if (method_dir and sequence_dir and data_dirs and not offline) or offline:
|
65
|
+
self.method_controller: MethodController = MethodController(
|
66
|
+
controller=self.comm,
|
67
|
+
src=method_dir,
|
68
|
+
data_dirs=data_dirs,
|
69
|
+
table=self.METHOD_TIMETABLE,
|
70
|
+
offline=offline,
|
71
|
+
injector_controller=InjectorController(
|
72
|
+
controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
|
73
|
+
),
|
74
|
+
)
|
75
|
+
self.sequence_controller: SequenceController = SequenceController(
|
76
|
+
controller=self.comm,
|
77
|
+
src=sequence_dir,
|
78
|
+
data_dirs=data_dirs,
|
79
|
+
table=self.SEQUENCE_TABLE,
|
80
|
+
method_controller=self.method_controller,
|
81
|
+
offline=offline,
|
82
|
+
)
|
83
|
+
elif not offline and (not method_dir or not sequence_dir or not data_dirs):
|
84
|
+
raise ValueError(
|
85
|
+
f"Expected a method dir: {method_dir}, sequence dir: {sequence_dir} and data dirs:{data_dirs} but one was None."
|
86
|
+
)
|
87
|
+
else:
|
88
|
+
raise ValueError("Expected error occured, please try again.")
|
77
89
|
|
78
90
|
def send(self, cmd: Union[Command, str]):
|
79
|
-
"""
|
80
|
-
Sends any Command or string to Chemstation.
|
91
|
+
"""Sends any Command or string to Chemstation.
|
81
92
|
|
82
93
|
:param cmd: the macro to send to Chemstation
|
83
94
|
"""
|
@@ -88,10 +99,9 @@ class HPLCController:
|
|
88
99
|
self.comm.send(cmd)
|
89
100
|
|
90
101
|
def receive(self) -> None | Response | str:
|
91
|
-
"""
|
92
|
-
Get the most recent response from Chemstation.
|
102
|
+
"""Get the most recent response from Chemstation.
|
93
103
|
|
94
|
-
:return: most recent response from
|
104
|
+
:return: most recent response from the most recently sent MACRO that returned a response.
|
95
105
|
"""
|
96
106
|
if not self.comm:
|
97
107
|
raise RuntimeError(
|
@@ -100,8 +110,7 @@ class HPLCController:
|
|
100
110
|
return self.comm.receive().value
|
101
111
|
|
102
112
|
def status(self) -> Status:
|
103
|
-
"""
|
104
|
-
Get the current status of the HPLC machine.
|
113
|
+
"""Get the current status of the HPLC machine.
|
105
114
|
|
106
115
|
:return: current status of the HPLC machine; Status types can be found in `pychemstation.utils.macro`
|
107
116
|
"""
|
@@ -112,8 +121,7 @@ class HPLCController:
|
|
112
121
|
return self.comm.get_status()
|
113
122
|
|
114
123
|
def switch_method(self, method_name: str):
|
115
|
-
"""
|
116
|
-
Allows the user to switch between pre-programmed methods. No need to append '.M'
|
124
|
+
"""Allows the user to switch between pre-programmed methods. No need to append '.M'
|
117
125
|
to the end of the method name. For example. for the method named 'General-Poroshell.M',
|
118
126
|
only 'General-Poroshell' is needed.
|
119
127
|
|
@@ -124,8 +132,7 @@ class HPLCController:
|
|
124
132
|
self.method_controller.switch(method_name)
|
125
133
|
|
126
134
|
def switch_sequence(self, sequence_name: str):
|
127
|
-
"""
|
128
|
-
Allows the user to switch between pre-programmed sequences. The sequence name does not need the '.S' extension.
|
135
|
+
"""Allows the user to switch between pre-programmed sequences. The sequence name does not need the '.S' extension.
|
129
136
|
For example: for the method named 'mySeq.S', only 'mySeq' is needed.
|
130
137
|
|
131
138
|
:param sequence_name: The name of the sequence file
|
@@ -138,8 +145,7 @@ class HPLCController:
|
|
138
145
|
add_timestamp: bool = True,
|
139
146
|
stall_while_running: bool = True,
|
140
147
|
):
|
141
|
-
"""
|
142
|
-
This is the preferred method to trigger a run.
|
148
|
+
"""This is the preferred method to trigger a run.
|
143
149
|
Starts the currently selected method, storing data
|
144
150
|
under the <data_dir>/<experiment_name>.D folder.
|
145
151
|
Device must be ready.
|
@@ -159,8 +165,7 @@ class HPLCController:
|
|
159
165
|
self.method_controller.stop()
|
160
166
|
|
161
167
|
def run_sequence(self, stall_while_running: bool = True):
|
162
|
-
"""
|
163
|
-
Starts the currently loaded sequence, storing data
|
168
|
+
"""Starts the currently loaded sequence, storing data
|
164
169
|
under one of the data_dirs/<sequence table name> folder.
|
165
170
|
Device must be ready.
|
166
171
|
|
@@ -169,24 +174,21 @@ class HPLCController:
|
|
169
174
|
self.sequence_controller.run(stall_while_running=stall_while_running)
|
170
175
|
|
171
176
|
def check_method_complete(self) -> Tuple[float, int]:
|
172
|
-
"""
|
173
|
-
Check if the currently running method (if any) is done.
|
177
|
+
"""Check if the currently running method (if any) is done.
|
174
178
|
|
175
|
-
:returns the percent of the method run completed, and whether the run is complete.
|
179
|
+
:returns: the percent of the method run completed, and whether the run is complete.
|
176
180
|
"""
|
177
181
|
return self.method_controller.check_hplc_run_finished()
|
178
182
|
|
179
183
|
def check_sequence_complete(self) -> Tuple[float, int]:
|
180
|
-
"""
|
181
|
-
Check if the currently running sequence (if any) is done.
|
184
|
+
"""Check if the currently running sequence (if any) is done.
|
182
185
|
|
183
186
|
:return: the percent of the sequence run completed, and whether the run is complete.
|
184
187
|
"""
|
185
188
|
return self.sequence_controller.check_hplc_run_finished()
|
186
189
|
|
187
190
|
def edit_method(self, updated_method: MethodDetails, save: bool = False):
|
188
|
-
"""
|
189
|
-
Updated the currently loaded method in ChemStation with provided values.
|
191
|
+
"""Updated the currently loaded method in ChemStation with provided values.
|
190
192
|
|
191
193
|
:param updated_method: the method with updated values, to be sent to Chemstation to modify the currently loaded method.
|
192
194
|
:param save: whether this method should be saved to disk, or just modified.
|
@@ -194,8 +196,7 @@ class HPLCController:
|
|
194
196
|
self.method_controller.edit(updated_method, save)
|
195
197
|
|
196
198
|
def edit_sequence(self, updated_sequence: SequenceTable):
|
197
|
-
"""
|
198
|
-
Updates the currently loaded sequence table with the provided table, and saves the sequence.
|
199
|
+
"""Updates the currently loaded sequence table with the provided table, and saves the sequence.
|
199
200
|
|
200
201
|
:param updated_sequence: The sequence table to be written to the currently loaded sequence table.
|
201
202
|
"""
|
@@ -206,8 +207,7 @@ class HPLCController:
|
|
206
207
|
custom_path: Optional[str] = None,
|
207
208
|
report_type: ReportType = ReportType.CSV,
|
208
209
|
) -> AgilentReport:
|
209
|
-
"""
|
210
|
-
Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
|
210
|
+
"""Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
|
211
211
|
|
212
212
|
:param custom_path: path to sequence folder
|
213
213
|
:param report_type: read either the TXT or CSV version
|
@@ -220,8 +220,7 @@ class HPLCController:
|
|
220
220
|
def get_last_run_method_data(
|
221
221
|
self, read_uv: bool = False, custom_path: Optional[str] = None
|
222
222
|
) -> Dict[int, AgilentHPLCChromatogram] | AgilentChannelChromatogramData:
|
223
|
-
"""
|
224
|
-
Returns the last run method data.
|
223
|
+
"""Returns the last run method data.
|
225
224
|
|
226
225
|
:param custom_path: If you want to just load method data but from a file path. This file path must be the complete file path.
|
227
226
|
:param read_uv: whether to also read the UV file
|
@@ -236,8 +235,7 @@ class HPLCController:
|
|
236
235
|
custom_path: Optional[str] = None,
|
237
236
|
report_type: ReportType = ReportType.CSV,
|
238
237
|
) -> List[AgilentReport]:
|
239
|
-
"""
|
240
|
-
Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
|
238
|
+
"""Return data contained in the REPORT files. Use `aghplctools` if you want more report processing utility.
|
241
239
|
|
242
240
|
:param custom_path: path to sequence folder
|
243
241
|
:param report_type: read either the TXT or CSV version
|
@@ -252,8 +250,7 @@ class HPLCController:
|
|
252
250
|
) -> (
|
253
251
|
List[Dict[int, AgilentHPLCChromatogram]] | List[AgilentChannelChromatogramData]
|
254
252
|
):
|
255
|
-
"""
|
256
|
-
Returns data for all rows in the last run sequence data.
|
253
|
+
"""Returns data for all rows in the last run sequence data.
|
257
254
|
|
258
255
|
:param custom_path: If you want to just load sequence data but from a file path. This file path must be the complete file path.
|
259
256
|
:param read_uv: whether to also read the UV file
|
@@ -265,11 +262,11 @@ class HPLCController:
|
|
265
262
|
|
266
263
|
def check_loaded_sequence(self) -> str:
|
267
264
|
"""Returns the name of the currently loaded sequence."""
|
268
|
-
return self.sequence_controller.
|
265
|
+
return self.sequence_controller.get_current_sequence_name()
|
269
266
|
|
270
267
|
def check_loaded_method(self) -> str:
|
271
268
|
"""Returns the name of the currently loaded method."""
|
272
|
-
return self.method_controller.
|
269
|
+
return self.method_controller.get_current_method_name()
|
273
270
|
|
274
271
|
def load_method(self) -> MethodDetails:
|
275
272
|
"""Returns details of the currently loaded method, such as its starting modifier conditions and timetable."""
|
@@ -279,7 +276,7 @@ class HPLCController:
|
|
279
276
|
"""Returns the currently loaded sequence."""
|
280
277
|
return self.sequence_controller.load()
|
281
278
|
|
282
|
-
def load_injector_program(self) -> InjectorTable:
|
279
|
+
def load_injector_program(self) -> InjectorTable | None:
|
283
280
|
return self.method_controller.injector_controller.load()
|
284
281
|
|
285
282
|
def standby(self):
|
pychemstation/utils/__init__.py
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
from . import abc_tables
|
2
|
+
from . import injector_types
|
3
|
+
from . import macro
|
4
|
+
from . import method_types
|
5
|
+
from . import num_utils
|
6
|
+
from . import parsing
|
7
|
+
from . import sequence_types
|
8
|
+
from . import spec_utils
|
9
|
+
from . import table_types
|
10
|
+
from . import tray_types
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"abc_tables",
|
14
|
+
"injector_types",
|
15
|
+
"macro",
|
16
|
+
"method_types",
|
17
|
+
"num_utils",
|
18
|
+
"parsing",
|
19
|
+
"sequence_types",
|
20
|
+
"spec_utils",
|
21
|
+
"table_types",
|
22
|
+
"tray_types",
|
23
|
+
]
|
@@ -18,17 +18,17 @@ from typing import Union
|
|
18
18
|
|
19
19
|
from result import Err, Ok, Result
|
20
20
|
|
21
|
-
from
|
22
|
-
HPLCAvailStatus,
|
23
|
-
Command,
|
24
|
-
Status,
|
25
|
-
Response,
|
26
|
-
)
|
21
|
+
from ..macro import HPLCAvailStatus, Command, Response, Status
|
27
22
|
|
28
23
|
|
29
24
|
class ABCCommunicationController(abc.ABC):
|
30
|
-
"""
|
31
|
-
|
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
|
32
32
|
"""
|
33
33
|
|
34
34
|
# maximum command number
|
@@ -42,12 +42,6 @@ class ABCCommunicationController(abc.ABC):
|
|
42
42
|
offline: bool = False,
|
43
43
|
debug: bool = False,
|
44
44
|
):
|
45
|
-
"""
|
46
|
-
:param comm_dir:
|
47
|
-
:param cmd_file: Name of command file
|
48
|
-
:param reply_file: Name of reply file
|
49
|
-
:param debug: whether to save log of sent commands
|
50
|
-
"""
|
51
45
|
if not offline:
|
52
46
|
self.debug = debug
|
53
47
|
if os.path.isdir(comm_dir):
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC
|
4
|
+
|
5
|
+
from .table import ABCTableController
|
6
|
+
from ..table_types import Table
|
7
|
+
from ...control.controllers import CommunicationController
|
8
|
+
|
9
|
+
|
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
|
+
|
18
|
+
def __init__(
|
19
|
+
self, controller: CommunicationController, table: Table, offline: bool
|
20
|
+
):
|
21
|
+
super().__init__(controller=controller, table=table)
|
22
|
+
self.offline = offline
|
23
|
+
|
24
|
+
def __new__(cls, *args, **kwargs):
|
25
|
+
if cls is ABCTableController:
|
26
|
+
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
27
|
+
return object.__new__(cls)
|
@@ -11,60 +11,69 @@ import math
|
|
11
11
|
import os
|
12
12
|
import time
|
13
13
|
import warnings
|
14
|
-
from typing import Dict, List, Optional, Tuple, Union
|
14
|
+
from typing import Dict, List, Optional, Tuple, Union, Set
|
15
15
|
|
16
16
|
import polling
|
17
17
|
import rainbow as rb
|
18
|
-
from result import Err,
|
18
|
+
from result import Err, Ok, Result
|
19
|
+
|
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 (
|
25
|
+
AgilentChannelChromatogramData,
|
26
|
+
AgilentHPLCChromatogram,
|
27
|
+
)
|
19
28
|
|
20
29
|
from .table import ABCTableController
|
21
|
-
from
|
30
|
+
from ...analysis.process_report import (
|
31
|
+
ReportType,
|
22
32
|
AgilentReport,
|
23
33
|
CSVProcessor,
|
24
|
-
ReportType,
|
25
34
|
TXTProcessor,
|
26
35
|
)
|
27
|
-
from
|
28
|
-
from pychemstation.analysis.chromatogram import (
|
29
|
-
AgilentChannelChromatogramData,
|
30
|
-
AgilentHPLCChromatogram,
|
31
|
-
)
|
32
|
-
from ....utils.macro import HPLCRunningStatus
|
33
|
-
from ....utils.method_types import MethodDetails
|
34
|
-
from ....utils.sequence_types import SequenceTable
|
35
|
-
from ....utils.table_types import Table, T
|
36
|
+
from ...control.controllers import CommunicationController
|
36
37
|
|
37
38
|
TableType = Union[MethodDetails, SequenceTable]
|
38
39
|
|
39
40
|
|
40
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
|
+
|
41
51
|
def __init__(
|
42
52
|
self,
|
43
53
|
controller: Optional[CommunicationController],
|
44
|
-
src: str,
|
45
|
-
data_dirs: List[str],
|
54
|
+
src: Optional[str],
|
55
|
+
data_dirs: Optional[List[str]],
|
46
56
|
table: Table,
|
47
57
|
offline: bool = False,
|
48
58
|
):
|
49
59
|
super().__init__(controller=controller, table=table)
|
50
|
-
warnings.warn(
|
51
|
-
"This abstract class is not meant to be initialized. Use MethodController or SequenceController."
|
52
|
-
)
|
53
60
|
self.table_state: Optional[TableType] = None
|
54
61
|
self.curr_run_starting_time: Optional[float] = None
|
55
62
|
self.timeout: Optional[float] = None
|
63
|
+
self.current_run_child_files: Set[str] = set()
|
56
64
|
|
57
65
|
if not offline:
|
58
66
|
if src and not os.path.isdir(src):
|
59
67
|
raise FileNotFoundError(f"dir: {src} not found.")
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
68
77
|
|
69
78
|
self.spectra: dict[str, AgilentHPLCChromatogram] = {
|
70
79
|
"A": AgilentHPLCChromatogram(),
|
@@ -79,8 +88,15 @@ class RunController(ABCTableController, abc.ABC):
|
|
79
88
|
self.uv: Dict[int, AgilentHPLCChromatogram] = {}
|
80
89
|
self.data_files: List = []
|
81
90
|
|
91
|
+
def __new__(cls, *args, **kwargs):
|
92
|
+
if cls is RunController:
|
93
|
+
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
94
|
+
return object.__new__(cls)
|
95
|
+
|
82
96
|
@abc.abstractmethod
|
83
|
-
def
|
97
|
+
def _fuzzy_match_most_recent_folder(
|
98
|
+
self, most_recent_folder: T, child_dirs: Set[str]
|
99
|
+
) -> Result[T, str]:
|
84
100
|
pass
|
85
101
|
|
86
102
|
@abc.abstractmethod
|
@@ -120,6 +136,12 @@ class RunController(ABCTableController, abc.ABC):
|
|
120
136
|
|
121
137
|
def check_hplc_run_finished(self) -> Tuple[float, bool]:
|
122
138
|
if self.controller:
|
139
|
+
try:
|
140
|
+
_, current_run_file = self.get_current_run_data_dir_file()
|
141
|
+
sample_file, extension, _ = current_run_file.partition(".D")
|
142
|
+
self.current_run_child_files.add(sample_file)
|
143
|
+
except Exception:
|
144
|
+
pass
|
123
145
|
done_running = self.controller.check_if_not_running()
|
124
146
|
if self.curr_run_starting_time and self.timeout:
|
125
147
|
time_passed = time.time() - self.curr_run_starting_time
|
@@ -136,11 +158,11 @@ class RunController(ABCTableController, abc.ABC):
|
|
136
158
|
raise ValueError("Controller is offline!")
|
137
159
|
|
138
160
|
def check_hplc_done_running(self) -> Ok[T] | Err[str]:
|
139
|
-
"""
|
140
|
-
Checks if ChemStation has finished running and can read data back
|
161
|
+
"""Checks if ChemStation has finished running and can read data back
|
141
162
|
|
142
163
|
:return: Data file object containing most recent run file information.
|
143
164
|
"""
|
165
|
+
self.current_run_child_files = set()
|
144
166
|
if self.timeout is not None:
|
145
167
|
finished_run = False
|
146
168
|
minutes = math.ceil(self.timeout / 60)
|
@@ -170,7 +192,9 @@ class RunController(ABCTableController, abc.ABC):
|
|
170
192
|
else:
|
171
193
|
raise ValueError("Timeout value is None, no comparison can be made.")
|
172
194
|
|
173
|
-
check_folder = self.
|
195
|
+
check_folder = self._fuzzy_match_most_recent_folder(
|
196
|
+
self.data_files[-1], self.current_run_child_files
|
197
|
+
)
|
174
198
|
if check_folder.is_ok() and finished_run:
|
175
199
|
return check_folder
|
176
200
|
elif check_folder.is_ok():
|
@@ -183,7 +207,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
183
207
|
except Exception:
|
184
208
|
self._reset_time()
|
185
209
|
return self.data_files[-1]
|
186
|
-
return Err("Run
|
210
|
+
return Err("Run not may not have completed.")
|
187
211
|
|
188
212
|
def get_uv_spectrum(self, path: str):
|
189
213
|
data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
|
@@ -212,9 +236,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
212
236
|
raise ValueError("Expected one of ReportType.TXT or ReportType.CSV")
|
213
237
|
|
214
238
|
def get_spectrum_at_channels(self, data_path: str):
|
215
|
-
"""
|
216
|
-
Load chromatogram for any channel in spectra dictionary.
|
217
|
-
"""
|
239
|
+
"""Load chromatogram for any channel in spectra dictionary."""
|
218
240
|
for channel, spec in self.spectra.items():
|
219
241
|
try:
|
220
242
|
spec.load_spectrum(data_path=data_path, channel=channel)
|
@@ -226,3 +248,16 @@ class RunController(ABCTableController, abc.ABC):
|
|
226
248
|
def _reset_time(self):
|
227
249
|
self.curr_run_starting_time = None
|
228
250
|
self.timeout = None
|
251
|
+
|
252
|
+
def get_current_run_data_dir_file(self) -> Tuple[str, str]:
|
253
|
+
self.send(Command.GET_CURRENT_RUN_DATA_DIR)
|
254
|
+
full_path_name = self.receive()
|
255
|
+
self.send(Command.GET_CURRENT_RUN_DATA_FILE)
|
256
|
+
current_sample_file = self.receive()
|
257
|
+
if full_path_name.is_ok() and current_sample_file.is_ok():
|
258
|
+
return (
|
259
|
+
full_path_name.ok_value.string_response,
|
260
|
+
current_sample_file.ok_value.string_response,
|
261
|
+
)
|
262
|
+
else:
|
263
|
+
raise ValueError("Couldn't read data dir and file.")
|
@@ -7,33 +7,39 @@ Authors: Lucy Hao
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
9
|
import abc
|
10
|
-
import warnings
|
11
10
|
from typing import Optional, Union
|
12
11
|
|
13
12
|
from result import Err, Result
|
14
13
|
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
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
|
20
19
|
|
21
20
|
TableType = Union[MethodDetails, SequenceTable]
|
22
21
|
|
23
22
|
|
24
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
|
+
|
25
29
|
def __init__(
|
26
30
|
self,
|
27
31
|
controller: Optional[CommunicationController],
|
28
32
|
table: Table,
|
29
33
|
):
|
30
|
-
warnings.warn(
|
31
|
-
"This abstract class is not meant to be initialized. Use MethodController or SequenceController."
|
32
|
-
)
|
33
34
|
self.controller = controller
|
34
35
|
self.table_locator = table
|
35
36
|
self.table_state: Optional[TableType] = None
|
36
37
|
|
38
|
+
def __new__(cls, *args, **kwargs):
|
39
|
+
if cls is ABCTableController:
|
40
|
+
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
41
|
+
return object.__new__(cls, *args, **kwargs)
|
42
|
+
|
37
43
|
def receive(self) -> Result[Response, str]:
|
38
44
|
if self.controller:
|
39
45
|
for _ in range(10):
|
@@ -59,8 +65,7 @@ class ABCTableController(abc.ABC):
|
|
59
65
|
raise ValueError("Controller is offline")
|
60
66
|
|
61
67
|
def sleep(self, seconds: int):
|
62
|
-
"""
|
63
|
-
Tells the HPLC to wait for a specified number of seconds.
|
68
|
+
"""Tells the HPLC to wait for a specified number of seconds.
|
64
69
|
|
65
70
|
:param seconds: number of seconds to wait
|
66
71
|
"""
|
@@ -93,6 +98,8 @@ class ABCTableController(abc.ABC):
|
|
93
98
|
raise ValueError("Controller is offline")
|
94
99
|
|
95
100
|
def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
|
101
|
+
if not (isinstance(val, int) or isinstance(val, float)):
|
102
|
+
raise ValueError(f"{val} must be an int or float.")
|
96
103
|
self.sleepy_send(
|
97
104
|
TableOperation.NEW_COL_VAL.value.format(
|
98
105
|
register=self.table_locator.register,
|
@@ -103,6 +110,8 @@ class ABCTableController(abc.ABC):
|
|
103
110
|
)
|
104
111
|
|
105
112
|
def add_new_col_text(self, col_name: RegisterFlag, val: str):
|
113
|
+
if not isinstance(val, str):
|
114
|
+
raise ValueError(f"{val} must be a str.")
|
106
115
|
self.sleepy_send(
|
107
116
|
TableOperation.NEW_COL_TEXT.value.format(
|
108
117
|
register=self.table_locator.register,
|
@@ -115,10 +124,12 @@ class ABCTableController(abc.ABC):
|
|
115
124
|
def _edit_row_num(
|
116
125
|
self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
|
117
126
|
):
|
127
|
+
if not (isinstance(val, int) or isinstance(val, float)):
|
128
|
+
raise ValueError(f"{val} must be an int or float.")
|
118
129
|
if row:
|
119
130
|
num_rows = self.get_num_rows()
|
120
131
|
if num_rows.is_ok():
|
121
|
-
if num_rows.
|
132
|
+
if num_rows.ok_value.num_response < row:
|
122
133
|
raise ValueError("Not enough rows to edit!")
|
123
134
|
|
124
135
|
self.sleepy_send(
|
@@ -134,10 +145,12 @@ class ABCTableController(abc.ABC):
|
|
134
145
|
def _edit_row_text(
|
135
146
|
self, col_name: RegisterFlag, val: str, row: Optional[int] = None
|
136
147
|
):
|
148
|
+
if not isinstance(val, str):
|
149
|
+
raise ValueError(f"{val} must be a str.")
|
137
150
|
if row:
|
138
151
|
num_rows = self.get_num_rows()
|
139
152
|
if num_rows.is_ok():
|
140
|
-
if num_rows.
|
153
|
+
if num_rows.ok_value.num_response < row:
|
141
154
|
raise ValueError("Not enough rows to edit!")
|
142
155
|
|
143
156
|
self.sleepy_send(
|
@@ -164,9 +177,7 @@ class ABCTableController(abc.ABC):
|
|
164
177
|
)
|
165
178
|
|
166
179
|
def add_row(self):
|
167
|
-
"""
|
168
|
-
Adds a row to the provided table for currently loaded method or sequence.
|
169
|
-
"""
|
180
|
+
"""Adds a row to the provided table for currently loaded method or sequence."""
|
170
181
|
self.sleepy_send(
|
171
182
|
TableOperation.NEW_ROW.value.format(
|
172
183
|
register=self.table_locator.register, table_name=self.table_locator.name
|
@@ -174,9 +185,7 @@ class ABCTableController(abc.ABC):
|
|
174
185
|
)
|
175
186
|
|
176
187
|
def delete_table(self):
|
177
|
-
"""
|
178
|
-
Deletes the table for the current loaded method or sequence.
|
179
|
-
"""
|
188
|
+
"""Deletes the table."""
|
180
189
|
self.sleepy_send(
|
181
190
|
TableOperation.DELETE_TABLE.value.format(
|
182
191
|
register=self.table_locator.register, table_name=self.table_locator.name
|
@@ -184,9 +193,7 @@ class ABCTableController(abc.ABC):
|
|
184
193
|
)
|
185
194
|
|
186
195
|
def new_table(self):
|
187
|
-
"""
|
188
|
-
Creates the table for the currently loaded method or sequence.
|
189
|
-
"""
|
196
|
+
"""Creates the table."""
|
190
197
|
self.send(
|
191
198
|
TableOperation.CREATE_TABLE.value.format(
|
192
199
|
register=self.table_locator.register, table_name=self.table_locator.name
|