pychemstation 0.5.12__tar.gz → 0.5.13.dev1__tar.gz

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-0.5.12 → pychemstation-0.5.13.dev1}/PKG-INFO +3 -2
  2. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/README.md +1 -1
  3. pychemstation-0.5.13.dev1/pychemstation/control/controllers/__init__.py +9 -0
  4. pychemstation-0.5.13.dev1/pychemstation/control/controllers/devices/column.py +12 -0
  5. pychemstation-0.5.13.dev1/pychemstation/control/controllers/devices/device.py +23 -0
  6. pychemstation-0.5.13.dev1/pychemstation/control/controllers/devices/pump.py +43 -0
  7. {pychemstation-0.5.12/pychemstation/control/controllers → pychemstation-0.5.13.dev1/pychemstation/control/controllers/tables}/method.py +90 -82
  8. {pychemstation-0.5.12/pychemstation/control/controllers → pychemstation-0.5.13.dev1/pychemstation/control/controllers/tables}/sequence.py +23 -14
  9. pychemstation-0.5.12/pychemstation/control/controllers/table_controller.py → pychemstation-0.5.13.dev1/pychemstation/control/controllers/tables/table.py +31 -10
  10. pychemstation-0.5.13.dev1/pychemstation/utils/__init__.py +0 -0
  11. pychemstation-0.5.13.dev1/pychemstation/utils/injector_types.py +30 -0
  12. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/macro.py +1 -0
  13. pychemstation-0.5.13.dev1/pychemstation/utils/pump_types.py +7 -0
  14. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/sequence_types.py +3 -2
  15. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/tray_types.py +4 -0
  16. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation.egg-info/PKG-INFO +3 -2
  17. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation.egg-info/SOURCES.txt +11 -3
  18. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation.egg-info/requires.txt +1 -0
  19. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pyproject.toml +3 -1
  20. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/setup.py +3 -2
  21. pychemstation-0.5.13.dev1/tests/__init__.py +0 -0
  22. pychemstation-0.5.13.dev1/tests/test_inj.py +38 -0
  23. pychemstation-0.5.12/pychemstation/control/controllers/__init__.py +0 -4
  24. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/LICENSE +0 -0
  25. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/__init__.py +0 -0
  26. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/analysis/__init__.py +0 -0
  27. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/analysis/base_spectrum.py +0 -0
  28. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/analysis/spec_utils.py +0 -0
  29. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/analysis/utils.py +0 -0
  30. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/control/__init__.py +0 -0
  31. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/control/controllers/comm.py +0 -0
  32. {pychemstation-0.5.12/pychemstation/utils → pychemstation-0.5.13.dev1/pychemstation/control/controllers/devices}/__init__.py +0 -0
  33. {pychemstation-0.5.12/tests → pychemstation-0.5.13.dev1/pychemstation/control/controllers/tables}/__init__.py +0 -0
  34. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/control/hplc.py +0 -0
  35. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/generated/__init__.py +0 -0
  36. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/generated/dad_method.py +0 -0
  37. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/generated/pump_method.py +0 -0
  38. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/chromatogram.py +0 -0
  39. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/method_types.py +0 -0
  40. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/parsing.py +0 -0
  41. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation/utils/table_types.py +0 -0
  42. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation.egg-info/dependency_links.txt +0 -0
  43. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/pychemstation.egg-info/top_level.txt +0 -0
  44. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/setup.cfg +0 -0
  45. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/tests/constants.py +0 -0
  46. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/tests/test_comb.py +0 -0
  47. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/tests/test_comm.py +0 -0
  48. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/tests/test_method.py +0 -0
  49. {pychemstation-0.5.12 → pychemstation-0.5.13.dev1}/tests/test_sequence.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.5.12
3
+ Version: 0.5.13.dev1
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
@@ -14,6 +14,7 @@ Requires-Dist: polling
14
14
  Requires-Dist: seabreeze
15
15
  Requires-Dist: xsdata
16
16
  Requires-Dist: result
17
+ Requires-Dist: rainbow
17
18
 
18
19
  # Agilent HPLC Macro Control
19
20
 
@@ -99,7 +100,7 @@ our [GitLab](https://gitlab.com/heingroup/device-api/pychemstation)!
99
100
 
100
101
  ## Authors and Acknowledgements
101
102
 
102
- Lucy Hao
103
+ Lucy Hao, Maria Politi
103
104
 
104
105
  - Adapted from [**AnalyticalLabware**](https://github.com/croningp/analyticallabware), created by members in the Cronin
105
106
  Group. Copyright © Cronin Group, used under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license.
@@ -82,7 +82,7 @@ our [GitLab](https://gitlab.com/heingroup/device-api/pychemstation)!
82
82
 
83
83
  ## Authors and Acknowledgements
84
84
 
85
- Lucy Hao
85
+ Lucy Hao, Maria Politi
86
86
 
87
87
  - Adapted from [**AnalyticalLabware**](https://github.com/croningp/analyticallabware), created by members in the Cronin
88
88
  Group. Copyright © Cronin Group, used under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license.
@@ -0,0 +1,9 @@
1
+ """
2
+ .. include:: README.md
3
+ """
4
+
5
+ from .comm import CommunicationController
6
+ from .devices.pump import PumpController
7
+ from .devices.column import ColumnController
8
+ from .tables.method import MethodController
9
+ from .tables.sequence import SequenceController
@@ -0,0 +1,12 @@
1
+ from ....control.controllers import CommunicationController
2
+ from .device import DeviceController
3
+ from ....utils.table_types import Table
4
+
5
+
6
+ class ColumnController(DeviceController):
7
+
8
+ def __init__(self, controller: CommunicationController, table: Table):
9
+ super().__init__(controller, table)
10
+
11
+ def get_row(self, row: int):
12
+ pass
@@ -0,0 +1,23 @@
1
+ import abc
2
+ from typing import Union
3
+
4
+ from ....control.controllers import CommunicationController
5
+ from ....control.controllers.tables.table import TableController
6
+ from ....utils.chromatogram import AgilentChannelChromatogramData
7
+ from ....utils.table_types import Table
8
+
9
+
10
+ class DeviceController(TableController, abc.ABC):
11
+
12
+ def __init__(self, controller: CommunicationController, table: Table):
13
+ super().__init__(controller, None, None, table)
14
+
15
+ @abc.abstractmethod
16
+ def get_row(self, row: int):
17
+ pass
18
+
19
+ def retrieve_recent_data_files(self):
20
+ raise NotImplementedError
21
+
22
+ def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
23
+ raise NotImplementedError
@@ -0,0 +1,43 @@
1
+ from ....control.controllers import CommunicationController
2
+ from .device import DeviceController
3
+ from ....utils.pump_types import Pump
4
+ from ....utils.table_types import Table
5
+
6
+
7
+ class PumpController(DeviceController):
8
+
9
+ def __init__(self, controller: CommunicationController, table: Table):
10
+ super().__init__(controller, table)
11
+ self.A1 = Pump(in_use=True, solvent="A1")
12
+ self.B1 = Pump(in_use=True, solvent="B1")
13
+ self.A2 = Pump(in_use=False, solvent="A2")
14
+ self.B2 = Pump(in_use=False, solvent="B2")
15
+
16
+ def validate_pumps(self):
17
+ invalid_A_pump_usage = self.A1.in_use and self.A2.in_use
18
+ invalid_B_pump_usage = self.B1.in_use and self.B2.in_use
19
+ if invalid_A_pump_usage or invalid_B_pump_usage:
20
+ raise AttributeError
21
+
22
+ def switch_pump(self, num: int, pump: str):
23
+ if pump == "A":
24
+ if num == 1:
25
+ self.A1.in_use = True
26
+ self.A2.in_use = False
27
+ elif num == 2:
28
+ self.A1.in_use = False
29
+ self.A2.in_use = True
30
+ elif pump == "B":
31
+ if num == 1:
32
+ self.B1.in_use = True
33
+ self.B2.in_use = False
34
+ elif num == 2:
35
+ self.B1.in_use = False
36
+ self.B2.in_use = True
37
+ self.purge()
38
+
39
+ def purge(self):
40
+ pass
41
+
42
+ def get_row(self, row: int):
43
+ pass
@@ -1,16 +1,15 @@
1
1
  import os
2
2
  import time
3
- from typing import Optional, Union
4
3
 
5
4
  from xsdata.formats.dataclass.parsers import XmlParser
6
5
 
7
- from ...control.controllers.table_controller import TableController
8
- from ...control.controllers.comm import CommunicationController
9
- from ...generated import PumpMethod, DadMethod, SolventElement
10
- from ...utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
11
- from ...utils.macro import Command
12
- from ...utils.method_types import PType, TimeTableEntry, Param, MethodDetails, HPLCMethodParams
13
- from ...utils.table_types import RegisterFlag, TableOperation, Table
6
+ from .table import TableController
7
+ from ....control.controllers import CommunicationController
8
+ from ....generated import PumpMethod, DadMethod, SolventElement
9
+ from ....utils.chromatogram import TIME_FORMAT, AgilentChannelChromatogramData
10
+ from ....utils.macro import *
11
+ from ....utils.method_types import *
12
+ from ....utils.table_types import *
14
13
 
15
14
 
16
15
  class MethodController(TableController):
@@ -46,9 +45,21 @@ class MethodController(TableController):
46
45
  )
47
46
 
48
47
  def get_row(self, row: int) -> TimeTableEntry:
48
+ flow = None
49
+ om = None
50
+
51
+ try:
52
+ flow = self.get_num(row, RegisterFlag.TIMETABLE_FLOW)
53
+ except RuntimeError:
54
+ pass
55
+ try:
56
+ om = self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION)
57
+ except RuntimeError:
58
+ pass
59
+
49
60
  return TimeTableEntry(start_time=self.get_num(row, RegisterFlag.TIME),
50
- organic_modifer=self.get_num(row, RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION),
51
- flow=self.get_num(row, RegisterFlag.TIMETABLE_FLOW))
61
+ organic_modifer=om,
62
+ flow=flow)
52
63
 
53
64
  def get_timetable(self, rows: int):
54
65
  uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
@@ -84,12 +95,11 @@ class MethodController(TableController):
84
95
  cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
85
96
  register=self.table.register,
86
97
  register_flag=RegisterFlag.POST_TIME))
87
- self.table_state = MethodDetails(
88
- name=method_name,
89
- timetable=timetable_rows,
90
- stop_time=stop_time,
91
- post_time=post_time,
92
- params=params)
98
+ self.table_state = MethodDetails(name=method_name,
99
+ timetable=timetable_rows,
100
+ stop_time=stop_time,
101
+ post_time=post_time,
102
+ params=params)
93
103
  return self.table_state
94
104
  else:
95
105
  raise RuntimeError(rows.err_value)
@@ -156,17 +166,16 @@ class MethodController(TableController):
156
166
  elif solvent.channel == "Channel_B":
157
167
  organic_modifier = solvent
158
168
 
159
- self.table_state = MethodDetails(
160
- name=method_name,
161
- params=HPLCMethodParams(organic_modifier=organic_modifier.percentage,
162
- flow=method.flow),
163
- stop_time=method.stop_time.stop_time_value,
164
- post_time=method.post_time.post_time_value,
165
- timetable=[TimeTableEntry(start_time=tte.time,
166
- organic_modifer=tte.percent_b,
167
- flow=method.flow
168
- ) for tte in method.timetable.timetable_entry],
169
- dad_wavelengthes=dad.signals.signal)
169
+ self.table_state = MethodDetails(name=method_name,
170
+ params=HPLCMethodParams(organic_modifier=organic_modifier.percentage,
171
+ flow=method.flow),
172
+ stop_time=method.stop_time.stop_time_value,
173
+ post_time=method.post_time.post_time_value,
174
+ timetable=[TimeTableEntry(start_time=tte.time,
175
+ organic_modifer=tte.percent_b,
176
+ flow=method.flow
177
+ ) for tte in method.timetable.timetable_entry],
178
+ dad_wavelengthes=dad.signals.signal)
170
179
  return self.table_state
171
180
  else:
172
181
  raise FileNotFoundError
@@ -186,7 +195,7 @@ class MethodController(TableController):
186
195
  ptype=PType.NUM)
187
196
  post_time: Param = Param(val=updated_method.post_time,
188
197
  chemstation_key=RegisterFlag.POST_TIME,
189
- ptype=PType.NUM) # TODO check postime works
198
+ ptype=PType.NUM)
190
199
  flow: Param = Param(val=updated_method.params.flow,
191
200
  chemstation_key=RegisterFlag.FLOW,
192
201
  ptype=PType.NUM)
@@ -205,19 +214,15 @@ class MethodController(TableController):
205
214
  self._update_param(initial_organic_modifier)
206
215
  self._update_param(flow)
207
216
  if self.table_state.stop_time:
208
- self._update_param(Param(val="Set",
209
- chemstation_key=RegisterFlag.STOPTIME_MODE,
210
- ptype=PType.STR))
217
+ self._update_param(Param(val="Set", chemstation_key=RegisterFlag.STOPTIME_MODE, ptype=PType.STR))
211
218
  self._update_param(max_time)
212
219
  else:
213
- self._update_param(Param(val="Off",
214
- chemstation_key=RegisterFlag.STOPTIME_MODE,
215
- ptype=PType.STR))
220
+ self._update_param(Param(val="Off", chemstation_key=RegisterFlag.STOPTIME_MODE, ptype=PType.STR))
216
221
  if self.table_state.post_time:
217
- self._update_param(Param(val="Set",
218
- chemstation_key=RegisterFlag.POSTIME_MODE,
219
- ptype=PType.STR))
222
+ self._update_param(Param(val="Set", chemstation_key=RegisterFlag.POSTIME_MODE, ptype=PType.STR))
220
223
  self._update_param(post_time)
224
+ else:
225
+ self._update_param(Param(val="Off", chemstation_key=RegisterFlag.POSTIME_MODE, ptype=PType.STR))
221
226
  self.download()
222
227
 
223
228
  def _update_param(self, method_param: Param):
@@ -246,34 +251,33 @@ class MethodController(TableController):
246
251
 
247
252
  def edit_row(self, row: TimeTableEntry, first_row: bool = False):
248
253
  if first_row:
249
- self.add_row()
250
- self.add_new_col_text(col_name=RegisterFlag.FUNCTION,
251
- val=RegisterFlag.SOLVENT_COMPOSITION.value)
252
- self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
253
- self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
254
- val=row.organic_modifer)
255
- self.add_row()
256
- self.get_num_rows()
257
- self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
258
- self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
259
- self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
254
+ if row.organic_modifer:
255
+ self.add_row()
256
+ self.add_new_col_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.SOLVENT_COMPOSITION.value)
257
+ self.add_new_col_num(col_name=RegisterFlag.TIME, val=row.start_time)
258
+ self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION, val=row.organic_modifer)
259
+ if row.flow:
260
+ self.add_row()
261
+ self.get_num_rows()
262
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
263
+ self.add_new_col_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
264
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
260
265
  self.download()
261
266
  else:
262
- self.add_row()
263
- self.get_num_rows()
264
- self.edit_row_text(col_name=RegisterFlag.FUNCTION,
265
- val=RegisterFlag.SOLVENT_COMPOSITION.value)
266
- self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
267
- self.edit_row_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION,
268
- val=row.organic_modifer)
269
- self.download()
270
-
271
- self.add_row()
272
- self.get_num_rows()
273
- self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
274
- self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
275
- self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
276
- self.download()
267
+ if row.organic_modifer:
268
+ self.add_row()
269
+ self.get_num_rows()
270
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.SOLVENT_COMPOSITION.value)
271
+ self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
272
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_SOLVENT_B_COMPOSITION, val=row.organic_modifer)
273
+ self.download()
274
+ if row.flow:
275
+ self.add_row()
276
+ self.get_num_rows()
277
+ self.edit_row_text(col_name=RegisterFlag.FUNCTION, val=RegisterFlag.FLOW.value)
278
+ self.edit_row_num(col_name=RegisterFlag.TIMETABLE_FLOW, val=row.flow)
279
+ self.edit_row_num(col_name=RegisterFlag.TIME, val=row.start_time)
280
+ self.download()
277
281
 
278
282
  def _update_method_timetable(self, timetable_rows: list[TimeTableEntry]):
279
283
  self.get_num_rows()
@@ -309,30 +313,34 @@ class MethodController(TableController):
309
313
  if not self.table_state:
310
314
  self.table_state = self.load()
311
315
 
312
- timestamp = time.strftime(TIME_FORMAT)
313
- self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dir,
314
- experiment_name=experiment_name,
315
- timestamp=timestamp))
316
-
317
- if self.check_hplc_is_running():
316
+ folder_name = ""
317
+ hplc_is_running = self.check_hplc_is_running()
318
+ while not hplc_is_running:
319
+ timestamp = time.strftime(TIME_FORMAT)
320
+ self.send(Command.RUN_METHOD_CMD.value.format(data_dir=self.data_dir,
321
+ experiment_name=experiment_name,
322
+ timestamp=timestamp))
318
323
  folder_name = f"{experiment_name}_{timestamp}.D"
319
- self.data_files.append(os.path.join(self.data_dir, folder_name))
320
-
321
- if stall_while_running:
322
- run_completed = self.check_hplc_done_running(method=self.table_state)
323
- if run_completed.is_ok():
324
- self.data_files[-1] = run_completed.value
325
- else:
326
- raise RuntimeError("Run error has occurred.")
324
+ hplc_is_running = self.check_hplc_is_running()
325
+
326
+ self.data_files.append(os.path.join(self.data_dir, folder_name))
327
+
328
+ if stall_while_running:
329
+ run_completed = self.check_hplc_done_running(method=self.table_state)
330
+ if run_completed.is_ok():
331
+ self.data_files[-1] = run_completed.ok_value
327
332
  else:
328
- self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
333
+ raise RuntimeError("Run error has occurred.")
334
+ else:
335
+ self.data_files[-1].dir = self.fuzzy_match_most_recent_folder(folder_name).ok_value
329
336
 
330
337
  def retrieve_recent_data_files(self) -> str:
331
338
  return self.data_files[-1]
332
339
 
333
- def get_data(self, custom_path: Optional[str] = None) -> AgilentChannelChromatogramData:
340
+ def get_data(self, custom_path: Optional[str] = None,
341
+ read_uv: bool = False) -> AgilentChannelChromatogramData:
334
342
  if not custom_path:
335
- self.get_spectrum(self.data_files[-1])
343
+ self.get_spectrum(self.data_files[-1], read_uv)
336
344
  else:
337
- self.get_spectrum(custom_path)
338
- return AgilentChannelChromatogramData(**self.spectra)
345
+ self.get_spectrum(custom_path, read_uv)
346
+ return AgilentChannelChromatogramData(**self.spectra) if not read_uv else self.uv
@@ -1,14 +1,14 @@
1
1
  import os
2
2
  import time
3
- from typing import Optional
3
+ from typing import Optional, Union
4
4
 
5
- from ...control.controllers.comm import CommunicationController
6
- from ...control.controllers.table_controller import TableController
7
- from ...utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData
8
- from ...utils.macro import Command
9
- from ...utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
10
- from ...utils.table_types import RegisterFlag, Table
11
- from ...utils.tray_types import TenVialColumn
5
+ from .table import TableController, ChromData
6
+ from ....control.controllers.comm import CommunicationController
7
+ from ....utils.chromatogram import SEQUENCE_TIME_FORMAT, AgilentChannelChromatogramData
8
+ from ....utils.macro import Command
9
+ from ....utils.sequence_types import SequenceTable, SequenceEntry, SequenceDataFiles, InjectionSource, SampleType
10
+ from ....utils.table_types import RegisterFlag, Table
11
+ from ....utils.tray_types import TenVialColumn, FiftyFourVialPlate
12
12
 
13
13
 
14
14
  class SequenceController(TableController):
@@ -117,8 +117,10 @@ class SequenceController(TableController):
117
117
 
118
118
  if row.vial_location:
119
119
  loc = row.vial_location
120
- if isinstance(row.vial_location, TenVialColumn):
120
+ if isinstance(loc, TenVialColumn):
121
121
  loc = row.vial_location.value
122
+ elif isinstance(loc, FiftyFourVialPlate):
123
+ loc = row.vial_location.value()
122
124
  self.edit_row_num(row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc)
123
125
 
124
126
  if row.method:
@@ -139,7 +141,10 @@ class SequenceController(TableController):
139
141
 
140
142
  if row.sample_name:
141
143
  self.edit_row_text(row=row_num, col_name=RegisterFlag.NAME, val=row.sample_name)
142
- self.edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
144
+ if row.data_file:
145
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.data_file)
146
+ else:
147
+ self.edit_row_text(row=row_num, col_name=RegisterFlag.DATA_FILE, val=row.sample_name)
143
148
 
144
149
  if row.sample_type:
145
150
  self.edit_row_num(row=row_num, col_name=RegisterFlag.SAMPLE_TYPE, val=row.sample_type.value)
@@ -176,15 +181,19 @@ class SequenceController(TableController):
176
181
  sequence_data_files: SequenceDataFiles = self.data_files[-1]
177
182
  return sequence_data_files.dir
178
183
 
179
- def get_data(self, custom_path: Optional[str] = None) -> list[AgilentChannelChromatogramData]:
184
+ def get_data(self, custom_path: Optional[str] = None,
185
+ read_uv: bool = False) -> list[AgilentChannelChromatogramData]:
180
186
  parent_dir = self.data_files[-1].dir if not custom_path else custom_path
181
187
  subdirs = [x[0] for x in os.walk(self.data_dir)]
182
188
  potential_folders = sorted(list(filter(lambda d: parent_dir in d, subdirs)))
183
189
  self.data_files[-1].child_dirs = [f for f in potential_folders if
184
190
  parent_dir in f and ".M" not in f and ".D" in f]
185
191
 
186
- spectra: list[AgilentChannelChromatogramData] = []
192
+ spectra: list[Union[AgilentChannelChromatogramData, ChromData]] = []
193
+ all_w_spectra: list[Union[AgilentChannelChromatogramData, ChromData]] = []
187
194
  for row in self.data_files[-1].child_dirs:
188
- self.get_spectrum(row)
195
+ self.get_spectrum(row, read_uv)
189
196
  spectra.append(AgilentChannelChromatogramData(**self.spectra))
190
- return spectra
197
+ all_w_spectra.append(self.uv)
198
+
199
+ return spectra if not read_uv else all_w_spectra
@@ -6,21 +6,32 @@ Authors: Lucy Hao
6
6
 
7
7
  import abc
8
8
  import os
9
+ from dataclasses import dataclass
9
10
  from typing import Union, Optional
10
11
 
12
+ import numpy as np
11
13
  import polling
14
+ from rainbow import DataFile
12
15
  from result import Result, Ok, Err
16
+ import pandas as pd
17
+ import rainbow as rb
13
18
 
14
- from ...control.controllers.comm import CommunicationController
15
- from ...utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
16
- from ...utils.macro import Command, HPLCRunningStatus, Response
17
- from ...utils.method_types import MethodDetails
18
- from ...utils.sequence_types import SequenceDataFiles, SequenceTable
19
- from ...utils.table_types import Table, TableOperation, RegisterFlag
19
+ from ....control.controllers.comm import CommunicationController
20
+ from ....utils.chromatogram import AgilentHPLCChromatogram, AgilentChannelChromatogramData
21
+ from ....utils.macro import Command, HPLCRunningStatus, Response
22
+ from ....utils.method_types import MethodDetails
23
+ from ....utils.sequence_types import SequenceDataFiles, SequenceTable
24
+ from ....utils.table_types import Table, TableOperation, RegisterFlag
20
25
 
21
26
  TableType = Union[MethodDetails, SequenceTable]
22
27
 
23
28
 
29
+ @dataclass
30
+ class ChromData:
31
+ x: np.array
32
+ y: np.array
33
+
34
+
24
35
  class TableController(abc.ABC):
25
36
 
26
37
  def __init__(self, controller: CommunicationController, src: str, data_dir: str, table: Table):
@@ -51,6 +62,8 @@ class TableController(abc.ABC):
51
62
 
52
63
  self.data_files: Union[list[SequenceDataFiles], list[str]] = []
53
64
 
65
+ self.uv = None
66
+
54
67
  # Initialize row counter for table operations
55
68
  self.send('Local Rows')
56
69
 
@@ -191,8 +204,8 @@ class TableController(abc.ABC):
191
204
  def check_hplc_is_running(self) -> bool:
192
205
  started_running = polling.poll(
193
206
  lambda: isinstance(self.controller.get_status(), HPLCRunningStatus),
194
- step=5,
195
- max_tries=100)
207
+ step=2,
208
+ max_tries=10)
196
209
  return started_running
197
210
 
198
211
  def check_hplc_done_running(self,
@@ -254,13 +267,21 @@ class TableController(abc.ABC):
254
267
  def get_data(self) -> Union[list[AgilentChannelChromatogramData], AgilentChannelChromatogramData]:
255
268
  pass
256
269
 
257
- def get_spectrum(self, data_file: str):
270
+ def get_uv_spectrum(self, path: str):
271
+ data_uv: DataFile = rb.agilent.chemstation.parse_file(os.path.join(path, "DAD1.UV"))
272
+ zipped_data = zip(data_uv.ylabels, data_uv.data)
273
+ self.uv = {str(w_a[0]): ChromData(x=data_uv.xlabels, y=w_a[1]) for w_a in zipped_data}
274
+
275
+ def get_spectrum(self, data_path: str, read_uv: bool = False):
258
276
  """
259
277
  Load chromatogram for any channel in spectra dictionary.
260
278
  """
279
+ if read_uv:
280
+ self.get_uv_spectrum(data_path)
281
+
261
282
  for channel, spec in self.spectra.items():
262
283
  try:
263
- spec.load_spectrum(data_path=data_file, channel=channel)
284
+ spec.load_spectrum(data_path=data_path, channel=channel)
264
285
  except FileNotFoundError:
265
286
  self.spectra[channel] = None
266
287
  print(f"No data at channel: {channel}")
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from pychemstation.utils.tray_types import Tray
5
+
6
+
7
+ @dataclass
8
+ class Draw:
9
+ amount: float
10
+ source: Tray
11
+ speed: Any
12
+ offset: Any
13
+
14
+
15
+ @dataclass
16
+ class Wait:
17
+ time: int
18
+
19
+
20
+ @dataclass
21
+ class Inject:
22
+ pass
23
+
24
+
25
+ InjectorFunction = [Draw, Wait, Inject]
26
+
27
+
28
+ @dataclass
29
+ class InjectorTable:
30
+ functions: list[InjectorFunction]
@@ -34,6 +34,7 @@ class Command(Enum):
34
34
  INSTRUMENT_OFF = 'macro "SHUTDOWN.MAC" ,go'
35
35
  INSTRUMENT_ON = 'LIDoOperation "TURN_ON"'
36
36
 
37
+ # Method and Sequence Related
37
38
  GET_METHOD_CMD = "response$ = _MethFile$"
38
39
  GET_ROWS_CMD = 'response_num = TabHdrVal({register}, "{table_name}", "{col_name}")'
39
40
  SWITCH_METHOD_CMD = 'LoadMethod "{method_dir}", "{method_name}.M"'
@@ -0,0 +1,7 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Pump:
6
+ solvent: str
7
+ in_use: bool
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from typing import Optional, Union
6
6
 
7
- from pychemstation.utils.tray_types import TenVialColumn
7
+ from pychemstation.utils.tray_types import TenVialColumn, Tray
8
8
 
9
9
 
10
10
  @dataclass
@@ -39,7 +39,8 @@ class InjectionSource(Enum):
39
39
  @dataclass
40
40
  class SequenceEntry:
41
41
  sample_name: str
42
- vial_location: Union[TenVialColumn, int]
42
+ vial_location: Tray
43
+ data_file: Optional[str] = None
43
44
  method: Optional[str] = None
44
45
  num_inj: Optional[int] = 1
45
46
  inj_vol: Optional[int] = 2
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
+ from typing import Union
5
6
 
6
7
 
7
8
  class Num(Enum):
@@ -50,3 +51,6 @@ class TenVialColumn(Enum):
50
51
  EIGHT = 8
51
52
  NINE = 9
52
53
  TEN = 10
54
+
55
+
56
+ Tray = Union[FiftyFourVialPlate, TenVialColumn]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pychemstation
3
- Version: 0.5.12
3
+ Version: 0.5.13.dev1
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
@@ -14,6 +14,7 @@ Requires-Dist: polling
14
14
  Requires-Dist: seabreeze
15
15
  Requires-Dist: xsdata
16
16
  Requires-Dist: result
17
+ Requires-Dist: rainbow
17
18
 
18
19
  # Agilent HPLC Macro Control
19
20
 
@@ -99,7 +100,7 @@ our [GitLab](https://gitlab.com/heingroup/device-api/pychemstation)!
99
100
 
100
101
  ## Authors and Acknowledgements
101
102
 
102
- Lucy Hao
103
+ Lucy Hao, Maria Politi
103
104
 
104
105
  - Adapted from [**AnalyticalLabware**](https://github.com/croningp/analyticallabware), created by members in the Cronin
105
106
  Group. Copyright © Cronin Group, used under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license.
@@ -16,17 +16,24 @@ pychemstation/control/__init__.py
16
16
  pychemstation/control/hplc.py
17
17
  pychemstation/control/controllers/__init__.py
18
18
  pychemstation/control/controllers/comm.py
19
- pychemstation/control/controllers/method.py
20
- pychemstation/control/controllers/sequence.py
21
- pychemstation/control/controllers/table_controller.py
19
+ pychemstation/control/controllers/devices/__init__.py
20
+ pychemstation/control/controllers/devices/column.py
21
+ pychemstation/control/controllers/devices/device.py
22
+ pychemstation/control/controllers/devices/pump.py
23
+ pychemstation/control/controllers/tables/__init__.py
24
+ pychemstation/control/controllers/tables/method.py
25
+ pychemstation/control/controllers/tables/sequence.py
26
+ pychemstation/control/controllers/tables/table.py
22
27
  pychemstation/generated/__init__.py
23
28
  pychemstation/generated/dad_method.py
24
29
  pychemstation/generated/pump_method.py
25
30
  pychemstation/utils/__init__.py
26
31
  pychemstation/utils/chromatogram.py
32
+ pychemstation/utils/injector_types.py
27
33
  pychemstation/utils/macro.py
28
34
  pychemstation/utils/method_types.py
29
35
  pychemstation/utils/parsing.py
36
+ pychemstation/utils/pump_types.py
30
37
  pychemstation/utils/sequence_types.py
31
38
  pychemstation/utils/table_types.py
32
39
  pychemstation/utils/tray_types.py
@@ -34,5 +41,6 @@ tests/__init__.py
34
41
  tests/constants.py
35
42
  tests/test_comb.py
36
43
  tests/test_comm.py
44
+ tests/test_inj.py
37
45
  tests/test_method.py
38
46
  tests/test_sequence.py
@@ -2,3 +2,4 @@ polling
2
2
  seabreeze
3
3
  xsdata
4
4
  result
5
+ rainbow
@@ -18,7 +18,9 @@ pdoc = "^15.0.1"
18
18
  xsdata = "^24.12"
19
19
  scipy = "^1.15.1"
20
20
  result = "^0.17.0"
21
-
21
+ rainbow-api = "^1.0.10"
22
+ rainbow = "^2.8.0"
23
+ pandas = "^2.2.3"
22
24
 
23
25
  [tool.poetry.group.dev.dependencies]
24
26
  mypy = "^1.14.1"
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="pychemstation",
8
- version="0.5.12",
8
+ version="0.5.13.dev1",
9
9
  author="Lucy Hao",
10
10
  author_email="lhao03@student.ubc.ca",
11
11
  description="Library to interact with Chemstation software, primarily used in Hein lab",
@@ -17,7 +17,8 @@ setuptools.setup(
17
17
  'polling',
18
18
  'seabreeze',
19
19
  'xsdata',
20
- 'result'
20
+ 'result',
21
+ 'rainbow'
21
22
  ],
22
23
  classifiers=[
23
24
  "Programming Language :: Python :: 3",
File without changes
@@ -0,0 +1,38 @@
1
+ import os
2
+ import unittest
3
+
4
+ from pychemstation.control import HPLCController
5
+ from tests.constants import *
6
+
7
+
8
+ class TestInj(unittest.TestCase):
9
+ def setUp(self):
10
+ path_constants = room(254)
11
+ for path in path_constants:
12
+ if not os.path.exists(path):
13
+ self.fail(
14
+ f"{path} does not exist on your system. If you would like to run tests, please change this path.")
15
+
16
+ self.hplc_controller = HPLCController(comm_dir=path_constants[0],
17
+ method_dir=path_constants[1],
18
+ data_dir=path_constants[2],
19
+ sequence_dir=path_constants[3])
20
+
21
+ def test_load_inj(self):
22
+ self.hplc_controller.switch_method(DEFAULT_METHOD)
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)
26
+ except Exception as e:
27
+ self.fail(f"Should have not failed, {e}")
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}")
36
+
37
+ if __name__ == '__main__':
38
+ unittest.main()
@@ -1,4 +0,0 @@
1
- from .method import MethodController
2
- from .sequence import SequenceController
3
- from .table_controller import TableController
4
- from .comm import CommunicationController