pychemstation 0.8.3__py3-none-any.whl → 0.10.1__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/__init__.py +4 -0
- pychemstation/analysis/base_spectrum.py +9 -9
- pychemstation/analysis/process_report.py +13 -7
- pychemstation/control/README.md +9 -1
- pychemstation/control/__init__.py +4 -0
- pychemstation/control/controllers/__init__.py +6 -0
- pychemstation/control/controllers/comm.py +12 -5
- pychemstation/control/controllers/devices/device.py +10 -7
- pychemstation/control/controllers/devices/injector.py +18 -84
- pychemstation/control/controllers/tables/method.py +35 -13
- pychemstation/control/controllers/tables/sequence.py +46 -37
- pychemstation/control/controllers/tables/table.py +46 -30
- pychemstation/control/hplc.py +27 -11
- pychemstation/generated/dad_method.py +1 -1
- pychemstation/generated/pump_method.py +1 -1
- pychemstation/utils/chromatogram.py +2 -5
- pychemstation/utils/injector_types.py +1 -1
- pychemstation/utils/macro.py +3 -3
- pychemstation/utils/method_types.py +2 -2
- pychemstation/utils/parsing.py +1 -0
- pychemstation/utils/sequence_types.py +3 -3
- pychemstation/{analysis → utils}/spec_utils.py +3 -3
- {pychemstation-0.8.3.dist-info → pychemstation-0.10.1.dist-info}/METADATA +20 -7
- pychemstation-0.10.1.dist-info/RECORD +37 -0
- pychemstation-0.8.3.dist-info/RECORD +0 -37
- /pychemstation/{analysis/utils.py → utils/num_utils.py} +0 -0
- {pychemstation-0.8.3.dist-info → pychemstation-0.10.1.dist-info}/WHEEL +0 -0
- {pychemstation-0.8.3.dist-info → pychemstation-0.10.1.dist-info}/licenses/LICENSE +0 -0
@@ -6,12 +6,12 @@ from abc import ABC, abstractmethod
|
|
6
6
|
import matplotlib.pyplot as plt
|
7
7
|
import numpy as np
|
8
8
|
from scipy import (
|
9
|
-
sparse,
|
10
|
-
signal,
|
11
9
|
integrate,
|
10
|
+
signal,
|
11
|
+
sparse,
|
12
12
|
)
|
13
13
|
|
14
|
-
from .
|
14
|
+
from ..utils.num_utils import find_nearest_value_index, interpolate_to_index
|
15
15
|
|
16
16
|
|
17
17
|
class AbstractSpectrum(ABC):
|
@@ -197,10 +197,10 @@ class AbstractSpectrum(ABC):
|
|
197
197
|
return (self.x.copy()[full_mask], self.y.copy()[full_mask])
|
198
198
|
|
199
199
|
def show_spectrum(
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
200
|
+
self,
|
201
|
+
filename=None,
|
202
|
+
title=None,
|
203
|
+
label=None,
|
204
204
|
):
|
205
205
|
"""Plots the spectral data using matplotlib.pyplot module.
|
206
206
|
|
@@ -385,12 +385,12 @@ class AbstractSpectrum(ABC):
|
|
385
385
|
|
386
386
|
if rule == "trapz":
|
387
387
|
return integrate.trapz(
|
388
|
-
self.y[left_idx
|
388
|
+
self.y[left_idx: right_idx + 1], self.x[left_idx: right_idx + 1]
|
389
389
|
)
|
390
390
|
|
391
391
|
elif rule == "simps":
|
392
392
|
return integrate.simps(
|
393
|
-
self.y[left_idx
|
393
|
+
self.y[left_idx: right_idx + 1], self.x[left_idx: right_idx + 1]
|
394
394
|
)
|
395
395
|
|
396
396
|
else:
|
@@ -4,15 +4,21 @@ import re
|
|
4
4
|
from abc import abstractmethod
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from enum import Enum
|
7
|
-
from typing import
|
7
|
+
from typing import AnyStr, Dict, List, Optional, Pattern
|
8
8
|
|
9
9
|
import pandas as pd
|
10
|
-
from aghplctools.ingestion.text import
|
11
|
-
|
12
|
-
|
10
|
+
from aghplctools.ingestion.text import (
|
11
|
+
_area_report_re,
|
12
|
+
_header_block_re,
|
13
|
+
_no_peaks_re,
|
14
|
+
_signal_info_re,
|
15
|
+
_signal_table_re,
|
16
|
+
chunk_string,
|
17
|
+
)
|
18
|
+
from result import Err, Ok, Result
|
13
19
|
|
14
20
|
from pychemstation.utils.chromatogram import AgilentHPLCChromatogram
|
15
|
-
from pychemstation.utils.tray_types import
|
21
|
+
from pychemstation.utils.tray_types import FiftyFourVialPlate, Tray
|
16
22
|
|
17
23
|
|
18
24
|
@dataclass
|
@@ -70,7 +76,7 @@ class CSVProcessor(ReportProcessor):
|
|
70
76
|
:returns: subset of complete report details, specifically the sample location, solvents in pumps,
|
71
77
|
and list of peaks at each wavelength channel.
|
72
78
|
"""
|
73
|
-
labels = os.path.join(self.path,
|
79
|
+
labels = os.path.join(self.path, 'REPORT00.CSV')
|
74
80
|
if os.path.exists(labels):
|
75
81
|
df_labels: Dict[int, Dict[int: AnyStr]] = pd.read_csv(labels, encoding="utf-16", header=None).to_dict()
|
76
82
|
vial_location = []
|
@@ -200,7 +206,7 @@ class TXTProcessor(ReportProcessor):
|
|
200
206
|
should be able to use the `parse_area_report` method of aghplctools v4.8.8
|
201
207
|
"""
|
202
208
|
if re.search(_no_peaks_re, report_text): # There are no peaks in Report.txt
|
203
|
-
raise ValueError(
|
209
|
+
raise ValueError('No peaks found in Report.txt')
|
204
210
|
blocks = _header_block_re.split(report_text)
|
205
211
|
signals = {} # output dictionary
|
206
212
|
for ind, block in enumerate(blocks):
|
pychemstation/control/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
#
|
1
|
+
# Examples of usecases
|
2
2
|
|
3
3
|
## Initialization
|
4
|
+
|
4
5
|
```python
|
5
6
|
from pychemstation.control import HPLCController
|
6
7
|
|
@@ -16,6 +17,7 @@ hplc_controller = HPLCController(data_dir=DATA_DIR,
|
|
16
17
|
```
|
17
18
|
|
18
19
|
## Switching a method
|
20
|
+
|
19
21
|
```python
|
20
22
|
hplc_controller.switch_method("General-Poroshell")
|
21
23
|
```
|
@@ -50,6 +52,7 @@ hplc_controller.edit_method(new_method)
|
|
50
52
|
```
|
51
53
|
|
52
54
|
## Running a method and get data from last run method
|
55
|
+
|
53
56
|
```python
|
54
57
|
hplc_controller.run_method(experiment_name="test_experiment")
|
55
58
|
chrom = hplc_controller.get_last_run_method_data()
|
@@ -57,10 +60,13 @@ channel_a_time = chrom.A.x
|
|
57
60
|
```
|
58
61
|
|
59
62
|
## Switching a sequence
|
63
|
+
|
60
64
|
```python
|
61
65
|
hplc_controller.switch_sequence(sequence_name="hplc_testing")
|
62
66
|
```
|
67
|
+
|
63
68
|
## Editing a Sequence Row
|
69
|
+
|
64
70
|
```python
|
65
71
|
from pychemstation.utils.sequence_types import *
|
66
72
|
from pychemstation.utils.tray_types import *
|
@@ -77,6 +83,7 @@ hplc_controller.edit_sequence_row(SequenceEntry(
|
|
77
83
|
```
|
78
84
|
|
79
85
|
## Editing entire Sequence Table
|
86
|
+
|
80
87
|
```python
|
81
88
|
from pychemstation.utils.tray_types import *
|
82
89
|
from pychemstation.utils.sequence_types import *
|
@@ -117,6 +124,7 @@ hplc_controller.edit_sequence(seq_table)
|
|
117
124
|
```
|
118
125
|
|
119
126
|
## Running a sequence and get data from last run sequence
|
127
|
+
|
120
128
|
```python
|
121
129
|
hplc_controller.run_sequence(seq_table)
|
122
130
|
chroms = hplc_controller.get_last_run_sequence_data()
|
@@ -11,11 +11,18 @@ Authors: Alexander Hammer, Hessam Mehr, Lucy Hao
|
|
11
11
|
"""
|
12
12
|
import os
|
13
13
|
import time
|
14
|
-
from typing import Optional
|
14
|
+
from typing import Optional, Union
|
15
15
|
|
16
|
-
from result import
|
16
|
+
from result import Err, Ok, Result
|
17
17
|
|
18
|
-
from ...utils.macro import
|
18
|
+
from ...utils.macro import (
|
19
|
+
str_to_status,
|
20
|
+
HPLCAvailStatus,
|
21
|
+
HPLCErrorStatus,
|
22
|
+
Command,
|
23
|
+
Status,
|
24
|
+
Response,
|
25
|
+
)
|
19
26
|
|
20
27
|
|
21
28
|
class CommunicationController:
|
@@ -193,14 +200,14 @@ class CommunicationController:
|
|
193
200
|
str_response_prefix = "String Responses:"
|
194
201
|
possible_response = self._receive(self.cmd_no)
|
195
202
|
if possible_response.is_ok():
|
196
|
-
lines = possible_response.
|
203
|
+
lines = possible_response.ok_value.splitlines()
|
197
204
|
for line in lines:
|
198
205
|
if str_response_prefix in line and num_response_prefix in line:
|
199
206
|
string_responses_dirty, _, numerical_responses = line.partition(num_response_prefix)
|
200
207
|
_, _, string_responses = string_responses_dirty.partition(str_response_prefix)
|
201
208
|
return Ok(Response(string_response=string_responses.strip(),
|
202
209
|
num_response=float(numerical_responses.strip())))
|
203
|
-
return Err(
|
210
|
+
return Err("Could not retrieve HPLC response")
|
204
211
|
else:
|
205
212
|
return Err(f"Could not establish response to HPLC: {possible_response}")
|
206
213
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import abc
|
2
|
-
from typing import Union,
|
2
|
+
from typing import List, Union, Dict, Optional
|
3
3
|
|
4
4
|
from result import Result
|
5
5
|
|
6
|
-
from ....analysis.process_report import
|
6
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
7
7
|
from ....control.controllers import CommunicationController
|
8
8
|
from ....control.controllers.tables.table import TableController
|
9
|
-
from ....utils.chromatogram import AgilentChannelChromatogramData
|
10
|
-
from ....utils.table_types import
|
9
|
+
from ....utils.chromatogram import AgilentChannelChromatogramData, AgilentHPLCChromatogram
|
10
|
+
from ....utils.table_types import T, Table
|
11
11
|
|
12
12
|
|
13
13
|
class DeviceController(TableController, abc.ABC):
|
@@ -26,11 +26,14 @@ class DeviceController(TableController, abc.ABC):
|
|
26
26
|
def retrieve_recent_data_files(self):
|
27
27
|
raise NotImplementedError
|
28
28
|
|
29
|
-
def get_data(self) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
30
|
-
raise NotImplementedError
|
31
|
-
|
32
29
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
33
30
|
raise NotImplementedError
|
34
31
|
|
35
32
|
def get_report(self, report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
36
33
|
raise NotImplementedError
|
34
|
+
|
35
|
+
def get_data_uv(self) -> Union[List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]]:
|
36
|
+
raise NotImplementedError
|
37
|
+
|
38
|
+
def get_data(self, custom_path: Optional[str] = None) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
39
|
+
raise NotImplementedError
|
@@ -1,8 +1,19 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
2
3
|
from .device import DeviceController
|
3
|
-
from ....
|
4
|
-
from ....utils.
|
5
|
-
|
4
|
+
from ....control.controllers import CommunicationController
|
5
|
+
from ....utils.injector_types import (
|
6
|
+
Draw,
|
7
|
+
Inject,
|
8
|
+
InjectorTable,
|
9
|
+
Mode,
|
10
|
+
Remote,
|
11
|
+
RemoteCommand,
|
12
|
+
SourceType,
|
13
|
+
Wait,
|
14
|
+
)
|
15
|
+
from ....utils.table_types import RegisterFlag, Table
|
16
|
+
from ....utils.tray_types import Tray
|
6
17
|
|
7
18
|
|
8
19
|
class InjectorController(DeviceController):
|
@@ -10,7 +21,7 @@ class InjectorController(DeviceController):
|
|
10
21
|
def __init__(self, controller: CommunicationController, table: Table, offline: bool):
|
11
22
|
super().__init__(controller, table, offline)
|
12
23
|
|
13
|
-
def get_row(self, row: int) ->
|
24
|
+
def get_row(self, row: int) -> None | Remote | Draw | Wait | Inject:
|
14
25
|
def return_tray_loc() -> Tray:
|
15
26
|
pass
|
16
27
|
|
@@ -32,86 +43,9 @@ class InjectorController(DeviceController):
|
|
32
43
|
return Remote(command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
|
33
44
|
duration=self.get_num(row, RegisterFlag.REMOTE_DUR))
|
34
45
|
|
35
|
-
def load(self) -> InjectorTable:
|
46
|
+
def load(self) -> InjectorTable | None:
|
36
47
|
rows = self.get_num_rows()
|
37
48
|
if rows.is_ok():
|
38
49
|
return InjectorTable(functions=[self.get_row(i) for i in range(int(rows.ok_value.num_response))])
|
39
50
|
|
40
|
-
|
41
|
-
columns_added = set()
|
42
|
-
|
43
|
-
def add_table_val(col_name: RegisterFlag, val: Union[str, int, float]):
|
44
|
-
nonlocal columns_added
|
45
|
-
if True:
|
46
|
-
if isinstance(val, str):
|
47
|
-
self._edit_row_text(col_name=col_name, val=val)
|
48
|
-
else:
|
49
|
-
self._edit_row_num(col_name=col_name, val=val)
|
50
|
-
else:
|
51
|
-
if isinstance(val, str):
|
52
|
-
self.add_new_col_text(col_name=col_name, val=val)
|
53
|
-
else:
|
54
|
-
self.add_new_col_num(col_name=col_name, val=val)
|
55
|
-
columns_added.add(col_name)
|
56
|
-
|
57
|
-
def add_inject(inject: Inject):
|
58
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=inject.__class__.__name__)
|
59
|
-
|
60
|
-
def add_draw(draw: Draw):
|
61
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=draw.__class__.__name__)
|
62
|
-
add_table_val(col_name=RegisterFlag.DRAW_SPEED, val=SourceType.DEFAULT.value)
|
63
|
-
add_table_val(col_name=RegisterFlag.DRAW_OFFSET, val=SourceType.DEFAULT.value)
|
64
|
-
|
65
|
-
if draw.amount:
|
66
|
-
add_table_val(col_name=RegisterFlag.DRAW_VOLUME, val=Mode.SET.value)
|
67
|
-
add_table_val(col_name=RegisterFlag.DRAW_VOLUME_VALUE, val=draw.amount)
|
68
|
-
else:
|
69
|
-
add_table_val(col_name=RegisterFlag.DRAW_VOLUME, val=Mode.DEFAULT.value)
|
70
|
-
|
71
|
-
if draw.location:
|
72
|
-
add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.LOCATION.value)
|
73
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION, val=draw.location)
|
74
|
-
elif draw.source:
|
75
|
-
add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.SPECIFIC_LOCATION.value)
|
76
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_UNIT, val=1)
|
77
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_TRAY, val=1)
|
78
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_ROW, val=1)
|
79
|
-
add_table_val(col_name=RegisterFlag.DRAW_LOCATION_COLUMN, val=1)
|
80
|
-
else:
|
81
|
-
add_table_val(col_name=RegisterFlag.DRAW_SOURCE, val=SourceType.DEFAULT.value)
|
82
|
-
|
83
|
-
def add_wait(wait: Wait):
|
84
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=wait.__class__.__name__)
|
85
|
-
add_table_val(col_name=RegisterFlag.TIME, val=wait.duration)
|
86
|
-
|
87
|
-
def add_remote(remote: Remote):
|
88
|
-
add_table_val(col_name=RegisterFlag.FUNCTION, val=remote.__class__.__name__)
|
89
|
-
add_table_val(col_name=RegisterFlag.REMOTE, val=remote.command.value)
|
90
|
-
add_table_val(col_name=RegisterFlag.REMOTE_DUR, val=remote.duration)
|
91
|
-
|
92
|
-
self.send(Command.SAVE_METHOD_CMD)
|
93
|
-
rows = self.get_num_rows()
|
94
|
-
if rows.is_ok():
|
95
|
-
existing_row_num = rows.value.num_response
|
96
|
-
for i, function in enumerate(injector_table.functions):
|
97
|
-
if (i+1) > existing_row_num:
|
98
|
-
self.add_row()
|
99
|
-
if isinstance(function, Inject):
|
100
|
-
add_inject(function)
|
101
|
-
elif isinstance(function, Draw):
|
102
|
-
add_draw(function)
|
103
|
-
elif isinstance(function, Wait):
|
104
|
-
add_wait(function)
|
105
|
-
elif isinstance(function, Remote):
|
106
|
-
add_remote(function)
|
107
|
-
self.download()
|
108
|
-
self.send(Command.SAVE_METHOD_CMD)
|
109
|
-
self.send(Command.SWITCH_METHOD_CMD)
|
110
|
-
existing_row_num = self.get_num_rows().ok_value.num_response
|
111
|
-
|
112
|
-
def download(self):
|
113
|
-
self.send('Sleep 1')
|
114
|
-
self.sleepy_send("DownloadRCMethod WLS1")
|
115
|
-
self.send('Sleep 1')
|
116
|
-
self.sleepy_send("DownloadLWls 1")
|
117
|
-
self.send('Sleep 1')
|
51
|
+
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
|
3
|
+
import warnings
|
4
|
+
from typing import Dict, List, Optional, Union
|
4
5
|
|
5
6
|
from result import Err, Ok, Result
|
6
7
|
from xsdata.formats.dataclass.parsers import XmlParser
|
@@ -13,9 +14,15 @@ from ....utils.chromatogram import (
|
|
13
14
|
AgilentChannelChromatogramData,
|
14
15
|
AgilentHPLCChromatogram,
|
15
16
|
)
|
16
|
-
from ....utils.macro import
|
17
|
-
from ....utils.method_types import
|
18
|
-
|
17
|
+
from ....utils.macro import Command
|
18
|
+
from ....utils.method_types import (
|
19
|
+
HPLCMethodParams,
|
20
|
+
MethodDetails,
|
21
|
+
Param,
|
22
|
+
PType,
|
23
|
+
TimeTableEntry,
|
24
|
+
)
|
25
|
+
from ....utils.table_types import RegisterFlag, T, Table, TableOperation
|
19
26
|
from ..devices.injector import InjectorController
|
20
27
|
from .table import TableController
|
21
28
|
|
@@ -129,6 +136,10 @@ class MethodController(TableController):
|
|
129
136
|
register=self.table_locator.register,
|
130
137
|
register_flag=RegisterFlag.MAX_TIME))
|
131
138
|
|
139
|
+
def get_total_runtime(self) -> Union[int, float]:
|
140
|
+
"""Returns total method runtime in minutes."""
|
141
|
+
return self.get_post_time() + self.get_stop_time()
|
142
|
+
|
132
143
|
def current_method(self, method_name: str):
|
133
144
|
"""
|
134
145
|
Checks if a given method is already loaded into Chemstation. Method name does not need the ".M" extension.
|
@@ -173,6 +184,7 @@ class MethodController(TableController):
|
|
173
184
|
:raises FileNotFoundError: Method does not exist
|
174
185
|
:return: method details
|
175
186
|
"""
|
187
|
+
warnings.warn("This method is not actively maintained.")
|
176
188
|
method_folder = f"{method_name}.M"
|
177
189
|
method_path = os.path.join(self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml")
|
178
190
|
dad_path = os.path.join(self.src, method_folder, "Agilent1200erDadDriver1.RapidControl.MethodXML.xml")
|
@@ -347,29 +359,39 @@ class MethodController(TableController):
|
|
347
359
|
raise RuntimeError("Method failed to start.")
|
348
360
|
|
349
361
|
self.data_files.append(os.path.join(self.data_dirs[0], folder_name))
|
362
|
+
self.timeout = (self.get_total_runtime()) * 60
|
350
363
|
|
351
364
|
if stall_while_running:
|
352
|
-
self.timeout = (self.get_stop_time() + self.get_post_time()) * 60
|
353
365
|
run_completed = self.check_hplc_done_running()
|
354
366
|
if run_completed.is_ok():
|
355
367
|
self.data_files[-1] = run_completed.ok_value
|
356
368
|
else:
|
357
369
|
raise RuntimeError("Run error has occurred.")
|
358
370
|
else:
|
359
|
-
self.data_files[-1]
|
371
|
+
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
|
372
|
+
while folder.is_err():
|
373
|
+
folder = self.fuzzy_match_most_recent_folder(self.data_files[-1])
|
374
|
+
if folder.is_ok():
|
375
|
+
self.data_files[-1] = folder.ok_value
|
376
|
+
else:
|
377
|
+
warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
|
378
|
+
warnings.warn(warning)
|
379
|
+
|
360
380
|
|
361
381
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
362
382
|
if os.path.exists(most_recent_folder):
|
363
383
|
return Ok(most_recent_folder)
|
364
384
|
return Err("Folder not found!")
|
365
385
|
|
366
|
-
def get_data(self, custom_path: Optional[str] = None
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
386
|
+
def get_data(self, custom_path: Optional[str] = None) -> AgilentChannelChromatogramData:
|
387
|
+
custom_path = custom_path if custom_path else self.data_files[-1]
|
388
|
+
self.get_spectrum_at_channels(custom_path)
|
389
|
+
return AgilentChannelChromatogramData(**self.spectra)
|
390
|
+
|
391
|
+
def get_data_uv(self, custom_path: Optional[str] = None) -> Dict[str, AgilentHPLCChromatogram]:
|
392
|
+
custom_path = custom_path if custom_path else self.data_files[-1]
|
393
|
+
self.get_uv_spectrum(custom_path)
|
394
|
+
return self.uv
|
373
395
|
|
374
396
|
def get_report(self, custom_path: Optional[str] = None,
|
375
397
|
report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
@@ -1,19 +1,29 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
|
-
from typing import
|
3
|
+
from typing import Dict, List, Optional
|
4
4
|
|
5
|
-
from result import
|
5
|
+
from result import Err, Ok, Result
|
6
6
|
from typing_extensions import override
|
7
7
|
|
8
|
-
from .
|
9
|
-
from .. import MethodController
|
10
|
-
from ....analysis.process_report import ReportType, AgilentReport
|
8
|
+
from ....analysis.process_report import AgilentReport, ReportType
|
11
9
|
from ....control.controllers.comm import CommunicationController
|
12
|
-
from ....utils.chromatogram import
|
10
|
+
from ....utils.chromatogram import (
|
11
|
+
SEQUENCE_TIME_FORMAT,
|
12
|
+
AgilentChannelChromatogramData,
|
13
|
+
AgilentHPLCChromatogram,
|
14
|
+
)
|
13
15
|
from ....utils.macro import Command
|
14
|
-
from ....utils.sequence_types import
|
16
|
+
from ....utils.sequence_types import (
|
17
|
+
InjectionSource,
|
18
|
+
SampleType,
|
19
|
+
SequenceDataFiles,
|
20
|
+
SequenceEntry,
|
21
|
+
SequenceTable,
|
22
|
+
)
|
15
23
|
from ....utils.table_types import RegisterFlag, Table
|
16
|
-
from ....utils.tray_types import
|
24
|
+
from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
|
25
|
+
from .. import MethodController
|
26
|
+
from .table import TableController
|
17
27
|
|
18
28
|
|
19
29
|
class SequenceController(TableController):
|
@@ -82,7 +92,7 @@ class SequenceController(TableController):
|
|
82
92
|
time.sleep(2)
|
83
93
|
self.send(Command.GET_SEQUENCE_CMD)
|
84
94
|
time.sleep(2)
|
85
|
-
parsed_response = self.receive().
|
95
|
+
parsed_response = self.receive().ok_value.string_response
|
86
96
|
|
87
97
|
assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
|
88
98
|
self.table_state = None
|
@@ -97,7 +107,7 @@ class SequenceController(TableController):
|
|
97
107
|
self.table_state = sequence_table
|
98
108
|
rows = self.get_num_rows()
|
99
109
|
if rows.is_ok():
|
100
|
-
existing_row_num = rows.
|
110
|
+
existing_row_num = rows.ok_value.num_response
|
101
111
|
wanted_row_num = len(sequence_table.rows)
|
102
112
|
while existing_row_num != wanted_row_num:
|
103
113
|
if wanted_row_num > existing_row_num:
|
@@ -127,7 +137,6 @@ class SequenceController(TableController):
|
|
127
137
|
self.add_row()
|
128
138
|
self.send(Command.SAVE_SEQUENCE_CMD)
|
129
139
|
num_rows = self.get_num_rows()
|
130
|
-
|
131
140
|
if row.vial_location:
|
132
141
|
loc = row.vial_location
|
133
142
|
if isinstance(loc, TenVialColumn):
|
@@ -135,7 +144,6 @@ class SequenceController(TableController):
|
|
135
144
|
elif isinstance(loc, FiftyFourVialPlate):
|
136
145
|
loc = row.vial_location.value()
|
137
146
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
|
138
|
-
|
139
147
|
if row.method:
|
140
148
|
method_dir = self.method_controller.src
|
141
149
|
possible_path = os.path.join(method_dir, row.method) + ".M\\"
|
@@ -143,23 +151,18 @@ class SequenceController(TableController):
|
|
143
151
|
if os.path.exists(possible_path):
|
144
152
|
method = os.path.join(method_dir, row.method)
|
145
153
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
|
146
|
-
|
147
154
|
if row.num_inj:
|
148
155
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj)
|
149
|
-
|
150
156
|
if row.inj_vol:
|
151
157
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol)
|
152
|
-
|
153
158
|
if row.inj_source:
|
154
159
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value)
|
155
|
-
|
156
160
|
if row.sample_name:
|
157
161
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
|
158
162
|
if row.data_file:
|
159
163
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file)
|
160
164
|
else:
|
161
165
|
self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
|
162
|
-
|
163
166
|
if row.sample_type:
|
164
167
|
self._edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
|
165
168
|
|
@@ -179,15 +182,15 @@ class SequenceController(TableController):
|
|
179
182
|
|
180
183
|
total_runtime = 0
|
181
184
|
for entry in self.table_state.rows:
|
182
|
-
curr_method_runtime = self.method_controller.
|
183
|
-
loaded_method = self.method_controller.get_method_name()
|
185
|
+
curr_method_runtime = self.method_controller.get_total_runtime()
|
186
|
+
loaded_method = self.method_controller.get_method_name().removesuffix(".M")
|
184
187
|
method_path = entry.method.split(sep="\\")
|
185
188
|
method_name = method_path[-1]
|
186
189
|
if loaded_method != method_name:
|
187
|
-
method_dir =
|
190
|
+
method_dir = "\\".join(method_path[:-1])+"\\" if len(method_path) > 1 else None
|
188
191
|
self.method_controller.switch(method_name=method_name,
|
189
192
|
alt_method_dir=method_dir)
|
190
|
-
curr_method_runtime = self.method_controller.
|
193
|
+
curr_method_runtime = self.method_controller.get_total_runtime()
|
191
194
|
total_runtime += curr_method_runtime
|
192
195
|
|
193
196
|
timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
|
@@ -196,8 +199,8 @@ class SequenceController(TableController):
|
|
196
199
|
|
197
200
|
if self.check_hplc_is_running():
|
198
201
|
folder_name = f"{self.table_state.name} {timestamp}"
|
199
|
-
|
200
|
-
|
202
|
+
data_file = SequenceDataFiles(dir=folder_name, sequence_name=self.table_state.name)
|
203
|
+
self.data_files.append(data_file)
|
201
204
|
|
202
205
|
if stall_while_running:
|
203
206
|
run_completed = self.check_hplc_done_running()
|
@@ -205,10 +208,8 @@ class SequenceController(TableController):
|
|
205
208
|
self.data_files[-1] = run_completed.ok_value
|
206
209
|
else:
|
207
210
|
raise RuntimeError("Run error has occurred.")
|
208
|
-
|
209
|
-
|
210
|
-
child_dirs=[],
|
211
|
-
sequence_name=self.table_state.name)
|
211
|
+
else:
|
212
|
+
raise RuntimeError("Sequence run did not start.")
|
212
213
|
|
213
214
|
@override
|
214
215
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: SequenceDataFiles) -> Result[SequenceDataFiles, str]:
|
@@ -248,17 +249,25 @@ class SequenceController(TableController):
|
|
248
249
|
except Exception:
|
249
250
|
return Err("Failed to get sequence folder")
|
250
251
|
|
251
|
-
def
|
252
|
-
|
253
|
-
if len(
|
254
|
-
self.data_files[-1] = self.fuzzy_match_most_recent_folder(
|
255
|
-
|
256
|
-
all_w_spectra: list[Dict[str, AgilentHPLCChromatogram]] = []
|
252
|
+
def get_data_uv(self,custom_path: Optional[str] = None) -> List[Dict[str, AgilentHPLCChromatogram]]:
|
253
|
+
custom_path = SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="") if custom_path else self.data_files[-1]
|
254
|
+
if len(custom_path.child_dirs) == 0:
|
255
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(custom_path).ok_value
|
256
|
+
all_w_spectra: List[Dict[str, AgilentHPLCChromatogram]] = []
|
257
257
|
for row in self.data_files[-1].child_dirs:
|
258
|
-
self.
|
259
|
-
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
258
|
+
self.get_uv_spectrum(row)
|
260
259
|
all_w_spectra.append(self.uv)
|
261
|
-
return
|
260
|
+
return all_w_spectra
|
261
|
+
|
262
|
+
def get_data(self, custom_path: Optional[str] = None) -> List[AgilentChannelChromatogramData]:
|
263
|
+
custom_path = SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="") if custom_path else self.data_files[-1]
|
264
|
+
if len(custom_path.child_dirs) == 0:
|
265
|
+
self.data_files[-1] = self.fuzzy_match_most_recent_folder(custom_path).ok_value
|
266
|
+
spectra: List[AgilentChannelChromatogramData] = []
|
267
|
+
for row in self.data_files[-1].child_dirs:
|
268
|
+
self.get_spectrum_at_channels(row)
|
269
|
+
spectra.append(AgilentChannelChromatogramData(**self.spectra))
|
270
|
+
return spectra
|
262
271
|
|
263
272
|
def get_report(self, custom_path: Optional[str] = None,
|
264
273
|
report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
|
@@ -268,7 +277,7 @@ class SequenceController(TableController):
|
|
268
277
|
child_dirs=[],
|
269
278
|
sequence_name="NA")).ok_value)
|
270
279
|
parent_dir = self.data_files[-1]
|
271
|
-
spectra = self.get_data(
|
280
|
+
spectra = self.get_data()
|
272
281
|
reports = []
|
273
282
|
for i, child_dir in enumerate(parent_dir.child_dirs):
|
274
283
|
metd_report = self.get_report_details(child_dir, report_type)
|
@@ -3,24 +3,34 @@ Abstract module containing shared logic for Method and Sequence tables.
|
|
3
3
|
|
4
4
|
Authors: Lucy Hao
|
5
5
|
"""
|
6
|
+
from __future__ import annotations
|
6
7
|
|
7
8
|
import abc
|
8
9
|
import math
|
9
10
|
import os
|
10
11
|
import time
|
11
|
-
|
12
|
+
import warnings
|
13
|
+
from typing import Dict, List, Optional, Tuple, Union
|
12
14
|
|
13
15
|
import polling
|
14
16
|
import rainbow as rb
|
15
|
-
from result import Result,
|
16
|
-
|
17
|
-
from ....analysis.process_report import
|
17
|
+
from result import Err, Result, Ok
|
18
|
+
|
19
|
+
from ....analysis.process_report import (
|
20
|
+
AgilentReport,
|
21
|
+
CSVProcessor,
|
22
|
+
ReportType,
|
23
|
+
TXTProcessor,
|
24
|
+
)
|
18
25
|
from ....control.controllers.comm import CommunicationController
|
19
|
-
from ....utils.chromatogram import
|
26
|
+
from ....utils.chromatogram import (
|
27
|
+
AgilentChannelChromatogramData,
|
28
|
+
AgilentHPLCChromatogram,
|
29
|
+
)
|
20
30
|
from ....utils.macro import Command, HPLCRunningStatus, Response
|
21
31
|
from ....utils.method_types import MethodDetails
|
22
32
|
from ....utils.sequence_types import SequenceDataFiles, SequenceTable
|
23
|
-
from ....utils.table_types import
|
33
|
+
from ....utils.table_types import RegisterFlag, T, Table, TableOperation
|
24
34
|
|
25
35
|
TableType = Union[MethodDetails, SequenceTable]
|
26
36
|
|
@@ -200,21 +210,25 @@ class TableController(abc.ABC):
|
|
200
210
|
return started_running
|
201
211
|
|
202
212
|
def check_hplc_run_finished(self) -> Tuple[float, bool]:
|
203
|
-
|
204
|
-
if
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
213
|
+
done_running = self.controller.check_if_not_running()
|
214
|
+
if self.curr_run_starting_time and self.timeout:
|
215
|
+
time_passed = (time.time() - self.curr_run_starting_time)
|
216
|
+
if time_passed > self.timeout:
|
217
|
+
enough_time_passed = time_passed >= self.timeout
|
218
|
+
run_finished = enough_time_passed and done_running
|
219
|
+
if run_finished:
|
220
|
+
self._reset_time()
|
221
|
+
return 0, run_finished
|
222
|
+
else:
|
223
|
+
time_left = self.timeout - time_passed
|
224
|
+
return time_left, self.controller.check_if_not_running()
|
225
|
+
return 0, self.controller.check_if_not_running()
|
226
|
+
|
227
|
+
def check_hplc_done_running(self) -> Ok[T] | Err[str]:
|
214
228
|
"""
|
215
229
|
Checks if ChemStation has finished running and can read data back
|
216
230
|
|
217
|
-
:return:
|
231
|
+
:return: Data file object containing most recent run file information.
|
218
232
|
"""
|
219
233
|
finished_run = False
|
220
234
|
minutes = math.ceil(self.timeout / 60)
|
@@ -244,16 +258,18 @@ class TableController(abc.ABC):
|
|
244
258
|
except Exception:
|
245
259
|
self._reset_time()
|
246
260
|
return check_folder
|
247
|
-
|
248
|
-
else:
|
249
|
-
return Err("Run did not complete as expected")
|
261
|
+
return Err("Run did not complete as expected")
|
250
262
|
|
251
263
|
@abc.abstractmethod
|
252
264
|
def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
|
253
265
|
pass
|
254
266
|
|
255
267
|
@abc.abstractmethod
|
256
|
-
def get_data(self) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
268
|
+
def get_data(self, custom_path: Optional[str] = None) -> Union[List[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
|
269
|
+
pass
|
270
|
+
|
271
|
+
@abc.abstractmethod
|
272
|
+
def get_data_uv(self) -> Union[List[Dict[str, AgilentHPLCChromatogram]], Dict[str, AgilentHPLCChromatogram]]:
|
257
273
|
pass
|
258
274
|
|
259
275
|
@abc.abstractmethod
|
@@ -263,11 +279,11 @@ class TableController(abc.ABC):
|
|
263
279
|
def get_uv_spectrum(self, path: str):
|
264
280
|
data_uv = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
|
265
281
|
times = data_uv.xlabels
|
266
|
-
|
267
|
-
|
268
|
-
for (i, w) in enumerate(
|
282
|
+
wavelengths = data_uv.ylabels
|
283
|
+
absorbances = data_uv.data.transpose()
|
284
|
+
for (i, w) in enumerate(wavelengths):
|
269
285
|
self.uv[w] = AgilentHPLCChromatogram()
|
270
|
-
self.uv[w].attach_spectrum(times,
|
286
|
+
self.uv[w].attach_spectrum(times, absorbances[i])
|
271
287
|
|
272
288
|
def get_report_details(self, path: str,
|
273
289
|
report_type: ReportType = ReportType.TXT) -> AgilentReport:
|
@@ -281,18 +297,18 @@ class TableController(abc.ABC):
|
|
281
297
|
self.report = csv_report.ok_value
|
282
298
|
return self.report
|
283
299
|
|
284
|
-
|
300
|
+
|
301
|
+
def get_spectrum_at_channels(self, data_path: str):
|
285
302
|
"""
|
286
303
|
Load chromatogram for any channel in spectra dictionary.
|
287
304
|
"""
|
288
|
-
if read_uv:
|
289
|
-
self.get_uv_spectrum(data_path)
|
290
305
|
for channel, spec in self.spectra.items():
|
291
306
|
try:
|
292
307
|
spec.load_spectrum(data_path=data_path, channel=channel)
|
293
308
|
except FileNotFoundError:
|
294
309
|
self.spectra[channel] = AgilentHPLCChromatogram()
|
295
|
-
|
310
|
+
warning = f"No data at channel: {channel}"
|
311
|
+
warnings.warn(warning)
|
296
312
|
|
297
313
|
def _reset_time(self):
|
298
314
|
self.curr_run_starting_time = None
|
pychemstation/control/hplc.py
CHANGED
@@ -3,12 +3,20 @@ Module to provide API for higher-level HPLC actions.
|
|
3
3
|
|
4
4
|
Authors: Lucy Hao
|
5
5
|
"""
|
6
|
+
from __future__ import annotations
|
6
7
|
|
7
|
-
from typing import
|
8
|
+
from typing import Dict, List, Optional, Tuple, Union
|
8
9
|
|
10
|
+
from pychemstation.utils.chromatogram import (
|
11
|
+
AgilentHPLCChromatogram,
|
12
|
+
)
|
9
13
|
from .controllers.devices.injector import InjectorController
|
10
|
-
from ..analysis.process_report import
|
11
|
-
from ..control.controllers import
|
14
|
+
from ..analysis.process_report import AgilentReport, ReportType
|
15
|
+
from ..control.controllers import (
|
16
|
+
CommunicationController,
|
17
|
+
MethodController,
|
18
|
+
SequenceController,
|
19
|
+
)
|
12
20
|
from ..utils.chromatogram import AgilentChannelChromatogramData
|
13
21
|
from ..utils.injector_types import InjectorTable
|
14
22
|
from ..utils.macro import Command, Response, Status
|
@@ -44,7 +52,8 @@ class HPLCController:
|
|
44
52
|
method_dir: str,
|
45
53
|
sequence_dir: str,
|
46
54
|
data_dirs: List[str],
|
47
|
-
offline: bool = False
|
55
|
+
offline: bool = False,
|
56
|
+
debug: bool = False,):
|
48
57
|
"""Initialize HPLC controller. The `hplc_talk.mac` macro file must be loaded in the Chemstation software.
|
49
58
|
`comm_dir` must match the file path in the macro file. All file paths are normal strings, with the left slash
|
50
59
|
double escaped: "C:\\my_folder\\"
|
@@ -56,7 +65,7 @@ class HPLCController:
|
|
56
65
|
:param sequence_dir: Name of directory where sequence files are stored.
|
57
66
|
:raises FileNotFoundError: If either `data_dir`, `method_dir`, `sequence_dir`, `sequence_data_dir`or `comm_dir` is not a valid directory.
|
58
67
|
"""
|
59
|
-
self.comm = CommunicationController(comm_dir=comm_dir) if not offline else None
|
68
|
+
self.comm = CommunicationController(comm_dir=comm_dir, debug=debug) if not offline else None
|
60
69
|
self.method_controller = MethodController(controller=self.comm,
|
61
70
|
src=method_dir,
|
62
71
|
data_dirs=data_dirs,
|
@@ -200,14 +209,17 @@ class HPLCController:
|
|
200
209
|
return self.method_controller.get_report(custom_path=custom_path, report_type=report_type)[0]
|
201
210
|
|
202
211
|
def get_last_run_method_data(self, read_uv: bool = False,
|
203
|
-
|
212
|
+
custom_path: Optional[str] = None) -> Dict[str, AgilentHPLCChromatogram] | AgilentChannelChromatogramData:
|
204
213
|
"""
|
205
214
|
Returns the last run method data.
|
206
215
|
|
207
|
-
:param
|
216
|
+
: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.
|
208
217
|
:param read_uv: whether to also read the UV file
|
209
218
|
"""
|
210
|
-
|
219
|
+
if read_uv:
|
220
|
+
return self.method_controller.get_data_uv(custom_path=custom_path)
|
221
|
+
else:
|
222
|
+
return self.method_controller.get_data(custom_path=custom_path)
|
211
223
|
|
212
224
|
def get_last_run_sequence_reports(self,
|
213
225
|
custom_path: Optional[str] = None,
|
@@ -221,14 +233,18 @@ class HPLCController:
|
|
221
233
|
return self.sequence_controller.get_report(custom_path=custom_path, report_type=report_type)
|
222
234
|
|
223
235
|
def get_last_run_sequence_data(self, read_uv: bool = False,
|
224
|
-
|
236
|
+
custom_path: Optional[str] = None) -> List[Dict[str, AgilentHPLCChromatogram]] | \
|
237
|
+
List[AgilentChannelChromatogramData]:
|
225
238
|
"""
|
226
239
|
Returns data for all rows in the last run sequence data.
|
227
240
|
|
228
|
-
:param
|
241
|
+
: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.
|
229
242
|
:param read_uv: whether to also read the UV file
|
230
243
|
"""
|
231
|
-
|
244
|
+
if read_uv:
|
245
|
+
return self.sequence_controller.get_data_uv(custom_path=custom_path)
|
246
|
+
else:
|
247
|
+
return self.sequence_controller.get_data(custom_path=custom_path)
|
232
248
|
|
233
249
|
def check_loaded_sequence(self) -> str:
|
234
250
|
"""Returns the name of the currently loaded sequence."""
|
@@ -6,16 +6,13 @@ from dataclasses import dataclass
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
|
-
from .parsing import CHFile
|
10
9
|
from ..analysis import AbstractSpectrum
|
11
|
-
|
12
|
-
# standard filenames for spectral data
|
13
|
-
CHANNELS = {"A": "01", "B": "02", "C": "03", "D": "04"}
|
10
|
+
from .parsing import CHFile
|
14
11
|
|
15
12
|
ACQUISITION_PARAMETERS = "acq.txt"
|
16
13
|
|
17
14
|
# format used in acquisition parameters
|
18
|
-
TIME_FORMAT = "%Y-%m-%d
|
15
|
+
TIME_FORMAT = "%Y-%m-%d %H-%M-%S"
|
19
16
|
SEQUENCE_TIME_FORMAT = "%Y-%m-%d %H-%M"
|
20
17
|
|
21
18
|
|
pychemstation/utils/macro.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from enum import Enum
|
4
|
-
from typing import Union
|
5
3
|
from dataclasses import dataclass
|
4
|
+
from enum import Enum
|
5
|
+
from typing import Union, Any, Type
|
6
6
|
|
7
7
|
|
8
8
|
@dataclass
|
@@ -87,7 +87,7 @@ class HPLCErrorStatus(Enum):
|
|
87
87
|
MALFORMED = "MALFORMED"
|
88
88
|
|
89
89
|
|
90
|
-
def str_to_status(status: str) ->
|
90
|
+
def str_to_status(status: str) -> Type[HPLCRunningStatus[Any] | HPLCErrorStatus[Any] | HPLCAvailStatus[Any]]:
|
91
91
|
if HPLCErrorStatus.has_member_key(status):
|
92
92
|
return HPLCErrorStatus[status]
|
93
93
|
if HPLCRunningStatus.has_member_key(status):
|
@@ -2,11 +2,11 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from enum import Enum
|
5
|
-
from typing import
|
5
|
+
from typing import Any, Optional, Union
|
6
6
|
|
7
|
+
from ..generated import Signal
|
7
8
|
from .injector_types import InjectorTable
|
8
9
|
from .table_types import RegisterFlag
|
9
|
-
from ..generated import Signal
|
10
10
|
|
11
11
|
|
12
12
|
class PType(Enum):
|
pychemstation/utils/parsing.py
CHANGED
@@ -2,16 +2,16 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from enum import Enum
|
5
|
-
from typing import Optional,
|
5
|
+
from typing import Optional, List
|
6
6
|
|
7
|
-
from pychemstation.utils.tray_types import
|
7
|
+
from pychemstation.utils.tray_types import Tray
|
8
8
|
|
9
9
|
|
10
10
|
@dataclass
|
11
11
|
class SequenceDataFiles:
|
12
12
|
sequence_name: str
|
13
13
|
dir: str
|
14
|
-
child_dirs: Optional[
|
14
|
+
child_dirs: Optional[List[str]] = None
|
15
15
|
|
16
16
|
|
17
17
|
class SampleType(Enum):
|
@@ -6,7 +6,7 @@ analysis.
|
|
6
6
|
import numpy as np
|
7
7
|
import scipy
|
8
8
|
|
9
|
-
from .
|
9
|
+
from ..utils.num_utils import find_nearest_value_index
|
10
10
|
|
11
11
|
|
12
12
|
def create_binary_peak_map(data):
|
@@ -156,14 +156,14 @@ def filter_noisy_regions(y_data, peaks_regions):
|
|
156
156
|
# compute the actual regions data points
|
157
157
|
y_data_regions = []
|
158
158
|
for region in peaks_regions:
|
159
|
-
y_data_regions.append(y_data[region[0]
|
159
|
+
y_data_regions.append(y_data[region[0]: region[-1]])
|
160
160
|
|
161
161
|
# compute noise data regions, i.e. in between peak regions
|
162
162
|
noise_data_regions = []
|
163
163
|
for row, _ in enumerate(peaks_regions):
|
164
164
|
try:
|
165
165
|
noise_data_regions.append(
|
166
|
-
y_data[peaks_regions[row][1]
|
166
|
+
y_data[peaks_regions[row][1]: peaks_regions[row + 1][0]]
|
167
167
|
)
|
168
168
|
except IndexError:
|
169
169
|
# exception for the last row -> discard
|
@@ -1,10 +1,17 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pychemstation
|
3
|
-
Version: 0.
|
4
|
-
Summary: Library to interact with Chemstation software, primarily used in Hein
|
3
|
+
Version: 0.10.1
|
4
|
+
Summary: Library to interact with Chemstation software, primarily used in Hein lagit branch -mb
|
5
|
+
Project-URL: Documentation, https://pychemstation-e5a086.gitlab.io/pychemstation.html
|
6
|
+
Project-URL: Repository, https://gitlab.com/heingroup/device-api/pychemstation
|
5
7
|
Author-email: lucyhao <hao.lucyy@gmail.com>
|
6
8
|
License-File: LICENSE
|
7
|
-
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
14
|
+
Requires-Python: >=3.10
|
8
15
|
Requires-Dist: aghplctools>=4.8.8
|
9
16
|
Requires-Dist: coverage>=7.6.1
|
10
17
|
Requires-Dist: matplotlib>=3.7.5
|
@@ -27,8 +34,12 @@ Description-Content-Type: text/markdown
|
|
27
34
|
|
28
35
|
[](https://pypi.org/project/pychemstation/)
|
29
36
|
|
37
|
+
> **_NOTE:_** If you are running Python **3.8**, use versions 0.**8**.x. If you are running Python **>=3.10**, use
|
38
|
+
> version 0.**10**.x. You are welcome to use newer pychemstation versions with older Python versions, but functionality
|
39
|
+
> is not guaranteed!
|
40
|
+
|
30
41
|
Unofficial Python package to control Agilent Chemstation; we are not affiliated with Agilent.
|
31
|
-
Check out the [docs](https://
|
42
|
+
Check out the [docs](https://pychemstation-e5a086.gitlab.io/pychemstation.html) for usage instructions. This project is under
|
32
43
|
active development, and breaking changes may occur at any moment.
|
33
44
|
|
34
45
|
## Getting started
|
@@ -110,6 +121,8 @@ Lucy Hao, Maria Politi
|
|
110
121
|
|
111
122
|
- Adapted from [**AnalyticalLabware**](https://github.com/croningp/analyticallabware), created by members in the Cronin
|
112
123
|
Group. Copyright © Cronin Group, used under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license.
|
113
|
-
- Adapted from the [MACROS](https://github.com/Bourne-Group/HPLCMethodOptimisationGUI)
|
114
|
-
|
115
|
-
|
124
|
+
- Adapted from the [MACROS](https://github.com/Bourne-Group/HPLCMethodOptimisationGUI) used in [**Operator-free HPLC
|
125
|
+
automated method development guided by Bayesian optimization
|
126
|
+
**](https://pubs.rsc.org/en/content/articlelanding/2024/dd/d4dd00062e),
|
127
|
+
created by members in the Bourne Group. Copyright © Bourne Group, used under
|
128
|
+
the [MIT](https://opensource.org/license/mit) license.
|
@@ -0,0 +1,37 @@
|
|
1
|
+
pychemstation/__init__.py,sha256=SpTl-Tg1B1HTyjNOE-8ue-N2wGnXN_2zl7RFUSxlkiM,33
|
2
|
+
pychemstation/analysis/__init__.py,sha256=Vi31PZ7fgIvyuIhkgCNvEYeV_jtUCa8FCrAGbpks7zc,83
|
3
|
+
pychemstation/analysis/base_spectrum.py,sha256=9WkOLk2qTAYTF1ALNUenVPoosOtBiLRvy2ni8zlGU5w,16540
|
4
|
+
pychemstation/analysis/process_report.py,sha256=ZOgcRUMGXdGMrMFcdzsSwdOk6OBp-PpcA83vSvnmVSg,11871
|
5
|
+
pychemstation/control/README.md,sha256=y73F-qh4g3k9Z9vBeQATqqhbwMfKB5MvGqJi5GgSUJQ,3357
|
6
|
+
pychemstation/control/__init__.py,sha256=Js79QczKZxDNZrzG1-4yl_whCoP2aw-yDAQJungiiic,100
|
7
|
+
pychemstation/control/hplc.py,sha256=f7NHcCWtc_ApasjU5VMQtEvWQxoIboi_-RU9dkjORrs,13215
|
8
|
+
pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
|
9
|
+
pychemstation/control/controllers/__init__.py,sha256=r7UU0u5zuJHO_KTqt-4Gy65BMlyXtxrdskiOhtO9Yw4,260
|
10
|
+
pychemstation/control/controllers/comm.py,sha256=AN3A3ThvIsOKWY7Kmb_tnE5pRUqI7O2ID8M54z_w-uE,7831
|
11
|
+
pychemstation/control/controllers/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
+
pychemstation/control/controllers/devices/device.py,sha256=JNBKVRka1I3LA1lElIeUO0j93BTK5IJufTPNq95OhNE,1473
|
13
|
+
pychemstation/control/controllers/devices/injector.py,sha256=s40jFd0B_wJn4ID6SgAk_F8WhnGbbflpiti4uwIhaSs,1950
|
14
|
+
pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
pychemstation/control/controllers/tables/method.py,sha256=LHoNRSTsSrrktghqNnU5KTRXDczcuGgqdqKEs_3sUXI,18609
|
16
|
+
pychemstation/control/controllers/tables/ms.py,sha256=JFD-tOhu8uRyKdl-E3-neRssii8MNqVRIlsrnFhNY_M,682
|
17
|
+
pychemstation/control/controllers/tables/sequence.py,sha256=DwX0wi5GhHmk8wnl89X2MKvqTSojLycV4IEvNjdVdWg,13400
|
18
|
+
pychemstation/control/controllers/tables/table.py,sha256=zMzsQgkLxM3LVe9w-OM8WjLZxTo9zrmBTNH182gAyh8,12750
|
19
|
+
pychemstation/generated/__init__.py,sha256=GAoZFAYbPVEJDkcOw3e1rgOqd7TCW0HyKNPM8OMehMg,1005
|
20
|
+
pychemstation/generated/dad_method.py,sha256=xTUiSCvkXcxBUhjVm1YZKu-tHs16k23pF-0xYrQSwWA,8408
|
21
|
+
pychemstation/generated/pump_method.py,sha256=797RsSDI2-QPf_BX8isZQx0O3aRx84EGIXJXhXw3IS0,12180
|
22
|
+
pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
+
pychemstation/utils/chromatogram.py,sha256=2Los_ix_wAi4yxG_9neGRnNYPre9_uC1mrne3Ygit5c,3242
|
24
|
+
pychemstation/utils/injector_types.py,sha256=PXwJK1uXs8hlQ6dWIEbAGfk2BpQJQmN3SlUbL4ntZz0,822
|
25
|
+
pychemstation/utils/macro.py,sha256=Lh8aGcwo9sC2Sfc19Wgms5d3VgBLV3VXdvspqNHYE18,2931
|
26
|
+
pychemstation/utils/method_types.py,sha256=ZOFMJ7wpqWBRJNIvOux-7Ou4nJVSuyWRHrd37wMnPa0,1638
|
27
|
+
pychemstation/utils/num_utils.py,sha256=rgpTJTrpsiBANbtsfys9xj0sqlTe__3J0OSeoygaQTM,2081
|
28
|
+
pychemstation/utils/parsing.py,sha256=iFdnie-v0u5JI4cctJye_yhWQxHgy72_PWZ7w57Ltvg,9318
|
29
|
+
pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
|
30
|
+
pychemstation/utils/sequence_types.py,sha256=WyJWL18Q86TgoUpYH2_CevoTZuhcui0EnyHYdrp3Nmo,1070
|
31
|
+
pychemstation/utils/spec_utils.py,sha256=MQZPDwCYZRfkEhNJQUt74huPexXBlJ3W4o7_230JWcE,10210
|
32
|
+
pychemstation/utils/table_types.py,sha256=7txqW_oNpkh4venSkGEtreVe6UV9dzNB1DTrIeTkQHA,3217
|
33
|
+
pychemstation/utils/tray_types.py,sha256=eOO-muUjadyvCM8JnYAZVyxJeyYBlENP1zXiFskAFbs,5049
|
34
|
+
pychemstation-0.10.1.dist-info/METADATA,sha256=gWFw70cbvfeIPV1T3IJDLDktm4FjjXn1JvPfTDQZoDw,5274
|
35
|
+
pychemstation-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
36
|
+
pychemstation-0.10.1.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
|
37
|
+
pychemstation-0.10.1.dist-info/RECORD,,
|
@@ -1,37 +0,0 @@
|
|
1
|
-
pychemstation/__init__.py,sha256=SpTl-Tg1B1HTyjNOE-8ue-N2wGnXN_2zl7RFUSxlkiM,33
|
2
|
-
pychemstation/analysis/__init__.py,sha256=EWoU47iyn9xGS-b44zK9eq50bSjOV4AC5dvt420YMI4,44
|
3
|
-
pychemstation/analysis/base_spectrum.py,sha256=dPULGlr-sMHNMDIdyzlvX8taw_nkBliKTb00lNI8cwk,16517
|
4
|
-
pychemstation/analysis/process_report.py,sha256=wysKaA06UeK_9ba2SZc9maP4P-HQfZDMD9NNt21kWcY,11850
|
5
|
-
pychemstation/analysis/spec_utils.py,sha256=UOo9hJR3evJfmaohEEsyb7aq6X996ofuUfu-GKjiDi8,10201
|
6
|
-
pychemstation/analysis/utils.py,sha256=rgpTJTrpsiBANbtsfys9xj0sqlTe__3J0OSeoygaQTM,2081
|
7
|
-
pychemstation/control/README.md,sha256=7Q0rY014y7Qq8wfL7GSQG0l2P1PXfmgB0qZ2YkkE7n0,3350
|
8
|
-
pychemstation/control/__init__.py,sha256=4xTy8X-mkn_PPZKr7w9rnj1wZhtmTesbQptPhpYmKXs,64
|
9
|
-
pychemstation/control/hplc.py,sha256=E_QFMOqUabiqCO6Pu4lZYTsiWhdQULw4AqyKPwiQx90,12628
|
10
|
-
pychemstation/control/controllers/README.md,sha256=S5cd4NJmPjs6TUH98BtPJJhiS1Lu-mxLCNS786ogOrQ,32
|
11
|
-
pychemstation/control/controllers/__init__.py,sha256=LuFEsVGN5sXuHW_DG61DWBDJ_fU4dMls4bQJ5XNRaok,166
|
12
|
-
pychemstation/control/controllers/comm.py,sha256=fRCFh3Ye-HnoGaur69NRdkLHNtdlMoJjIOTqeJe1tAk,7720
|
13
|
-
pychemstation/control/controllers/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
pychemstation/control/controllers/devices/device.py,sha256=d9SwasjXTXplkRK5eH3asrNPo3BBZmAz6CKBYAuKSIc,1249
|
15
|
-
pychemstation/control/controllers/devices/injector.py,sha256=ynPQtvMFt1iK0LQBf4ZEYdxJCyavmashXwyCQbmRjuw,5542
|
16
|
-
pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
pychemstation/control/controllers/tables/method.py,sha256=sX83sHlnaAm8Wop_d28RDAk4DBPvQWduH3Atodtmw4A,17714
|
18
|
-
pychemstation/control/controllers/tables/ms.py,sha256=JFD-tOhu8uRyKdl-E3-neRssii8MNqVRIlsrnFhNY_M,682
|
19
|
-
pychemstation/control/controllers/tables/sequence.py,sha256=1bZdoVK877KuAwKTjo-NWvOU8rHxx8cQBL4I71ZN4EM,13105
|
20
|
-
pychemstation/control/controllers/tables/table.py,sha256=Adis9584LJqP4yjLj5XrIxqq1jzdBiDieOG4vc90EIU,12327
|
21
|
-
pychemstation/generated/__init__.py,sha256=GAoZFAYbPVEJDkcOw3e1rgOqd7TCW0HyKNPM8OMehMg,1005
|
22
|
-
pychemstation/generated/dad_method.py,sha256=zfS9op450CRSGPKkUr9qUyPBbND06b9N8SUU9j4cosM,8408
|
23
|
-
pychemstation/generated/pump_method.py,sha256=c_FB14rgODZyH5eDb3kxZAI77xRj1HMpQkE2R6MypNA,12180
|
24
|
-
pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
-
pychemstation/utils/chromatogram.py,sha256=s6mqru88E9YQGLbdr2Thm2plf8urCFoHHkMqW6_1Rz4,3338
|
26
|
-
pychemstation/utils/injector_types.py,sha256=hTgC-4xnO-EXOLOsCZ0L6TX4KUsB14Q-5jU4_hCzmfM,822
|
27
|
-
pychemstation/utils/macro.py,sha256=WgXGR8fgyR_QhRML9NUD3oZZBT4LLSI9uwuragHEe2k,2904
|
28
|
-
pychemstation/utils/method_types.py,sha256=5FK7RThLhaQcLrzRi_qLnlPqZuGPtwwipP6eMoq0kpE,1638
|
29
|
-
pychemstation/utils/parsing.py,sha256=bnFIsZZwFy9NKzVUf517yN-ogzQbm0hp_aho3KUD6Is,9317
|
30
|
-
pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
|
31
|
-
pychemstation/utils/sequence_types.py,sha256=x2EClcq6ROdzeLZg63XcXXTknwl2aZ48Vuyru0xZjgA,1086
|
32
|
-
pychemstation/utils/table_types.py,sha256=7txqW_oNpkh4venSkGEtreVe6UV9dzNB1DTrIeTkQHA,3217
|
33
|
-
pychemstation/utils/tray_types.py,sha256=eOO-muUjadyvCM8JnYAZVyxJeyYBlENP1zXiFskAFbs,5049
|
34
|
-
pychemstation-0.8.3.dist-info/METADATA,sha256=vfeuRByzef7lYuAQgYyON587lphk76NR3cPOQZyhwfQ,4575
|
35
|
-
pychemstation-0.8.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
36
|
-
pychemstation-0.8.3.dist-info/licenses/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
|
37
|
-
pychemstation-0.8.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|