pychemstation 0.8.3__py3-none-any.whl → 0.9.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 (33) hide show
  1. pychemstation/__init__.py +1 -1
  2. pychemstation/analysis/__init__.py +4 -1
  3. pychemstation/analysis/base_spectrum.py +4 -4
  4. pychemstation/{utils → analysis}/chromatogram.py +4 -7
  5. pychemstation/analysis/process_report.py +121 -74
  6. pychemstation/control/README.md +22 -46
  7. pychemstation/control/__init__.py +5 -0
  8. pychemstation/control/controllers/__init__.py +2 -0
  9. pychemstation/control/controllers/comm.py +39 -18
  10. pychemstation/control/controllers/devices/device.py +27 -14
  11. pychemstation/control/controllers/devices/injector.py +33 -89
  12. pychemstation/control/controllers/tables/method.py +266 -111
  13. pychemstation/control/controllers/tables/ms.py +7 -4
  14. pychemstation/control/controllers/tables/sequence.py +171 -82
  15. pychemstation/control/controllers/tables/table.py +192 -116
  16. pychemstation/control/hplc.py +117 -83
  17. pychemstation/generated/__init__.py +0 -2
  18. pychemstation/generated/dad_method.py +1 -1
  19. pychemstation/generated/pump_method.py +15 -19
  20. pychemstation/utils/injector_types.py +1 -1
  21. pychemstation/utils/macro.py +12 -11
  22. pychemstation/utils/method_types.py +3 -2
  23. pychemstation/{analysis/utils.py → utils/num_utils.py} +2 -2
  24. pychemstation/utils/parsing.py +1 -11
  25. pychemstation/utils/sequence_types.py +4 -5
  26. pychemstation/{analysis → utils}/spec_utils.py +1 -2
  27. pychemstation/utils/table_types.py +10 -9
  28. pychemstation/utils/tray_types.py +48 -38
  29. {pychemstation-0.8.3.dist-info → pychemstation-0.9.0.dist-info}/METADATA +63 -24
  30. pychemstation-0.9.0.dist-info/RECORD +37 -0
  31. pychemstation-0.8.3.dist-info/RECORD +0 -37
  32. {pychemstation-0.8.3.dist-info → pychemstation-0.9.0.dist-info}/WHEEL +0 -0
  33. {pychemstation-0.8.3.dist-info → pychemstation-0.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,30 @@
1
1
  import os
2
2
  import time
3
- from typing import Optional, List, Dict
3
+ import warnings
4
+ from typing import Dict, List, Optional
4
5
 
5
- from result import Result, Ok, Err
6
+ from result import Err, Ok, Result
6
7
  from typing_extensions import override
7
8
 
8
- from .table import TableController
9
- from .. import MethodController
10
- from ....analysis.process_report import ReportType, AgilentReport
9
+ from ....analysis.process_report import AgilentReport, ReportType
11
10
  from ....control.controllers.comm import CommunicationController
12
- from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData, AgilentHPLCChromatogram
11
+ from pychemstation.analysis.chromatogram import (
12
+ SEQUENCE_TIME_FORMAT,
13
+ AgilentChannelChromatogramData,
14
+ AgilentHPLCChromatogram,
15
+ )
13
16
  from ....utils.macro import Command
14
- from ....utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
17
+ from ....utils.sequence_types import (
18
+ InjectionSource,
19
+ SampleType,
20
+ SequenceDataFiles,
21
+ SequenceEntry,
22
+ SequenceTable,
23
+ )
15
24
  from ....utils.table_types import RegisterFlag, Table
16
- from ....utils.tray_types import TenVialColumn, FiftyFourVialPlate
25
+ from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
26
+ from .. import MethodController
27
+ from .table import TableController
17
28
 
18
29
 
19
30
  class SequenceController(TableController):
@@ -21,15 +32,24 @@ class SequenceController(TableController):
21
32
  Class containing sequence related logic
22
33
  """
23
34
 
24
- def __init__(self, controller: CommunicationController,
25
- method_controller: MethodController,
26
- src: str,
27
- data_dirs: List[str],
28
- table: Table,
29
- offline: bool):
35
+ def __init__(
36
+ self,
37
+ controller: CommunicationController,
38
+ method_controller: MethodController,
39
+ src: str,
40
+ data_dirs: List[str],
41
+ table: Table,
42
+ offline: bool,
43
+ ):
30
44
  self.method_controller = method_controller
31
45
  self.data_files: List[SequenceDataFiles] = []
32
- super().__init__(controller=controller, src=src, data_dirs=data_dirs, table=table, offline=offline)
46
+ super().__init__(
47
+ controller=controller,
48
+ src=src,
49
+ data_dirs=data_dirs,
50
+ table=table,
51
+ offline=offline,
52
+ )
33
53
 
34
54
  def load(self) -> SequenceTable:
35
55
  rows = self.get_num_rows()
@@ -39,7 +59,10 @@ class SequenceController(TableController):
39
59
  if rows.is_ok() and seq_name.is_ok():
40
60
  self.table_state = SequenceTable(
41
61
  name=seq_name.ok_value.string_response.partition(".S")[0],
42
- rows=[self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))])
62
+ rows=[
63
+ self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))
64
+ ],
65
+ )
43
66
  return self.table_state
44
67
  raise RuntimeError(rows.err_value)
45
68
 
@@ -51,15 +74,20 @@ class SequenceController(TableController):
51
74
  inj_vol = float(self.get_text(row, RegisterFlag.INJ_VOL))
52
75
  inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
53
76
  sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
54
- vial_enum = TenVialColumn(vial_location) if vial_location <= 10 else FiftyFourVialPlate.from_int(
55
- num=vial_location)
56
- return SequenceEntry(sample_name=sample_name,
57
- vial_location=vial_enum,
58
- method=None if len(method) == 0 else method,
59
- num_inj=num_inj,
60
- inj_vol=inj_vol,
61
- inj_source=inj_source,
62
- sample_type=sample_type)
77
+ vial_enum = (
78
+ TenVialColumn(vial_location)
79
+ if vial_location <= 10
80
+ else FiftyFourVialPlate.from_int(num=vial_location)
81
+ )
82
+ return SequenceEntry(
83
+ sample_name=sample_name,
84
+ vial_location=vial_enum,
85
+ method=None if len(method) == 0 else method,
86
+ num_inj=num_inj,
87
+ inj_vol=inj_vol,
88
+ inj_source=inj_source,
89
+ sample_type=sample_type,
90
+ )
63
91
 
64
92
  def check(self) -> str:
65
93
  time.sleep(2)
@@ -82,7 +110,7 @@ class SequenceController(TableController):
82
110
  time.sleep(2)
83
111
  self.send(Command.GET_SEQUENCE_CMD)
84
112
  time.sleep(2)
85
- parsed_response = self.receive().value.string_response
113
+ parsed_response = self.receive().ok_value.string_response
86
114
 
87
115
  assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
88
116
  self.table_state = None
@@ -97,7 +125,7 @@ class SequenceController(TableController):
97
125
  self.table_state = sequence_table
98
126
  rows = self.get_num_rows()
99
127
  if rows.is_ok():
100
- existing_row_num = rows.value.num_response
128
+ existing_row_num = rows.ok_value.num_response
101
129
  wanted_row_num = len(sequence_table.rows)
102
130
  while existing_row_num != wanted_row_num:
103
131
  if wanted_row_num > existing_row_num:
@@ -127,15 +155,15 @@ class SequenceController(TableController):
127
155
  self.add_row()
128
156
  self.send(Command.SAVE_SEQUENCE_CMD)
129
157
  num_rows = self.get_num_rows()
130
-
131
158
  if row.vial_location:
132
159
  loc = row.vial_location
133
160
  if isinstance(loc, TenVialColumn):
134
161
  loc = row.vial_location.value
135
162
  elif isinstance(loc, FiftyFourVialPlate):
136
163
  loc = row.vial_location.value()
137
- self._edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
138
-
164
+ self._edit_row_num(
165
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc
166
+ )
139
167
  if row.method:
140
168
  method_dir = self.method_controller.src
141
169
  possible_path = os.path.join(method_dir, row.method) + ".M\\"
@@ -143,25 +171,36 @@ class SequenceController(TableController):
143
171
  if os.path.exists(possible_path):
144
172
  method = os.path.join(method_dir, row.method)
145
173
  self._edit_row_text(row=row_num, col_name=RegisterFlag.METHOD, val=method)
146
-
147
174
  if row.num_inj:
148
- self._edit_row_num(row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj)
149
-
175
+ self._edit_row_num(
176
+ row=row_num, col_name=RegisterFlag.NUM_INJ, val=row.num_inj
177
+ )
150
178
  if row.inj_vol:
151
- self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol)
152
-
179
+ self._edit_row_text(
180
+ row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol
181
+ )
153
182
  if row.inj_source:
154
- self._edit_row_text(row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value)
155
-
183
+ self._edit_row_text(
184
+ row=row_num, col_name=RegisterFlag.INJ_SOR, val=row.inj_source.value
185
+ )
156
186
  if row.sample_name:
157
- self._edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
187
+ self._edit_row_text(
188
+ row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name
189
+ )
158
190
  if row.data_file:
159
- self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file)
191
+ self._edit_row_text(
192
+ row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file
193
+ )
160
194
  else:
161
- self._edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
162
-
195
+ self._edit_row_text(
196
+ row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name
197
+ )
163
198
  if row.sample_type:
164
- self._edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
199
+ self._edit_row_num(
200
+ row=row_num,
201
+ col_name=RegisterFlag.SAMPLE_TYPE,
202
+ val=row.sample_type.value,
203
+ )
165
204
 
166
205
  self.send(Command.SAVE_SEQUENCE_CMD)
167
206
 
@@ -179,15 +218,18 @@ class SequenceController(TableController):
179
218
 
180
219
  total_runtime = 0
181
220
  for entry in self.table_state.rows:
182
- curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
183
- loaded_method = self.method_controller.get_method_name()[:-2]
221
+ curr_method_runtime = self.method_controller.get_total_runtime()
222
+ loaded_method = self.method_controller.get_method_name().removesuffix(".M")
184
223
  method_path = entry.method.split(sep="\\")
185
224
  method_name = method_path[-1]
186
225
  if loaded_method != method_name:
187
- method_dir = os.path.join(*method_path[:-1]) if len(method_path) > 1 else None
188
- self.method_controller.switch(method_name=method_name,
189
- alt_method_dir=method_dir)
190
- curr_method_runtime = self.method_controller.get_post_time() + self.method_controller.get_stop_time()
226
+ method_dir = (
227
+ "\\".join(method_path[:-1]) + "\\" if len(method_path) > 1 else None
228
+ )
229
+ self.method_controller.switch(
230
+ method_name=method_name, alt_method_dir=method_dir
231
+ )
232
+ curr_method_runtime = self.method_controller.get_total_runtime()
191
233
  total_runtime += curr_method_runtime
192
234
 
193
235
  timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
@@ -196,34 +238,43 @@ class SequenceController(TableController):
196
238
 
197
239
  if self.check_hplc_is_running():
198
240
  folder_name = f"{self.table_state.name} {timestamp}"
199
- self.data_files.append(SequenceDataFiles(dir=folder_name,
200
- sequence_name=self.table_state.name))
241
+ data_file = SequenceDataFiles(
242
+ dir=folder_name, sequence_name=self.table_state.name
243
+ )
244
+ self.data_files.append(data_file)
201
245
 
202
246
  if stall_while_running:
203
247
  run_completed = self.check_hplc_done_running()
204
248
  if run_completed.is_ok():
205
249
  self.data_files[-1] = run_completed.ok_value
206
250
  else:
207
- raise RuntimeError("Run error has occurred.")
208
- else:
209
- self.data_files[-1] = SequenceDataFiles(dir=folder_name,
210
- child_dirs=[],
211
- sequence_name=self.table_state.name)
251
+ warnings.warn("Run may have not completed.")
252
+ else:
253
+ raise RuntimeError("Sequence run did not start.")
212
254
 
213
255
  @override
214
- def fuzzy_match_most_recent_folder(self, most_recent_folder: SequenceDataFiles) -> Result[SequenceDataFiles, str]:
256
+ def fuzzy_match_most_recent_folder(
257
+ self, most_recent_folder: SequenceDataFiles
258
+ ) -> Result[SequenceDataFiles, str]:
215
259
  if os.path.isdir(most_recent_folder.dir):
216
260
  subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
217
- potential_folders = sorted(list(filter(lambda d: most_recent_folder.dir in d, subdirs)))
218
- most_recent_folder.child_dirs = [f for f in potential_folders if
219
- most_recent_folder.dir in f and ".M" not in f and ".D" in f]
261
+ potential_folders = sorted(
262
+ list(filter(lambda d: most_recent_folder.dir in d, subdirs))
263
+ )
264
+ most_recent_folder.child_dirs = [
265
+ f
266
+ for f in potential_folders
267
+ if most_recent_folder.dir in f and ".M" not in f and ".D" in f
268
+ ]
220
269
  return Ok(most_recent_folder)
221
270
 
222
271
  try:
223
272
  potential_folders = []
224
273
  for d in self.data_dirs:
225
274
  subdirs = [x[0] for x in os.walk(d)]
226
- potential_folders = sorted(list(filter(lambda d: most_recent_folder.dir in d, subdirs)))
275
+ potential_folders = sorted(
276
+ list(filter(lambda d: most_recent_folder.dir in d, subdirs))
277
+ )
227
278
  if len(potential_folders) > 0:
228
279
  break
229
280
  assert len(potential_folders) > 0
@@ -238,41 +289,79 @@ class SequenceController(TableController):
238
289
  potential_folders = []
239
290
  for d in self.data_dirs:
240
291
  subdirs = [x[0] for x in os.walk(d)]
241
- potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
292
+ potential_folders = sorted(
293
+ list(filter(lambda d: parent_dir in d, subdirs))
294
+ )
242
295
  if len(potential_folders) > 0:
243
296
  break
244
297
  assert len(potential_folders) > 0
245
- most_recent_folder.child_dirs = [f for f in potential_folders if
246
- parent_dir in f and ".M" not in f and ".D" in f]
298
+ most_recent_folder.child_dirs = [
299
+ f
300
+ for f in potential_folders
301
+ if parent_dir in f and ".M" not in f and ".D" in f
302
+ ]
247
303
  return Ok(most_recent_folder)
248
- except Exception:
249
- return Err("Failed to get sequence folder")
250
-
251
- def get_data(self, custom_path: Optional[str] = None,
252
- read_uv: bool = False) -> List[AgilentChannelChromatogramData]:
253
- if len(self.data_files[-1].child_dirs) == 0:
254
- self.data_files[-1] = self.fuzzy_match_most_recent_folder(self.data_files[-1]).ok_value
255
- spectra: list[AgilentChannelChromatogramData] = []
256
- all_w_spectra: list[Dict[str, AgilentHPLCChromatogram]] = []
304
+ except Exception as e:
305
+ error = f"Failed to get sequence folder: {e}"
306
+ return Err(error)
307
+
308
+ def get_data_uv(
309
+ self, custom_path: Optional[str] = None
310
+ ) -> List[Dict[int, AgilentHPLCChromatogram]]:
311
+ custom_path = (
312
+ SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
313
+ if custom_path
314
+ else self.data_files[-1]
315
+ )
316
+ if len(custom_path.child_dirs) == 0:
317
+ self.data_files[-1] = self.fuzzy_match_most_recent_folder(
318
+ custom_path
319
+ ).ok_value
320
+ all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
257
321
  for row in self.data_files[-1].child_dirs:
258
- self.get_spectrum(row, read_uv)
259
- spectra.append(AgilentChannelChromatogramData(**self.spectra))
322
+ self.get_uv_spectrum(row)
260
323
  all_w_spectra.append(self.uv)
261
- return spectra if not read_uv else all_w_spectra
324
+ return all_w_spectra
325
+
326
+ def get_data(
327
+ self, custom_path: Optional[str] = None
328
+ ) -> List[AgilentChannelChromatogramData]:
329
+ custom_path = (
330
+ SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
331
+ if custom_path
332
+ else self.data_files[-1]
333
+ )
334
+ if len(custom_path.child_dirs) == 0:
335
+ self.data_files[-1] = self.fuzzy_match_most_recent_folder(
336
+ custom_path
337
+ ).ok_value
338
+ spectra: List[AgilentChannelChromatogramData] = []
339
+ for row in self.data_files[-1].child_dirs:
340
+ self.get_spectrum_at_channels(row)
341
+ spectra.append(AgilentChannelChromatogramData(**self.spectra))
342
+ return spectra
262
343
 
263
- def get_report(self, custom_path: Optional[str] = None,
264
- report_type: ReportType = ReportType.TXT) -> List[AgilentReport]:
344
+ def get_report(
345
+ self,
346
+ custom_path: Optional[str] = None,
347
+ report_type: ReportType = ReportType.TXT,
348
+ ) -> List[AgilentReport]:
265
349
  if custom_path:
266
350
  self.data_files.append(
267
- self.fuzzy_match_most_recent_folder(most_recent_folder=SequenceDataFiles(dir=custom_path,
268
- child_dirs=[],
269
- sequence_name="NA")).ok_value)
351
+ self.fuzzy_match_most_recent_folder(
352
+ most_recent_folder=SequenceDataFiles(
353
+ dir=custom_path, child_dirs=[], sequence_name="NA"
354
+ )
355
+ ).ok_value
356
+ )
270
357
  parent_dir = self.data_files[-1]
271
- spectra = self.get_data(custom_path=custom_path)
358
+ spectra = self.get_data()
272
359
  reports = []
273
360
  for i, child_dir in enumerate(parent_dir.child_dirs):
274
361
  metd_report = self.get_report_details(child_dir, report_type)
275
- child_spectra: List[AgilentHPLCChromatogram] = list(spectra[i].__dict__.values())
362
+ child_spectra: List[AgilentHPLCChromatogram] = list(
363
+ spectra[i].__dict__.values()
364
+ )
276
365
  for j, signal in enumerate(metd_report.signals):
277
366
  possible_data = child_spectra[j]
278
367
  if len(possible_data.x) > 0: