wasatch 2.2.13__tar.gz → 2.2.15__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 (89) hide show
  1. {wasatch-2.2.13 → wasatch-2.2.15}/CHANGELOG.md +24 -0
  2. {wasatch-2.2.13 → wasatch-2.2.15}/PKG-INFO +1 -1
  3. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/AndorDevice.py +110 -54
  4. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/AutoRamanRequest.py +20 -0
  5. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/DeviceFinderUSB.py +3 -3
  6. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/EEPROM.py +256 -162
  7. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/FeatureIdentificationDevice.py +34 -17
  8. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/ROI.py +12 -4
  9. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/SPIDevice.py +2 -2
  10. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/SpectrometerSettings.py +64 -207
  11. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/SpectrometerState.py +1 -0
  12. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/WasatchBus.py +2 -2
  13. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/__init__.py +1 -1
  14. {wasatch-2.2.13 → wasatch-2.2.15}/.example_pypirc +0 -0
  15. {wasatch-2.2.13 → wasatch-2.2.15}/.flake8 +0 -0
  16. {wasatch-2.2.13 → wasatch-2.2.15}/.gitignore +0 -0
  17. {wasatch-2.2.13 → wasatch-2.2.15}/.pylintrc +0 -0
  18. {wasatch-2.2.13 → wasatch-2.2.15}/Doxyfile +0 -0
  19. {wasatch-2.2.13 → wasatch-2.2.15}/LICENSE +0 -0
  20. {wasatch-2.2.13 → wasatch-2.2.15}/MANIFEST.in +0 -0
  21. {wasatch-2.2.13 → wasatch-2.2.15}/Makefile +0 -0
  22. {wasatch-2.2.13 → wasatch-2.2.15}/README.md +0 -0
  23. {wasatch-2.2.13 → wasatch-2.2.15}/README_PYPI.md +0 -0
  24. {wasatch-2.2.13 → wasatch-2.2.15}/README_RPI.md +0 -0
  25. {wasatch-2.2.13 → wasatch-2.2.15}/README_SETTINGS.md +0 -0
  26. {wasatch-2.2.13 → wasatch-2.2.15}/README_VIRTUAL.md +0 -0
  27. {wasatch-2.2.13 → wasatch-2.2.15}/WasatchShell/README.md +0 -0
  28. {wasatch-2.2.13 → wasatch-2.2.15}/WasatchShell/laser-test.py +0 -0
  29. {wasatch-2.2.13 → wasatch-2.2.15}/WasatchShell/load-test.py +0 -0
  30. {wasatch-2.2.13 → wasatch-2.2.15}/WasatchShell/one-shot.py +0 -0
  31. {wasatch-2.2.13 → wasatch-2.2.15}/WasatchShell/wasatch-shell.py +0 -0
  32. {wasatch-2.2.13 → wasatch-2.2.15}/demo-auto-raman.py +0 -0
  33. {wasatch-2.2.13 → wasatch-2.2.15}/demo-simple.py +0 -0
  34. {wasatch-2.2.13 → wasatch-2.2.15}/demo-virtual.py +0 -0
  35. {wasatch-2.2.13 → wasatch-2.2.15}/demo-workflow.py +0 -0
  36. {wasatch-2.2.13 → wasatch-2.2.15}/demo.py +0 -0
  37. {wasatch-2.2.13 → wasatch-2.2.15}/environments/conda-linux.yml +0 -0
  38. {wasatch-2.2.13 → wasatch-2.2.15}/environments/conda-macos.yml +0 -0
  39. {wasatch-2.2.13 → wasatch-2.2.15}/environments/conda-rpi.yml +0 -0
  40. {wasatch-2.2.13 → wasatch-2.2.15}/environments/conda-win10.yml +0 -0
  41. {wasatch-2.2.13 → wasatch-2.2.15}/pyproject.toml +0 -0
  42. {wasatch-2.2.13 → wasatch-2.2.15}/requirements.txt +0 -0
  43. {wasatch-2.2.13 → wasatch-2.2.15}/screenshots/multiplatform.png +0 -0
  44. {wasatch-2.2.13 → wasatch-2.2.15}/scripts/dark-vs-integ.py +0 -0
  45. {wasatch-2.2.13 → wasatch-2.2.15}/scripts/deploy +0 -0
  46. {wasatch-2.2.13 → wasatch-2.2.15}/setup.cfg +0 -0
  47. {wasatch-2.2.13 → wasatch-2.2.15}/testSpectrometers/SiG_785/eeprom/EEPROM-EM-9c65d19f4c.json +0 -0
  48. {wasatch-2.2.13 → wasatch-2.2.15}/testSpectrometers/SiG_785/readings/enlighten-20210329-094722-799520-WP-00686.csv +0 -0
  49. {wasatch-2.2.13 → wasatch-2.2.15}/testSpectrometers/SiG_785/readings/enlighten-20210329-094755-288958-WP-00686.csv +0 -0
  50. {wasatch-2.2.13 → wasatch-2.2.15}/testSpectrometers/WP-00887/eeprom/WP-00887-mock.json +0 -0
  51. {wasatch-2.2.13 → wasatch-2.2.15}/testSpectrometers/WP-00904/eeprom/WP-00904-mock.json +0 -0
  52. {wasatch-2.2.13 → wasatch-2.2.15}/testSpectrometers/WP-00913/eeprom/WP-00913-mock.json +0 -0
  53. {wasatch-2.2.13 → wasatch-2.2.15}/tests/existing_implementations.py +0 -0
  54. {wasatch-2.2.13 → wasatch-2.2.15}/tests/noise-by-setpoint.py +0 -0
  55. {wasatch-2.2.13 → wasatch-2.2.15}/tests/queues.py +0 -0
  56. {wasatch-2.2.13 → wasatch-2.2.15}/tests/unit.py +0 -0
  57. {wasatch-2.2.13 → wasatch-2.2.15}/udev/10-wasatch.rules +0 -0
  58. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/AbstractUSBDevice.py +0 -0
  59. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/AutoRaman.py +0 -0
  60. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/BLEDevice.py +0 -0
  61. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/BalanceAcquisition.py +0 -0
  62. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/CSVLoader.py +0 -0
  63. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/CommandSettings.py +0 -0
  64. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/ControlObject.py +0 -0
  65. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/DetectorROI.py +0 -0
  66. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/DetectorRegions.py +0 -0
  67. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/DeviceID.py +0 -0
  68. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/EEPROM_multical.sav +0 -0
  69. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/FPGAOptions.py +0 -0
  70. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/FirmwareRequirements.py +0 -0
  71. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/HardwareInfo.py +0 -0
  72. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/IMX385.py +0 -0
  73. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/InterfaceDevice.py +0 -0
  74. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/MockUSBDevice.py +0 -0
  75. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/OceanDevice.py +0 -0
  76. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/PollStatus.py +0 -0
  77. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/ProcessedReading.py +0 -0
  78. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/Reading.py +0 -0
  79. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/RealUSBDevice.py +0 -0
  80. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/SpectrometerRequest.py +0 -0
  81. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/SpectrometerResponse.py +0 -0
  82. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/StatusMessage.py +0 -0
  83. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/TakeOneRequest.py +0 -0
  84. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/WasatchDevice.py +0 -0
  85. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/WasatchDeviceWrapper.py +0 -0
  86. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/WrapperWorker.py +0 -0
  87. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/applog.py +0 -0
  88. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/simulation_protocol.py +0 -0
  89. {wasatch-2.2.13 → wasatch-2.2.15}/wasatch/utils.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ - 2024-12-06 2.2.15
4
+ - XL Fixes
5
+ - fixed scan averaging
6
+ - fixed detector temperature readout
7
+ - support vertical ROI (successfully tested (118, 138) ie (center 128, height 20))
8
+ - flag non-downloaded Andor EEPROMs as 'stubbed'
9
+ - EEPROM
10
+ - allow MultiWavelengthCalibration.set to support any EEPROM property
11
+ - fixed Raman Intensity Calibration (broke in 2.2.14)
12
+ - raman_intensity_calibration_order (standardized on 5th-order polynomial)
13
+ - force integral ROI
14
+ - 2024-11-25 2.2.14
15
+ - EEPROM
16
+ - added subformat 5 (EEPROM.MultiWavelengthCalibration)
17
+ - bumped rev to 17
18
+ - deprecated subformat 4 (regions)
19
+ - SpectrometerSettings
20
+ - deprecated integer excitation_nm
21
+ - moved vertical ROI to wasatch.ROI
22
+ - added calibrations(), select_calibration(n)
23
+ - FeatureInterfaceDevice
24
+ - added set/get_scans_to_average
25
+ - SpectrometerState
26
+ - added onboard_averaging
3
27
  - 2024-11-01 2.2.13
4
28
  - support for InGaAs linear pixel calibration
5
29
  - re-enable STM32 serial number
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wasatch
3
- Version: 2.2.13
3
+ Version: 2.2.15
4
4
  Summary: This package is a driver for Wasatch Photonics spectrometers
5
5
  Author-email: Wasatch Photonics <edort@wasatchphotonics.com>
6
6
  Description-Content-Type: text/markdown
@@ -11,6 +11,7 @@ from .SpectrometerResponse import SpectrometerResponse
11
11
  from .InterfaceDevice import InterfaceDevice
12
12
  from .DeviceID import DeviceID
13
13
  from .Reading import Reading
14
+ from .ROI import ROI
14
15
 
15
16
  log = logging.getLogger(__name__)
16
17
 
@@ -47,7 +48,7 @@ class AndorDevice(InterfaceDevice):
47
48
  SUCCESS = 20002 #!< see load_error_codes()
48
49
  SHUTTER_SPEED_MS = 50 #!< allow time for mechanical shutter to stabilize
49
50
 
50
- def __init__(self, device_id, message_queue=None, alert_queue=None): # -> None
51
+ def __init__(self, device_id, message_queue=None, alert_queue=None):
51
52
  # if passed a string representation of a DeviceID, deserialize it
52
53
  super().__init__()
53
54
  if type(device_id) is str:
@@ -67,6 +68,8 @@ class AndorDevice(InterfaceDevice):
67
68
 
68
69
  self.immediate_mode = False
69
70
 
71
+ # An AndorDevice has-a SpectrometerSettings, which has-a EEPROM, which
72
+ # has-a MultiWavelengthCalibration
70
73
  self.settings = SpectrometerSettings(self.device_id)
71
74
  self.summed_spectra = None
72
75
  self.sum_count = 0
@@ -125,51 +128,55 @@ class AndorDevice(InterfaceDevice):
125
128
  # set Andor defaults for important "EEPROM" settings
126
129
  # (all but has_cooling can be overridden via config file)
127
130
 
128
- # Andor API doesn't have access to detector info
129
- # Note that we use non-iDus cameras, including the Newton
131
+ # Andor API doesn't(?) seem to have access to detector info.
132
+ # Note that we use non-iDus cameras, including the Newton.
130
133
  self.settings.eeprom.detector = "iDus"
131
- self.settings.eeprom.wavelength_coeffs = [0,1,0,0]
134
+
132
135
  self.settings.eeprom.has_cooling = True
133
136
  self.settings.eeprom.startup_integration_time_ms = 10
134
137
  self.settings.eeprom.startup_temp_degC = -60
135
138
  self.settings.eeprom.detector_gain = 1
136
139
  self.settings.eeprom.detector_gain_odd = 1
137
140
 
141
+ # MultiWavelengthCalibration attributes
142
+ self.settings.eeprom.multi_wavelength_calibration.set("wavelength_coeffs", [0,1,0,0,0])
143
+
138
144
  self.process_f = self._init_process_funcs()
139
145
 
140
146
  ###############################################################
141
147
  # Private Methods
142
148
  ###############################################################
143
149
 
144
- def _init_process_funcs(self): # -> dict[str, Callable[..., Any]]
150
+ def _init_process_funcs(self):
145
151
  process_f = {}
146
152
 
147
- process_f["connect"] = self.connect
148
- process_f["acquire_data"] = self.acquire_data
149
- process_f["set_shutter_enable"] = self.set_shutter_enable
150
- process_f["set_integration_time_ms"] = self.set_integration_time_ms
151
- process_f["get_serial_number"] = self.get_serial_number
152
- process_f["init_tec_setpoint"] = self.init_tec_setpoint
153
- process_f["set_tec_setpoint"] = self.set_tec_setpoint
154
- process_f["init_detector_area"] = self.init_detector_area
155
- process_f["scans_to_average"] = self.scans_to_average
156
- process_f["high_gain_mode_enable"] = self.high_gain_mode_enable
157
- process_f["save_config"] = self.save_config
153
+ process_f["connect"] = self.connect
154
+ process_f["acquire_data"] = self.acquire_data
155
+ process_f["set_shutter_enable"] = self.set_shutter_enable
156
+ process_f["set_integration_time_ms"] = self.set_integration_time_ms
157
+ process_f["get_serial_number"] = self.get_serial_number
158
+ process_f["init_tec_setpoint"] = self.init_tec_setpoint
159
+ process_f["set_tec_setpoint"] = self.set_tec_setpoint
160
+ process_f["init_detector_area"] = self.init_detector_area
161
+ process_f["scans_to_average"] = self.scans_to_average
162
+ process_f["high_gain_mode_enable"] = self.high_gain_mode_enable
163
+ process_f["save_config"] = self.save_config
164
+ process_f["vertical_binning"] = self.set_vertical_binning
158
165
 
159
166
  ##################################################################
160
167
  # What follows is the old init-lambdas that are squashed into process_f
161
168
  # Long term, the upstream requests should be changed to match the new format
162
169
  # This is an easy fix for the time being to make things behave
163
170
  ##################################################################
164
- process_f["integration_time_ms"] = lambda x: self.set_integration_time_ms(x) # conversion from millisec to microsec
165
- process_f["fan_enable"] = lambda x: self.set_fan_enable(bool(x))
166
- process_f["shutter_enable"] = lambda x: self.set_shutter_enable(bool(x))
167
- process_f["detector_tec_enable"] = lambda x: self.toggle_tec(bool(x))
168
- process_f["detector_tec_setpoint_degC"] = lambda x: self.set_tec_setpoint(int(round(x)))
171
+ process_f["integration_time_ms"] = lambda x: self.set_integration_time_ms(x)
172
+ process_f["fan_enable"] = lambda x: self.set_fan_enable(bool(x))
173
+ process_f["shutter_enable"] = lambda x: self.set_shutter_enable(bool(x))
174
+ process_f["detector_tec_enable"] = lambda x: self.toggle_tec(bool(x))
175
+ process_f["detector_tec_setpoint_degC"] = lambda x: self.set_tec_setpoint(int(round(x)))
169
176
 
170
177
  return process_f
171
178
 
172
- def high_gain_mode_enable(self, enabled: bool): # -> SpectrometerResponse
179
+ def high_gain_mode_enable(self, enabled):
173
180
  if enabled:
174
181
  result = self.driver.SetPreAmpGain(self.gain_idx[-1])
175
182
  assert(self.SUCCESS == result), f"unable to set detector gain, got value of {result}"
@@ -181,22 +188,16 @@ class AndorDevice(InterfaceDevice):
181
188
  log.debug(f"for {enabled} setting gain to {self.gain_options[0]}")
182
189
  return
183
190
 
184
- # MZ: nothing seems to call this?
185
- def _update_wavelength_coeffs(self, coeffs: list[float]): # -> None
186
- self.settings.eeprom.wavelength_coeffs = coeffs
187
- self.config_values['wavelength_coeffs'] = coeffs
188
- self.save_config()
189
-
190
- def set_fan_enable(self, x: bool): # -> SpectrometerResponse
191
+ def set_fan_enable(self, x):
191
192
  self.check_result(self.driver.SetFanMode(int(x)), f"Andor Fan On {x}")
192
193
  return SpectrometerResponse()
193
194
 
194
- def _get_default_data_dir(self): # -> str
195
+ def _get_default_data_dir(self):
195
196
  if os.name == "nt":
196
197
  return os.path.join(os.path.expanduser("~"), "Documents", "EnlightenSpectra")
197
198
  return os.path.join(os.environ["HOME"], "EnlightenSpectra")
198
199
 
199
- def _check_config_file(self): # -> bool
200
+ def _check_config_file(self):
200
201
  self.config_dir = os.path.join(self._get_default_data_dir(), 'config')
201
202
  self.config_file = os.path.join(self.config_dir, self.serial + '.json')
202
203
  if not os.path.exists(self.config_dir):
@@ -228,10 +229,10 @@ class AndorDevice(InterfaceDevice):
228
229
 
229
230
  return convertedSpec
230
231
 
231
- def _take_one_averaged_reading(self): # -> SpectrometerResponse
232
+ def _take_one_averaged_reading(self):
232
233
  averaging_enabled = (self.settings.state.scans_to_average > 1)
233
234
 
234
- if averaging_enabled and not self.settings.state.free_running_mode:
235
+ if averaging_enabled:
235
236
  # collect the entire averaged spectrum at once (added for
236
237
  # BatchCollection with laser delay)
237
238
  #
@@ -258,7 +259,7 @@ class AndorDevice(InterfaceDevice):
258
259
  if self.tec_enabled:
259
260
  log.debug("TEC enabled, so reading temperature")
260
261
 
261
- use_float = True # seems to work on 785XL (WP-01635)
262
+ use_float = True # seems to work on 785XL (WP-01635 and WP-01491)
262
263
 
263
264
  if use_float:
264
265
  c_temp = c_float()
@@ -269,7 +270,11 @@ class AndorDevice(InterfaceDevice):
269
270
 
270
271
  label = self.get_error_code(result)
271
272
 
272
- if label in ["DRV_SUCCESS", "DRV_TEMP_STABILIZED", "DRV_TEMP_NOT_REACHED", "DRV_TEMP_DRIFT", "DRV_TEMP_NOT_STABILIZED"]:
273
+ if label in [ "DRV_SUCCESS",
274
+ "DRV_TEMPERATURE_DRIFT",
275
+ "DRV_TEMPERATURE_STABILIZED",
276
+ "DRV_TEMPERATURE_NOT_REACHED",
277
+ "DRV_TEMPERATURE_NOT_STABILIZED" ]:
273
278
  reading.detector_temperature_degC = c_temp.value
274
279
  log.debug(f"Andor temperature {reading.detector_temperature_degC:.2f} ({label})")
275
280
  else:
@@ -336,12 +341,12 @@ class AndorDevice(InterfaceDevice):
336
341
  # reading.dump_area_scan()
337
342
  return SpectrometerResponse(data=reading)
338
343
 
339
- def _close_ex_shutter(self): # -> SpectrometerResponse
344
+ def _close_ex_shutter(self):
340
345
  self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 2), "SetShutterEx(2)")
341
346
  self.settings.state.shutter_enabled = False
342
347
  return SpectrometerResponse(True)
343
348
 
344
- def _open_ex_shutter(self): # -> SpectrometerResponse
349
+ def _open_ex_shutter(self):
345
350
  self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 1), "SetShutterEx(1)")
346
351
  self.settings.state.shutter_enabled = True
347
352
  return SpectrometerResponse(True)
@@ -358,7 +363,7 @@ class AndorDevice(InterfaceDevice):
358
363
  raise RuntimeError(msg)
359
364
  log.debug(f"successfully called {func}")
360
365
 
361
- def connect(self): # -> SpectrometerResponse
366
+ def connect(self):
362
367
  if self.dll_fail:
363
368
  return SpectrometerResponse(False, error_msg="can't find Andor DLL; please confirm Andor Driver Pack 2 installed")
364
369
 
@@ -380,13 +385,20 @@ class AndorDevice(InterfaceDevice):
380
385
  self.init_detector_area() # step 7
381
386
 
382
387
  if not self._check_config_file():
388
+ log.debug("stubbing Andor EEPROM")
389
+ self.settings.eeprom.stubbed = True
383
390
  self.config_values = {
384
391
  'detector_serial_number': self.serial,
385
- 'wavelength_coeffs': [0,1,0,0],
392
+ 'wavelength_coeffs': [0,1,0,0,0],
386
393
  'excitation_nm_float': 0,
387
394
  'raman_intensity_coeffs': [],
388
395
  'raman_intensity_calibration_order': 0,
389
- 'invert_x_axis': False
396
+ 'invert_x_axis': False,
397
+ 'roi_horizontal_start': 0,
398
+ 'roi_horizontal_end': 0,
399
+ 'roi_vertical_region_1_start': 0,
400
+ 'roi_vertical_region_1_end': 0,
401
+ 'stubbed': True
390
402
  }
391
403
  log.debug(f"connect: config file not found, so defaulting to these: {self.config_values}")
392
404
  self.save_config()
@@ -411,6 +423,14 @@ class AndorDevice(InterfaceDevice):
411
423
  # step 17 (WasatchNET doesn't do this)
412
424
  self._obtain_gain_info()
413
425
 
426
+ # step 18 (WasatchNET doesn't do this)
427
+ roi = ROI(self.settings.eeprom.roi_vertical_region_1_start,
428
+ self.settings.eeprom.roi_vertical_region_1_end)
429
+ if roi.start != 0 and roi.end != 0:
430
+ # although camera can probably support the (0, 0) case, it's
431
+ # convenient to treat as unconfigured defaults
432
+ self.set_vertical_binning(roi)
433
+
414
434
  # success!
415
435
  log.info("AndorDevice successfully connected")
416
436
 
@@ -419,6 +439,40 @@ class AndorDevice(InterfaceDevice):
419
439
  self.settings.eeprom.has_cooling = True
420
440
  return SpectrometerResponse(data=True)
421
441
 
442
+ def set_vertical_binning(self, roi):
443
+ """
444
+ Note that this follows the same (start, end) vertical ROI API as
445
+ FeatureInterfaceDevice.set_vertical_binning, and dynamically translates
446
+ that into the (middle, height) Andor API.
447
+ """
448
+
449
+ if isinstance(roi, ROI):
450
+ start, end = roi.start, roi.end
451
+ elif len(roi) == 2:
452
+ start, end = roi[0], roi[1]
453
+ else:
454
+ log.error("set_vertical_binning requires an ROI object or tuple of (start, stop) lines")
455
+ return SpectrometerResponse(data=False, error_msg="invalid start and stop lines")
456
+
457
+ if start < 0 or end < 0:
458
+ log.error("set_vertical_binning requires POSITIVE (start, stop) lines")
459
+ return SpectrometerResponse(data=False, error_msg="invalid start and stop lines")
460
+
461
+ # enforce ascending order
462
+ if start >= end:
463
+ log.error("set_vertical_binning requires ascending order (ignoring %d, %d)", start, end)
464
+ return SpectrometerResponse(data=False, error_msg="invalid start and stop lines")
465
+
466
+
467
+ height = end - start
468
+ center = int(round(height / 2, 0)) + start
469
+
470
+ log.debug(f"setting Single-Track vertical binning of ROI (start {start}, end {end}) (center {center}, height {height})")
471
+ self.check_result(self.driver.SetReadMode(3), "SetReadMode(single-track)")
472
+ self.check_result(self.driver.SetSingleTrack(center, height), "SetSingleTrack")
473
+
474
+ return SpectrometerResponse(data=True)
475
+
422
476
  def save_config(self, eeprom=None):
423
477
  """
424
478
  The user has edited the "virtual EEPROM", for instance using ENLIGHTEN's
@@ -428,7 +482,6 @@ class AndorDevice(InterfaceDevice):
428
482
  @param eeprom: if provided, overwrite current settings with those in the
429
483
  passed dict before writing to disk
430
484
  """
431
- log.debug("save_config: here")
432
485
  if eeprom is not None:
433
486
  self.update_config_from_eeprom(eeprom)
434
487
 
@@ -444,13 +497,13 @@ class AndorDevice(InterfaceDevice):
444
497
  # first, copy over any EEPROM fields which have a different name in
445
498
  # wasatch.EEPROM vs the external JSON file
446
499
  for json_name, python_name in self.config_names_to_eeprom.items():
447
- self.config_values[json_name] = getattr(eeprom, python_name)
500
+ self.config_values[json_name] = eeprom.multi_wavelength_calibration.get(python_name)
448
501
 
449
502
  # now do all the standard attributes of the wasatch.EEPROM, adding
450
503
  # their values into the same dict
451
- for python_name, python_value in eeprom.__dict__.items():
504
+ for python_name in eeprom.__dict__:
452
505
  if python_name in self.config_values:
453
- self.config_values[python_name] = python_value
506
+ self.config_values[python_name] = eeprom.multi_wavelength_calibration.get(python_name)
454
507
 
455
508
  def _load_config_values(self):
456
509
  """
@@ -463,10 +516,11 @@ class AndorDevice(InterfaceDevice):
463
516
  # handle wp_ prefixes
464
517
  for k, v in self.config_names_to_eeprom.items():
465
518
  if k in self.config_values:
466
- setattr(self.settings.eeprom, v, self.config_values[k])
519
+ self.settings.eeprom.multi_wavelength_calibration.set(v, self.config_values[k])
467
520
 
468
521
  # same spelling
469
522
  for k in [ 'model',
523
+ 'stubbed',
470
524
  'detector',
471
525
  'serial_number',
472
526
  'invert_x_axis',
@@ -474,33 +528,35 @@ class AndorDevice(InterfaceDevice):
474
528
  'excitation_nm_float',
475
529
  'roi_horizontal_end',
476
530
  'roi_horizontal_start',
531
+ 'roi_vertical_region_1_start',
532
+ 'roi_vertical_region_1_end',
477
533
  'raman_intensity_coeffs',
478
534
  'raman_intensity_calibration_order',
479
535
  'startup_temp_degC',
480
536
  'startup_integration_time_ms' ]:
481
537
  if k in self.config_values:
482
- setattr(self.settings.eeprom, k, self.config_values[k])
538
+ self.settings.eeprom.multi_wavelength_calibration.set(k, self.config_values[k])
483
539
 
484
540
  # default missing-but-obvious fields
485
541
  if "raman_intensity_coeffs" in self.config_values:
486
542
  if "raman_intensity_calibration_order" not in self.config_values:
487
- self.settings.eeprom.raman_intensity_calibration_order = len(self.settings.eeprom.raman_intensity_coeffs) - 1
543
+ self.settings.eeprom.multi_wavelength_calibration.set("raman_intensity_calibration_order", len(self.settings.eeprom.raman_intensity_coeffs) - 1)
488
544
 
489
545
  # post-load initialization
490
546
  if 'startup_temp_degC' in self.config_values:
491
547
  self.set_tec_setpoint(self.settings.eeprom.startup_temp_degC)
492
548
 
493
- def acquire_data(self): # -> SpectrometerResponse
549
+ def acquire_data(self):
494
550
  reading = self._take_one_averaged_reading()
495
551
  return reading
496
552
 
497
- def set_shutter_enable(self, enable: bool): # -> SpectrometerResponse
553
+ def set_shutter_enable(self, enable):
498
554
  if enable:
499
555
  return self._open_ex_shutter()
500
556
  else:
501
557
  return self._close_ex_shutter()
502
558
 
503
- def set_integration_time_ms(self, ms: float): # -> SpectrometerResponse
559
+ def set_integration_time_ms(self, ms):
504
560
  self.integration_time_ms = ms
505
561
  log.debug(f"setting integration time to {self.integration_time_ms}ms")
506
562
 
@@ -566,13 +622,13 @@ class AndorDevice(InterfaceDevice):
566
622
  self.check_result(self.driver.SetTemperature(self.setpoint_deg_c), f"SetTemperature({self.setpoint_deg_c})")
567
623
  return SpectrometerResponse(True)
568
624
 
569
- def init_detector_area(self): # -> SpectrometerResponse
625
+ def init_detector_area(self):
570
626
  xPixels = c_int()
571
627
  yPixels = c_int()
572
628
  self.check_result(self.driver.GetDetector(byref(xPixels), byref(yPixels)), "GetDetector(x, y)")
573
- log.debug(f"detector {xPixels.value} width x {yPixels.value} height")
574
629
  self.pixels = xPixels.value
575
630
  self.height = yPixels.value
631
+ log.debug(f"detector {self.pixels} width x {self.height} height")
576
632
  return SpectrometerResponse(True)
577
633
 
578
634
  def _obtain_gain_info(self):
@@ -592,7 +648,7 @@ class AndorDevice(InterfaceDevice):
592
648
  self.gain_options = self.gain_options[::-1]
593
649
  log.debug(f"obtained gain options for spec, values were {self.gain_options}")
594
650
 
595
- def init_detector_speed(self): # -> SpectrometerResponse
651
+ def init_detector_speed(self):
596
652
  speed = c_float()
597
653
 
598
654
  # for CCDs, set vertical to recommended
@@ -623,7 +679,7 @@ class AndorDevice(InterfaceDevice):
623
679
  log.debug(f"set AD channel {ADnumber} with horizontal speed {HSnumber} ({STemp})")
624
680
  return SpectrometerResponse(True)
625
681
 
626
- def scans_to_average(self, value: int): # -> SpectrometerResponse
682
+ def scans_to_average(self, value):
627
683
  self.sum_count = 0
628
684
  self.settings.state.scans_to_average = int(value)
629
685
  return SpectrometerResponse(True)
@@ -74,3 +74,23 @@ class AutoRamanRequest:
74
74
  buf.extend([self.saturation & 0xff, (self.saturation >> 8) & 0xff])
75
75
  buf.extend([self.max_avg & 0xff ])
76
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 = 32,
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]))
@@ -161,14 +161,14 @@ class DeviceFinderUSB:
161
161
  return [DeviceID(device) for device in pyusb_devices]
162
162
 
163
163
  def find_usb_devices(self, poll = False):
164
- log.debug("DeviceFinderUSB.find_usb_devices: starting")
164
+ # log.debug("DeviceFinderUSB.find_usb_devices: starting")
165
165
  device_ids = []
166
166
 
167
167
  # MZ/ED: If USE_MONITORING is True, I had to disable the call to remove_all in enlighten.Controller
168
168
  if self.startup_scan < self.MIN_POLLING_SCANS or not self.USE_MONITORING or poll:
169
169
  # our first few scans should always be a bus poll
170
170
  # this is because no events will be registered
171
- log.debug(f"DeviceFinderUSB.find_usb_devices: just doing a bus poll for startup_scan {self.startup_scan}")
171
+ # log.debug(f"DeviceFinderUSB.find_usb_devices: just doing a bus poll for startup_scan {self.startup_scan}")
172
172
  device_ids = self.bus_polling()
173
173
  self.startup_scan += 1
174
174
  elif platform.system() == "Windows":
@@ -177,7 +177,7 @@ class DeviceFinderUSB:
177
177
  device_ids = self.linux_monitoring()
178
178
  else:
179
179
  device_ids = self.bus_polling()
180
- log.debug(f"DeviceFinderUSB.find_usb_devices: returning {len(device_ids)} devices")
180
+ # log.debug(f"DeviceFinderUSB.find_usb_devices: returning {len(device_ids)} devices")
181
181
  return device_ids
182
182
 
183
183
  def mac_monitoring(self):