pychemstation 0.5.14.dev1__py3-none-any.whl → 0.6.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.
@@ -0,0 +1,219 @@
1
+ """
2
+ pip install pandas
3
+ pip install aghplctools==4.8.6
4
+ """
5
+
6
+ import os
7
+ import re
8
+ from dataclasses import dataclass
9
+ from typing import List, AnyStr
10
+
11
+ import pandas as pd
12
+ from aghplctools.ingestion.text import _no_peaks_re, _area_report_re, _header_block_re, _signal_info_re, \
13
+ _signal_table_re, chunk_string
14
+ from result import Result, Err, Ok
15
+
16
+
17
+ @dataclass
18
+ class AgilentPeak:
19
+ peak_number: int
20
+ retention_time: float
21
+ peak_type: str
22
+ width: float
23
+ area: float
24
+ height: float
25
+ height_percent: float
26
+
27
+
28
+ _column_re_dictionary = { # regex matches for column and unit combinations
29
+ 'Peak': { # peak index
30
+ '#': '[ ]+(?P<Peak>[\d]+)', # number
31
+ },
32
+ 'RetTime': { # retention time
33
+ '[min]': '(?P<RetTime>[\d]+.[\d]+)', # minutes
34
+ },
35
+ 'Type': { # peak type
36
+ '': '(?P<Type>[A-Z]{1,3}(?: [A-Z]{1,2})*)', # todo this is different from <4.8.8 aghplc tools
37
+ },
38
+ 'Width': { # peak width
39
+ '[min]': '(?P<Width>[\d]+.[\d]+[e+-]*[\d]+)',
40
+ },
41
+ 'Area': { # peak area
42
+ '[mAU*s]': '(?P<Area>[\d]+.[\d]+[e+-]*[\d]+)', # area units
43
+ '%': '(?P<percent>[\d]+.[\d]+[e+-]*[\d]+)', # percent
44
+ },
45
+ 'Height': { # peak height
46
+ '[mAU]': '(?P<Height>[\d]+.[\d]+[e+-]*[\d]+)',
47
+ },
48
+ 'Name': {
49
+ '': '(?P<Name>[^\s]+(?:\s[^\s]+)*)', # peak name
50
+ },
51
+ }
52
+
53
+
54
+ def build_peak_regex(signal_table: str):
55
+ """
56
+ Builds a peak regex from a signal table
57
+
58
+ :param signal_table: block of lines associated with an area table
59
+ :return: peak line regex object (<=3.6 _sre.SRE_PATTERN, >=3.7 re.Pattern)
60
+ """
61
+ split_table = signal_table.split('\n')
62
+ if len(split_table) <= 4: # catch peak table with no values
63
+ return None
64
+ # todo verify that these indicies are always true
65
+ column_line = split_table[2] # table column line
66
+ unit_line = split_table[3] # column unit line
67
+ length_line = [len(val) + 1 for val in split_table[4].split('|')] # length line
68
+
69
+ # iterate over header values and units to build peak table regex
70
+ peak_re_string = []
71
+ for header, unit in zip(
72
+ chunk_string(column_line, length_line),
73
+ chunk_string(unit_line, length_line)
74
+ ):
75
+ if header == '': # todo create a better catch for an undefined header
76
+ continue
77
+ try:
78
+ peak_re_string.append(
79
+ _column_re_dictionary[header][unit] # append the appropriate regex
80
+ )
81
+ except KeyError: # catch for undefined regexes (need to be built)
82
+ raise KeyError(f'The header/unit combination "{header}" "{unit}" is not defined in the peak regex '
83
+ f'dictionary. Let Lars know.')
84
+ return re.compile(
85
+ '[ ]+'.join(peak_re_string) # constructed string delimited by 1 or more spaces
86
+ + '[\s]*' # and any remaining white space
87
+ )
88
+
89
+
90
+ # todo should be able to use the parse_area_report method of aghplctools v4.8.8
91
+
92
+ def parse_area_report(report_text: str) -> dict:
93
+ """
94
+ Interprets report text and parses the area report section, converting it to dictionary.
95
+
96
+ :param report_text: plain text version of the report.
97
+ :raises ValueError: if there are no peaks defined in the report text file
98
+ :return: dictionary of signals in the form
99
+ dict[wavelength][retention time (float)][Width/Area/Height/etc.]
100
+ """
101
+ if re.search(_no_peaks_re, report_text): # There are no peaks in Report.txt
102
+ raise ValueError(f'No peaks found in Report.txt')
103
+ blocks = _header_block_re.split(report_text)
104
+ signals = {} # output dictionary
105
+ for ind, block in enumerate(blocks):
106
+ # area report block
107
+ if _area_report_re.match(block): # match area report block
108
+ # break into signal blocks
109
+ signal_blocks = _signal_table_re.split(blocks[ind + 1])
110
+ # iterate over signal blocks
111
+ for table in signal_blocks:
112
+ si = _signal_info_re.match(table)
113
+ if si is not None:
114
+ # some error state (e.g. 'not found')
115
+ if si.group('error') != '':
116
+ continue
117
+ wavelength = float(si.group('wavelength'))
118
+ if wavelength in signals:
119
+ # placeholder error raise just in case (this probably won't happen)
120
+ raise KeyError(
121
+ f'The wavelength {float(si.group("wavelength"))} is already in the signals dictionary')
122
+ signals[wavelength] = {}
123
+ # build peak regex
124
+ peak_re = build_peak_regex(table)
125
+ if peak_re is None: # if there are no columns (empty table), continue
126
+ continue
127
+ for line in table.split('\n'):
128
+ peak = peak_re.match(line)
129
+ if peak is not None:
130
+ signals[wavelength][float(peak.group('RetTime'))] = {}
131
+ current = signals[wavelength][float(peak.group('RetTime'))]
132
+ for key in _column_re_dictionary:
133
+ if key in peak.re.groupindex:
134
+ try: # try float conversion, otherwise continue
135
+ value = float(peak.group(key))
136
+ except ValueError:
137
+ value = peak.group(key)
138
+ current[key] = value
139
+ else: # ensures defined
140
+ current[key] = None
141
+ return signals
142
+
143
+
144
+ def process_export_report(file_path, target_wavelengths=None, min_retention_time=0, max_retention_time=999):
145
+ # # Pull signals from the file
146
+ # from aghplctools.ingestion.text import pull_hplc_area_from_txt
147
+ # signals = pull_hplc_area_from_txt(file_path)
148
+
149
+ with open(file_path, 'r', encoding='utf-16') as openfile:
150
+ text = openfile.read()
151
+
152
+ try:
153
+ signals = parse_area_report(text)
154
+ except ValueError as e:
155
+ # value error thrown if there are no peaks found in the report
156
+ print(e)
157
+ return [], [], []
158
+
159
+ # filter wavelengths by the ones to keep
160
+ if target_wavelengths is not None:
161
+ signals = {key: signals[key] for key in target_wavelengths if key in signals}
162
+
163
+ wavelengths = []
164
+ retention_times = []
165
+ areas = []
166
+
167
+ for wavelength, wavelength_dict in signals.items():
168
+ for ret_time, ret_time_dict in wavelength_dict.items():
169
+ if min_retention_time <= ret_time <= max_retention_time:
170
+ wavelengths.append(wavelength)
171
+ retention_times.append(ret_time)
172
+ areas.append(ret_time_dict['Area'])
173
+
174
+ return wavelengths, retention_times, areas
175
+
176
+
177
+ def process_folder(folder_path, target_wavelengths=None, min_retention_time=0, max_retention_time=999):
178
+ # folder path is the path to the overall folder, and inside there should be subfolders for each LC sample
179
+ # each subfolder should have a Report.TXT file
180
+ # sample_names = []
181
+ wavelengths = []
182
+ retention_times = []
183
+ peak_areas = []
184
+
185
+ # Get a list of all items (files and directories) in the folder
186
+ items = [os.path.join(folder_path, item) for item in os.listdir(folder_path)]
187
+
188
+ # Filter only directories from the list
189
+ # folders = [item for item in items if os.path.isdir(item)]
190
+
191
+ # # Sort the folders by creation date
192
+ # sorted_folders = sorted(folders, key=lambda f: os.stat(f).st_ctime)
193
+
194
+ for filename in items:
195
+ if filename.endswith('Report.TXT'):
196
+ # file_path = os.path.join(subfolder, filename)
197
+ file_wavelengths, file_retention_times, file_peak_areas = process_export_report(filename,
198
+ target_wavelengths,
199
+ min_retention_time,
200
+ max_retention_time)
201
+ wavelengths.extend(file_wavelengths)
202
+ retention_times.extend(file_retention_times)
203
+ peak_areas.extend(file_peak_areas)
204
+
205
+ results_df = pd.DataFrame({'Wavelengths': wavelengths, 'Retention Times': retention_times, 'Areas': peak_areas})
206
+
207
+ # Save the results to a CSV file
208
+ # results_csv_path = os.path.join(folder_path, 'all_sample_data.csv') # Specify the desired file path
209
+ # results_df.to_csv(results_csv_path, index=False)
210
+ # print(f"Results saved to {results_csv_path}")
211
+ return results_df
212
+
213
+
214
+ def process_csv_report(folder_path: str, num: int) -> Result[List[AgilentPeak], AnyStr]:
215
+ potential_report = os.path.join(folder_path, f'REPORT0{num}.CSV')
216
+ if os.path.exists(potential_report):
217
+ df = pd.read_csv(potential_report, encoding="utf-16", header=None)
218
+ return Ok(df.apply(lambda row: AgilentPeak(*row), axis=1))
219
+ return Err("No report found")
File without changes
@@ -1,11 +1,117 @@
1
1
  from ....control.controllers import CommunicationController
2
2
  from .device import DeviceController
3
- from ....utils.table_types import Table
3
+ from ....utils.injector_types import *
4
+ from ....utils.macro import Command
5
+ from ....utils.table_types import Table, RegisterFlag
4
6
 
5
7
 
6
8
  class InjectorController(DeviceController):
7
- def get_row(self, row: int):
8
- pass
9
9
 
10
10
  def __init__(self, controller: CommunicationController, table: Table):
11
11
  super().__init__(controller, table)
12
+
13
+ def get_row(self, row: int) -> InjectorFunction:
14
+ def return_tray_loc() -> Tray:
15
+ pass
16
+
17
+ function = self.get_text(row, RegisterFlag.FUNCTION)
18
+ if function == "Wait":
19
+ return Wait(duration=self.get_num(row, RegisterFlag.TIME))
20
+ elif function == "Inject":
21
+ return Inject()
22
+ elif function == "Draw":
23
+ # TODO: better error handling
24
+ is_source = SourceType(self.get_text(row, RegisterFlag.DRAW_SOURCE))
25
+ is_volume = Mode(self.get_text(row, RegisterFlag.DRAW_VOLUME))
26
+ vol = self.get_num(row, RegisterFlag.DRAW_VOLUME_VALUE) if is_volume == Mode.SET else None
27
+ if is_source is SourceType.SPECIFIC_LOCATION:
28
+ return Draw(amount=vol, source=return_tray_loc())
29
+ elif is_source is SourceType.LOCATION:
30
+ return Draw(amount=vol, location=self.get_text(row, RegisterFlag.DRAW_LOCATION))
31
+ elif function == "Remote":
32
+ return Remote(command=RemoteCommand(self.get_text(row, RegisterFlag.REMOTE)),
33
+ duration=self.get_num(row, RegisterFlag.REMOTE_DUR))
34
+
35
+ def load(self) -> InjectorTable:
36
+ rows = self.get_num_rows()
37
+ if rows.is_ok():
38
+ return InjectorTable(functions=[self.get_row(i) for i in range(int(rows.ok_value.num_response))])
39
+
40
+ def edit(self, injector_table: InjectorTable):
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')
@@ -4,6 +4,7 @@ import time
4
4
  from xsdata.formats.dataclass.parsers import XmlParser
5
5
 
6
6
  from .table import TableController
7
+ from ..devices.injector import InjectorController
7
8
  from ....control.controllers import CommunicationController
8
9
  from ....generated import PumpMethod, DadMethod, SolventElement
9
10
  from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
@@ -17,7 +18,13 @@ class MethodController(TableController):
17
18
  Class containing method related logic
18
19
  """
19
20
 
20
- def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table, offline: bool):
21
+ def __init__(self, controller: CommunicationController,
22
+ src: str,
23
+ data_dir: str,
24
+ table: Table,
25
+ offline: bool,
26
+ injector_controller: InjectorController):
27
+ self.injector_controller = injector_controller
21
28
  super().__init__(controller, src, data_dir, table, offline=offline)
22
29
 
23
30
  def check(self) -> str:
@@ -310,8 +317,6 @@ class MethodController(TableController):
310
317
 
311
318
  :param experiment_name: Name of the experiment
312
319
  """
313
- if not self.table_state:
314
- self.table_state = self.load()
315
320
 
316
321
  folder_name = ""
317
322
  hplc_is_running = False
@@ -331,13 +336,17 @@ class MethodController(TableController):
331
336
  self.data_files.append(os.path.join(self.data_dir, folder_name))
332
337
 
333
338
  if stall_while_running:
339
+
340
+ if not self.table_state:
341
+ self.table_state = self.load()
342
+
334
343
  run_completed = self.check_hplc_done_running(method=self.table_state)
335
344
  if run_completed.is_ok():
336
345
  self.data_files[-1] = run_completed.ok_value
337
346
  else:
338
347
  raise RuntimeError("Run error has occurred.")
339
348
  else:
340
- self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
349
+ self.data_files[-1] = self.fuzzy_match_most_recent_folder(folder_name).ok_value
341
350
 
342
351
  def retrieve_recent_data_files(self) -> str:
343
352
  return self.data_files[-1]
@@ -6,6 +6,7 @@ Authors: Lucy Hao
6
6
 
7
7
  import abc
8
8
  import os
9
+ import warnings
9
10
  from dataclasses import dataclass
10
11
  from typing import Union, Optional
11
12
 
@@ -33,8 +34,8 @@ class ChromData:
33
34
  class TableController(abc.ABC):
34
35
 
35
36
  def __init__(self, controller: CommunicationController,
36
- src: str,
37
- data_dir: str,
37
+ src: Optional[str],
38
+ data_dir: Optional[str],
38
39
  table: Table,
39
40
  offline: bool = False):
40
41
  self.controller = controller
@@ -45,26 +46,27 @@ class TableController(abc.ABC):
45
46
  # Initialize row counter for table operations
46
47
  self.send('Local Rows')
47
48
 
48
- if os.path.isdir(src):
49
+ if src and os.path.isdir(src):
49
50
  self.src: str = src
50
51
  else:
51
- raise FileNotFoundError(f"dir: {src} not found.")
52
+ warnings.warn(f"dir: {src} not found.")
52
53
 
53
- if os.path.isdir(data_dir):
54
+ if data_dir and os.path.isdir(data_dir):
54
55
  self.data_dir: str = data_dir
55
56
  else:
56
- raise FileNotFoundError(f"dir: {data_dir} not found.")
57
-
58
- self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
59
- "A": AgilentHPLCChromatogram(self.data_dir),
60
- "B": AgilentHPLCChromatogram(self.data_dir),
61
- "C": AgilentHPLCChromatogram(self.data_dir),
62
- "D": AgilentHPLCChromatogram(self.data_dir),
63
- "E": AgilentHPLCChromatogram(self.data_dir),
64
- "F": AgilentHPLCChromatogram(self.data_dir),
65
- "G": AgilentHPLCChromatogram(self.data_dir),
66
- "H": AgilentHPLCChromatogram(self.data_dir),
67
- }
57
+ warnings.warn(f"dir: {data_dir} not found.")
58
+
59
+ if hasattr(self, "data_dir"):
60
+ self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
61
+ "A": AgilentHPLCChromatogram(self.data_dir),
62
+ "B": AgilentHPLCChromatogram(self.data_dir),
63
+ "C": AgilentHPLCChromatogram(self.data_dir),
64
+ "D": AgilentHPLCChromatogram(self.data_dir),
65
+ "E": AgilentHPLCChromatogram(self.data_dir),
66
+ "F": AgilentHPLCChromatogram(self.data_dir),
67
+ "G": AgilentHPLCChromatogram(self.data_dir),
68
+ "H": AgilentHPLCChromatogram(self.data_dir),
69
+ }
68
70
  else:
69
71
  self.spectra: dict[str, Optional[AgilentHPLCChromatogram]] = {
70
72
  "A": AgilentHPLCChromatogram(),
@@ -80,8 +82,6 @@ class TableController(abc.ABC):
80
82
 
81
83
  self.uv = None
82
84
 
83
-
84
-
85
85
  def receive(self) -> Result[Response, str]:
86
86
  for _ in range(10):
87
87
  try:
@@ -107,7 +107,7 @@ class TableController(abc.ABC):
107
107
  """
108
108
  self.send(Command.SLEEP_CMD.value.format(seconds=seconds))
109
109
 
110
- def get_num(self, row: int, col_name: RegisterFlag) -> float:
110
+ def get_num(self, row: int, col_name: RegisterFlag) -> Union[int, float]:
111
111
  return self.controller.get_num_val(TableOperation.GET_ROW_VAL.value.format(register=self.table.register,
112
112
  table_name=self.table.name,
113
113
  row=row,
@@ -6,8 +6,10 @@ Authors: Lucy Hao
6
6
 
7
7
  from typing import Union, Optional
8
8
 
9
+ from .controllers.devices.injector import InjectorController
9
10
  from ..control.controllers import MethodController, SequenceController, CommunicationController
10
11
  from ..utils.chromatogram import AgilentChannelChromatogramData
12
+ from ..utils.injector_types import InjectorTable
11
13
  from ..utils.macro import Command, HPLCRunningStatus, HPLCAvailStatus, HPLCErrorStatus, Response
12
14
  from ..utils.method_types import MethodDetails
13
15
  from ..utils.sequence_types import SequenceTable, SequenceEntry
@@ -27,8 +29,13 @@ class HPLCController:
27
29
  )
28
30
 
29
31
  INJECTOR_TABLE = Table(
30
- register="_LeoAlsMethod",
31
- name="ProgTable"
32
+ register="RCWLS1Pretreatment[1]",
33
+ name="InstructionTable"
34
+ )
35
+
36
+ MSD_TABLE = Table(
37
+ register="MSACQINFO[1]",
38
+ name="SprayChamber"
32
39
  )
33
40
 
34
41
  def __init__(self,
@@ -48,7 +55,9 @@ class HPLCController:
48
55
  src=method_dir,
49
56
  data_dir=data_dir,
50
57
  table=self.METHOD_TIMETABLE,
51
- offline=offline)
58
+ offline=offline,
59
+ injector_controller=InjectorController(controller=self.comm,
60
+ table=self.INJECTOR_TABLE))
52
61
  self.sequence_controller = SequenceController(controller=self.comm,
53
62
  src=sequence_dir,
54
63
  data_dir=data_dir,
@@ -176,6 +185,9 @@ class HPLCController:
176
185
  """
177
186
  return self.method_controller.check()
178
187
 
188
+ def load_injector_program(self) -> InjectorTable:
189
+ return self.method_controller.injector_controller.load()
190
+
179
191
  def load_method(self) -> MethodDetails:
180
192
  """
181
193
  Returns all details of the currently loaded method, including its timetable.
@@ -1,20 +1,31 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any
2
+ from enum import Enum
3
+ from typing import Union, Optional
3
4
 
4
5
  from pychemstation.utils.tray_types import Tray
5
6
 
6
7
 
8
+ class SourceType(Enum):
9
+ DEFAULT = "ActualPosition"
10
+ SPECIFIC_LOCATION = "ActualPositionPlusLocation"
11
+ LOCATION = "Location"
12
+
13
+
14
+ class Mode(Enum):
15
+ DEFAULT = "Default"
16
+ SET = "Set"
17
+
18
+
7
19
  @dataclass
8
20
  class Draw:
9
- amount: float
10
- source: Tray
11
- speed: Any
12
- offset: Any
21
+ amount: Optional[float] = None
22
+ location: Optional[str] = None
23
+ source: Optional[Tray] = None
13
24
 
14
25
 
15
26
  @dataclass
16
27
  class Wait:
17
- time: int
28
+ duration: int
18
29
 
19
30
 
20
31
  @dataclass
@@ -22,7 +33,18 @@ class Inject:
22
33
  pass
23
34
 
24
35
 
25
- InjectorFunction = [Draw, Wait, Inject]
36
+ class RemoteCommand(Enum):
37
+ START = "START"
38
+ PREPARE = "PREPARE"
39
+
40
+
41
+ @dataclass
42
+ class Remote:
43
+ command: RemoteCommand
44
+ duration: int
45
+
46
+
47
+ InjectorFunction = Union[Draw, Wait, Inject, Remote]
26
48
 
27
49
 
28
50
  @dataclass
@@ -37,7 +37,7 @@ class Command(Enum):
37
37
  # Method and Sequence Related
38
38
  GET_METHOD_CMD = "response$ = _MethFile$"
39
39
  GET_ROWS_CMD = 'response_num = TabHdrVal({register}, "{table_name}", "{col_name}")'
40
- SWITCH_METHOD_CMD = 'LoadMethod "{method_dir}", "{method_name}.M"'
40
+ SWITCH_METHOD_CMD = 'LoadMethod _MethPath$, _MethFile$'
41
41
  START_METHOD_CMD = "StartMethod"
42
42
  RUN_METHOD_CMD = 'RunMethod "{data_dir}",, "{experiment_name}_{timestamp}"'
43
43
  STOP_METHOD_CMD = "StopMethod"
@@ -4,6 +4,7 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from typing import Union, Any, Optional
6
6
 
7
+ from .injector_types import InjectorTable
7
8
  from .table_types import RegisterFlag
8
9
  from ..generated import Signal
9
10
 
@@ -36,7 +37,7 @@ class TimeTableEntry:
36
37
 
37
38
  @dataclass
38
39
  class MethodDetails:
39
- """An Agilent Chemstation method, TODO is to add MS parameters
40
+ """An Agilent Chemstation method, TODO is to add MS parameters, injector parameters
40
41
 
41
42
  :attribute name: the name of the method, should be the same as the Chemstation method name.
42
43
  :attribute timetable: list of entries in the method timetable
@@ -49,6 +50,7 @@ class MethodDetails:
49
50
  name: str
50
51
  params: HPLCMethodParams
51
52
  timetable: list[TimeTableEntry]
53
+ injector_program: Optional[InjectorTable] = None
52
54
  stop_time: Optional[int] = None
53
55
  post_time: Optional[int] = None
54
56
  dad_wavelengthes: Optional[list[Signal]] = None
@@ -65,6 +65,26 @@ class RegisterFlag(Enum):
65
65
  SAMPLE_TYPE = "SampleType"
66
66
  DATA_FILE = "DataFileName"
67
67
 
68
+ # for Injector Table
69
+ ## Draw
70
+ DRAW_SOURCE = "DrawSource"
71
+ DRAW_VOLUME = "DrawVolume_Mode"
72
+ DRAW_SPEED = "DrawSpeed_Mode"
73
+ DRAW_OFFSET = "DrawOffset_Mode"
74
+ # SetObjHdrVal RCWLS1Pretreatment[1], "DrawVolume_Value", 1
75
+ DRAW_VOLUME_VALUE = "DrawVolume_Value"
76
+ DRAW_LOCATION = "DrawLocation"
77
+ DRAW_LOCATION_TRAY = "DrawLocationPlus_Tray"
78
+ DRAW_LOCATION_UNIT = "DrawLocationPlus_Unit"
79
+ DRAW_LOCATION_ROW = "DrawLocationPlus_Row"
80
+ DRAW_LOCATION_COLUMN = "DrawLocationPlus_Column"
81
+ ## Inject
82
+ ## Wait
83
+ ## Remote
84
+ REMOTE = "RemoteLine"
85
+ REMOTE_DUR = "RemoteDuration"
86
+
87
+
68
88
 
69
89
  @dataclass
70
90
  class Table:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.5.14.dev1
3
+ Version: 0.6.0
4
4
  Summary: Library to interact with Chemstation software, primarily used in Hein lab
5
5
  Home-page: https://gitlab.com/heingroup/device-api/pychemstation
6
6
  Author: Lucy Hao
@@ -1,11 +1,12 @@
1
1
  pychemstation/__init__.py,sha256=SpTl-Tg1B1HTyjNOE-8ue-N2wGnXN_2zl7RFUSxlkiM,33
2
2
  pychemstation/analysis/__init__.py,sha256=EWoU47iyn9xGS-b44zK9eq50bSjOV4AC5dvt420YMI4,44
3
3
  pychemstation/analysis/base_spectrum.py,sha256=XPf9eJ72uz0CnxCY5uFOyu1MbVX-OTTXeN1tLzIMok4,16536
4
+ pychemstation/analysis/process_report.py,sha256=d290KgPY-cMlQg4yHaspLFxgso_yu9qNxXG6SF2qpuo,9054
4
5
  pychemstation/analysis/spec_utils.py,sha256=UOo9hJR3evJfmaohEEsyb7aq6X996ofuUfu-GKjiDi8,10201
5
6
  pychemstation/analysis/utils.py,sha256=ISupAOb_yqA4_DZRK9v18UL-XjUQccAicIJKb1VMnGg,2055
6
7
  pychemstation/control/__init__.py,sha256=4xTy8X-mkn_PPZKr7w9rnj1wZhtmTesbQptPhpYmKXs,64
7
8
  pychemstation/control/comm.py,sha256=u44g1hTluQ0yUG93Un-QAshScoDpgYRrZfFTgweP5tY,7386
8
- pychemstation/control/hplc.py,sha256=UlafodQoFFow2UVo-INK1ZjxyJ5vrVvv1TjB5VyAsHo,9178
9
+ pychemstation/control/hplc.py,sha256=LkNB_wKNaW3pU-OZlYfwFbXGWU2Iwmr1WD5c3dCWEbY,9742
9
10
  pychemstation/control/controllers/__init__.py,sha256=EM6LBNSTJqYVatmnvPq0P-S3q0POA88c-y64zL79I_I,252
10
11
  pychemstation/control/controllers/comm.py,sha256=IU4I_Q42VNCNUlVi93MxCmw2EBY9hiBDkU9FxubKg3c,7441
11
12
  pychemstation/control/controllers/method.py,sha256=XUclB7lQ_SIkquR58MBmmi9drHIPEq9AR8VprTLenvI,15503
@@ -13,14 +14,15 @@ pychemstation/control/controllers/sequence.py,sha256=kYNxxck2I-q9mZDEZwG8bJ_99Ff
13
14
  pychemstation/control/controllers/table_controller.py,sha256=70ovnIjLKkJborS1ztk445Mv42TtUM9jUniaQmZuyWQ,11031
14
15
  pychemstation/control/controllers/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
16
  pychemstation/control/controllers/devices/column.py,sha256=SCpCnVFZFUM9LM51MbWkVcBRayN3WFxy7lz9gs2PYeY,348
17
+ pychemstation/control/controllers/devices/dad.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
18
  pychemstation/control/controllers/devices/device.py,sha256=SF1JK93FjmACnYrlKvldX3gEeA21qnXZegeNhc9QJGQ,738
17
- pychemstation/control/controllers/devices/injector.py,sha256=PfkSQlpE1zMzwyJ55Km1AdtPtdOS2RNyHDwAt_ZNa6M,349
19
+ pychemstation/control/controllers/devices/injector.py,sha256=1iZgPQYw6AcV1VKw0RG-k94czE8lCRVWx5NCCubIOZA,5516
18
20
  pychemstation/control/controllers/devices/pump.py,sha256=DJQh4lNXEraeC1CWrsKmsITOjuYlRI3tih_XRB3F1hg,1404
19
21
  pychemstation/control/controllers/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- pychemstation/control/controllers/tables/method.py,sha256=Woni_nAEWRZ79wf2WixLnJWZONNdGgZGGV9GG7m0qCQ,16189
22
+ pychemstation/control/controllers/tables/method.py,sha256=3GdAQ6Cwf20QL3v7eMkwNieWbJdtr9GN12USbSVl_iE,16426
21
23
  pychemstation/control/controllers/tables/ms.py,sha256=JFD-tOhu8uRyKdl-E3-neRssii8MNqVRIlsrnFhNY_M,682
22
24
  pychemstation/control/controllers/tables/sequence.py,sha256=vqwJeV38YWdFnaDXvZVOGYl-UCV9lmMbh8Fj5kQ3mqY,8815
23
- pychemstation/control/controllers/tables/table.py,sha256=pJCtMDGHweBhm_BEo0qhh2AjCjYAx1QyIY0AShs707A,12411
25
+ pychemstation/control/controllers/tables/table.py,sha256=J-uNIFHVznPJqOKPmmqrJBXt70KlxmZlSdtEVM2pEKM,12576
24
26
  pychemstation/control/table/__init__.py,sha256=RgMN4uIWHdNUHpGRBWdzmzAbk7XEKl6Y-qtqWCxzSZU,124
25
27
  pychemstation/control/table/method.py,sha256=THVoGomSXff_CTU3eAYme0BYwkPzab5UgZKsiZ29QSk,12196
26
28
  pychemstation/control/table/sequence.py,sha256=Eri52AnbE3BGthfrRSvYKYciquUzvHKo0lYUTySYYE8,10542
@@ -30,23 +32,24 @@ pychemstation/generated/dad_method.py,sha256=0W8Z5WDtF5jpIcudMqb7XrkTnR2EGg_QOCs
30
32
  pychemstation/generated/pump_method.py,sha256=sUhE2Oo00nzVcoONtq3EMWsN4wLSryXbG8f3EeViWKg,12174
31
33
  pychemstation/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
34
  pychemstation/utils/chromatogram.py,sha256=-q3_hL9GTyi4C95os7IwAiOrkTM4EXIiigm-nW9pFmM,3221
33
- pychemstation/utils/injector_types.py,sha256=xOkvlX_cH_QmuKbyXj8Lc2JEA_6g82az1zB4oTwcKN0,386
34
- pychemstation/utils/macro.py,sha256=OCQRRC-f46RlhenZfe7ldSgxBhzcZ5JlW3Ej-9OfeSI,2847
35
- pychemstation/utils/method_types.py,sha256=e7c2nWub6pT_S8ZF6sd7uXYm9ZBno0mARnzc1hnSHPY,1516
35
+ pychemstation/utils/injector_types.py,sha256=SD452SwEHNx4Xs2p-mhg6X0pd99XewQ1Zbu-r9kPOJs,817
36
+ pychemstation/utils/macro.py,sha256=o1dypIKhEMbDea34VFh4gKu-kWIjXZAFyqK4FYjCKjo,2836
37
+ pychemstation/utils/method_types.py,sha256=3qxKeREl_97GnQ74qcA3kxibnvWI4ueb-6XZlxcV2Hg,1632
36
38
  pychemstation/utils/parsing.py,sha256=bnFIsZZwFy9NKzVUf517yN-ogzQbm0hp_aho3KUD6Is,9317
37
39
  pychemstation/utils/pump_types.py,sha256=HWQHxscGn19NTrfYBwQRCO2VcYfwyko7YfBO5uDhEm4,93
38
40
  pychemstation/utils/sequence_types.py,sha256=4cNpmRdPLN5oGN7ozHgT21E65aBO8vV3ZcRXMOQ3EA8,1084
39
- pychemstation/utils/table_types.py,sha256=mlbxPAiPvO_EBba5OSzuJcpCL0srrC7uUfm_lKsOsmA,2557
41
+ pychemstation/utils/table_types.py,sha256=0kg7gZXFk7O5l0K1BEBaF4OFFdja3-hFUG9UbN5PBcs,3173
40
42
  pychemstation/utils/tray_types.py,sha256=MaHN36rhcEI5mAY95VU8hfP9HhAlngQvMYq-2oyC0hc,764
41
43
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- tests/constants.py,sha256=4FPJjW97yn6QiHNQVSlqjjJUE-Bh9SmyjIvgAeaGQBU,2450
43
- tests/test_comb.py,sha256=EDE1Ve0A_EK4qro9imZsrD0xXrQN8hAowiOWPFtw3dM,5515
44
- tests/test_comm.py,sha256=EYOpVXzEMQLGhhKYDPO-KaLcJdPSMPTD9Y4jSI0yVQY,2516
45
- tests/test_inj.py,sha256=yaPGZoHiOC3ZSgsmrtiqp8QtSo2bMxB9FJhaFlOpad0,1412
46
- tests/test_method.py,sha256=Up2EEysYwldPT9GJw27Mycs6qi_Y2W3opjhc7wnLU9U,3215
47
- tests/test_sequence.py,sha256=Nz2iqp1cJgw6kcQvnwSkfBmhxpOH62PoEu6o_5rO-PY,4929
48
- pychemstation-0.5.14.dev1.dist-info/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
49
- pychemstation-0.5.14.dev1.dist-info/METADATA,sha256=-kS4rtI8LDxt1GKutFyCSSMi2fx65plU2b7EgUb6m1c,4376
50
- pychemstation-0.5.14.dev1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
51
- pychemstation-0.5.14.dev1.dist-info/top_level.txt,sha256=zXfKu_4nYWwPHo3OsuhshMNC3SPkcoTGCyODjURaghY,20
52
- pychemstation-0.5.14.dev1.dist-info/RECORD,,
44
+ tests/constants.py,sha256=55DOLpylLt12mb_gSgzEo-EYynwTmI2uH7PcrTM6Iq0,2455
45
+ tests/test_comb.py,sha256=TS-CbtiPbntL4u6E1gSZ6xquNp6cQxIFdJqUr2ak7PA,5515
46
+ tests/test_comm.py,sha256=iwl-Ey-xoytXmlNrjG84pDm82Ry_QUX6wY4gmVh4NDc,2516
47
+ tests/test_inj.py,sha256=Blpk-z9PQuqo4xQ7AUi0CS2czMiYm-pqZe75OFTXru4,1092
48
+ tests/test_method.py,sha256=KB7yAtVb4gZftnYzh-VfPb9LGVZOHUIW6OljEYRtbhA,4570
49
+ tests/test_proc_rep.py,sha256=WhUiFqDD1Aey_Nc6Hvbd2zo48K4MWjwDhfO9LiwEQ7M,703
50
+ tests/test_sequence.py,sha256=vs5-dqkItRds_tPM2-N6MNJ37FB0nLRFaDzBV8d42i8,4880
51
+ pychemstation-0.6.0.dist-info/LICENSE,sha256=9bdF75gIf1MecZ7oymqWgJREVz7McXPG-mjqrTmzzD8,18658
52
+ pychemstation-0.6.0.dist-info/METADATA,sha256=mO3-TN1CbOOEDxaUfAdwfZvRE65V3IvlXecYdb0uQBE,4370
53
+ pychemstation-0.6.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
54
+ pychemstation-0.6.0.dist-info/top_level.txt,sha256=zXfKu_4nYWwPHo3OsuhshMNC3SPkcoTGCyODjURaghY,20
55
+ pychemstation-0.6.0.dist-info/RECORD,,
tests/constants.py CHANGED
@@ -4,7 +4,7 @@ from pychemstation.utils.method_types import *
4
4
  from pychemstation.utils.sequence_types import *
5
5
 
6
6
  DEFAULT_METHOD = "GENERAL-POROSHELL-OPT"
7
- DEFAULT_SEQUENCE = "LLETest"
7
+ DEFAULT_SEQUENCE = "hplc_testing"
8
8
 
9
9
  # CONSTANTS: paths only work in Hein group HPLC machine in room 242
10
10
  DEFAULT_COMMAND_PATH = "C:\\Users\\User\\Desktop\\Lucy\\"
tests/test_comb.py CHANGED
@@ -10,7 +10,7 @@ run_too = True
10
10
 
11
11
  class TestCombinations(unittest.TestCase):
12
12
  def setUp(self):
13
- path_constants = room(254)
13
+ path_constants = room(242)
14
14
  for path in path_constants:
15
15
  if not os.path.exists(path):
16
16
  self.fail(
tests/test_comm.py CHANGED
@@ -10,7 +10,7 @@ from tests.constants import *
10
10
  class TestComm(unittest.TestCase):
11
11
 
12
12
  def setUp(self):
13
- path_constants = room(254)
13
+ path_constants = room(242)
14
14
  for path in path_constants:
15
15
  if not os.path.exists(path):
16
16
  self.fail(
tests/test_inj.py CHANGED
@@ -17,22 +17,17 @@ class TestInj(unittest.TestCase):
17
17
  method_dir=path_constants[1],
18
18
  data_dir=path_constants[2],
19
19
  sequence_dir=path_constants[3])
20
+ # self.hplc_controller.switch_method(DEFAULT_METHOD)
20
21
 
21
22
  def test_load_inj(self):
22
- self.hplc_controller.switch_method(DEFAULT_METHOD)
23
23
  try:
24
- gp_mtd = self.hplc_controller.method_controller.load_from_disk(DEFAULT_METHOD)
25
- self.assertTrue(gp_mtd.first_row.organic_modifier == 5)
24
+ inj_table = self.hplc_controller.load_injector_program()
25
+ self.assertTrue(len(inj_table.functions) == 2)
26
26
  except Exception as e:
27
27
  self.fail(f"Should have not failed, {e}")
28
28
 
29
- def test_edit_inj(self):
30
- self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
31
- new_method = gen_rand_method()
32
- try:
33
- self.hplc_controller.edit_method(new_method)
34
- except Exception as e:
35
- self.fail(f"Should have not failed: {e}")
29
+
30
+
36
31
 
37
32
  if __name__ == '__main__':
38
33
  unittest.main()
tests/test_method.py CHANGED
@@ -28,7 +28,25 @@ class TestMethod(unittest.TestCase):
28
28
 
29
29
  def test_edit_method(self):
30
30
  self.hplc_controller.method_controller.switch(DEFAULT_METHOD)
31
- new_method = gen_rand_method()
31
+ new_method = MethodDetails(name=DEFAULT_METHOD,
32
+ timetable=[TimeTableEntry(start_time=1,
33
+ organic_modifer=20,
34
+ flow=0.65),
35
+ TimeTableEntry(start_time=2,
36
+ organic_modifer=30,
37
+ flow=0.65),
38
+ TimeTableEntry(start_time=2.5,
39
+ organic_modifer=60,
40
+ flow=0.65),
41
+ TimeTableEntry(start_time=3,
42
+ organic_modifer=80,
43
+ flow=0.65),
44
+ TimeTableEntry(start_time=3.5,
45
+ organic_modifer=100,
46
+ flow=0.65)],
47
+ stop_time=4,
48
+ post_time=1,
49
+ params=HPLCMethodParams(organic_modifier=5, flow=0.65))
32
50
  try:
33
51
  self.hplc_controller.edit_method(new_method)
34
52
  except Exception as e:
tests/test_proc_rep.py ADDED
@@ -0,0 +1,27 @@
1
+ import unittest
2
+
3
+ from result import Result
4
+
5
+ from pychemstation.analysis.process_report import process_csv_report
6
+
7
+
8
+ class TestReport(unittest.TestCase):
9
+
10
+ def test_process_reporttxt(self):
11
+ try:
12
+ # TODO
13
+ print('yes')
14
+ except Exception as e:
15
+ self.fail(f"Should have not failed, {e}")
16
+
17
+ def test_report_csv(self):
18
+ try:
19
+ possible_peaks: Result = process_csv_report(folder_path="0_2025-03-15 19-14-35.D", num=1)
20
+ self.assertTrue(len(possible_peaks.ok_value) == 16)
21
+ print('yes')
22
+ except Exception as e:
23
+ self.fail(f"Should have not failed: {e}")
24
+
25
+
26
+ if __name__ == '__main__':
27
+ unittest.main()
tests/test_sequence.py CHANGED
@@ -2,13 +2,12 @@ import os
2
2
  import unittest
3
3
 
4
4
  from pychemstation.control import HPLCController
5
- from pychemstation.utils.sequence_types import *
6
5
  from tests.constants import *
7
6
 
8
7
 
9
8
  class TestSequence(unittest.TestCase):
10
9
  def setUp(self):
11
- path_constants = room(254)
10
+ path_constants = room(242)
12
11
  for path in path_constants:
13
12
  if not os.path.exists(path):
14
13
  self.fail(