wasatch 2.3.2__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 (53) hide show
  1. wasatch/AbstractUSBDevice.py +29 -0
  2. wasatch/AndorDevice.py +910 -0
  3. wasatch/AreaScanImage.py +19 -0
  4. wasatch/AutoRaman.py +457 -0
  5. wasatch/AutoRamanRequest.py +96 -0
  6. wasatch/BLEDevice.py +1032 -0
  7. wasatch/BalanceAcquisition.py +136 -0
  8. wasatch/CSVLoader.py +188 -0
  9. wasatch/CommandSettings.py +95 -0
  10. wasatch/ControlObject.py +17 -0
  11. wasatch/DetectorROI.py +36 -0
  12. wasatch/DetectorRegions.py +158 -0
  13. wasatch/DeviceFinderBLE.py +107 -0
  14. wasatch/DeviceFinderTCP.py +20 -0
  15. wasatch/DeviceFinderUSB.py +236 -0
  16. wasatch/DeviceID.py +344 -0
  17. wasatch/EEPROM.py +1450 -0
  18. wasatch/EEPROM_multical.sav +48 -0
  19. wasatch/FPGAOptions.py +125 -0
  20. wasatch/FeatureIdentificationDevice.py +3961 -0
  21. wasatch/FirmwareRequirements.py +76 -0
  22. wasatch/HardwareInfo.py +36 -0
  23. wasatch/IDSCamera.py +916 -0
  24. wasatch/IDSDevice.py +329 -0
  25. wasatch/IMX385.py +179 -0
  26. wasatch/InterfaceDevice.py +40 -0
  27. wasatch/MockUSBDevice.py +353 -0
  28. wasatch/OceanDevice.py +215 -0
  29. wasatch/PollStatus.py +15 -0
  30. wasatch/ProcessedReading.py +352 -0
  31. wasatch/ROI.py +33 -0
  32. wasatch/Reading.py +80 -0
  33. wasatch/RealUSBDevice.py +64 -0
  34. wasatch/SPIDevice.py +628 -0
  35. wasatch/SpectrometerRequest.py +15 -0
  36. wasatch/SpectrometerResponse.py +38 -0
  37. wasatch/SpectrometerSettings.py +446 -0
  38. wasatch/SpectrometerState.py +255 -0
  39. wasatch/StatusMessage.py +14 -0
  40. wasatch/TCPDevice.py +409 -0
  41. wasatch/TakeOneRequest.py +43 -0
  42. wasatch/WasatchBus.py +87 -0
  43. wasatch/WasatchDevice.py +1060 -0
  44. wasatch/WasatchDeviceWrapper.py +452 -0
  45. wasatch/WrapperWorker.py +304 -0
  46. wasatch/__init__.py +4 -0
  47. wasatch/applog.py +177 -0
  48. wasatch/simulation_protocol.py +227 -0
  49. wasatch/utils.py +731 -0
  50. wasatch-2.3.2.dist-info/LICENSE +21 -0
  51. wasatch-2.3.2.dist-info/METADATA +347 -0
  52. wasatch-2.3.2.dist-info/RECORD +53 -0
  53. wasatch-2.3.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,19 @@
1
+ class AreaScanImage:
2
+
3
+ FRAME_COUNT = 0
4
+
5
+ def __init__(self, data=None, width=None, height=None, format_name=None, width_orig=None, height_orig=None, pathname_png=None, line_index=None):
6
+ self.data = data
7
+ self.width = width
8
+ self.height = height
9
+ self.width_orig = width_orig
10
+ self.height_orig = height_orig
11
+ self.format_name = format_name
12
+ self.pathname_png = pathname_png
13
+ self.line_index = line_index
14
+
15
+ self.frame_count = AreaScanImage.FRAME_COUNT
16
+ AreaScanImage.FRAME_COUNT += 1
17
+
18
+ def __repr__(self):
19
+ return f"AreaScanImage<frame {self.frame_count}, line_index {line_index}, width {self.width} (orig {self.width_orig}), height {self.height} (orig {self.height_orig}), format_name {self.format_name}, pathname_png {self.pathname_png}>"
wasatch/AutoRaman.py ADDED
@@ -0,0 +1,457 @@
1
+ import os
2
+ import time
3
+ import math
4
+ import numpy as np
5
+ import logging
6
+
7
+ from datetime import datetime
8
+
9
+ from .SpectrometerResponse import SpectrometerResponse
10
+ from .SpectrometerRequest import SpectrometerRequest
11
+ from .Reading import Reading
12
+
13
+ from . import utils
14
+
15
+ log = logging.getLogger(__name__)
16
+
17
+ class AutoRaman:
18
+ """
19
+ This class encapsulates Dieter's Auto-Raman algorithm, which optimizes
20
+ integration time (and gain on XS series spectrometers) to achieve a
21
+ target window of counts, then uses the configured measurement time
22
+ to maximize scan averaging at with those acquisition parameters.
23
+
24
+ As calling software will not necessarily expect the configured "default"
25
+ integration time and gain to change, the class restores those to previous
26
+ levels after a measurement.
27
+
28
+ @par Design Considerations
29
+
30
+ Mark made the following changes from Dieter's original algo:
31
+
32
+ - don't take throwaways during averaged signal or dark collections,
33
+ as acquisition parameters aren't changing and sensor should be
34
+ stable
35
+ - don't include laser warning delay when computing num_avg, since
36
+ laser is already enabled and firing
37
+
38
+ Mark also made the following decisions regarding ENLIGHTEN integration:
39
+
40
+ - return the optimized integration time and gain back to ENLIGHTEN
41
+ in the Reading object so they will be the new GUI settings if the
42
+ user simply hits "Play" to resume free-running spectra.
43
+ - ENLIGHTEN will override the init_int_time and init_gain defaults
44
+ in AutoRamanRequest so that if the user hits "Auto-Raman Measurement"
45
+ a second time, the previous settings will be used as the new starting
46
+ point, and (ideally) the initial spectrum will determine that no further
47
+ optimization is required.
48
+
49
+ Points to consider:
50
+
51
+ - consider rolling the "last optimization" measurement directly into the
52
+ averaged sample spectra (since it had its own throwaway and presumably
53
+ represents a "stable" reading). This could potentially allow for one more
54
+ averaged dark, depending on max_ms.
55
+ - ENLIGHTEN's laser button should still be able to turn the laser OFF at
56
+ any point.
57
+ """
58
+
59
+ INTER_SPECTRA_DELAY_MS = 50
60
+
61
+ def __init__(self, wasatch_device):
62
+ self.wasatch_device = wasatch_device
63
+
64
+ self.progress_count = 0
65
+ self.progress_total = 0
66
+ self.optimizing = False
67
+
68
+ def measure(self, auto_raman_request):
69
+ if auto_raman_request.onboard:
70
+ return self.measure_firmware(auto_raman_request)
71
+ else:
72
+ return self.measure_software(auto_raman_request)
73
+
74
+ ############################################################################
75
+ # #
76
+ # Firmware Implementation #
77
+ # #
78
+ ############################################################################
79
+
80
+ def measure_firmware(self, auto_raman_request):
81
+ hardware = self.wasatch_device.hardware
82
+
83
+ # marshall the parameters into a binary payload
84
+ params = auto_raman_request.serialize()
85
+ log.debug(f"measure_firmware: params {utils.to_hex(params)}")
86
+
87
+ # generate a request for FID to execute (note this could be a BLEDevice in the future)
88
+ req = SpectrometerRequest("get_line", kwargs={"auto_raman_params": params})
89
+ log.debug(f"measure_firmware: req {req}")
90
+
91
+ # perform the measurement -- all the work occurs here
92
+ hardware.queue_message("progress_bar", -1)
93
+ result = hardware.handle_requests([req])[0]
94
+ hardware.queue_message("progress_bar", 100)
95
+
96
+ # process the result
97
+ if result is None or isinstance(result, bool) or result.error_msg != '':
98
+ raise(Exception(f"get_spectrum returned {result}"))
99
+ spectrum = result.data.spectrum
100
+
101
+ reading = Reading(hardware.device_id)
102
+ reading.spectrum = np.array(spectrum)
103
+ reading.dark = None
104
+ reading.averaged = True
105
+ reading.sum_count = 1 # todo: get_scans_to_average()
106
+ reading.new_integration_time_ms = None # todo: get_integration_time_ms()
107
+ reading.new_gain_db = None # todo: get_detector_gain()
108
+ reading.laser_enabled = True
109
+ reading.laser_power_perc = hardware.settings.state.laser_power_perc
110
+ reading.laser_power_mW = hardware.settings.state.laser_power_mW
111
+
112
+ return SpectrometerResponse(data=reading)
113
+
114
+ ############################################################################
115
+ # #
116
+ # Software Implementation #
117
+ # #
118
+ ############################################################################
119
+
120
+ def from_db_to_linear(self, x):
121
+ return 10 ** (x / 20.0)
122
+
123
+ def from_linear_to_db(self, x):
124
+ return 20 * math.log(x, 10)
125
+
126
+ def inter_spectrum_delay(self):
127
+ time.sleep(self.INTER_SPECTRA_DELAY_MS / 1000)
128
+
129
+ def measure_software(self, auto_raman_request):
130
+ """
131
+ @returns a Reading wrapped in a SpectrometerResponse
132
+ """
133
+ if auto_raman_request is None:
134
+ log.error("measure requires AutoRamanRequest")
135
+ return None
136
+
137
+ self.settings = self.wasatch_device.settings
138
+ self.hardware = self.wasatch_device.hardware
139
+
140
+ self.optimizing = True
141
+
142
+ log.debug(f"measure: auto_raman_request {auto_raman_request}")
143
+ self.bump_progress_bar()
144
+
145
+ # cache initial state
146
+ self.start_time = datetime.now()
147
+ initial_laser_warning_delay_sec = self.settings.state.laser_warning_delay_sec
148
+
149
+ # apply requested laser warning delay
150
+ if self.settings.is_xs():
151
+ self.hardware.set_laser_warning_delay_sec(auto_raman_request.laser_warning_delay_sec)
152
+
153
+ # generate auto-Raman measurement
154
+ reading = self.get_auto_spectrum(auto_raman_request)
155
+ if reading.spectrum is None:
156
+ log.debug("looks like Auto-Raman measurement was cancelled")
157
+ self.set_laser_enable(False)
158
+
159
+ # restore previous laser warning delay
160
+ if self.settings.is_xs():
161
+ self.hardware.set_laser_warning_delay_sec(initial_laser_warning_delay_sec)
162
+
163
+ return SpectrometerResponse(data=reading)
164
+
165
+ def bump_progress_bar(self):
166
+ if self.optimizing:
167
+ self.hardware.queue_message("progress_bar", -1)
168
+ else:
169
+ self.progress_count += 1
170
+ self.hardware.queue_message("progress_bar", 100 * (self.progress_count / self.progress_total))
171
+
172
+ def get_avg_spectrum(self, int_time, gain_db, num_avg, throwaway=True, first=None, label="unknown"):
173
+ """ Takes a single throwaway, then averages num_avg spectra """
174
+
175
+ if self.hardware.check_alert("auto_raman_cancel"):
176
+ return
177
+
178
+ self.set_integration_time_ms(int_time)
179
+ self.set_gain_db(gain_db)
180
+ self.inter_spectrum_delay()
181
+
182
+ # perform one throwaway
183
+ if throwaway:
184
+ throwaway = self.get_spectrum()
185
+ self.save(throwaway, f"{label} throwaway")
186
+
187
+ if self.hardware.check_alert("auto_raman_cancel"):
188
+ return
189
+
190
+ if first is None:
191
+ sum_spectrum = np.zeros(self.settings.pixels())
192
+ start = 0
193
+ else:
194
+ # we were given the "first" spectrum for use in the average
195
+ sum_spectrum = first
196
+ start = 1
197
+
198
+ for i in range(start, num_avg):
199
+ self.bump_progress_bar()
200
+
201
+ spectrum = np.array(self.get_spectrum())
202
+ self.save(spectrum, f"{label} {i+1}/{num_avg}")
203
+
204
+ if self.hardware.check_alert("auto_raman_cancel"):
205
+ return
206
+
207
+ sum_spectrum += spectrum
208
+ self.inter_spectrum_delay()
209
+
210
+ return sum_spectrum / num_avg
211
+
212
+ def save(self, spectrum, label=None):
213
+ """ Save each spectrum in row-ordered CSV if debug environment variable enabled """
214
+ if "WASATCH_SAVE_AUTO_RAMAN" in os.environ:
215
+ with open("auto-raman-debug.csv", "a") as outfile:
216
+ now = datetime.now().strftime('%F %T.%f')[:-3]
217
+ values = ", ".join([f"{v:.2f}" for v in spectrum])
218
+ outfile.write(f"{now}, {label}, {values}\n")
219
+
220
+ def get_auto_spectrum(self, request):
221
+ """
222
+ @returns a Reading with specturm, dark and sum_count populated
223
+ """
224
+ log.debug(f"get_auto_spectrum: start (max_ms {request.max_ms})")
225
+
226
+ reading = Reading(self.hardware.device_id)
227
+
228
+ int_time = request.start_integ_ms
229
+ gain_db = request.start_gain_db
230
+
231
+ gain_linear = self.from_db_to_linear(gain_db)
232
+ min_gain_linear = self.from_db_to_linear(request.min_gain_db)
233
+ max_gain_linear = self.from_db_to_linear(request.max_gain_db)
234
+
235
+ num_avg = 1
236
+
237
+ # enable the laser and wait for it to fire
238
+ self.set_laser_enable(True)
239
+ self.hardware.queue_message("laser_firing_indicators", True)
240
+ warning_delay_sec = self.get_laser_warning_delay_sec()
241
+ if warning_delay_sec > 0:
242
+ self.hardware.queue_message("marquee_info", f"waiting {warning_delay_sec}sec for laser to fire")
243
+ time.sleep(warning_delay_sec)
244
+
245
+ laser_warmup_sec = self.settings.eeprom.laser_warmup_sec
246
+ if laser_warmup_sec > 0:
247
+ self.hardware.queue_message("marquee_info", f"waiting {laser_warmup_sec}sec for laser to stabilize")
248
+ time.sleep(laser_warmup_sec)
249
+
250
+ # get one Raman spectrum to start (no dark)
251
+ log.debug(f"taking initial spectrum (integ {int_time}, gain {gain_db})")
252
+ self.hardware.queue_message("marquee_info", "optimizing acquisition parameters")
253
+ spectrum = self.get_avg_spectrum(int_time, gain_db, num_avg=1, label="initial", throwaway=True)
254
+ if spectrum is None:
255
+ return reading
256
+
257
+ max_signal = spectrum.max()
258
+
259
+ # integration/gain scaling
260
+ quit_loop = False
261
+ loop_count = 0
262
+ while not quit_loop:
263
+
264
+ loop_count += 1
265
+ scale_factor = request.target_counts / max_signal
266
+ log.debug(f"loop {loop_count}: counts {max_signal}, scale {scale_factor:.2f}")
267
+
268
+ # We distribute scaling among integration time and linear gain
269
+ #
270
+ # mode: int time first
271
+ #
272
+ # if too small:
273
+ # 1. increase int time from start to max
274
+ # 2. increase gain from start to max
275
+ #
276
+ # if too large:
277
+ # 1. decrease gain to min
278
+ # 2. decrease int time
279
+
280
+ # in mode 'int time first':
281
+ # - we will first increase integration time - this will give best quality spectrum
282
+ # - if the integration time is at the maximum, we will increase gain to reach expected
283
+ # signal levels
284
+
285
+ prev_integration_time = int_time
286
+ prev_gain_db = gain_db
287
+
288
+ if scale_factor > 1.0:
289
+
290
+ # do not grow too fast
291
+ if scale_factor > request.max_factor:
292
+ scale_factor = request.max_factor
293
+
294
+ # increase int time first
295
+ log.debug(f"get_auto_spectrum: scaling int_time {int_time} UP by scale_factor {scale_factor:.2f}")
296
+ int_time *= scale_factor
297
+
298
+ # check int time does not exceed maximum
299
+ if int_time > request.max_integ_ms:
300
+ # however much int time exceeds the max, transfer scaling to gain
301
+ gain_factor = int_time / request.max_integ_ms
302
+ log.debug(f"get_auto_spectrum: scaling gain_linear {gain_linear} UP by gain_factor {gain_factor:.2f}")
303
+ gain_linear *= gain_factor
304
+ int_time = request.max_integ_ms
305
+
306
+ elif scale_factor < 1.0:
307
+
308
+ # if saturating, accelerate drop. do not use signal factor
309
+ if max_signal >= request.saturation:
310
+ scale_factor = request.drop_factor
311
+
312
+ # decrease gain first (INCREASE int time first implies DECREASE int time last)
313
+ log.debug(f"get_auto_spectrum: scaling gain_linear {gain_linear:.2f} DOWN by scale_factor {scale_factor:.2f}")
314
+ gain_linear *= scale_factor
315
+
316
+ # check we did not drop below min gain
317
+ if gain_linear < min_gain_linear:
318
+ # dump the overshoot into decreasing int time
319
+ int_factor = gain_linear / min_gain_linear
320
+ log.debug(f"get_auto_spectrum: scaling int_time {int_time} DOWN by int_factor {int_factor:.2f}")
321
+ int_time *= int_factor
322
+ gain_linear = min_gain_linear
323
+
324
+ # gain is rounded to 0.1 dB
325
+ gain_db = round(self.from_linear_to_db(gain_linear), 1)
326
+ gain_db = min(request.max_gain_db, max(gain_db, request.min_gain_db))
327
+
328
+ # integration time is integral (ms)
329
+ int_time = round(int_time)
330
+
331
+ if (int_time == prev_integration_time) and (gain_db == prev_gain_db):
332
+ # nothing has changed - rounding problem?
333
+ # Here we do not distinguish between int time forst and gain first, the changes are very small
334
+ if scale_factor > 1.0:
335
+ # was supposed to increase
336
+
337
+ # prefer to increase integration time
338
+ if int_time < request.max_integ_ms:
339
+ int_time += 1
340
+ else:
341
+ # failover to increasing gain
342
+ if self.settings.is_xs() and (gain_db < request.max_gain_db):
343
+ gain_db += 0.1
344
+ else:
345
+ quit_loop = True
346
+ else:
347
+ # was supposed to shrink
348
+
349
+ # prefer to shrink gain
350
+ if self.settings.is_xs() and (gain_db > request.min_gain_db):
351
+ gain_db -= 0.1
352
+ else:
353
+ # fail-over to shrinking integration
354
+ if int_time > request.min_integ_ms:
355
+ int_time -= 1
356
+ else:
357
+ quit_loop = True
358
+
359
+ log.debug(f"integ now {int_time}, gain now {gain_db:.1f} (linear {gain_linear:.4f})")
360
+
361
+ log.debug(f"Taking spectrum #{loop_count}")
362
+ self.hardware.queue_message("marquee_info", "optimizing acquisition parameters")
363
+ spectrum = self.get_avg_spectrum(int_time, gain_db, num_avg=1, label="optimizing", throwaway=True)
364
+ if spectrum is None:
365
+ return reading
366
+
367
+ max_signal = spectrum.max()
368
+
369
+ if max_signal < request.max_counts and max_signal > request.min_counts:
370
+ log.debug(f"===> achieved window (max_signal {max_signal} in range ({request.min_counts}, {request.max_counts}))")
371
+ quit_loop = True
372
+ elif max_signal < request.min_counts and int_time >= request.max_integ_ms and gain_db >= request.max_gain_db:
373
+ log.debug("can't achieve window within acquisition parameter limits")
374
+ quit_loop = True
375
+
376
+ # decide on number of averages - all times in ms
377
+ # include dark + signal
378
+
379
+ self.optimizing = False
380
+ total = math.floor(request.max_ms / int_time) # total number of sample + dark spectra we have time for
381
+ num_avg = math.ceil((total + 1) / 2) # how many darks to collect
382
+ num_avg = max(1, num_avg)
383
+ num_avg = min(num_avg, request.max_avg)
384
+ self.progress_count = 0
385
+ self.progress_total = 2 * num_avg - 1 # darks + remaining samples
386
+ expected_ms = int_time * self.progress_total
387
+ log.debug(f"based on max_ms {request.max_ms} and int_time {int_time} ms, computed num_avg {num_avg} (expected_ms {expected_ms})")
388
+
389
+ # now get the spectrum with the chosen parameters
390
+ # take two averaged spectra here: avg signal first, then turn laser off, then take dark
391
+ # (this saves the laser warm up)
392
+
393
+ # 1. signal - laser is still on
394
+ self.hardware.queue_message("marquee_info", f"averaging {num_avg} Raman spectra at {int_time}ms")
395
+ avg_sample = self.get_avg_spectrum(int_time, gain_db, num_avg, throwaway=False, first=spectrum, label="signal")
396
+ if avg_sample is None:
397
+ return reading
398
+ self.save(avg_sample, "averaged sample")
399
+
400
+ # 2. turn laser off
401
+ self.set_laser_enable(False)
402
+ self.hardware.queue_message("laser_firing_indicators", False)
403
+
404
+ # 3. take dark
405
+ self.hardware.queue_message("marquee_info", f"averaging {num_avg} dark spectra at {int_time}ms")
406
+ avg_dark = self.get_avg_spectrum(int_time, gain_db, num_avg, throwaway=True, label="dark")
407
+ if avg_dark is None:
408
+ return reading
409
+ self.save(avg_dark, "averaged dark")
410
+
411
+ # note that we don't actually perform dark subtraction here -- we return
412
+ # both the averaged Raman sample and the averaged dark, so that the
413
+ # caller can decide when / how to perform dark subtraction
414
+
415
+ reading.spectrum = avg_sample
416
+ reading.dark = avg_dark
417
+ reading.averaged = True
418
+ reading.sum_count = num_avg
419
+ reading.new_integration_time_ms = int_time
420
+ reading.new_gain_db = gain_db
421
+ reading.laser_enabled = True
422
+ reading.laser_power_perc = self.hardware.settings.state.laser_power_perc
423
+ reading.laser_power_mW = self.hardware.settings.state.laser_power_mW
424
+
425
+ log.debug("done")
426
+ return reading
427
+
428
+ ############################################################################
429
+ # wrappers over stupidly complicated WasatchDevice interface
430
+ ############################################################################
431
+
432
+ def get_laser_warning_delay_sec(self):
433
+ response = self.hardware.handle_requests([SpectrometerRequest('get_laser_warning_delay_sec')])[0]
434
+ log.debug(f"get_laser_warning_delay_sec: response {response}")
435
+ if response is None or response.data is None:
436
+ return 5
437
+ return response.data
438
+
439
+ def set_laser_enable(self, flag):
440
+ self.hardware.handle_requests([SpectrometerRequest('set_laser_enable', args=[flag])])
441
+
442
+ def set_integration_time_ms(self, ms):
443
+ if ms != self.settings.state.integration_time_ms:
444
+ self.hardware.handle_requests([SpectrometerRequest('set_integration_time_ms', args=[ms])])
445
+
446
+ def set_gain_db(self, db):
447
+ if self.settings.is_xs():
448
+ if abs(db - self.settings.state.gain_db) > 0.05:
449
+ self.hardware.handle_requests([SpectrometerRequest('set_detector_gain', args=[db])])
450
+
451
+ def get_spectrum(self):
452
+ result = self.hardware.handle_requests([SpectrometerRequest("get_line")])[0]
453
+ if result is None or isinstance(result, bool) or result.error_msg != '':
454
+ raise(Exception(f"get_spectrum returned {result}"))
455
+ spectrum = result.data.spectrum
456
+ self.inter_spectrum_delay()
457
+ return np.array(spectrum)
@@ -0,0 +1,96 @@
1
+ class AutoRamanRequest:
2
+
3
+ MAX_MS = 10000
4
+ START_INTEG_MS = 100
5
+ START_GAIN_DB = 0
6
+ MAX_INTEG_MS = 2000
7
+ MIN_INTEG_MS = 10
8
+ MAX_GAIN_DB = 30
9
+ MIN_GAIN_DB = 0
10
+ TARGET_COUNTS = 45000
11
+ MAX_COUNTS = 50000
12
+ MIN_COUNTS = 40000
13
+ MAX_FACTOR = 5
14
+ DROP_FACTOR = 0.5
15
+ SATURATION = 65000
16
+ MAX_AVG = 100
17
+ LASER_WARNING_DELAY_SEC = 3
18
+
19
+ def __init__(self, max_ms = None,
20
+ start_integ_ms = None,
21
+ start_gain_db = None,
22
+ max_integ_ms = None,
23
+ min_integ_ms = None,
24
+ max_gain_db = None,
25
+ min_gain_db = None,
26
+ target_counts = None,
27
+ max_counts = None,
28
+ min_counts = None,
29
+ max_factor = None,
30
+ drop_factor = None,
31
+ saturation = None,
32
+ max_avg = None,
33
+ laser_warning_delay_sec = None,
34
+ onboard = False):
35
+
36
+ # this seems like it could be shorter, but Python doesn't allow
37
+ # 'max_ms = AutoRamanRequest.MAX_MS` constructor parameters
38
+ self.max_ms = max_ms if max_ms is not None else self.MAX_MS
39
+ self.start_integ_ms = start_integ_ms if start_integ_ms is not None else self.START_INTEG_MS
40
+ self.start_gain_db = start_gain_db if start_gain_db is not None else self.START_GAIN_DB
41
+ self.max_integ_ms = max_integ_ms if max_integ_ms is not None else self.MAX_INTEG_MS
42
+ self.min_integ_ms = min_integ_ms if min_integ_ms is not None else self.MIN_INTEG_MS
43
+ self.max_gain_db = max_gain_db if max_gain_db is not None else self.MAX_GAIN_DB
44
+ self.min_gain_db = min_gain_db if min_gain_db is not None else self.MIN_GAIN_DB
45
+ self.target_counts = target_counts if target_counts is not None else self.TARGET_COUNTS
46
+ self.max_counts = max_counts if max_counts is not None else self.MAX_COUNTS
47
+ self.min_counts = min_counts if min_counts is not None else self.MIN_COUNTS
48
+ self.max_factor = max_factor if max_factor is not None else self.MAX_FACTOR
49
+ self.drop_factor = drop_factor if drop_factor is not None else self.DROP_FACTOR
50
+ self.saturation = saturation if saturation is not None else self.SATURATION
51
+ self.max_avg = max_avg if max_avg is not None else self.MAX_AVG
52
+ self.laser_warning_delay_sec = laser_warning_delay_sec if laser_warning_delay_sec is not None else self.LASER_WARNING_DELAY_SEC
53
+
54
+ self.onboard = onboard
55
+
56
+ def __repr__(self):
57
+ return f"AutoRamanRequest <onboard {self.onboard}, max_ms {self.max_ms}, start_integ_ms {self.start_integ_ms}, start_gain_db {self.start_gain_db}, max_integ_ms {self.max_integ_ms}, min_integ_ms {self.min_integ_ms}, max_gain_db {self.max_gain_db}, min_gain_db {self.min_gain_db}, target_counts {self.target_counts}, max_counts {self.max_counts}, min_counts {self.min_counts}, max_factor {self.max_factor}, drop_factor {self.drop_factor}, saturation {self.saturation}, max_avg {self.max_avg}, laser_warning_delay_sec {self.laser_warning_delay_sec}>"
58
+
59
+ # for use with onboard firmware-based AutoRaman
60
+ def serialize(self):
61
+ buf = []
62
+ buf.extend([self.max_ms & 0xff, (self.max_ms >> 8) & 0xff])
63
+ buf.extend([self.start_integ_ms & 0xff, (self.start_integ_ms>> 8) & 0xff])
64
+ buf.extend([self.start_gain_db & 0xff ])
65
+ buf.extend([self.max_integ_ms & 0xff, (self.max_integ_ms >> 8) & 0xff])
66
+ buf.extend([self.min_integ_ms & 0xff, (self.min_integ_ms >> 8) & 0xff])
67
+ buf.extend([self.max_gain_db & 0xff ])
68
+ buf.extend([self.min_gain_db & 0xff ])
69
+ buf.extend([self.target_counts & 0xff, (self.target_counts >> 8) & 0xff])
70
+ buf.extend([self.max_counts & 0xff, (self.max_counts >> 8) & 0xff])
71
+ buf.extend([self.min_counts & 0xff, (self.min_counts >> 8) & 0xff])
72
+ buf.extend([self.max_factor & 0xff ])
73
+ buf.extend([int(self.drop_factor) & 0xff, int((self.drop_factor - int(self.drop_factor)) * 256)])
74
+ buf.extend([self.saturation & 0xff, (self.saturation >> 8) & 0xff])
75
+ buf.extend([self.max_avg & 0xff ])
76
+ return buf
77
+
78
+ if __name__ == "__main__":
79
+ aar = AutoRamanRequest(
80
+ max_ms = 10000,
81
+ start_integ_ms = 100,
82
+ start_gain_db = 0,
83
+ max_integ_ms = 2000,
84
+ min_integ_ms = 10,
85
+ max_gain_db = 30,
86
+ min_gain_db = 0,
87
+ target_counts = 45000,
88
+ max_counts = 50000,
89
+ min_counts = 40000,
90
+ max_factor = 5,
91
+ drop_factor = 0.8,
92
+ saturation = 65000,
93
+ max_avg = 100)
94
+
95
+ buf = aar.serialize()
96
+ print("0x" + " ".join([f"{b:02x}" for b in buf]))