pychemstation 0.7.0.dev2__py3-none-any.whl → 0.8.0__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.
Files changed (49) hide show
  1. pychemstation/analysis/base_spectrum.py +3 -6
  2. pychemstation/analysis/process_report.py +248 -225
  3. pychemstation/analysis/utils.py +3 -1
  4. pychemstation/control/README.md +124 -0
  5. pychemstation/control/controllers/README.md +1 -0
  6. pychemstation/control/controllers/__init__.py +0 -2
  7. pychemstation/control/controllers/comm.py +27 -20
  8. pychemstation/control/controllers/devices/device.py +17 -4
  9. pychemstation/control/controllers/tables/method.py +57 -39
  10. pychemstation/control/controllers/tables/sequence.py +98 -28
  11. pychemstation/control/controllers/tables/table.py +121 -126
  12. pychemstation/control/hplc.py +82 -37
  13. pychemstation/generated/dad_method.py +3 -3
  14. pychemstation/generated/pump_method.py +7 -7
  15. pychemstation/out.txt +145 -0
  16. pychemstation/tests.ipynb +310 -0
  17. pychemstation/utils/chromatogram.py +5 -1
  18. pychemstation/utils/injector_types.py +2 -2
  19. pychemstation/utils/macro.py +1 -1
  20. pychemstation/utils/table_types.py +3 -0
  21. pychemstation/utils/tray_types.py +59 -39
  22. {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.0.dist-info}/METADATA +25 -21
  23. pychemstation-0.8.0.dist-info/RECORD +39 -0
  24. {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.0.dist-info}/WHEEL +1 -2
  25. pychemstation/control/comm.py +0 -206
  26. pychemstation/control/controllers/devices/column.py +0 -12
  27. pychemstation/control/controllers/devices/dad.py +0 -0
  28. pychemstation/control/controllers/devices/pump.py +0 -43
  29. pychemstation/control/controllers/method.py +0 -338
  30. pychemstation/control/controllers/sequence.py +0 -190
  31. pychemstation/control/controllers/table_controller.py +0 -266
  32. pychemstation/control/table/__init__.py +0 -3
  33. pychemstation/control/table/method.py +0 -274
  34. pychemstation/control/table/sequence.py +0 -210
  35. pychemstation/control/table/table_controller.py +0 -201
  36. pychemstation-0.7.0.dev2.dist-info/RECORD +0 -58
  37. pychemstation-0.7.0.dev2.dist-info/top_level.txt +0 -2
  38. tests/__init__.py +0 -0
  39. tests/constants.py +0 -88
  40. tests/test_comb.py +0 -136
  41. tests/test_comm.py +0 -65
  42. tests/test_inj.py +0 -39
  43. tests/test_method.py +0 -99
  44. tests/test_nightly.py +0 -80
  45. tests/test_proc_rep.py +0 -52
  46. tests/test_runs_stable.py +0 -125
  47. tests/test_sequence.py +0 -125
  48. tests/test_stable.py +0 -276
  49. {pychemstation-0.7.0.dev2.dist-info → pychemstation-0.8.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,16 +1,13 @@
1
- import pickle
2
- import os
3
1
  import logging
4
-
2
+ import os
3
+ import pickle
5
4
  from abc import ABC, abstractmethod
6
5
 
7
- import numpy as np
8
6
  import matplotlib.pyplot as plt
9
-
7
+ import numpy as np
10
8
  from scipy import (
11
9
  sparse,
12
10
  signal,
13
- interpolate,
14
11
  integrate,
15
12
  )
16
13
 
@@ -1,254 +1,277 @@
1
+ import abc
1
2
  import os
2
3
  import re
4
+ from abc import abstractmethod
3
5
  from dataclasses import dataclass
4
- from typing import List, AnyStr, Dict
6
+ from enum import Enum
7
+ from typing import List, AnyStr, Dict, Optional, Pattern
5
8
 
6
9
  import pandas as pd
7
10
  from aghplctools.ingestion.text import _no_peaks_re, _area_report_re, _header_block_re, _signal_info_re, \
8
11
  _signal_table_re, chunk_string
9
12
  from result import Result, Err, Ok
10
13
 
14
+ from pychemstation.utils.chromatogram import AgilentHPLCChromatogram
11
15
  from pychemstation.utils.tray_types import Tray, FiftyFourVialPlate
12
16
 
13
17
 
14
18
  @dataclass
15
19
  class AgilentPeak:
16
- peak_number: int
17
20
  retention_time: float
18
- peak_type: str
19
21
  width: float
20
22
  area: float
21
23
  height: float
22
- height_percent: float
24
+ peak_number: Optional[int]
25
+ peak_type: Optional[str]
26
+ height_percent: Optional[float]
27
+
28
+
29
+ @dataclass
30
+ class Signals:
31
+ wavelength: int
32
+ peaks: List[AgilentPeak]
33
+ data: Optional[AgilentHPLCChromatogram]
23
34
 
24
35
 
25
36
  @dataclass
26
37
  class AgilentReport:
27
- vial_location: Tray
28
- signals: Dict[AnyStr, List[AgilentPeak]]
29
- solvents: Dict[AnyStr, AnyStr]
30
-
31
-
32
- _column_re_dictionary = { # regex matches for column and unit combinations
33
- 'Peak': { # peak index
34
- '#': '[ ]+(?P<Peak>[\d]+)', # number
35
- },
36
- 'RetTime': { # retention time
37
- '[min]': '(?P<RetTime>[\d]+.[\d]+)', # minutes
38
- },
39
- 'Type': { # peak type
40
- '': '(?P<Type>[A-Z]{1,3}(?: [A-Z]{1,2})*)', # todo this is different from <4.8.8 aghplc tools
41
- },
42
- 'Width': { # peak width
43
- '[min]': '(?P<Width>[\d]+.[\d]+[e+-]*[\d]+)',
44
- },
45
- 'Area': { # peak area
46
- '[mAU*s]': '(?P<Area>[\d]+.[\d]+[e+-]*[\d]+)', # area units
47
- '%': '(?P<percent>[\d]+.[\d]+[e+-]*[\d]+)', # percent
48
- },
49
- 'Height': { # peak height
50
- '[mAU]': '(?P<Height>[\d]+.[\d]+[e+-]*[\d]+)',
51
- },
52
- 'Name': {
53
- '': '(?P<Name>[^\s]+(?:\s[^\s]+)*)', # peak name
54
- },
55
- }
56
-
57
-
58
- def build_peak_regex(signal_table: str) -> re.Pattern[AnyStr]:
59
- """
60
- Builds a peak regex from a signal table
38
+ vial_location: Optional[Tray]
39
+ signals: List[Signals]
40
+ solvents: Optional[Dict[AnyStr, AnyStr]]
41
+
42
+
43
+ class ReportType(Enum):
44
+ TXT = 0
45
+ CSV = 1
46
+
47
+
48
+ class ReportProcessor(abc.ABC):
49
+ def __init__(self, path: str):
50
+ self.path = path
51
+
52
+ @abstractmethod
53
+ def process_report(self) -> Result[AgilentReport, AnyStr]:
54
+ pass
55
+
56
+
57
+ class CSVProcessor(ReportProcessor):
58
+ def __init__(self, path: str):
59
+ """
60
+ Class to process reports in CSV form.
61
+
62
+ :param path: the parent folder that contains the CSV report(s) to parse.
63
+ """
64
+ super().__init__(path)
65
+
66
+ def process_report(self) -> Result[AgilentReport, AnyStr]:
67
+ """
68
+ Method to parse details from CSV report.
69
+
70
+ :returns: subset of complete report details, specifically the sample location, solvents in pumps,
71
+ and list of peaks at each wavelength channel.
72
+ """
73
+ labels = os.path.join(self.path, f'REPORT00.CSV')
74
+ if os.path.exists(labels):
75
+ df_labels: Dict[int, Dict[int: AnyStr]] = pd.read_csv(labels, encoding="utf-16", header=None).to_dict()
76
+ vial_location = []
77
+ signals = {}
78
+ solvents = {}
79
+ for pos, val in df_labels[0].items():
80
+ if val == "Location":
81
+ vial_location = df_labels[1][pos]
82
+ elif "Solvent" in val:
83
+ if val not in solvents.keys():
84
+ solvents[val] = df_labels[2][pos]
85
+ elif val == "Number of Signals":
86
+ num_signals = int(df_labels[1][pos])
87
+ for s in range(1, num_signals + 1):
88
+ df = pd.read_csv(os.path.join(self.path, f'REPORT0{s}.CSV'),
89
+ encoding="utf-16", header=None)
90
+ peaks = df.apply(lambda row: AgilentPeak(*row), axis=1)
91
+ wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[0][-3:]
92
+ signals[wavelength] = peaks
93
+ break
61
94
 
62
- :param signal_table: block of lines associated with an area table
63
- :return: peak line regex object (<=3.6 _sre.SRE_PATTERN, >=3.7 re.Pattern)
64
- """
65
- split_table = signal_table.split('\n')
66
- if len(split_table) <= 4: # catch peak table with no values
67
- return None
68
- # todo verify that these indicies are always true
69
- column_line = split_table[2] # table column line
70
- unit_line = split_table[3] # column unit line
71
- length_line = [len(val) + 1 for val in split_table[4].split('|')] # length line
72
-
73
- # iterate over header values and units to build peak table regex
74
- peak_re_string = []
75
- for header, unit in zip(
76
- chunk_string(column_line, length_line),
77
- chunk_string(unit_line, length_line)
78
- ):
79
- if header == '': # todo create a better catch for an undefined header
80
- continue
81
- try:
82
- peak_re_string.append(
83
- _column_re_dictionary[header][unit] # append the appropriate regex
84
- )
85
- except KeyError: # catch for undefined regexes (need to be built)
86
- raise KeyError(f'The header/unit combination "{header}" "{unit}" is not defined in the peak regex '
87
- f'dictionary. Let Lars know.')
88
- return re.compile(
89
- '[ ]+'.join(peak_re_string) # constructed string delimited by 1 or more spaces
90
- + '[\s]*' # and any remaining white space
91
- )
95
+ return Ok(AgilentReport(
96
+ signals=[Signals(wavelength=w, peaks=s, data=None) for w, s in signals.items()],
97
+ vial_location=FiftyFourVialPlate.from_int(int(vial_location)),
98
+ solvents=solvents
99
+ ))
92
100
 
101
+ return Err("No report found")
93
102
 
94
- # todo should be able to use the parse_area_report method of aghplctools v4.8.8
95
103
 
96
- def parse_area_report(report_text: str) -> dict:
104
+ class TXTProcessor(ReportProcessor):
97
105
  """
98
- Interprets report text and parses the area report section, converting it to dictionary.
99
-
100
- :param report_text: plain text version of the report.
101
- :raises ValueError: if there are no peaks defined in the report text file
102
- :return: dictionary of signals in the form
103
- dict[wavelength][retention time (float)][Width/Area/Height/etc.]
106
+ Regex matches for column and unit combinations, courtesy of Veronica Lai.
104
107
  """
105
- if re.search(_no_peaks_re, report_text): # There are no peaks in Report.txt
106
- raise ValueError(f'No peaks found in Report.txt')
107
- blocks = _header_block_re.split(report_text)
108
- signals = {} # output dictionary
109
- for ind, block in enumerate(blocks):
110
- # area report block
111
- if _area_report_re.match(block): # match area report block
112
- # break into signal blocks
113
- signal_blocks = _signal_table_re.split(blocks[ind + 1])
114
- # iterate over signal blocks
115
- for table in signal_blocks:
116
- si = _signal_info_re.match(table)
117
- if si is not None:
118
- # some error state (e.g. 'not found')
119
- if si.group('error') != '':
120
- continue
121
- wavelength = float(si.group('wavelength'))
122
- if wavelength in signals:
123
- # placeholder error raise just in case (this probably won't happen)
124
- raise KeyError(
125
- f'The wavelength {float(si.group("wavelength"))} is already in the signals dictionary')
126
- signals[wavelength] = {}
127
- # build peak regex
128
- peak_re = build_peak_regex(table)
129
- if peak_re is None: # if there are no columns (empty table), continue
130
- continue
131
- for line in table.split('\n'):
132
- peak = peak_re.match(line)
133
- if peak is not None:
134
- signals[wavelength][float(peak.group('RetTime'))] = {}
135
- current = signals[wavelength][float(peak.group('RetTime'))]
136
- for key in _column_re_dictionary:
137
- if key in peak.re.groupindex:
138
- try: # try float conversion, otherwise continue
139
- value = float(peak.group(key))
140
- except ValueError:
141
- value = peak.group(key)
142
- current[key] = value
143
- else: # ensures defined
144
- current[key] = None
145
- return signals
146
-
147
-
148
- def process_export_report(file_path, target_wavelengths=None, min_retention_time=0, max_retention_time=999):
149
- # # Pull signals from the file
150
- # from aghplctools.ingestion.text import pull_hplc_area_from_txt
151
- # signals = pull_hplc_area_from_txt(file_path)
152
-
153
- with open(file_path, 'r', encoding='utf-16') as openfile:
154
- text = openfile.read()
155
-
156
- try:
157
- signals = parse_area_report(text)
158
- except ValueError as e:
159
- # value error thrown if there are no peaks found in the report
160
- print(e)
161
- return [], [], []
162
-
163
- # filter wavelengths by the ones to keep
164
- if target_wavelengths is not None:
165
- signals = {key: signals[key] for key in target_wavelengths if key in signals}
166
-
167
- wavelengths = []
168
- retention_times = []
169
- areas = []
170
-
171
- for wavelength, wavelength_dict in signals.items():
172
- for ret_time, ret_time_dict in wavelength_dict.items():
173
- if min_retention_time <= ret_time <= max_retention_time:
174
- wavelengths.append(wavelength)
175
- retention_times.append(ret_time)
176
- areas.append(ret_time_dict['Area'])
177
-
178
- return wavelengths, retention_times, areas
179
-
180
-
181
- def process_folder(folder_path, target_wavelengths=None, min_retention_time=0, max_retention_time=999):
182
- # folder path is the path to the overall folder, and inside there should be subfolders for each LC sample
183
- # each subfolder should have a Report.TXT file
184
- # sample_names = []
185
- wavelengths = []
186
- retention_times = []
187
- peak_areas = []
188
-
189
- # Get a list of all items (files and directories) in the folder
190
- items = [os.path.join(folder_path, item) for item in os.listdir(folder_path)]
191
-
192
- # Filter only directories from the list
193
- # folders = [item for item in items if os.path.isdir(item)]
194
-
195
- # # Sort the folders by creation date
196
- # sorted_folders = sorted(folders, key=lambda f: os.stat(f).st_ctime)
197
-
198
- for filename in items:
199
- if filename.endswith('Report.TXT'):
200
- # file_path = os.path.join(subfolder, filename)
201
- file_wavelengths, file_retention_times, file_peak_areas = process_export_report(filename,
202
- target_wavelengths,
203
- min_retention_time,
204
- max_retention_time)
205
- wavelengths.extend(file_wavelengths)
206
- retention_times.extend(file_retention_times)
207
- peak_areas.extend(file_peak_areas)
208
-
209
- results_df = pd.DataFrame({'Wavelengths': wavelengths, 'Retention Times': retention_times, 'Areas': peak_areas})
210
-
211
- # Save the results to a CSV file
212
- # results_csv_path = os.path.join(folder_path, 'all_sample_data.csv') # Specify the desired file path
213
- # results_df.to_csv(results_csv_path, index=False)
214
- # print(f"Results saved to {results_csv_path}")
215
- return results_df
216
-
217
-
218
- def process_csv_report(folder_path: str) -> Result[AgilentReport, AnyStr]:
219
- labels = os.path.join(folder_path, f'REPORT00.CSV')
220
- if os.path.exists(labels):
221
- df_labels: Dict[int, Dict[int: AnyStr]] = pd.read_csv(labels, encoding="utf-16", header=None).to_dict()
222
- vial_location = []
223
- signals = {}
224
- solvents = {}
225
- for pos, val in df_labels[0].items():
226
- if val == "Location":
227
- vial_location = df_labels[1][pos]
228
- elif "Solvent" in val:
229
- if val not in solvents.keys():
230
- solvents[val] = df_labels[2][pos]
231
- elif val == "Number of Signals":
232
- num_signals = int(df_labels[1][pos])
233
- for s in range(1, num_signals + 1):
234
- peaks = process_peaks(os.path.join(folder_path, f'REPORT0{s}.CSV'))
235
- if peaks.is_ok():
236
- wavelength = df_labels[1][pos + s].partition(",4 Ref=off")[0][-3:]
237
- signals[wavelength] = peaks.ok_value
238
- break
239
-
240
- return Ok(AgilentReport(
241
- signals=signals,
242
- vial_location=FiftyFourVialPlate.from_int(vial_location),
243
- solvents=solvents
244
- ))
108
+ _column_re_dictionary = {
109
+ 'Peak': { # peak index
110
+ '#': '[ ]+(?P<Peak>[\d]+)', # number
111
+ },
112
+ 'RetTime': { # retention time
113
+ '[min]': '(?P<RetTime>[\d]+.[\d]+)', # minutes
114
+ },
115
+ 'Type': { # peak type
116
+ '': '(?P<Type>[A-Z]{1,3}(?: [A-Z]{1,2})*)', # todo this is different from <4.8.8 aghplc tools
117
+ },
118
+ 'Width': { # peak width
119
+ '[min]': '(?P<Width>[\d]+.[\d]+[e+-]*[\d]+)',
120
+ },
121
+ 'Area': { # peak area
122
+ '[mAU*s]': '(?P<Area>[\d]+.[\d]+[e+-]*[\d]+)', # area units
123
+ '%': '(?P<percent>[\d]+.[\d]+[e+-]*[\d]+)', # percent
124
+ },
125
+ 'Height': { # peak height
126
+ '[mAU]': '(?P<Height>[\d]+.[\d]+[e+-]*[\d]+)',
127
+ },
128
+ 'Name': {
129
+ '': '(?P<Name>[^\s]+(?:\s[^\s]+)*)', # peak name
130
+ },
131
+ }
132
+
133
+ def __init__(self, path: str, min_ret_time: int = 0,
134
+ max_ret_time: int = 999,
135
+ target_wavelength_range: List[int] = range(200, 300)):
136
+ """
137
+ Class to process reports in CSV form.
138
+
139
+ :param path: the parent folder that contains the CSV report(s) to parse.
140
+ :param min_ret_time: peaks after this value (min) will be returned
141
+ :param max_ret_time: peaks will only be returned up to this time (min)
142
+ :param target_wavelength_range: range of wavelengths to return
143
+ """
144
+ self.target_wavelength_range = target_wavelength_range
145
+ self.min_ret_time = min_ret_time
146
+ self.max_ret_time = max_ret_time
147
+ super().__init__(path)
148
+
149
+ def process_report(self) -> Result[AgilentReport, AnyStr]:
150
+ """
151
+ Method to parse details from CSV report.
152
+
153
+ :returns: subset of complete report details, specifically the sample location, solvents in pumps,
154
+ and list of peaks at each wavelength channel.
155
+
156
+ If you want more functionality, use `aghplctools`.
157
+ `from aghplctools.ingestion.text import pull_hplc_area_from_txt`
158
+ `signals = pull_hplc_area_from_txt(file_path)`
159
+ """
160
+
161
+ with open(os.path.join(self.path, "REPORT.TXT"), 'r', encoding='utf-16') as openfile:
162
+ text = openfile.read()
245
163
 
246
- return Err("No report found")
247
-
248
-
249
- def process_peaks(folder_path: str) -> Result[List[AgilentPeak], AnyStr]:
250
- try:
251
- df = pd.read_csv(folder_path, encoding="utf-16", header=None)
252
- return Ok(df.apply(lambda row: AgilentPeak(*row), axis=1))
253
- except Exception:
254
- return Err("Trouble reading report")
164
+ try:
165
+ signals = self.parse_area_report(text)
166
+ except ValueError as e:
167
+ return Err("No peaks found: " + str(e))
168
+
169
+ signals = {key: signals[key] for key in self.target_wavelength_range if key in signals}
170
+
171
+ parsed_signals = []
172
+ for wavelength, wavelength_dict in signals.items():
173
+ current_wavelength_signals = Signals(wavelength=wavelength, peaks=[], data=None)
174
+ for ret_time, ret_time_dict in wavelength_dict.items():
175
+ if self.min_ret_time <= ret_time <= self.max_ret_time:
176
+ current_wavelength_signals.peaks.append(AgilentPeak(retention_time=ret_time,
177
+ area=ret_time_dict['Area'],
178
+ width=ret_time_dict['Width'],
179
+ height=ret_time_dict['Height'],
180
+ peak_number=None,
181
+ peak_type=ret_time_dict['Type'],
182
+ height_percent=None))
183
+ parsed_signals.append(current_wavelength_signals)
184
+
185
+ return Ok(AgilentReport(vial_location=None,
186
+ solvents=None,
187
+ signals=parsed_signals))
188
+
189
+ def parse_area_report(self, report_text: str) -> Dict:
190
+ """
191
+ Interprets report text and parses the area report section, converting it to dictionary.
192
+ Courtesy of Veronica Lai.
193
+
194
+ :param report_text: plain text version of the report.
195
+ :raises ValueError: if there are no peaks defined in the report text file
196
+ :return: dictionary of signals in the form
197
+ dict[wavelength][retention time (float)][Width/Area/Height/etc.]
198
+
199
+ If you want more functionality, use `aghplctools`.
200
+ should be able to use the `parse_area_report` method of aghplctools v4.8.8
201
+ """
202
+ if re.search(_no_peaks_re, report_text): # There are no peaks in Report.txt
203
+ raise ValueError(f'No peaks found in Report.txt')
204
+ blocks = _header_block_re.split(report_text)
205
+ signals = {} # output dictionary
206
+ for ind, block in enumerate(blocks):
207
+ # area report block
208
+ if _area_report_re.match(block): # match area report block
209
+ # break into signal blocks
210
+ signal_blocks = _signal_table_re.split(blocks[ind + 1])
211
+ # iterate over signal blocks
212
+ for table in signal_blocks:
213
+ si = _signal_info_re.match(table)
214
+ if si is not None:
215
+ # some error state (e.g. 'not found')
216
+ if si.group('error') != '':
217
+ continue
218
+ wavelength = float(si.group('wavelength'))
219
+ if wavelength in signals:
220
+ # placeholder error raise just in case (this probably won't happen)
221
+ raise KeyError(
222
+ f'The wavelength {float(si.group("wavelength"))} is already in the signals dictionary')
223
+ signals[wavelength] = {}
224
+ # build peak regex
225
+ peak_re = self.build_peak_regex(table)
226
+ if peak_re is None: # if there are no columns (empty table), continue
227
+ continue
228
+ for line in table.split('\n'):
229
+ peak = peak_re.match(line)
230
+ if peak is not None:
231
+ signals[wavelength][float(peak.group('RetTime'))] = {}
232
+ current = signals[wavelength][float(peak.group('RetTime'))]
233
+ for key in self._column_re_dictionary:
234
+ if key in peak.re.groupindex:
235
+ try: # try float conversion, otherwise continue
236
+ value = float(peak.group(key))
237
+ except ValueError:
238
+ value = peak.group(key)
239
+ current[key] = value
240
+ else: # ensures defined
241
+ current[key] = None
242
+ return signals
243
+
244
+ def build_peak_regex(self, signal_table: str) -> Pattern[AnyStr]:
245
+ """
246
+ Builds a peak regex from a signal table. Courtesy of Veronica Lai.
247
+
248
+ :param signal_table: block of lines associated with an area table
249
+ :return: peak line regex object (<=3.6 _sre.SRE_PATTERN, >=3.7 re.Pattern)
250
+ """
251
+ split_table = signal_table.split('\n')
252
+ if len(split_table) <= 4: # catch peak table with no values
253
+ return None
254
+ # todo verify that these indicies are always true
255
+ column_line = split_table[2] # table column line
256
+ unit_line = split_table[3] # column unit line
257
+ length_line = [len(val) + 1 for val in split_table[4].split('|')] # length line
258
+
259
+ # iterate over header values and units to build peak table regex
260
+ peak_re_string = []
261
+ for header, unit in zip(
262
+ chunk_string(column_line, length_line),
263
+ chunk_string(unit_line, length_line)
264
+ ):
265
+ if header == '': # todo create a better catch for an undefined header
266
+ continue
267
+ try:
268
+ peak_re_string.append(
269
+ self._column_re_dictionary[header][unit] # append the appropriate regex
270
+ )
271
+ except KeyError: # catch for undefined regexes (need to be built)
272
+ raise KeyError(f'The header/unit combination "{header}" "{unit}" is not defined in the peak regex '
273
+ f'dictionary. Let Lars know.')
274
+ return re.compile(
275
+ '[ ]+'.join(peak_re_string) # constructed string delimited by 1 or more spaces
276
+ + '[\s]*' # and any remaining white space
277
+ )
@@ -1,7 +1,9 @@
1
+ from typing import Tuple
2
+
1
3
  import numpy as np
2
4
 
3
5
 
4
- def find_nearest_value_index(array, value) -> tuple[float, int]:
6
+ def find_nearest_value_index(array, value) -> Tuple[float, int]:
5
7
  """Returns closest value and its index in a given array.
6
8
 
7
9
  :param array: An array to search in.
@@ -0,0 +1,124 @@
1
+ # Examples of usecases
2
+
3
+ ## Initialization
4
+ ```python
5
+ from pychemstation.control import HPLCController
6
+
7
+ DEFAULT_METHOD_DIR = "C:\\ChemStation\\1\\Methods\\"
8
+ DATA_DIR = "C:\\Users\\Public\\Documents\\ChemStation\\3\\Data"
9
+ SEQUENCE_DIR = "C:\\USERS\\PUBLIC\\DOCUMENTS\\CHEMSTATION\\3\\Sequence"
10
+ DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
11
+
12
+ hplc_controller = HPLCController(data_dir=DATA_DIR,
13
+ comm_dir=DEFAULT_COMMAND_PATH,
14
+ method_dir=DEFAULT_METHOD_DIR,
15
+ sequence_dir=SEQUENCE_DIR)
16
+ ```
17
+
18
+ ## Switching a method
19
+ ```python
20
+ hplc_controller.switch_method("General-Poroshell")
21
+ ```
22
+
23
+ ## Editing a method
24
+
25
+ ```python
26
+ from pychemstation.utils.method_types import *
27
+
28
+ new_method = MethodDetails(
29
+ name="My_Method",
30
+ params=HPLCMethodParams(
31
+ organic_modifier=7,
32
+ flow=0.44),
33
+ timetable=[
34
+ TimeTableEntry(
35
+ start_time=0.10,
36
+ organic_modifer=7,
37
+ flow=0.34
38
+ ),
39
+ TimeTableEntry(
40
+ start_time=1,
41
+ organic_modifer=99,
42
+ flow=0.55
43
+ )
44
+ ],
45
+ stop_time=5,
46
+ post_time=2
47
+ )
48
+
49
+ hplc_controller.edit_method(new_method)
50
+ ```
51
+
52
+ ## Running a method and get data from last run method
53
+ ```python
54
+ hplc_controller.run_method(experiment_name="test_experiment")
55
+ chrom = hplc_controller.get_last_run_method_data()
56
+ channel_a_time = chrom.A.x
57
+ ```
58
+
59
+ ## Switching a sequence
60
+ ```python
61
+ hplc_controller.switch_sequence(sequence_name="hplc_testing")
62
+ ```
63
+ ## Editing a Sequence Row
64
+ ```python
65
+ from pychemstation.utils.sequence_types import *
66
+ from pychemstation.utils.tray_types import *
67
+
68
+ hplc_controller.edit_sequence_row(SequenceEntry(
69
+ vial_location=FiftyFourVialPlate(plate=Plate.TWO, letter=Letter.A, num=Num.SEVEN).value(),
70
+ method="General-Poroshell",
71
+ num_inj=3,
72
+ inj_vol=4,
73
+ sample_name="Blank",
74
+ sample_type=SampleType.BLANK,
75
+ inj_source=InjectionSource.HIP_ALS
76
+ ), 1)
77
+ ```
78
+
79
+ ## Editing entire Sequence Table
80
+ ```python
81
+ from pychemstation.utils.tray_types import *
82
+ from pychemstation.utils.sequence_types import *
83
+
84
+ seq_table = SequenceTable(
85
+ name=DEFAULT_SEQUENCE,
86
+ rows=[
87
+ SequenceEntry(
88
+ vial_location=FiftyFourVialPlate(plate=Plate.TWO, letter=Letter.A, num=Num.SEVEN).value(),
89
+ method="General-Poroshell",
90
+ num_inj=3,
91
+ inj_vol=4,
92
+ sample_name="Control",
93
+ sample_type=SampleType.CONTROL,
94
+ inj_source=InjectionSource.MANUAL
95
+ ),
96
+ SequenceEntry(
97
+ vial_location=TenVialColumn.ONE.value,
98
+ method="General-Poroshell",
99
+ num_inj=1,
100
+ inj_vol=1,
101
+ sample_name="Sample",
102
+ sample_type=SampleType.SAMPLE,
103
+ inj_source=InjectionSource.AS_METHOD
104
+ ),
105
+ SequenceEntry(
106
+ vial_location=10,
107
+ method="General-Poroshell",
108
+ num_inj=3,
109
+ inj_vol=4,
110
+ sample_name="Blank",
111
+ sample_type=SampleType.BLANK,
112
+ inj_source=InjectionSource.HIP_ALS
113
+ ),
114
+ ]
115
+ )
116
+ hplc_controller.edit_sequence(seq_table)
117
+ ```
118
+
119
+ ## Running a sequence and get data from last run sequence
120
+ ```python
121
+ hplc_controller.run_sequence(seq_table)
122
+ chroms = hplc_controller.get_last_run_sequence_data()
123
+ channel_A_time = chroms[0].A.x
124
+ ```
@@ -0,0 +1 @@
1
+ # Chemstation Devices and Tables