pychemstation 0.10.6__py3-none-any.whl → 0.10.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/control/README.md +1 -1
- pychemstation/{utils/mocking → control/controllers/abc_tables}/abc_comm.py +1 -6
- pychemstation/control/controllers/abc_tables/device.py +5 -0
- pychemstation/control/controllers/abc_tables/run.py +42 -14
- pychemstation/control/controllers/abc_tables/table.py +15 -6
- pychemstation/control/controllers/comm.py +26 -5
- pychemstation/control/controllers/data_aq/method.py +10 -13
- pychemstation/control/controllers/data_aq/sequence.py +118 -88
- pychemstation/control/controllers/devices/injector.py +4 -4
- pychemstation/control/hplc.py +38 -26
- pychemstation/utils/macro.py +11 -0
- pychemstation/utils/mocking/mock_comm.py +1 -1
- pychemstation/utils/sequence_types.py +3 -2
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dist-info}/METADATA +3 -3
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dist-info}/RECORD +17 -17
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dist-info}/WHEEL +0 -0
- {pychemstation-0.10.6.dist-info → pychemstation-0.10.7.dist-info}/licenses/LICENSE +0 -0
pychemstation/control/README.md
CHANGED
@@ -10,7 +10,7 @@ DATA_DIR_2 = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data"
|
|
10
10
|
DATA_DIR_3 = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data"
|
11
11
|
|
12
12
|
# Initialize HPLC Controller
|
13
|
-
hplc_controller = HPLCController(
|
13
|
+
hplc_controller = HPLCController(extra_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)
|
@@ -18,12 +18,7 @@ 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 ....utils.macro import Status, HPLCAvailStatus, Command, Response
|
27
22
|
|
28
23
|
|
29
24
|
class ABCCommunicationController(abc.ABC):
|
@@ -13,3 +13,8 @@ class DeviceController(ABCTableController, ABC):
|
|
13
13
|
):
|
14
14
|
super().__init__(controller=controller, table=table)
|
15
15
|
self.offline = offline
|
16
|
+
|
17
|
+
def __new__(cls, *args, **kwargs):
|
18
|
+
if cls is ABCTableController:
|
19
|
+
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
20
|
+
return object.__new__(cls)
|
@@ -11,13 +11,17 @@ 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 pychemstation.analysis.chromatogram import (
|
21
|
+
AgilentChannelChromatogramData,
|
22
|
+
AgilentHPLCChromatogram,
|
23
|
+
)
|
19
24
|
|
20
|
-
from .table import ABCTableController
|
21
25
|
from ....analysis.process_report import (
|
22
26
|
AgilentReport,
|
23
27
|
CSVProcessor,
|
@@ -25,14 +29,11 @@ from ....analysis.process_report import (
|
|
25
29
|
TXTProcessor,
|
26
30
|
)
|
27
31
|
from ....control.controllers.comm import CommunicationController
|
28
|
-
from
|
29
|
-
AgilentChannelChromatogramData,
|
30
|
-
AgilentHPLCChromatogram,
|
31
|
-
)
|
32
|
-
from ....utils.macro import HPLCRunningStatus
|
32
|
+
from ....utils.macro import Command, HPLCRunningStatus
|
33
33
|
from ....utils.method_types import MethodDetails
|
34
34
|
from ....utils.sequence_types import SequenceTable
|
35
|
-
from ....utils.table_types import
|
35
|
+
from ....utils.table_types import T, Table
|
36
|
+
from .table import ABCTableController
|
36
37
|
|
37
38
|
TableType = Union[MethodDetails, SequenceTable]
|
38
39
|
|
@@ -47,12 +48,10 @@ class RunController(ABCTableController, abc.ABC):
|
|
47
48
|
offline: bool = False,
|
48
49
|
):
|
49
50
|
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
51
|
self.table_state: Optional[TableType] = None
|
54
52
|
self.curr_run_starting_time: Optional[float] = None
|
55
53
|
self.timeout: Optional[float] = None
|
54
|
+
self.current_run_child_files: Set[str] = set()
|
56
55
|
|
57
56
|
if not offline:
|
58
57
|
if src and not os.path.isdir(src):
|
@@ -79,8 +78,15 @@ class RunController(ABCTableController, abc.ABC):
|
|
79
78
|
self.uv: Dict[int, AgilentHPLCChromatogram] = {}
|
80
79
|
self.data_files: List = []
|
81
80
|
|
81
|
+
def __new__(cls, *args, **kwargs):
|
82
|
+
if cls is RunController:
|
83
|
+
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
84
|
+
return object.__new__(cls)
|
85
|
+
|
82
86
|
@abc.abstractmethod
|
83
|
-
def fuzzy_match_most_recent_folder(
|
87
|
+
def fuzzy_match_most_recent_folder(
|
88
|
+
self, most_recent_folder: T, child_files: Set[str]
|
89
|
+
) -> Result[T, str]:
|
84
90
|
pass
|
85
91
|
|
86
92
|
@abc.abstractmethod
|
@@ -120,6 +126,12 @@ class RunController(ABCTableController, abc.ABC):
|
|
120
126
|
|
121
127
|
def check_hplc_run_finished(self) -> Tuple[float, bool]:
|
122
128
|
if self.controller:
|
129
|
+
try:
|
130
|
+
_, current_run_file = self.get_current_run_data_dir_file()
|
131
|
+
sample_file, extension, _ = current_run_file.partition(".D")
|
132
|
+
self.current_run_child_files.add(sample_file)
|
133
|
+
except Exception:
|
134
|
+
pass
|
123
135
|
done_running = self.controller.check_if_not_running()
|
124
136
|
if self.curr_run_starting_time and self.timeout:
|
125
137
|
time_passed = time.time() - self.curr_run_starting_time
|
@@ -141,6 +153,7 @@ class RunController(ABCTableController, abc.ABC):
|
|
141
153
|
|
142
154
|
:return: Data file object containing most recent run file information.
|
143
155
|
"""
|
156
|
+
self.current_run_child_files = set()
|
144
157
|
if self.timeout is not None:
|
145
158
|
finished_run = False
|
146
159
|
minutes = math.ceil(self.timeout / 60)
|
@@ -170,7 +183,9 @@ class RunController(ABCTableController, abc.ABC):
|
|
170
183
|
else:
|
171
184
|
raise ValueError("Timeout value is None, no comparison can be made.")
|
172
185
|
|
173
|
-
check_folder = self.fuzzy_match_most_recent_folder(
|
186
|
+
check_folder = self.fuzzy_match_most_recent_folder(
|
187
|
+
self.data_files[-1], self.current_run_child_files
|
188
|
+
)
|
174
189
|
if check_folder.is_ok() and finished_run:
|
175
190
|
return check_folder
|
176
191
|
elif check_folder.is_ok():
|
@@ -226,3 +241,16 @@ class RunController(ABCTableController, abc.ABC):
|
|
226
241
|
def _reset_time(self):
|
227
242
|
self.curr_run_starting_time = None
|
228
243
|
self.timeout = None
|
244
|
+
|
245
|
+
def get_current_run_data_dir_file(self) -> Tuple[str, str]:
|
246
|
+
self.send(Command.GET_CURRENT_RUN_DATA_DIR)
|
247
|
+
full_path_name = self.receive()
|
248
|
+
self.send(Command.GET_CURRENT_RUN_DATA_FILE)
|
249
|
+
current_sample_file = self.receive()
|
250
|
+
if full_path_name.is_ok() and current_sample_file.is_ok():
|
251
|
+
return (
|
252
|
+
full_path_name.ok_value.string_response,
|
253
|
+
current_sample_file.ok_value.string_response,
|
254
|
+
)
|
255
|
+
else:
|
256
|
+
raise ValueError("Couldn't read data dir and file.")
|
@@ -7,7 +7,6 @@ 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
|
@@ -27,13 +26,15 @@ class ABCTableController(abc.ABC):
|
|
27
26
|
controller: Optional[CommunicationController],
|
28
27
|
table: Table,
|
29
28
|
):
|
30
|
-
warnings.warn(
|
31
|
-
"This abstract class is not meant to be initialized. Use MethodController or SequenceController."
|
32
|
-
)
|
33
29
|
self.controller = controller
|
34
30
|
self.table_locator = table
|
35
31
|
self.table_state: Optional[TableType] = None
|
36
32
|
|
33
|
+
def __new__(cls, *args, **kwargs):
|
34
|
+
if cls is ABCTableController:
|
35
|
+
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
36
|
+
return object.__new__(cls, *args, **kwargs)
|
37
|
+
|
37
38
|
def receive(self) -> Result[Response, str]:
|
38
39
|
if self.controller:
|
39
40
|
for _ in range(10):
|
@@ -93,6 +94,8 @@ class ABCTableController(abc.ABC):
|
|
93
94
|
raise ValueError("Controller is offline")
|
94
95
|
|
95
96
|
def add_new_col_num(self, col_name: RegisterFlag, val: Union[int, float]):
|
97
|
+
if not (isinstance(val, int) or isinstance(val, float)):
|
98
|
+
raise ValueError(f"{val} must be an int or float.")
|
96
99
|
self.sleepy_send(
|
97
100
|
TableOperation.NEW_COL_VAL.value.format(
|
98
101
|
register=self.table_locator.register,
|
@@ -103,6 +106,8 @@ class ABCTableController(abc.ABC):
|
|
103
106
|
)
|
104
107
|
|
105
108
|
def add_new_col_text(self, col_name: RegisterFlag, val: str):
|
109
|
+
if not isinstance(val, str):
|
110
|
+
raise ValueError(f"{val} must be a str.")
|
106
111
|
self.sleepy_send(
|
107
112
|
TableOperation.NEW_COL_TEXT.value.format(
|
108
113
|
register=self.table_locator.register,
|
@@ -115,10 +120,12 @@ class ABCTableController(abc.ABC):
|
|
115
120
|
def _edit_row_num(
|
116
121
|
self, col_name: RegisterFlag, val: Union[int, float], row: Optional[int] = None
|
117
122
|
):
|
123
|
+
if not (isinstance(val, int) or isinstance(val, float)):
|
124
|
+
raise ValueError(f"{val} must be an int or float.")
|
118
125
|
if row:
|
119
126
|
num_rows = self.get_num_rows()
|
120
127
|
if num_rows.is_ok():
|
121
|
-
if num_rows.
|
128
|
+
if num_rows.ok_value.num_response < row:
|
122
129
|
raise ValueError("Not enough rows to edit!")
|
123
130
|
|
124
131
|
self.sleepy_send(
|
@@ -134,10 +141,12 @@ class ABCTableController(abc.ABC):
|
|
134
141
|
def _edit_row_text(
|
135
142
|
self, col_name: RegisterFlag, val: str, row: Optional[int] = None
|
136
143
|
):
|
144
|
+
if not isinstance(val, str):
|
145
|
+
raise ValueError(f"{val} must be a str.")
|
137
146
|
if row:
|
138
147
|
num_rows = self.get_num_rows()
|
139
148
|
if num_rows.is_ok():
|
140
|
-
if num_rows.
|
149
|
+
if num_rows.ok_value.num_response < row:
|
141
150
|
raise ValueError("Not enough rows to edit!")
|
142
151
|
|
143
152
|
self.sleepy_send(
|
@@ -11,17 +11,17 @@ Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
|
|
11
11
|
"""
|
12
12
|
|
13
13
|
import time
|
14
|
-
from typing import Optional, Union
|
14
|
+
from typing import Optional, Union, Tuple, List
|
15
15
|
|
16
16
|
from result import Err, Ok, Result
|
17
17
|
|
18
18
|
from ...utils.macro import (
|
19
|
-
str_to_status,
|
20
|
-
HPLCErrorStatus,
|
21
19
|
Command,
|
20
|
+
HPLCErrorStatus,
|
22
21
|
Status,
|
22
|
+
str_to_status,
|
23
23
|
)
|
24
|
-
from
|
24
|
+
from .abc_tables.abc_comm import ABCCommunicationController
|
25
25
|
|
26
26
|
|
27
27
|
class CommunicationController(ABCCommunicationController):
|
@@ -76,7 +76,7 @@ class CommunicationController(ABCCommunicationController):
|
|
76
76
|
if res.is_err():
|
77
77
|
return HPLCErrorStatus.NORESPONSE
|
78
78
|
if res.is_ok():
|
79
|
-
parsed_response = self.receive().
|
79
|
+
parsed_response = self.receive().ok_value.string_response
|
80
80
|
self._most_recent_hplc_status = str_to_status(parsed_response)
|
81
81
|
return self._most_recent_hplc_status
|
82
82
|
else:
|
@@ -148,3 +148,24 @@ class CommunicationController(ABCCommunicationController):
|
|
148
148
|
return Err(
|
149
149
|
f"Failed to receive reply to command #{cmd_no} due to {err} caused by {err_msg}."
|
150
150
|
)
|
151
|
+
|
152
|
+
def get_chemstation_dirs(self) -> Tuple[str, str, List[str]]:
|
153
|
+
method_dir, sequence_dir, data_dirs = None, None, None
|
154
|
+
self.send(Command.GET_METHOD_DIR)
|
155
|
+
res = self.receive()
|
156
|
+
if res.is_ok():
|
157
|
+
method_dir = res.ok_value.string_response
|
158
|
+
self.send(Command.GET_SEQUENCE_DIR)
|
159
|
+
res = self.receive()
|
160
|
+
if res.is_ok():
|
161
|
+
sequence_dir = res.ok_value.string_response
|
162
|
+
self.send(Command.GET_DATA_DIRS)
|
163
|
+
res = self.receive()
|
164
|
+
if res.is_ok():
|
165
|
+
data_dirs = res.ok().string_response.split("|")
|
166
|
+
if method_dir and sequence_dir and data_dirs:
|
167
|
+
return method_dir, sequence_dir, data_dirs
|
168
|
+
else:
|
169
|
+
raise ValueError(
|
170
|
+
"Please provide the method, sequence and data directories, could not be found."
|
171
|
+
)
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import os
|
4
4
|
import time
|
5
5
|
import warnings
|
6
|
-
from typing import List, Optional, Union, Dict
|
6
|
+
from typing import List, Optional, Union, Dict, Set
|
7
7
|
|
8
8
|
from result import Err, Ok, Result
|
9
9
|
|
@@ -413,8 +413,6 @@ class MethodController(RunController):
|
|
413
413
|
:param stall_while_running: whether to stall or immediately return
|
414
414
|
:param add_timestamp: if should add timestamp to experiment name
|
415
415
|
"""
|
416
|
-
|
417
|
-
folder_name = ""
|
418
416
|
hplc_is_running = False
|
419
417
|
tries = 0
|
420
418
|
while tries < 10 and not hplc_is_running:
|
@@ -427,18 +425,15 @@ class MethodController(RunController):
|
|
427
425
|
else experiment_name,
|
428
426
|
)
|
429
427
|
)
|
430
|
-
|
431
|
-
f"{experiment_name}_{timestamp}.D"
|
432
|
-
if add_timestamp
|
433
|
-
else f"{experiment_name}.D"
|
434
|
-
)
|
428
|
+
|
435
429
|
hplc_is_running = self.check_hplc_is_running()
|
436
430
|
tries += 1
|
437
431
|
|
432
|
+
data_dir, data_file = self.get_current_run_data_dir_file()
|
438
433
|
if not hplc_is_running:
|
439
434
|
raise RuntimeError("Method failed to start.")
|
440
435
|
|
441
|
-
self.data_files.append(os.path.join(
|
436
|
+
self.data_files.append(os.path.join(os.path.normpath(data_dir), data_file))
|
442
437
|
self.timeout = (self.get_total_runtime()) * 60
|
443
438
|
|
444
439
|
if stall_while_running:
|
@@ -446,18 +441,20 @@ class MethodController(RunController):
|
|
446
441
|
if run_completed.is_ok():
|
447
442
|
self.data_files[-1] = run_completed.ok_value
|
448
443
|
else:
|
449
|
-
raise RuntimeError("Run error has occurred.")
|
444
|
+
raise RuntimeError(f"Run error has occurred:{run_completed.err_value}.")
|
450
445
|
else:
|
451
|
-
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
|
446
|
+
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1], None)
|
452
447
|
while folder.is_err():
|
453
|
-
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
|
448
|
+
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1], None)
|
454
449
|
if folder.is_ok():
|
455
450
|
self.data_files[-1] = folder.ok_value
|
456
451
|
else:
|
457
452
|
warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
|
458
453
|
warnings.warn(warning)
|
459
454
|
|
460
|
-
def fuzzy_match_most_recent_folder(
|
455
|
+
def fuzzy_match_most_recent_folder(
|
456
|
+
self, most_recent_folder: T, child_dirs: Optional[Set[str]]
|
457
|
+
) -> Result[str, str]:
|
461
458
|
if isinstance(most_recent_folder, str) or isinstance(most_recent_folder, bytes):
|
462
459
|
if os.path.exists(most_recent_folder):
|
463
460
|
return Ok(most_recent_folder)
|
@@ -1,13 +1,12 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
3
|
import warnings
|
4
|
-
from typing import Any, Dict, List, Optional, Union
|
4
|
+
from typing import Any, Dict, List, Optional, Union, Set
|
5
5
|
|
6
6
|
from result import Err, Ok, Result
|
7
7
|
from typing_extensions import override
|
8
8
|
|
9
9
|
from pychemstation.analysis.chromatogram import (
|
10
|
-
SEQUENCE_TIME_FORMAT,
|
11
10
|
AgilentChannelChromatogramData,
|
12
11
|
AgilentHPLCChromatogram,
|
13
12
|
)
|
@@ -92,6 +91,7 @@ class SequenceController(RunController):
|
|
92
91
|
def get_row(self, row: int) -> SequenceEntry:
|
93
92
|
sample_name = self.get_text(row, RegisterFlag.NAME)
|
94
93
|
vial_location = self.try_int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
|
94
|
+
data_file = self.get_text(row, RegisterFlag.DATA_FILE)
|
95
95
|
method = self.get_text(row, RegisterFlag.METHOD)
|
96
96
|
num_inj = self.try_int(self.get_num(row, RegisterFlag.NUM_INJ))
|
97
97
|
inj_vol = self.try_float(self.get_text(row, RegisterFlag.INJ_VOL))
|
@@ -106,6 +106,7 @@ class SequenceController(RunController):
|
|
106
106
|
inj_vol=inj_vol,
|
107
107
|
inj_source=inj_source,
|
108
108
|
sample_type=sample_type,
|
109
|
+
data_file=data_file,
|
109
110
|
)
|
110
111
|
|
111
112
|
def check(self) -> str:
|
@@ -127,13 +128,17 @@ class SequenceController(RunController):
|
|
127
128
|
self.send(f'_SeqPath$ = "{self.src}"')
|
128
129
|
self.send(Command.SWITCH_SEQUENCE_CMD)
|
129
130
|
time.sleep(2)
|
130
|
-
self.
|
131
|
-
time.sleep(2)
|
132
|
-
parsed_response = self.receive().ok_value.string_response
|
131
|
+
parsed_response = self.get_current_sequence_name()
|
133
132
|
|
134
133
|
assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
|
135
134
|
self.table_state = self.load()
|
136
135
|
|
136
|
+
def get_current_sequence_name(self):
|
137
|
+
self.send(Command.GET_SEQUENCE_CMD)
|
138
|
+
time.sleep(2)
|
139
|
+
parsed_response = self.receive().ok_value.string_response
|
140
|
+
return parsed_response
|
141
|
+
|
137
142
|
def edit(self, sequence_table: SequenceTable):
|
138
143
|
"""
|
139
144
|
Updates the currently loaded sequence table with the provided table. This method will delete the existing sequence table and remake it.
|
@@ -151,13 +156,13 @@ class SequenceController(RunController):
|
|
151
156
|
self.send(Command.SAVE_SEQUENCE_CMD)
|
152
157
|
for i in range(int(wanted_row_num)):
|
153
158
|
self.add_row()
|
154
|
-
self.
|
159
|
+
self.save()
|
155
160
|
self.send(Command.SWITCH_SEQUENCE_CMD)
|
156
161
|
|
157
162
|
for i, row in enumerate(sequence_table.rows):
|
158
163
|
self._edit_row(row=row, row_num=i + 1)
|
159
164
|
self.sleep(1)
|
160
|
-
self.
|
165
|
+
self.save()
|
161
166
|
self.send(Command.SWITCH_SEQUENCE_CMD)
|
162
167
|
|
163
168
|
def _edit_row(self, row: SequenceEntry, row_num: int):
|
@@ -171,62 +176,78 @@ class SequenceController(RunController):
|
|
171
176
|
if num_rows.is_ok():
|
172
177
|
while num_rows.ok_value.num_response < row_num:
|
173
178
|
self.add_row()
|
174
|
-
self.
|
179
|
+
self.save()
|
175
180
|
num_rows = self.get_num_rows()
|
176
181
|
if row.vial_location:
|
177
|
-
self.edit_vial_location(row.vial_location, row_num)
|
182
|
+
self.edit_vial_location(row.vial_location, row_num, save=False)
|
178
183
|
if row.method:
|
179
|
-
self.edit_method_name(row.method, row_num)
|
184
|
+
self.edit_method_name(row.method, row_num, save=False)
|
180
185
|
if row.num_inj:
|
181
|
-
self.edit_num_injections(row.num_inj, row_num)
|
186
|
+
self.edit_num_injections(row.num_inj, row_num, save=False)
|
182
187
|
if row.inj_vol:
|
183
|
-
self.edit_injection_volume(row.inj_vol, row_num)
|
188
|
+
self.edit_injection_volume(row.inj_vol, row_num, save=False)
|
184
189
|
if row.inj_source:
|
185
|
-
self.edit_injection_source(row.inj_source, row_num)
|
190
|
+
self.edit_injection_source(row.inj_source, row_num, save=False)
|
186
191
|
if row.sample_name:
|
187
|
-
self.edit_sample_name(row.sample_name, row_num)
|
192
|
+
self.edit_sample_name(row.sample_name, row_num, save=False)
|
188
193
|
if row.data_file:
|
189
|
-
self.edit_data_file(row.data_file, row_num)
|
190
|
-
elif row.sample_name and not row.data_file:
|
191
|
-
self.edit_data_file(row.sample_name, row_num)
|
194
|
+
self.edit_data_file(row.data_file, row_num, save=False)
|
192
195
|
if row.sample_type:
|
193
|
-
self.edit_sample_type(row.sample_type, row_num)
|
194
|
-
self.
|
196
|
+
self.edit_sample_type(row.sample_type, row_num, save=False)
|
197
|
+
self.save()
|
195
198
|
|
196
|
-
def edit_sample_type(
|
199
|
+
def edit_sample_type(
|
200
|
+
self, sample_type: SampleType, row_num: int, save: bool = True
|
201
|
+
):
|
197
202
|
self._edit_row_num(
|
198
203
|
row=row_num,
|
199
204
|
col_name=RegisterFlag.SAMPLE_TYPE,
|
200
205
|
val=sample_type.value,
|
201
206
|
)
|
207
|
+
if save:
|
208
|
+
self.save()
|
202
209
|
|
203
|
-
def edit_data_file(self, data_file: str, row_num: int):
|
210
|
+
def edit_data_file(self, data_file: str, row_num: int, save: bool = True):
|
204
211
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=data_file)
|
212
|
+
if save:
|
213
|
+
self.save()
|
205
214
|
|
206
|
-
def edit_sample_name(self, sample_name: str, row_num: int):
|
215
|
+
def edit_sample_name(self, sample_name: str, row_num: int, save: bool = True):
|
207
216
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=sample_name)
|
217
|
+
if save:
|
218
|
+
self.save()
|
208
219
|
|
209
|
-
def edit_injection_source(
|
220
|
+
def edit_injection_source(
|
221
|
+
self, inj_source: InjectionSource, row_num: int, save: bool = True
|
222
|
+
):
|
210
223
|
self._edit_row_text(
|
211
224
|
row=row_num, col_name=RegisterFlag.INJ_SOR, val=inj_source.value
|
212
225
|
)
|
226
|
+
if save:
|
227
|
+
self.save()
|
213
228
|
|
214
|
-
def edit_injection_volume(
|
229
|
+
def edit_injection_volume(
|
230
|
+
self, inj_vol: Union[int, float], row_num: int, save: bool = True
|
231
|
+
):
|
215
232
|
self._edit_row_text(
|
216
233
|
row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(inj_vol)
|
217
234
|
)
|
235
|
+
if save:
|
236
|
+
self.save()
|
218
237
|
|
219
|
-
def edit_num_injections(self, num_inj: int, row_num: int):
|
238
|
+
def edit_num_injections(self, num_inj: int, row_num: int, save: bool = True):
|
220
239
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=num_inj)
|
221
240
|
|
222
|
-
def edit_method_name(self, method: str, row_num: int):
|
241
|
+
def edit_method_name(self, method: str, row_num: int, save: bool = True):
|
223
242
|
method_dir = self.method_controller.src
|
224
243
|
possible_path = os.path.join(method_dir, method) + ".M\\"
|
225
244
|
if os.path.exists(possible_path):
|
226
245
|
method = os.path.join(method_dir, method)
|
227
246
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
|
247
|
+
if save:
|
248
|
+
self.save()
|
228
249
|
|
229
|
-
def edit_vial_location(self, loc: Tray, row_num: int):
|
250
|
+
def edit_vial_location(self, loc: Tray, row_num: int, save: bool = True):
|
230
251
|
loc_num = -1
|
231
252
|
if isinstance(loc, TenVialColumn):
|
232
253
|
loc_num = loc.value
|
@@ -235,6 +256,11 @@ class SequenceController(RunController):
|
|
235
256
|
self._edit_row_num(
|
236
257
|
row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
|
237
258
|
)
|
259
|
+
if save:
|
260
|
+
self.save()
|
261
|
+
|
262
|
+
def save(self):
|
263
|
+
self.send(Command.SAVE_SEQUENCE_CMD)
|
238
264
|
|
239
265
|
def run(self, stall_while_running: bool = True):
|
240
266
|
"""
|
@@ -248,7 +274,8 @@ class SequenceController(RunController):
|
|
248
274
|
else:
|
249
275
|
raise ValueError("Controller is offline!")
|
250
276
|
|
251
|
-
|
277
|
+
current_sequence_name = self.get_current_sequence_name()
|
278
|
+
if not self.table_state or self.table_state.name not in current_sequence_name:
|
252
279
|
self.table_state = self.load()
|
253
280
|
|
254
281
|
total_runtime = 0.0
|
@@ -270,7 +297,6 @@ class SequenceController(RunController):
|
|
270
297
|
curr_method_runtime = self.method_controller.get_total_runtime()
|
271
298
|
total_runtime += curr_method_runtime
|
272
299
|
|
273
|
-
timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
|
274
300
|
self.send(Command.RUN_SEQUENCE_CMD.value)
|
275
301
|
self.timeout = total_runtime * 60
|
276
302
|
|
@@ -282,11 +308,24 @@ class SequenceController(RunController):
|
|
282
308
|
break
|
283
309
|
|
284
310
|
if hplc_running:
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
311
|
+
full_path_name, current_sample_file = None, None
|
312
|
+
for _ in range(5):
|
313
|
+
try:
|
314
|
+
full_path_name, current_sample_file = (
|
315
|
+
self.get_current_run_data_dir_file()
|
316
|
+
)
|
317
|
+
break
|
318
|
+
except ValueError:
|
319
|
+
pass
|
320
|
+
if full_path_name and current_sample_file:
|
321
|
+
data_file = SequenceDataFiles(
|
322
|
+
sequence_name=self.table_state.name,
|
323
|
+
dir=full_path_name,
|
324
|
+
_data_files=[r.data_file for r in self.table_state.rows],
|
325
|
+
)
|
326
|
+
self.data_files.append(data_file)
|
327
|
+
else:
|
328
|
+
raise ValueError("Data directory for sequence was not found.")
|
290
329
|
|
291
330
|
if stall_while_running:
|
292
331
|
run_completed = self.check_hplc_done_running()
|
@@ -299,54 +338,39 @@ class SequenceController(RunController):
|
|
299
338
|
|
300
339
|
@override
|
301
340
|
def fuzzy_match_most_recent_folder(
|
302
|
-
self, most_recent_folder: T
|
341
|
+
self, most_recent_folder: T, child_dirs: Optional[Set[str]]
|
303
342
|
) -> Result[SequenceDataFiles, str]:
|
304
343
|
if isinstance(most_recent_folder, SequenceDataFiles):
|
305
|
-
if os.path.isdir(most_recent_folder.dir):
|
306
|
-
subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
|
307
|
-
potential_folders = sorted(
|
308
|
-
list(filter(lambda d: most_recent_folder.dir in d, subdirs))
|
309
|
-
)
|
310
|
-
most_recent_folder.child_dirs = [
|
311
|
-
f
|
312
|
-
for f in potential_folders
|
313
|
-
if most_recent_folder.dir in f and ".M" not in f and ".D" in f
|
314
|
-
]
|
315
|
-
return Ok(most_recent_folder)
|
316
|
-
|
317
344
|
try:
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
if parent_dir in f and ".M" not in f and ".D" in f
|
348
|
-
]
|
349
|
-
return Ok(most_recent_folder)
|
345
|
+
if most_recent_folder.dir and os.path.isdir(most_recent_folder.dir):
|
346
|
+
subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
|
347
|
+
if (
|
348
|
+
child_dirs
|
349
|
+
and len(child_dirs) > 0
|
350
|
+
and set(most_recent_folder._data_files) == child_dirs
|
351
|
+
):
|
352
|
+
most_recent_folder.child_dirs = [
|
353
|
+
os.path.join(most_recent_folder.dir, f) for f in child_dirs
|
354
|
+
]
|
355
|
+
else:
|
356
|
+
potential_folders: List[str] = sorted(
|
357
|
+
list(
|
358
|
+
filter(
|
359
|
+
lambda d: most_recent_folder.dir in d,
|
360
|
+
subdirs,
|
361
|
+
)
|
362
|
+
)
|
363
|
+
)
|
364
|
+
most_recent_folder.child_dirs = [
|
365
|
+
f
|
366
|
+
for f in potential_folders
|
367
|
+
if most_recent_folder.dir in f
|
368
|
+
and ".M" not in f
|
369
|
+
and ".D" in f
|
370
|
+
]
|
371
|
+
return Ok(most_recent_folder)
|
372
|
+
else:
|
373
|
+
return Err("No sequence folder found, please give the full path.")
|
350
374
|
except Exception as e:
|
351
375
|
error = f"Failed to get sequence folder: {e}"
|
352
376
|
return Err(error)
|
@@ -354,16 +378,20 @@ class SequenceController(RunController):
|
|
354
378
|
|
355
379
|
def get_data_mult_uv(self, custom_path: Optional[str] = None):
|
356
380
|
seq_data_dir = (
|
357
|
-
SequenceDataFiles(dir=custom_path,
|
381
|
+
SequenceDataFiles(dir=custom_path, sequence_name="")
|
358
382
|
if custom_path
|
359
383
|
else self.data_files[-1]
|
360
384
|
)
|
361
385
|
if len(seq_data_dir.child_dirs) == 0:
|
362
|
-
|
363
|
-
seq_data_dir
|
364
|
-
)
|
386
|
+
search_folder = self.fuzzy_match_most_recent_folder(
|
387
|
+
seq_data_dir, set(seq_data_dir.child_dirs)
|
388
|
+
)
|
389
|
+
if search_folder.is_ok():
|
390
|
+
seq_data_dir = search_folder.ok_value
|
391
|
+
else:
|
392
|
+
raise FileNotFoundError(search_folder.err_value)
|
365
393
|
all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
|
366
|
-
for row in
|
394
|
+
for row in seq_data_dir.child_dirs:
|
367
395
|
all_w_spectra.append(self.get_data_uv(custom_path=row))
|
368
396
|
return all_w_spectra
|
369
397
|
|
@@ -381,13 +409,13 @@ class SequenceController(RunController):
|
|
381
409
|
self, custom_path: Optional[str] = None
|
382
410
|
) -> List[AgilentChannelChromatogramData]:
|
383
411
|
seq_file_dir = (
|
384
|
-
SequenceDataFiles(dir=custom_path,
|
412
|
+
SequenceDataFiles(dir=custom_path, sequence_name="")
|
385
413
|
if custom_path
|
386
414
|
else self.data_files[-1]
|
387
415
|
)
|
388
416
|
if len(seq_file_dir.child_dirs) == 0:
|
389
417
|
self.data_files[-1] = self.fuzzy_match_most_recent_folder(
|
390
|
-
seq_file_dir
|
418
|
+
seq_file_dir, set(seq_file_dir.child_dirs)
|
391
419
|
).ok_value
|
392
420
|
spectra: List[AgilentChannelChromatogramData] = []
|
393
421
|
for row in self.data_files[-1].child_dirs:
|
@@ -404,8 +432,10 @@ class SequenceController(RunController):
|
|
404
432
|
self.data_files.append(
|
405
433
|
self.fuzzy_match_most_recent_folder(
|
406
434
|
most_recent_folder=SequenceDataFiles(
|
407
|
-
dir=custom_path,
|
408
|
-
|
435
|
+
dir=custom_path,
|
436
|
+
sequence_name="NA",
|
437
|
+
),
|
438
|
+
child_dirs=None,
|
409
439
|
).ok_value
|
410
440
|
)
|
411
441
|
parent_dir = self.data_files[-1]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from ..abc_tables.device import DeviceController
|
2
4
|
from ....control.controllers import CommunicationController
|
3
5
|
from ....utils.injector_types import (
|
@@ -90,7 +92,7 @@ class InjectorController(DeviceController):
|
|
90
92
|
)
|
91
93
|
raise ValueError("No valid function found.")
|
92
94
|
|
93
|
-
def load(self) -> InjectorTable:
|
95
|
+
def load(self) -> InjectorTable | None:
|
94
96
|
rows = self.get_num_rows()
|
95
97
|
if rows.is_ok():
|
96
98
|
row_response = rows.value
|
@@ -101,6 +103,4 @@ class InjectorController(DeviceController):
|
|
101
103
|
for i in range(int(row_response.num_response))
|
102
104
|
]
|
103
105
|
)
|
104
|
-
|
105
|
-
return InjectorTable(functions=[])
|
106
|
-
raise ValueError("Unexpected error")
|
106
|
+
raise ValueError("Couldn't read injector table rows.")
|
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
|
@@ -31,14 +32,16 @@ class HPLCController:
|
|
31
32
|
|
32
33
|
INJECTOR_TABLE = Table(register="RCWLS1Pretreatment[1]", name="InstructionTable")
|
33
34
|
|
35
|
+
DAD_TABLE = Table(register="RCDAD1Method[1]", name="Timetable")
|
36
|
+
|
34
37
|
MSD_TABLE = Table(register="MSACQINFO[1]", name="SprayChamber")
|
35
38
|
|
36
39
|
def __init__(
|
37
40
|
self,
|
38
41
|
comm_dir: str,
|
39
|
-
method_dir: str,
|
40
|
-
sequence_dir: str,
|
41
|
-
|
42
|
+
method_dir: Optional[str] = None,
|
43
|
+
sequence_dir: Optional[str] = None,
|
44
|
+
extra_data_dirs: Optional[List[str]] = None,
|
42
45
|
offline: bool = False,
|
43
46
|
debug: bool = False,
|
44
47
|
):
|
@@ -47,33 +50,42 @@ class HPLCController:
|
|
47
50
|
double escaped: "C:\\my_folder\\"
|
48
51
|
|
49
52
|
: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
53
|
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
54
|
:raises FileNotFoundError: If either `data_dir`, `method_dir`, `sequence_dir`, `sequence_data_dir`or `comm_dir` is not a valid directory.
|
55
55
|
"""
|
56
56
|
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,
|
57
|
+
comm_dir=comm_dir, debug=debug, offline=offline
|
76
58
|
)
|
59
|
+
data_dirs: List[str] = []
|
60
|
+
if not offline:
|
61
|
+
if not method_dir or not sequence_dir or not extra_data_dirs:
|
62
|
+
method_dir, sequence_dir, data_dirs = self.comm.get_chemstation_dirs()
|
63
|
+
if extra_data_dirs:
|
64
|
+
data_dirs.extend(extra_data_dirs)
|
65
|
+
data_dirs = list(set([os.path.normpath(p) for p in data_dirs]))
|
66
|
+
if method_dir and sequence_dir and data_dirs:
|
67
|
+
self.method_controller: MethodController = MethodController(
|
68
|
+
controller=self.comm,
|
69
|
+
src=method_dir,
|
70
|
+
data_dirs=data_dirs,
|
71
|
+
table=self.METHOD_TIMETABLE,
|
72
|
+
offline=offline,
|
73
|
+
injector_controller=InjectorController(
|
74
|
+
controller=self.comm, table=self.INJECTOR_TABLE, offline=offline
|
75
|
+
),
|
76
|
+
)
|
77
|
+
self.sequence_controller: SequenceController = SequenceController(
|
78
|
+
controller=self.comm,
|
79
|
+
src=sequence_dir,
|
80
|
+
data_dirs=data_dirs,
|
81
|
+
table=self.SEQUENCE_TABLE,
|
82
|
+
method_controller=self.method_controller,
|
83
|
+
offline=offline,
|
84
|
+
)
|
85
|
+
else:
|
86
|
+
raise ValueError(
|
87
|
+
"Expected a method dir, sequence dir and data dirs but they were None."
|
88
|
+
)
|
77
89
|
|
78
90
|
def send(self, cmd: Union[Command, str]):
|
79
91
|
"""
|
@@ -279,7 +291,7 @@ class HPLCController:
|
|
279
291
|
"""Returns the currently loaded sequence."""
|
280
292
|
return self.sequence_controller.load()
|
281
293
|
|
282
|
-
def load_injector_program(self) -> InjectorTable:
|
294
|
+
def load_injector_program(self) -> InjectorTable | None:
|
283
295
|
return self.method_controller.injector_controller.load()
|
284
296
|
|
285
297
|
def standby(self):
|
pychemstation/utils/macro.py
CHANGED
@@ -48,6 +48,17 @@ class Command(Enum):
|
|
48
48
|
SAVE_METHOD_CMD = 'SaveMethod _MethPath$, _MethFile$, "{commit_msg}"'
|
49
49
|
GET_SEQUENCE_CMD = "response$ = _SeqFile$"
|
50
50
|
RUN_SEQUENCE_CMD = "RunSequence"
|
51
|
+
CURRENT_RUNNING_SEQ_LINE = "response_num = _SEQCURRLINE1"
|
52
|
+
|
53
|
+
# Get directories
|
54
|
+
GET_METHOD_DIR = "response$ = _METHPATH$"
|
55
|
+
GET_SEQUENCE_DIR = "response$ = _SEQUENCEPATHS$"
|
56
|
+
GET_DATA_DIRS = "response$ = _DATAPATHS$"
|
57
|
+
GET_CURRENT_RUN_DATA_DIR = "response$ = _DATAPath$"
|
58
|
+
GET_CURRENT_RUN_DATA_FILE = "response$ = _DATAFILE1$"
|
59
|
+
|
60
|
+
# Debuggng
|
61
|
+
ERROR = "response$ = _ERROR$"
|
51
62
|
|
52
63
|
|
53
64
|
class HPLCRunningStatus(Enum):
|
@@ -11,6 +11,7 @@ from pychemstation.utils.tray_types import Tray
|
|
11
11
|
class SequenceDataFiles:
|
12
12
|
sequence_name: str
|
13
13
|
dir: str
|
14
|
+
_data_files: List[str] = field(default_factory=list)
|
14
15
|
child_dirs: List[str] = field(default_factory=list)
|
15
16
|
|
16
17
|
|
@@ -38,9 +39,9 @@ class InjectionSource(Enum):
|
|
38
39
|
|
39
40
|
@dataclass
|
40
41
|
class SequenceEntry:
|
41
|
-
|
42
|
+
data_file: str
|
42
43
|
vial_location: Tray
|
43
|
-
|
44
|
+
sample_name: Optional[str] = None
|
44
45
|
method: Optional[str] = None
|
45
46
|
num_inj: Optional[int] = 1
|
46
47
|
inj_vol: Optional[float] = 2
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pychemstation
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.7
|
4
4
|
Summary: Library to interact with Chemstation software, primarily used in Hein lab
|
5
5
|
Project-URL: Documentation, https://pychemstation-e5a086.gitlab.io/pychemstation.html
|
6
6
|
Project-URL: Repository, https://gitlab.com/heingroup/device-api/pychemstation
|
@@ -18,7 +18,7 @@ Requires-Dist: matplotlib>=3.7.5
|
|
18
18
|
Requires-Dist: pandas>=2.0.3
|
19
19
|
Requires-Dist: pdoc>=14.7.0
|
20
20
|
Requires-Dist: polling>=0.3.2
|
21
|
-
Requires-Dist: pre-commit>=
|
21
|
+
Requires-Dist: pre-commit>=3.5.0
|
22
22
|
Requires-Dist: pytest>=7.3.5
|
23
23
|
Requires-Dist: rainbow-api>=1.0.10
|
24
24
|
Requires-Dist: result>=0.17.0
|
@@ -90,7 +90,7 @@ DATA_DIR_2 = "C:\\Users\\Public\\Documents\\ChemStation\\2\\Data"
|
|
90
90
|
DATA_DIR_3 = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data"
|
91
91
|
|
92
92
|
# Initialize HPLC Controller
|
93
|
-
hplc_controller = HPLCController(
|
93
|
+
hplc_controller = HPLCController(extra_data_dirs=[DATA_DIR_2, DATA_DIR_3],
|
94
94
|
comm_dir=DEFAULT_COMMAND_PATH,
|
95
95
|
method_dir=DEFAULT_METHOD_DIR,
|
96
96
|
sequence_dir=SEQUENCE_DIR)
|
@@ -3,40 +3,40 @@ pychemstation/analysis/__init__.py,sha256=mPNnp0TmkoUxrTGcT6wNKMyCiOar5vC0cTPmFL
|
|
3
3
|
pychemstation/analysis/base_spectrum.py,sha256=t_VoxAtBph1V7S4fOsziERHiOBkYP0_nH7LTwbTEvcE,16529
|
4
4
|
pychemstation/analysis/chromatogram.py,sha256=cHxPd5-miA6L3FjwN5cSFyq4xEeZoHWFFk8gU6tCgg4,3946
|
5
5
|
pychemstation/analysis/process_report.py,sha256=xckcpqnYfzFTIo1nhgwP5A4GPJsW3PQ_qzuy5Z1KOuI,14714
|
6
|
-
pychemstation/control/README.md,sha256=
|
6
|
+
pychemstation/control/README.md,sha256=yVMhIOq3YL-8EzqZDWGXt36GzSjzqiI4fwkJ35bAzD0,3130
|
7
7
|
pychemstation/control/__init__.py,sha256=7lSkY7Qa7Ikdz82-2FESc_oqktv7JndsjltCkiMqnMI,147
|
8
|
-
pychemstation/control/hplc.py,sha256=
|
8
|
+
pychemstation/control/hplc.py,sha256=PY6P_GTnVxL3rAwPHtJmiAm5iTw9X6xjYghyKpBpYFM,13063
|
9
9
|
pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
|
10
10
|
pychemstation/control/controllers/__init__.py,sha256=EWvvITwY6RID5b1ilVsPowP85uzmIt3LYW0rvyN-3x0,146
|
11
|
-
pychemstation/control/controllers/comm.py,sha256=
|
11
|
+
pychemstation/control/controllers/comm.py,sha256=a3qp_u2vm_UO3VTDwKEEkJfHIWgF4Gxne0h03dQe0A8,5878
|
12
12
|
pychemstation/control/controllers/abc_tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
pychemstation/control/controllers/abc_tables/
|
14
|
-
pychemstation/control/controllers/abc_tables/
|
15
|
-
pychemstation/control/controllers/abc_tables/
|
13
|
+
pychemstation/control/controllers/abc_tables/abc_comm.py,sha256=G2JSCNwLrLYPYBdcf_WuIl6dvJORmmQe5wih6TdvPiU,5226
|
14
|
+
pychemstation/control/controllers/abc_tables/device.py,sha256=ZJQID_M53bUwVSleBL5MjFJFrExZO1xdYKRFn5VToug,641
|
15
|
+
pychemstation/control/controllers/abc_tables/run.py,sha256=L958yy3vBxN0qxHVfMb7wgxxHAvSQq4zggWlon6vPs4,9294
|
16
|
+
pychemstation/control/controllers/abc_tables/table.py,sha256=4c4W_9OzrY-SfH_oV6oBZkfn1Vp5XvNYbWUvbGY3G2s,7699
|
16
17
|
pychemstation/control/controllers/data_aq/__init__.py,sha256=w-Zgbit10niOQfz780ZmRHjUFxV1hMkdui7fOMPqeLA,132
|
17
|
-
pychemstation/control/controllers/data_aq/method.py,sha256=
|
18
|
-
pychemstation/control/controllers/data_aq/sequence.py,sha256=
|
18
|
+
pychemstation/control/controllers/data_aq/method.py,sha256=BqAW2XdlJJQj08P282h_NPTJofdBKiNJgdis9j0c9lc,18183
|
19
|
+
pychemstation/control/controllers/data_aq/sequence.py,sha256=here444uwwIH9m08ls8_wapA9088uvqX5DYCHFsarcU,17422
|
19
20
|
pychemstation/control/controllers/devices/__init__.py,sha256=QpgGnLXyWiB96KIB98wMccEi8oOUUaLxvBCyevJzcOg,75
|
20
|
-
pychemstation/control/controllers/devices/injector.py,sha256=
|
21
|
+
pychemstation/control/controllers/devices/injector.py,sha256=OxrF-SbV006bCh_j9o-clavc_d8Loh3myhFerbvjLg4,4033
|
21
22
|
pychemstation/generated/__init__.py,sha256=xnEs0QTjeuGYO3tVUIy8GDo95GqTV1peEjosGckpOu0,977
|
22
23
|
pychemstation/generated/dad_method.py,sha256=xTUiSCvkXcxBUhjVm1YZKu-tHs16k23pF-0xYrQSwWA,8408
|
23
24
|
pychemstation/generated/pump_method.py,sha256=s3MckKDw2-nZUC5lHrJVvXYdneWP8-9UvblNuGryPHY,12092
|
24
25
|
pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
26
|
pychemstation/utils/injector_types.py,sha256=z2iWwTklGm0GRDCL9pnPCovQrwyRwxv8w5w5Xh7Pj3U,1152
|
26
|
-
pychemstation/utils/macro.py,sha256=
|
27
|
+
pychemstation/utils/macro.py,sha256=4Tn__0EeoDT3exbzTKde5epd7Oj-rhUh-l9EkXTHn7c,3242
|
27
28
|
pychemstation/utils/method_types.py,sha256=_2djFz_uWFc9aoZcyPMIjC5KYBs003WPoQGx7ZhMiOg,1649
|
28
29
|
pychemstation/utils/num_utils.py,sha256=dDs8sLZ_SdtvDKhyhF3IkljiVf16IYqpMTO5tEk9vMk,2079
|
29
30
|
pychemstation/utils/parsing.py,sha256=mzdpxrH5ux4-_i4CwZvnIYnIwAnRnOptKb3fZyYJcx0,9307
|
30
31
|
pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
|
31
|
-
pychemstation/utils/sequence_types.py,sha256=
|
32
|
+
pychemstation/utils/sequence_types.py,sha256=Bf_oGm-adRejDUmYrzV2GEx16NIO6jlH_ZMIYVKl8gg,1981
|
32
33
|
pychemstation/utils/spec_utils.py,sha256=lS27Xi4mFNDWBfmBqOoxTcVchPAkLK2mSdoaWDOfaPI,10211
|
33
34
|
pychemstation/utils/table_types.py,sha256=I5xy7tpmMWOMb6tFfWVE1r4wnSsgky5sZU9oNtHU9BE,3451
|
34
35
|
pychemstation/utils/tray_types.py,sha256=FUjCUnozt5ZjCl5Vg9FioPHGTdKa43SQI1QPVkoBmV0,6078
|
35
36
|
pychemstation/utils/mocking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
|
-
pychemstation/utils/mocking/
|
37
|
-
pychemstation/utils/mocking/mock_comm.py,sha256=nYSK2S85iDACIY73rX56yyqyaWo6Oaj3H7gvMsrkRJA,150
|
37
|
+
pychemstation/utils/mocking/mock_comm.py,sha256=4DcKmUxp-LYNXjywT_za1_GpqKa4sFTj7F2V3r_qsA0,156
|
38
38
|
pychemstation/utils/mocking/mock_hplc.py,sha256=Hx6127C7d3miYGCZYxbN-Q3PU8kpMgXYX2n6we2Twgw,25
|
39
|
-
pychemstation-0.10.
|
40
|
-
pychemstation-0.10.
|
41
|
-
pychemstation-0.10.
|
42
|
-
pychemstation-0.10.
|
39
|
+
pychemstation-0.10.7.dist-info/METADATA,sha256=oDYpd4ohtIgRyHstF4Pdnn-EP5YSN0ozESFeAQLu_CE,5939
|
40
|
+
pychemstation-0.10.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
41
|
+
pychemstation-0.10.7.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
|
42
|
+
pychemstation-0.10.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|