pychemstation 0.10.4__py3-none-any.whl → 0.10.5__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.
@@ -1,14 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import time
3
5
  import warnings
4
- from typing import Dict, List, Optional, Union
6
+ from typing import List, Optional, Union, Dict
5
7
 
6
8
  from result import Err, Ok, Result
7
- from xsdata.formats.dataclass.parsers import XmlParser
8
9
 
9
10
  from ....analysis.process_report import AgilentReport, ReportType
10
11
  from ....control.controllers import CommunicationController
11
- from ....generated import DadMethod, PumpMethod, SolventElement
12
12
  from pychemstation.analysis.chromatogram import (
13
13
  TIME_FORMAT,
14
14
  AgilentChannelChromatogramData,
@@ -22,7 +22,7 @@ from ....utils.method_types import (
22
22
  PType,
23
23
  TimeTableEntry,
24
24
  )
25
- from ....utils.table_types import RegisterFlag, T, Table, TableOperation
25
+ from ....utils.table_types import RegisterFlag, Table, TableOperation, T
26
26
  from ..devices.injector import InjectorController
27
27
  from .table import TableController
28
28
 
@@ -61,20 +61,22 @@ class MethodController(TableController):
61
61
  return "ERROR"
62
62
 
63
63
  def get_method_params(self) -> HPLCMethodParams:
64
- return HPLCMethodParams(
65
- organic_modifier=self.controller.get_num_val(
66
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
67
- register=self.table_locator.register,
68
- register_flag=RegisterFlag.SOLVENT_B_COMPOSITION,
69
- )
70
- ),
71
- flow=self.controller.get_num_val(
72
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
73
- register=self.table_locator.register,
74
- register_flag=RegisterFlag.FLOW,
75
- )
76
- ),
77
- )
64
+ if self.controller:
65
+ return HPLCMethodParams(
66
+ organic_modifier=self.controller.get_num_val(
67
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
68
+ register=self.table_locator.register,
69
+ register_flag=RegisterFlag.SOLVENT_B_COMPOSITION,
70
+ )
71
+ ),
72
+ flow=self.controller.get_num_val(
73
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
74
+ register=self.table_locator.register,
75
+ register_flag=RegisterFlag.FLOW,
76
+ )
77
+ ),
78
+ )
79
+ raise ValueError("Communication controller is offline!")
78
80
 
79
81
  def get_row(self, row: int) -> TimeTableEntry:
80
82
  flow = None
@@ -89,6 +91,9 @@ class MethodController(TableController):
89
91
  except RuntimeError:
90
92
  pass
91
93
 
94
+ if om is None and flow is None:
95
+ raise ValueError("Both flow and organic modifier is None")
96
+
92
97
  return TimeTableEntry(
93
98
  start_time=self.get_num(row, RegisterFlag.TIME),
94
99
  organic_modifer=om,
@@ -97,7 +102,7 @@ class MethodController(TableController):
97
102
 
98
103
  def get_timetable(self, rows: int):
99
104
  uncoalesced_timetable_rows = [self.get_row(r + 1) for r in range(rows)]
100
- timetable_rows = {}
105
+ timetable_rows: Dict[str, TimeTableEntry] = {}
101
106
  for row in uncoalesced_timetable_rows:
102
107
  time_key = str(row.start_time)
103
108
  if time_key not in timetable_rows.keys():
@@ -141,20 +146,24 @@ class MethodController(TableController):
141
146
  return method_name
142
147
 
143
148
  def get_post_time(self) -> Union[int, float]:
144
- return self.controller.get_num_val(
145
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
146
- register=self.table_locator.register,
147
- register_flag=RegisterFlag.POST_TIME,
149
+ if self.controller:
150
+ return self.controller.get_num_val(
151
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
152
+ register=self.table_locator.register,
153
+ register_flag=RegisterFlag.POST_TIME,
154
+ )
148
155
  )
149
- )
156
+ raise ValueError("Communication controller is not online!")
150
157
 
151
158
  def get_stop_time(self) -> Union[int, float]:
152
- return self.controller.get_num_val(
153
- cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
154
- register=self.table_locator.register,
155
- register_flag=RegisterFlag.MAX_TIME,
159
+ if self.controller:
160
+ return self.controller.get_num_val(
161
+ cmd=TableOperation.GET_OBJ_HDR_VAL.value.format(
162
+ register=self.table_locator.register,
163
+ register_flag=RegisterFlag.MAX_TIME,
164
+ )
156
165
  )
157
- )
166
+ raise ValueError("Communication controller is not online!")
158
167
 
159
168
  def get_total_runtime(self) -> Union[int, float]:
160
169
  """Returns total method runtime in minutes."""
@@ -198,60 +207,6 @@ class MethodController(TableController):
198
207
  assert parsed_response == f"{method_name}.M", "Switching Methods failed."
199
208
  self.table_state = None
200
209
 
201
- def load_from_disk(self, method_name: str) -> MethodDetails:
202
- """
203
- Retrieve method details of an existing method. Don't need to append ".M" to the end. This assumes the
204
- organic modifier is in Channel B and that Channel A contains the aq layer. Additionally, assumes
205
- only two solvents are being used.
206
-
207
- :param method_name: name of method to load details of
208
- :raises FileNotFoundError: Method does not exist
209
- :return: method details
210
- """
211
- warnings.warn("This method is not actively maintained.")
212
- method_folder = f"{method_name}.M"
213
- method_path = os.path.join(
214
- self.src, method_folder, "AgilentPumpDriver1.RapidControl.MethodXML.xml"
215
- )
216
- dad_path = os.path.join(
217
- self.src,
218
- method_folder,
219
- "Agilent1200erDadDriver1.RapidControl.MethodXML.xml",
220
- )
221
-
222
- if os.path.exists(os.path.join(self.src, f"{method_name}.M")):
223
- parser = XmlParser()
224
- method = parser.parse(method_path, PumpMethod)
225
- dad = parser.parse(dad_path, DadMethod)
226
-
227
- organic_modifier: Optional[SolventElement] = None
228
-
229
- if len(method.solvent_composition.solvent_element) == 2:
230
- for solvent in method.solvent_composition.solvent_element:
231
- if solvent.channel == "Channel_B":
232
- organic_modifier = solvent
233
-
234
- self.table_state = MethodDetails(
235
- name=method_name,
236
- params=HPLCMethodParams(
237
- organic_modifier=organic_modifier.percentage, flow=method.flow
238
- ),
239
- stop_time=method.stop_time.stop_time_value,
240
- post_time=method.post_time.post_time_value,
241
- timetable=[
242
- TimeTableEntry(
243
- start_time=tte.time,
244
- organic_modifer=tte.percent_b,
245
- flow=method.flow,
246
- )
247
- for tte in method.timetable.timetable_entry
248
- ],
249
- dad_wavelengthes=dad.signals.signal,
250
- )
251
- return self.table_state
252
- else:
253
- raise FileNotFoundError
254
-
255
210
  def edit(self, updated_method: MethodDetails, save: bool):
256
211
  """Updated the currently loaded method in ChemStation with provided values.
257
212
 
@@ -317,13 +272,13 @@ class MethodController(TableController):
317
272
  self,
318
273
  new_flow: Union[int, float],
319
274
  new_initial_om: Union[int, float],
320
- new_stop_time: Union[int, float],
321
- new_post_time: Union[int, float],
275
+ new_stop_time: Union[int, float] | None,
276
+ new_post_time: Union[int, float] | None,
322
277
  ):
323
278
  self.delete_table()
324
279
  self.edit_initial_om(new_initial_om)
325
280
  self.edit_flow(new_flow)
326
- if self.table_state.stop_time:
281
+ if new_stop_time:
327
282
  self.edit_stop_time(new_stop_time)
328
283
  else:
329
284
  self._update_param(
@@ -333,7 +288,7 @@ class MethodController(TableController):
333
288
  ptype=PType.STR,
334
289
  )
335
290
  )
336
- if self.table_state.post_time:
291
+ if new_post_time:
337
292
  self.edit_post_time(new_post_time)
338
293
  else:
339
294
  self._update_param(
@@ -502,21 +457,23 @@ class MethodController(TableController):
502
457
  warning = f"Data folder {self.data_files[-1]} may not exist, returning and will check again after run is done."
503
458
  warnings.warn(warning)
504
459
 
505
- def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[T, str]:
506
- if os.path.exists(most_recent_folder):
507
- return Ok(most_recent_folder)
508
- return Err("Folder not found!")
460
+ def fuzzy_match_most_recent_folder(self, most_recent_folder: T) -> Result[str, str]:
461
+ if isinstance(most_recent_folder, str) or isinstance(most_recent_folder, bytes):
462
+ if os.path.exists(most_recent_folder):
463
+ return Ok(most_recent_folder)
464
+ return Err("Folder not found!")
465
+ raise ValueError("Folder is not a str or byte type.")
509
466
 
510
467
  def get_data(
511
468
  self, custom_path: Optional[str] = None
512
469
  ) -> AgilentChannelChromatogramData:
513
470
  custom_path = custom_path if custom_path else self.data_files[-1]
514
471
  self.get_spectrum_at_channels(custom_path)
515
- return AgilentChannelChromatogramData(**self.spectra)
472
+ return AgilentChannelChromatogramData.from_dict(self.spectra)
516
473
 
517
474
  def get_data_uv(
518
475
  self, custom_path: Optional[str] = None
519
- ) -> Dict[str, AgilentHPLCChromatogram]:
476
+ ) -> dict[int, AgilentHPLCChromatogram]:
520
477
  custom_path = custom_path if custom_path else self.data_files[-1]
521
478
  self.get_uv_spectrum(custom_path)
522
479
  return self.uv
@@ -1,18 +1,20 @@
1
1
  import os
2
2
  import time
3
3
  import warnings
4
- from typing import Dict, List, Optional
4
+ from typing import Dict, List, Optional, Any
5
5
 
6
6
  from result import Err, Ok, Result
7
7
  from typing_extensions import override
8
8
 
9
- from ....analysis.process_report import AgilentReport, ReportType
10
- from ....control.controllers.comm import CommunicationController
11
9
  from pychemstation.analysis.chromatogram import (
12
10
  SEQUENCE_TIME_FORMAT,
13
11
  AgilentChannelChromatogramData,
14
12
  AgilentHPLCChromatogram,
15
13
  )
14
+ from . import MethodController
15
+
16
+ from ....analysis.process_report import AgilentReport, ReportType
17
+ from ....control.controllers.comm import CommunicationController
16
18
  from ....utils.macro import Command
17
19
  from ....utils.sequence_types import (
18
20
  InjectionSource,
@@ -21,9 +23,8 @@ from ....utils.sequence_types import (
21
23
  SequenceEntry,
22
24
  SequenceTable,
23
25
  )
24
- from ....utils.table_types import RegisterFlag, Table
25
- from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn
26
- from .. import MethodController
26
+ from ....utils.table_types import RegisterFlag, T, Table
27
+ from ....utils.tray_types import FiftyFourVialPlate, TenVialColumn, Tray
27
28
  from .table import TableController
28
29
 
29
30
 
@@ -34,7 +35,7 @@ class SequenceController(TableController):
34
35
 
35
36
  def __init__(
36
37
  self,
37
- controller: CommunicationController,
38
+ controller: Optional[CommunicationController],
38
39
  method_controller: MethodController,
39
40
  src: str,
40
41
  data_dirs: List[str],
@@ -57,7 +58,7 @@ class SequenceController(TableController):
57
58
  seq_name = self.receive()
58
59
 
59
60
  if rows.is_ok() and seq_name.is_ok():
60
- self.table_state = SequenceTable(
61
+ self.table_state: SequenceTable = SequenceTable(
61
62
  name=seq_name.ok_value.string_response.partition(".S")[0],
62
63
  rows=[
63
64
  self.get_row(r + 1) for r in range(int(rows.ok_value.num_response))
@@ -66,19 +67,37 @@ class SequenceController(TableController):
66
67
  return self.table_state
67
68
  raise RuntimeError(rows.err_value)
68
69
 
70
+ def try_int(self, val: Any) -> Optional[int]:
71
+ try:
72
+ return int(val)
73
+ except ValueError:
74
+ return None
75
+
76
+ def try_float(self, val: Any) -> Optional[float]:
77
+ try:
78
+ return float(val)
79
+ except ValueError:
80
+ return None
81
+
82
+ def try_vial_location(self, val: Any) -> Tray:
83
+ try:
84
+ return (
85
+ TenVialColumn(val)
86
+ if val <= 10
87
+ else FiftyFourVialPlate.from_int(num=val)
88
+ )
89
+ except ValueError:
90
+ raise ValueError("Expected vial location, is empty.")
91
+
69
92
  def get_row(self, row: int) -> SequenceEntry:
70
93
  sample_name = self.get_text(row, RegisterFlag.NAME)
71
- vial_location = int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
94
+ vial_location = self.try_int(self.get_num(row, RegisterFlag.VIAL_LOCATION))
72
95
  method = self.get_text(row, RegisterFlag.METHOD)
73
- num_inj = int(self.get_num(row, RegisterFlag.NUM_INJ))
74
- inj_vol = float(self.get_text(row, RegisterFlag.INJ_VOL))
96
+ num_inj = self.try_int(self.get_num(row, RegisterFlag.NUM_INJ))
97
+ inj_vol = self.try_float(self.get_text(row, RegisterFlag.INJ_VOL))
75
98
  inj_source = InjectionSource(self.get_text(row, RegisterFlag.INJ_SOR))
76
99
  sample_type = SampleType(self.get_num(row, RegisterFlag.SAMPLE_TYPE))
77
- vial_enum = (
78
- TenVialColumn(vial_location)
79
- if vial_location <= 10
80
- else FiftyFourVialPlate.from_int(num=vial_location)
81
- )
100
+ vial_enum = self.try_vial_location(vial_location)
82
101
  return SequenceEntry(
83
102
  sample_name=sample_name,
84
103
  vial_location=vial_enum,
@@ -113,7 +132,7 @@ class SequenceController(TableController):
113
132
  parsed_response = self.receive().ok_value.string_response
114
133
 
115
134
  assert parsed_response == f"{seq_name}.S", "Switching sequence failed."
116
- self.table_state = None
135
+ self.table_state = self.load()
117
136
 
118
137
  def edit(self, sequence_table: SequenceTable):
119
138
  """
@@ -157,12 +176,13 @@ class SequenceController(TableController):
157
176
  num_rows = self.get_num_rows()
158
177
  if row.vial_location:
159
178
  loc = row.vial_location
179
+ loc_num = -1
160
180
  if isinstance(loc, TenVialColumn):
161
- loc = row.vial_location.value
181
+ loc_num = loc.value
162
182
  elif isinstance(loc, FiftyFourVialPlate):
163
- loc = row.vial_location.value()
183
+ loc_num = loc.value()
164
184
  self._edit_row_num(
165
- row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc
185
+ row=row_num, col_name=RegisterFlag.VIAL_LOCATION, val=loc_num
166
186
  )
167
187
  if row.method:
168
188
  method_dir = self.method_controller.src
@@ -177,7 +197,7 @@ class SequenceController(TableController):
177
197
  )
178
198
  if row.inj_vol:
179
199
  self._edit_row_text(
180
- row=row_num, col_name=RegisterFlag.INJ_VOL, val=row.inj_vol
200
+ row=row_num, col_name=RegisterFlag.INJ_VOL, val=str(row.inj_vol)
181
201
  )
182
202
  if row.inj_source:
183
203
  self._edit_row_text(
@@ -210,26 +230,32 @@ class SequenceController(TableController):
210
230
  under the <data_dir>/<sequence table name> folder.
211
231
  Device must be ready.
212
232
  """
213
- self.controller.send(Command.SAVE_METHOD_CMD)
214
- self.controller.send(Command.SAVE_SEQUENCE_CMD)
233
+ if self.controller:
234
+ self.controller.send(Command.SAVE_METHOD_CMD)
235
+ self.controller.send(Command.SAVE_SEQUENCE_CMD)
236
+ else:
237
+ raise ValueError("Controller is offline!")
215
238
 
216
239
  if not self.table_state:
217
240
  self.table_state = self.load()
218
241
 
219
- total_runtime = 0
242
+ total_runtime = 0.0
220
243
  for entry in self.table_state.rows:
221
244
  curr_method_runtime = self.method_controller.get_total_runtime()
222
245
  loaded_method = self.method_controller.get_method_name().removesuffix(".M")
223
- method_path = entry.method.split(sep="\\")
224
- method_name = method_path[-1]
225
- if loaded_method != method_name:
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()
246
+ if entry.method:
247
+ method_path = entry.method.split(sep="\\")
248
+ method_name = method_path[-1]
249
+ if loaded_method != method_name:
250
+ method_dir = (
251
+ "\\".join(method_path[:-1]) + "\\"
252
+ if len(method_path) > 1
253
+ else None
254
+ )
255
+ self.method_controller.switch(
256
+ method_name=method_name, alt_method_dir=method_dir
257
+ )
258
+ curr_method_runtime = self.method_controller.get_total_runtime()
233
259
  total_runtime += curr_method_runtime
234
260
 
235
261
  timestamp = time.strftime(SEQUENCE_TIME_FORMAT)
@@ -254,91 +280,100 @@ class SequenceController(TableController):
254
280
 
255
281
  @override
256
282
  def fuzzy_match_most_recent_folder(
257
- self, most_recent_folder: SequenceDataFiles
283
+ self, most_recent_folder: T
258
284
  ) -> Result[SequenceDataFiles, str]:
259
- if os.path.isdir(most_recent_folder.dir):
260
- subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
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
- ]
269
- return Ok(most_recent_folder)
270
-
271
- try:
272
- potential_folders = []
273
- for d in self.data_dirs:
274
- subdirs = [x[0] for x in os.walk(d)]
285
+ if isinstance(most_recent_folder, SequenceDataFiles):
286
+ if os.path.isdir(most_recent_folder.dir):
287
+ subdirs = [x[0] for x in os.walk(most_recent_folder.dir)]
275
288
  potential_folders = sorted(
276
289
  list(filter(lambda d: most_recent_folder.dir in d, subdirs))
277
290
  )
278
- if len(potential_folders) > 0:
279
- break
280
- assert len(potential_folders) > 0
281
- parent_dirs = []
282
- for folder in potential_folders:
283
- path = os.path.normpath(folder)
284
- split_folder = path.split(os.sep)
285
- if most_recent_folder.dir in split_folder[-1]:
286
- parent_dirs.append(folder)
287
- parent_dir = sorted(parent_dirs, reverse=True)[0]
288
-
289
- potential_folders = []
290
- for d in self.data_dirs:
291
- subdirs = [x[0] for x in os.walk(d)]
292
- potential_folders = sorted(
293
- list(filter(lambda d: parent_dir in d, subdirs))
294
- )
295
- if len(potential_folders) > 0:
296
- break
297
- assert len(potential_folders) > 0
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
- ]
303
- return Ok(most_recent_folder)
304
- except Exception as e:
305
- error = f"Failed to get sequence folder: {e}"
306
- return Err(error)
291
+ most_recent_folder.child_dirs = [
292
+ f
293
+ for f in potential_folders
294
+ if most_recent_folder.dir in f and ".M" not in f and ".D" in f
295
+ ]
296
+ return Ok(most_recent_folder)
307
297
 
308
- def get_data_uv(
309
- self, custom_path: Optional[str] = None
310
- ) -> List[Dict[int, AgilentHPLCChromatogram]]:
311
- custom_path = (
298
+ try:
299
+ potential_folders = []
300
+ for d in self.data_dirs:
301
+ subdirs = [x[0] for x in os.walk(d)]
302
+ potential_folders = sorted(
303
+ list(filter(lambda d: most_recent_folder.dir in d, subdirs))
304
+ )
305
+ if len(potential_folders) > 0:
306
+ break
307
+ assert len(potential_folders) > 0
308
+ parent_dirs = []
309
+ for folder in potential_folders:
310
+ path = os.path.normpath(folder)
311
+ split_folder = path.split(os.sep)
312
+ if most_recent_folder.dir in split_folder[-1]:
313
+ parent_dirs.append(folder)
314
+ parent_dir = sorted(parent_dirs, reverse=True)[0]
315
+
316
+ potential_folders = []
317
+ for d in self.data_dirs:
318
+ subdirs = [x[0] for x in os.walk(d)]
319
+ potential_folders = sorted(
320
+ list(filter(lambda d: parent_dir in d, subdirs))
321
+ )
322
+ if len(potential_folders) > 0:
323
+ break
324
+ assert len(potential_folders) > 0
325
+ most_recent_folder.child_dirs = [
326
+ f
327
+ for f in potential_folders
328
+ if parent_dir in f and ".M" not in f and ".D" in f
329
+ ]
330
+ return Ok(most_recent_folder)
331
+ except Exception as e:
332
+ error = f"Failed to get sequence folder: {e}"
333
+ return Err(error)
334
+ return Err("Expected SequenceDataFile type.")
335
+
336
+ def get_data_mult_uv(self, custom_path: Optional[str] = None):
337
+ seq_data_dir = (
312
338
  SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
313
339
  if custom_path
314
340
  else self.data_files[-1]
315
341
  )
316
- if len(custom_path.child_dirs) == 0:
342
+ if len(seq_data_dir.child_dirs) == 0:
317
343
  self.data_files[-1] = self.fuzzy_match_most_recent_folder(
318
- custom_path
344
+ seq_data_dir
319
345
  ).ok_value
320
346
  all_w_spectra: List[Dict[int, AgilentHPLCChromatogram]] = []
321
347
  for row in self.data_files[-1].child_dirs:
322
- self.get_uv_spectrum(row)
323
- all_w_spectra.append(self.uv)
348
+ all_w_spectra.append(self.get_data_uv(custom_path=row))
324
349
  return all_w_spectra
325
350
 
351
+ def get_data_uv(
352
+ self, custom_path: Optional[str] = None
353
+ ) -> Dict[int, AgilentHPLCChromatogram]:
354
+ if isinstance(custom_path, str):
355
+ self.get_uv_spectrum(custom_path)
356
+ return self.uv
357
+ raise ValueError(
358
+ "Path should exist when calling from sequence. Provide a child path (contains the method)."
359
+ )
360
+
326
361
  def get_data(
327
362
  self, custom_path: Optional[str] = None
328
363
  ) -> List[AgilentChannelChromatogramData]:
329
- custom_path = (
364
+ seq_file_dir = (
330
365
  SequenceDataFiles(dir=custom_path, child_dirs=[], sequence_name="")
331
366
  if custom_path
332
367
  else self.data_files[-1]
333
368
  )
334
- if len(custom_path.child_dirs) == 0:
369
+ if len(seq_file_dir.child_dirs) == 0:
335
370
  self.data_files[-1] = self.fuzzy_match_most_recent_folder(
336
- custom_path
371
+ seq_file_dir
337
372
  ).ok_value
338
373
  spectra: List[AgilentChannelChromatogramData] = []
339
374
  for row in self.data_files[-1].child_dirs:
340
375
  self.get_spectrum_at_channels(row)
341
- spectra.append(AgilentChannelChromatogramData(**self.spectra))
376
+ spectra.append(AgilentChannelChromatogramData.from_dict(self.spectra))
342
377
  return spectra
343
378
 
344
379
  def get_report(
@@ -363,8 +398,12 @@ class SequenceController(TableController):
363
398
  spectra[i].__dict__.values()
364
399
  )
365
400
  for j, signal in enumerate(metd_report.signals):
366
- possible_data = child_spectra[j]
367
- if len(possible_data.x) > 0:
368
- signal.data = possible_data
401
+ assert len(metd_report.signals) <= len(child_spectra)
402
+ try:
403
+ possible_data = child_spectra[j]
404
+ if len(possible_data.x) > 0:
405
+ signal.data = possible_data
406
+ except IndexError:
407
+ raise ValueError(j)
369
408
  reports.append(metd_report)
370
409
  return reports