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
wasatch/IDSDevice.py ADDED
@@ -0,0 +1,329 @@
1
+ import logging
2
+ import json
3
+ import copy
4
+ import os
5
+
6
+ from .SpectrometerResponse import SpectrometerResponse, ErrorLevel
7
+ from .SpectrometerSettings import SpectrometerSettings
8
+ from .InterfaceDevice import InterfaceDevice
9
+ from .IDSCamera import IDSCamera
10
+ from .DeviceID import DeviceID
11
+ from .Reading import Reading
12
+ from .IMX385 import IMX385
13
+ from .ROI import ROI
14
+
15
+ from wasatch import utils
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+ class IDSDevice(InterfaceDevice):
20
+ """
21
+ @see https://www.ids-imaging.us/manuals/ids-peak/ids-peak-api-documentation/2.15.0/en/python.html
22
+ """
23
+
24
+ ############################################################################
25
+ # lifecycle
26
+ ############################################################################
27
+
28
+ def __init__(self, device_id, message_queue=None, alert_queue=None):
29
+ super().__init__()
30
+
31
+ self.device_id = device_id
32
+ self.process_f = self.init_process_funcs()
33
+ self.device = None
34
+ self.camera = IDSCamera()
35
+
36
+ self.imx385 = IMX385() # re-using existing binning
37
+
38
+ self.session_reading_count = 0
39
+ self.reset_averaging()
40
+
41
+ def connect(self):
42
+ """
43
+ This method is called by WrapperWorker.run. At the end of the method,
44
+ an InterfaceDevice should have a non-null .settings with a populated
45
+ SpectrometerSettings.
46
+ """
47
+ log.debug(f"connect: trying to connect to {self.device_id}")
48
+ if not self.camera.connect():
49
+ log.error("connect: unable to connect to IDSCamera")
50
+ return SpectrometerResponse(False)
51
+
52
+ log.debug(f"connect: trying to start {self.device_id}")
53
+ self.camera.start()
54
+
55
+ # initialize default settings
56
+ self.settings = SpectrometerSettings(self.device_id)
57
+ self.settings.eeprom.excitation_nm_float = 785
58
+ self.settings.eeprom.wavecal_coeffs = [0, 1, 0, 0, 0]
59
+
60
+ # stomp from camera
61
+ self.settings.eeprom.model = self.camera.model_name
62
+ self.settings.eeprom.serial_number = self.camera.serial_number
63
+ self.settings.eeprom.detector_serial_number = self.camera.serial_number
64
+ self.settings.eeprom.active_pixels_horizontal = self.camera.width
65
+ self.settings.eeprom.active_pixels_vertical = self.camera.height
66
+ self.settings.eeprom.roi_vertical_region_1_start = 0
67
+ self.settings.eeprom.roi_vertical_region_1_end = self.camera.height
68
+ self.settings.eeprom.invert_x_axis = True
69
+
70
+ # stomp from virtual eeprom
71
+ self.init_from_json()
72
+ self.settings.eeprom.multi_wavelength_calibration.initialize()
73
+
74
+ # now that we've got our "final settings," compute derived values
75
+ self.settings.update_wavecal()
76
+
77
+ # pass vertical ROI down into Camera (where binning occurs)
78
+ self.set_start_line(self.settings.eeprom.roi_vertical_region_1_start)
79
+ self.set_stop_line (self.settings.eeprom.roi_vertical_region_1_end)
80
+ # no need to pass horizontal ROI, because that's handled in ENLIGHTEN
81
+
82
+ self.set_integration_time_ms(15)
83
+
84
+ # since area scan is so important to this camera, perform full rotation
85
+ self.camera.set_rotate_180(self.settings.eeprom.invert_x_axis)
86
+
87
+ log.debug("connect: success")
88
+ return SpectrometerResponse(True)
89
+
90
+ def disconnect(self):
91
+ if self.camera is not None:
92
+ log.debug("closing IDSCamera")
93
+ self.camera.close()
94
+ self.camera = None
95
+
96
+ def init_from_json(self):
97
+ """
98
+ {
99
+ "detector_serial_number": "4108809482",
100
+ "excitation_nm_float": 785.0,
101
+ "invert_x_axis": true,
102
+ "wavelength_coeffs": [
103
+ 821.19,
104
+ 0.106,
105
+ 4.00E-05,
106
+ -1.00E+07,
107
+ 5.00E-11
108
+ ],
109
+ "wp_model": "WP-785XS-FS-OEM+STARVIS",
110
+ "wp_serial_number": "WP-002288"
111
+ }
112
+
113
+ """
114
+ config_dir = os.path.join(utils.get_default_data_dir(), "config")
115
+ json_path = os.path.join(config_dir, f"IDS-{self.camera.serial_number}.json")
116
+ if os.path.exists(json_path):
117
+ with open(json_path) as config_file:
118
+ data = json.load(config_file)
119
+ log.debug(f"loaded from {json_path}: {data}")
120
+
121
+ def stomp(k, attr=None):
122
+ if attr is None:
123
+ attr = k
124
+ log.debug(f"attempting to stomp {k}")
125
+ else:
126
+ log.debug(f"attempting to stomp {k} --> {attr}")
127
+
128
+ if k in data:
129
+ value = data[k]
130
+ log.debug(f"stomping eeprom.{attr} = {value}")
131
+ setattr(self.settings.eeprom, attr, value)
132
+
133
+ for k in [ "detector_gain",
134
+ "excitation_nm_float",
135
+ "invert_x_axis",
136
+ "horiz_binning_enabled",
137
+ "horiz_binning_mode",
138
+ "startup_integration_time_ms",
139
+ "wavelength_coeffs",
140
+ "roi_horizontal_end",
141
+ "roi_horizontal_start",
142
+ "roi_vertical_region_1_start",
143
+ "roi_vertical_region_1_end" ]:
144
+ stomp(k)
145
+ for k, attr in [ [ "wp_model", "model" ],
146
+ [ "wp_serial_number", "serial_number" ] ]:
147
+ stomp(k, attr)
148
+
149
+ def set_integration_time_ms(self, ms):
150
+ """
151
+ It did not look like we need to stop/start the camera when changing exposure time:
152
+ cpp/afl_features_live_qtwidgets/backend.cpp BackEnd::SetExposure
153
+ """
154
+ log.debug(f"set_integration_time_ms: {ms}ms")
155
+ self.settings.state.integration_time_ms = self.camera.set_integration_time_ms(ms)
156
+ return SpectrometerResponse(True)
157
+
158
+ def set_gain_factor(self, factor):
159
+ """ Appears to be a scalar multiplier rather than dB, since it defaults to 1.0? """
160
+ log.debug(f"set_gain_factor: gain factor {factor:.1f}")
161
+ self.settings.state.detector_gain = self.camera.set_gain_factor(factor)
162
+ return SpectrometerResponse(True)
163
+
164
+ def set_gain_db(self, db):
165
+ log.debug(f"set_gain_db: gain {db:.1f}")
166
+ self.settings.state.gain_db = db
167
+ factor = utils.from_db_to_linear(db)
168
+ return self.set_gain_factor(factor)
169
+
170
+ def set_start_line(self, line):
171
+ log.debug(f"set_start_line: line {line}")
172
+ self.camera.set_start_line(line)
173
+ return SpectrometerResponse(True)
174
+
175
+ def set_stop_line(self, line):
176
+ log.debug(f"set_stop_line: line {line}")
177
+ self.camera.set_stop_line(line)
178
+ return SpectrometerResponse(True)
179
+
180
+ def set_vertical_roi(self, roi):
181
+ if isinstance(roi, ROI):
182
+ start, end = roi.start, roi.end
183
+ elif len(roi) == 2:
184
+ start, end = roi[0], roi[1]
185
+ else:
186
+ log.error("set_vertical_roi requires an ROI object or tuple of (start, stop) lines")
187
+ return SpectrometerResponse(data=False, error_msg="invalid start and stop lines")
188
+
189
+ log.debug(f"set_vertical_roi: start {start}, end {end}")
190
+ self.set_start_line(start)
191
+ self.set_stop_line(end)
192
+ return SpectrometerResponse(True)
193
+
194
+ def set_output_format_name(self, format_name):
195
+ if format_name not in self.camera.SUPPORTED_CONVERSIONS:
196
+ log.error("unsupported output format name {format_name}")
197
+ return SpectrometerResponse(False)
198
+
199
+ log.debug(f"setting output_format_name to {format_name}")
200
+ self.camera.init_image_converter(format_name)
201
+ return SpectrometerResponse(True)
202
+
203
+ def set_area_scan_enable(self, flag):
204
+ log.debug(f"set_area_scan_enable: flag {flag}")
205
+ self.camera.area_scan_enabled = flag
206
+ return SpectrometerResponse(True)
207
+
208
+ def set_scans_to_average(self, n):
209
+ self.settings.state.scans_to_average = n
210
+ log.debug(f"set_scans_to_average {self.settings.state.scans_to_average}")
211
+ return SpectrometerResponse(True)
212
+
213
+ def get_spectrum(self):
214
+ log.debug(f"get_spectrum: calling send_trigger")
215
+ self.camera.send_trigger()
216
+ log.debug(f"get_spectrum: calling get_spectrum")
217
+ spectrum = self.camera.get_spectrum()
218
+ if spectrum is None:
219
+ log.error("get_spectrum: received None?!")
220
+
221
+ spectrum = self.apply_horizontal_binning(spectrum)
222
+
223
+ log.debug(f"get_spectrum: done")
224
+ return spectrum
225
+
226
+ def apply_horizontal_binning(self, spectrum):
227
+ if not self.settings.eeprom.horiz_binning_enabled:
228
+ return spectrum
229
+
230
+ mode = self.settings.eeprom.multi_wavelength_calibration.get("horiz_binning_mode")
231
+ if mode == IMX385.BIN_2X2:
232
+ return self.imx385.bin_2x2(spectrum)
233
+ elif mode == IMX385.CORRECT_SSC:
234
+ return self.imx385.correct_ssc(spectrum, self.settings.wavelengths)
235
+ elif mode == IMX385.CORRECT_SSC_BIN_2X2:
236
+ spectrum = self.imx385.correct_ssc(spectrum, self.settings.wavelengths)
237
+ return self.imx385.bin_2x2(spectrum)
238
+ elif mode == IMX385.BIN_4X2:
239
+ return self.imx385.bin_4x2(spectrum)
240
+ elif mode == IMX385.BIN_4X2_INTERP:
241
+ return self.imx385.bin_4x2_interp(spectrum, self.settings.wavelengths)
242
+ elif mode == IMX385.BIN_4X2_AVG:
243
+ return self.imx385.bin_4x2_avg(spectrum)
244
+ else:
245
+ # there may be legacy units in the field where this byte is
246
+ # uninitialized to 0xff...treat as 0x00 for now
247
+ log.error("invalid horizontal binning mode {mode}...defaulting to bin_2x2")
248
+ return self.imx385.bin_2x2(spectrum)
249
+
250
+
251
+ ############################################################################
252
+ # operations
253
+ ############################################################################
254
+
255
+ def reset_averaging(self):
256
+ log.debug("reset averaging")
257
+ self.summed_spectra = None
258
+ self.sum_count = 0
259
+
260
+ def acquire_data(self):
261
+ # pre-process scan averaging
262
+ log.debug(f"acquire_data: scans_to_average {self.settings.state.scans_to_average}")
263
+ if self.settings.state.scans_to_average > 1:
264
+ if self.sum_count > self.settings.state.scans_to_average:
265
+ self.reset_averaging()
266
+
267
+ reading = Reading(self.device_id)
268
+
269
+ reading.spectrum = self.get_spectrum()
270
+
271
+ # attach an AreaScanImage if there is one
272
+ reading.area_scan_image = self.camera.last_area_scan_image
273
+ self.camera.last_area_scan_image = None # don't re-send the same one twice
274
+
275
+ self.session_reading_count += 1
276
+ reading.session_count = self.session_reading_count
277
+
278
+ # post-process scan averaging
279
+ if self.settings.state.scans_to_average > 1:
280
+ if self.summed_spectra is None:
281
+ self.summed_spectra = copy.copy(reading.spectrum)
282
+ else:
283
+ for pixel, intensity in enumerate(reading.spectrum):
284
+ self.summed_spectra[pixel] += intensity
285
+
286
+ self.sum_count += 1
287
+ reading.sum_count = self.sum_count
288
+
289
+ if self.sum_count >= self.settings.state.scans_to_average:
290
+ log.debug("acquire_data: averaging complete")
291
+ for i in range(len(self.summed_spectra)):
292
+ self.summed_spectra[i] = round(1.0 * self.summed_spectra[i] / self.sum_count, 2)
293
+ reading.averaged = True
294
+ self.reset_averaging()
295
+ else:
296
+ log.debug(f"acquire_data: averaged {self.sum_count}/{self.settings.state.scans_to_average}")
297
+
298
+ log.debug(f"acquire_data: returning {reading}")
299
+ return SpectrometerResponse(data=reading)
300
+
301
+ ############################################################################
302
+ # utility
303
+ ############################################################################
304
+
305
+ def to_hex(self, data):
306
+ return "[ " + " ".join([f"0x{v:02x}" for v in data]) + " ]"
307
+
308
+ def init_process_funcs(self):
309
+ process_f = {}
310
+
311
+ process_f["connect"] = self.connect
312
+ process_f["disconnect"] = self.disconnect
313
+ process_f["close"] = self.disconnect
314
+
315
+ process_f["acquire_data"] = self.acquire_data
316
+
317
+ # deliberately don't support gain_factor or detector_gain
318
+ process_f["gain_db"] = lambda x: self.set_gain_db(float(x))
319
+ process_f["integration_time_ms"] = lambda x: self.set_integration_time_ms(int(x))
320
+ process_f["scans_to_average"] = lambda x: self.set_scans_to_average(int(x))
321
+
322
+ process_f["vertical_binning"] = lambda x: self.set_vertical_roi(x)
323
+ process_f["start_line"] = lambda x: self.set_start_line(x)
324
+ process_f["stop_line"] = lambda x: self.set_stop_line(x)
325
+ process_f["area_scan_enable"] = lambda x: self.set_area_scan_enable(bool(x))
326
+
327
+ process_f["output_format_name"] = lambda x: self.set_output_format_name(x)
328
+
329
+ return process_f
wasatch/IMX385.py ADDED
@@ -0,0 +1,179 @@
1
+ import numpy as np
2
+ import logging
3
+
4
+ log = logging.getLogger(__name__)
5
+
6
+ class IMX385:
7
+ """
8
+ Long-term, it might be worth having separate classes for HorizontalBinning
9
+ and IMX385, which would allow characteristics for other sensors to go into
10
+ other classes. That would suggest having different EEPROM fields for color-
11
+ correction vs horizontal binning, etc. At this point we're not sure if we're
12
+ going to keep any of this, so not over-engineering (or over-allocating limit-
13
+ ed EEPROM space) for now.
14
+ """
15
+
16
+ BIN_2X2 = 0
17
+ CORRECT_SSC = 1
18
+ CORRECT_SSC_BIN_2X2 = 2
19
+ BIN_4X2 = 3
20
+ BIN_4X2_INTERP = 4
21
+ BIN_4X2_AVG = 5
22
+
23
+ def __init__(self):
24
+ self.load_ssc_factors()
25
+
26
+ def load_ssc_factors(self):
27
+ # from Spectral Sensitivity Characteristics graph in IMX385-LQR-C datasheet (p22)
28
+ self.ssc_factors = {
29
+ "red": np.asarray([ 0.0800, 0.0797, 0.0782, 0.0767, 0.0751, 0.0736, 0.0721, 0.0705, 0.0692, 0.0679, 0.0666, 0.0653, 0.0640, 0.0627, 0.0614, 0.0601, 0.0588, 0.0576, 0.0563, 0.0550, 0.0537, 0.0524, 0.0511, 0.0499, 0.0494, 0.0489, 0.0483, 0.0478, 0.0473, 0.0467, 0.0462, 0.0457, 0.0451, 0.0446, 0.0441, 0.0435, 0.0430, 0.0425, 0.0419, 0.0414, 0.0409, 0.0403, 0.0398, 0.0392, 0.0385, 0.0379, 0.0373, 0.0367, 0.0361, 0.0355, 0.0349, 0.0343, 0.0336, 0.0330, 0.0324, 0.0318, 0.0312, 0.0306, 0.0300, 0.0305, 0.0309, 0.0313, 0.0318, 0.0322, 0.0326, 0.0331, 0.0335, 0.0340, 0.0344, 0.0348, 0.0353, 0.0357, 0.0361, 0.0366, 0.0370, 0.0375, 0.0379, 0.0383, 0.0388, 0.0392, 0.0396, 0.0401, 0.0408, 0.0415, 0.0422, 0.0428, 0.0435, 0.0442, 0.0449, 0.0456, 0.0462, 0.0469, 0.0476, 0.0483, 0.0490, 0.0497, 0.0508, 0.0523, 0.0538, 0.0553, 0.0569, 0.0584, 0.0599, 0.0615, 0.0630, 0.0645, 0.0661, 0.0676, 0.0691, 0.0708, 0.0727, 0.0746, 0.0765, 0.0784, 0.0802, 0.0821, 0.0840, 0.0859, 0.0878, 0.0897, 0.0900, 0.0900, 0.0900, 0.0900, 0.0900, 0.0900, 0.0897, 0.0886, 0.0874, 0.0863, 0.0852, 0.0841, 0.0830, 0.0819, 0.0808, 0.0796, 0.0784, 0.0772, 0.0759, 0.0747, 0.0735, 0.0723, 0.0711, 0.0700, 0.0700, 0.0700, 0.0700, 0.0700, 0.0700, 0.0700, 0.0700, 0.0700, 0.0712, 0.0724, 0.0737, 0.0749, 0.0761, 0.0773, 0.0786, 0.0798, 0.0872, 0.0959, 0.1047, 0.1134, 0.1221, 0.1325, 0.1570, 0.1815, 0.2060, 0.2305, 0.2550, 0.2795, 0.3098, 0.3445, 0.3792, 0.4139, 0.4486, 0.4867, 0.5266, 0.5665, 0.6050, 0.6417, 0.6784, 0.7141, 0.7435, 0.7729, 0.8024, 0.8317, 0.8593, 0.8869, 0.9145, 0.9298, 0.9420, 0.9543, 0.9632, 0.9694, 0.9755, 0.9816, 0.9877, 0.9909, 0.9922, 0.9936, 0.9949, 0.9963, 0.9977, 0.9990, 0.9994, 0.9974, 0.9953, 0.9933, 0.9912, 0.9894, 0.9879, 0.9863, 0.9848, 0.9833, 0.9817, 0.9802, 0.9800, 0.9800, 0.9800, 0.9800, 0.9800, 0.9800, 0.9800, 0.9800, 0.9800, 0.9790, 0.9779, 0.9768, 0.9757, 0.9746, 0.9735, 0.9723, 0.9712, 0.9701, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9700, 0.9688, 0.9664, 0.9639, 0.9615, 0.9590, 0.9566, 0.9541, 0.9517, 0.9487, 0.9447, 0.9406, 0.9365, 0.9324, 0.9283, 0.9242, 0.9202, 0.9136, 0.9069, 0.9002, 0.8935, 0.8869, 0.8802, 0.8735, 0.8668, 0.8601, 0.8552, 0.8503, 0.8454, 0.8405, 0.8356, 0.8307, 0.8258, 0.8209, 0.8173, 0.8139, 0.8106, 0.8072, 0.8039, 0.8006, 0.7972, 0.7939, 0.7905, 0.7887, 0.7872, 0.7857, 0.7841, 0.7826, 0.7811, 0.7804, 0.7818, 0.7831, 0.7845, 0.7859, 0.7872, 0.7886, 0.7899, 0.7926, 0.7953, 0.7981, 0.8008, 0.8035, 0.8062, 0.8089, 0.8117, 0.8144, 0.8171, 0.8198, 0.8226, 0.8253, 0.8280, 0.8300, 0.8300, 0.8300, 0.8300, 0.8297, 0.8282, 0.8267, 0.8251, 0.8236, 0.8221, 0.8205, 0.8177, 0.8142, 0.8107, 0.8072, 0.8037, 0.8002, 0.7969, 0.7935, 0.7902, 0.7868, 0.7835, 0.7802, 0.7768, 0.7735, 0.7701, 0.7687, 0.7673, 0.7660, 0.7646, 0.7633, 0.7619, 0.7605, 0.7588, 0.7567, 0.7547, 0.7526, 0.7506, 0.7485, 0.7465, 0.7445, 0.7424, 0.7404, 0.7377, 0.7349, 0.7320, 0.7292, 0.7264, 0.7236, 0.7207, 0.7179, 0.7151, 0.7123, 0.7091, 0.7047, 0.7002, 0.6957, 0.6913, 0.6868, 0.6824, 0.6779, 0.6735, 0.6693, 0.6659, 0.6626, 0.6592, 0.6559, 0.6526, 0.6492, 0.6459, 0.6425, 0.6392, 0.6359, 0.6327, 0.6294, 0.6261, 0.6229, 0.6196, 0.6163, 0.6131, 0.6098, 0.6065, 0.6033, 0.6000, 0.5980, 0.5959, 0.5939, 0.5918, 0.5898, 0.5877, 0.5857, 0.5837, 0.5816, 0.5796, 0.5775, 0.5755, 0.5735, 0.5714, 0.5692, 0.5668, 0.5643, 0.5619, 0.5594, 0.5570, 0.5545, 0.5521, 0.5496, 0.5472, 0.5448, 0.5423, 0.5398, 0.5365, 0.5331, 0.5298, 0.5264, 0.5231, 0.5198, 0.5164, 0.5131, 0.5097, 0.5058, 0.5018, 0.4979, 0.4940, 0.4901, 0.4862, 0.4822, 0.4783, 0.4744, 0.4705, 0.4666, 0.4627, 0.4587, 0.4548, 0.4509, 0.4470, 0.4431, 0.4391, 0.4352, 0.4313, 0.4282, 0.4256, 0.4230, 0.4204, 0.4177, 0.4151, 0.4125, 0.4099, 0.4072, 0.4046, 0.4020, 0.3993, 0.3962, 0.3931, 0.3901, 0.3870, 0.3840, 0.3809, 0.3778, 0.3748, 0.3717, 0.3686, 0.3656, 0.3625, 0.3595, 0.3568, 0.3541, 0.3513, 0.3486, 0.3459, 0.3432, 0.3404, 0.3377, 0.3350, 0.3323, 0.3296, 0.3268, 0.3241, 0.3214, 0.3185, 0.3154, 0.3124, 0.3093, 0.3063, 0.3032, 0.3001, 0.2971, 0.2940, 0.2909, 0.2879, 0.2848, 0.2818, 0.2787, 0.2756, 0.2726, 0.2695, 0.2664, 0.2634, 0.2603, 0.2573, 0.2542, 0.2511, 0.2481, 0.2450, 0.2419, 0.2389, 0.2358, 0.2328, 0.2298, 0.2282, 0.2266, 0.2249, 0.2233, 0.2217, 0.2200, 0.2184, 0.2168, 0.2151, 0.2135, 0.2119, 0.2102, 0.2086, 0.2070, 0.2053, 0.2037, 0.2021, 0.2004, 0.1988, 0.1972, 0.1955, 0.1939, 0.1923, 0.1906, 0.1886, 0.1864, 0.1842, 0.1820, 0.1797, 0.1775, 0.1753, 0.1731, 0.1708, 0.1686, 0.1664, 0.1641, 0.1619, 0.1597, 0.1575, 0.1552, 0.1530, 0.1508, 0.1480, 0.1449, 0.1419, 0.1388, 0.1358, 0.1327, 0.1296, 0.1266, 0.1235, 0.1204, 0.1174, 0.1143, 0.1113, 0.1091, 0.1077, 0.1063, 0.1048, 0.1034, 0.1019, 0.1005, 0.0991, 0.0976, 0.0962, 0.0947, 0.0933, 0.0919, 0.0904, 0.0900, 0.0900, 0.0900, 0.0900, 0.0900, 0.0900 ]),
30
+ "blue": np.asarray([ 0.3900, 0.3925, 0.3949, 0.3974, 0.3998, 0.4056, 0.4118, 0.4179, 0.4240, 0.4301, 0.4363, 0.4424, 0.4485, 0.4546, 0.4608, 0.4669, 0.4730, 0.4791, 0.4856, 0.4921, 0.4985, 0.5050, 0.5115, 0.5180, 0.5245, 0.5310, 0.5374, 0.5439, 0.5504, 0.5569, 0.5634, 0.5699, 0.5746, 0.5793, 0.5840, 0.5888, 0.5935, 0.5982, 0.6029, 0.6076, 0.6123, 0.6170, 0.6217, 0.6263, 0.6309, 0.6355, 0.6401, 0.6447, 0.6493, 0.6515, 0.6532, 0.6550, 0.6567, 0.6585, 0.6602, 0.6620, 0.6637, 0.6655, 0.6672, 0.6690, 0.6700, 0.6700, 0.6700, 0.6700, 0.6700, 0.6700, 0.6700, 0.6699, 0.6686, 0.6672, 0.6658, 0.6645, 0.6631, 0.6618, 0.6604, 0.6565, 0.6516, 0.6467, 0.6418, 0.6357, 0.6289, 0.6221, 0.6153, 0.6085, 0.6016, 0.5948, 0.5872, 0.5777, 0.5682, 0.5587, 0.5491, 0.5396, 0.5301, 0.5206, 0.5099, 0.4992, 0.4885, 0.4778, 0.4670, 0.4563, 0.4455, 0.4347, 0.4238, 0.4129, 0.4020, 0.3911, 0.3802, 0.3694, 0.3586, 0.3479, 0.3372, 0.3265, 0.3158, 0.3050, 0.2959, 0.2883, 0.2806, 0.2730, 0.2653, 0.2577, 0.2500, 0.2432, 0.2364, 0.2296, 0.2228, 0.2160, 0.2092, 0.2024, 0.1971, 0.1927, 0.1882, 0.1837, 0.1793, 0.1748, 0.1704, 0.1659, 0.1615, 0.1573, 0.1532, 0.1491, 0.1450, 0.1409, 0.1368, 0.1328, 0.1287, 0.1246, 0.1205, 0.1164, 0.1123, 0.1089, 0.1062, 0.1036, 0.1010, 0.0984, 0.0957, 0.0931, 0.0905, 0.0879, 0.0852, 0.0826, 0.0800, 0.0783, 0.0765, 0.0748, 0.0730, 0.0713, 0.0695, 0.0678, 0.0660, 0.0643, 0.0625, 0.0608, 0.0597, 0.0591, 0.0586, 0.0580, 0.0575, 0.0569, 0.0563, 0.0558, 0.0552, 0.0547, 0.0541, 0.0536, 0.0530, 0.0524, 0.0519, 0.0513, 0.0508, 0.0502, 0.0495, 0.0487, 0.0479, 0.0471, 0.0462, 0.0454, 0.0446, 0.0438, 0.0430, 0.0421, 0.0413, 0.0405, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0400, 0.0408, 0.0422, 0.0436, 0.0449, 0.0463, 0.0476, 0.0490, 0.0504, 0.0517, 0.0531, 0.0544, 0.0558, 0.0572, 0.0585, 0.0599, 0.0617, 0.0635, 0.0654, 0.0672, 0.0690, 0.0709, 0.0727, 0.0745, 0.0764, 0.0782, 0.0801, 0.0819, 0.0837, 0.0856, 0.0874, 0.0892, 0.0906, 0.0915, 0.0924, 0.0934, 0.0943, 0.0953, 0.0962, 0.0972, 0.0981, 0.0990, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.0996, 0.0989, 0.0981, 0.0974, 0.0967, 0.0960, 0.0953, 0.0945, 0.0938, 0.0931, 0.0924, 0.0916, 0.0909, 0.0902, 0.0895, 0.0887, 0.0879, 0.0872, 0.0864, 0.0856, 0.0849, 0.0841, 0.0833, 0.0826, 0.0818, 0.0810, 0.0803, 0.0797, 0.0793, 0.0788, 0.0783, 0.0779, 0.0774, 0.0770, 0.0765, 0.0761, 0.0756, 0.0752, 0.0747, 0.0743, 0.0738, 0.0734, 0.0729, 0.0724, 0.0720, 0.0715, 0.0711, 0.0706, 0.0702, 0.0706, 0.0716, 0.0727, 0.0737, 0.0747, 0.0757, 0.0768, 0.0778, 0.0788, 0.0798, 0.0817, 0.0837, 0.0858, 0.0878, 0.0898, 0.0919, 0.0939, 0.0960, 0.0980, 0.1001, 0.1069, 0.1137, 0.1205, 0.1273, 0.1341, 0.1410, 0.1478, 0.1582, 0.1705, 0.1827, 0.1950, 0.2066, 0.2177, 0.2288, 0.2400, 0.2511, 0.2622, 0.2734, 0.2845, 0.2957, 0.3075, 0.3197, 0.3320, 0.3443, 0.3565, 0.3688, 0.3810, 0.3928, 0.4033, 0.4138, 0.4243, 0.4348, 0.4453, 0.4530, 0.4584, 0.4639, 0.4693, 0.4748, 0.4802, 0.4856, 0.4907, 0.4942, 0.4977, 0.5012, 0.5047, 0.5082, 0.5100, 0.5100, 0.5100, 0.5100, 0.5100, 0.5100, 0.5100, 0.5087, 0.5073, 0.5060, 0.5046, 0.5033, 0.5019, 0.5005, 0.4983, 0.4956, 0.4929, 0.4902, 0.4874, 0.4847, 0.4820, 0.4795, 0.4778, 0.4760, 0.4743, 0.4725, 0.4708, 0.4690, 0.4673, 0.4655, 0.4638, 0.4620, 0.4603, 0.4581, 0.4558, 0.4535, 0.4512, 0.4489, 0.4466, 0.4443, 0.4420, 0.4397, 0.4374, 0.4351, 0.4328, 0.4305, 0.4280, 0.4253, 0.4227, 0.4201, 0.4175, 0.4148, 0.4122, 0.4096, 0.4070, 0.4043, 0.4017, 0.3988, 0.3955, 0.3921, 0.3888, 0.3855, 0.3821, 0.3788, 0.3754, 0.3721, 0.3687, 0.3652, 0.3617, 0.3582, 0.3547, 0.3512, 0.3477, 0.3442, 0.3407, 0.3372, 0.3337, 0.3302, 0.3269, 0.3237, 0.3204, 0.3171, 0.3139, 0.3106, 0.3073, 0.3041, 0.3008, 0.2975, 0.2943, 0.2910, 0.2880, 0.2851, 0.2822, 0.2794, 0.2765, 0.2736, 0.2707, 0.2678, 0.2649, 0.2621, 0.2592, 0.2563, 0.2534, 0.2505, 0.2480, 0.2455, 0.2431, 0.2406, 0.2382, 0.2357, 0.2333, 0.2308, 0.2284, 0.2260, 0.2235, 0.2211, 0.2193, 0.2180, 0.2167, 0.2154, 0.2141, 0.2128, 0.2115, 0.2102, 0.2089, 0.2077, 0.2064, 0.2051, 0.2038, 0.2025, 0.2012, 0.1999, 0.1974, 0.1950, 0.1925, 0.1900, 0.1876, 0.1851, 0.1827, 0.1802, 0.1778, 0.1753, 0.1729, 0.1704, 0.1678, 0.1652, 0.1626, 0.1600, 0.1573, 0.1547, 0.1521, 0.1495, 0.1469, 0.1442, 0.1416, 0.1390, 0.1364, 0.1337, 0.1311, 0.1285, 0.1259, 0.1232, 0.1206, 0.1180, 0.1154, 0.1127, 0.1101, 0.1086, 0.1072, 0.1057, 0.1043, 0.1029, 0.1014, 0.1000, 0.0985, 0.0971, 0.0956, 0.0942, 0.0928, 0.0913, 0.0900, 0.0900, 0.0900, 0.0900, 0.0900 ]),
31
+ "green": np.asarray([ 0.0700, 0.0691, 0.0681, 0.0672, 0.0662, 0.0653, 0.0643, 0.0634, 0.0625, 0.0615, 0.0606, 0.0597, 0.0588, 0.0579, 0.0570, 0.0562, 0.0553, 0.0544, 0.0535, 0.0527, 0.0518, 0.0509, 0.0500, 0.0500, 0.0500, 0.0500, 0.0500, 0.0500, 0.0500, 0.0500, 0.0500, 0.0500, 0.0513, 0.0526, 0.0538, 0.0551, 0.0564, 0.0577, 0.0590, 0.0603, 0.0616, 0.0629, 0.0642, 0.0654, 0.0667, 0.0680, 0.0693, 0.0706, 0.0720, 0.0734, 0.0747, 0.0761, 0.0774, 0.0788, 0.0808, 0.0878, 0.0949, 0.1019, 0.1089, 0.1159, 0.1259, 0.1401, 0.1544, 0.1687, 0.1830, 0.1998, 0.2191, 0.2384, 0.2576, 0.2769, 0.2961, 0.3196, 0.3441, 0.3685, 0.3930, 0.4175, 0.4419, 0.4651, 0.4884, 0.5117, 0.5350, 0.5583, 0.5816, 0.6049, 0.6281, 0.6461, 0.6636, 0.6810, 0.6985, 0.7160, 0.7329, 0.7472, 0.7615, 0.7758, 0.7901, 0.8021, 0.8089, 0.8157, 0.8225, 0.8293, 0.8361, 0.8429, 0.8497, 0.8553, 0.8609, 0.8665, 0.8720, 0.8776, 0.8832, 0.8888, 0.8943, 0.8999, 0.9030, 0.9061, 0.9091, 0.9122, 0.9153, 0.9183, 0.9204, 0.9214, 0.9223, 0.9233, 0.9242, 0.9251, 0.9261, 0.9270, 0.9280, 0.9289, 0.9298, 0.9300, 0.9300, 0.9300, 0.9300, 0.9300, 0.9300, 0.9300, 0.9300, 0.9300, 0.9292, 0.9284, 0.9275, 0.9266, 0.9257, 0.9249, 0.9240, 0.9231, 0.9222, 0.9214, 0.9205, 0.9187, 0.9156, 0.9126, 0.9095, 0.9064, 0.9034, 0.9003, 0.8973, 0.8942, 0.8911, 0.8870, 0.8823, 0.8776, 0.8729, 0.8682, 0.8635, 0.8588, 0.8540, 0.8493, 0.8446, 0.8399, 0.8343, 0.8288, 0.8232, 0.8176, 0.8120, 0.8065, 0.8009, 0.7953, 0.7896, 0.7801, 0.7706, 0.7610, 0.7515, 0.7420, 0.7325, 0.7230, 0.7115, 0.6993, 0.6870, 0.6748, 0.6625, 0.6502, 0.6380, 0.6249, 0.6102, 0.5955, 0.5808, 0.5661, 0.5514, 0.5367, 0.5220, 0.5078, 0.4955, 0.4833, 0.4710, 0.4588, 0.4465, 0.4343, 0.4220, 0.4098, 0.3986, 0.3875, 0.3764, 0.3652, 0.3541, 0.3430, 0.3318, 0.3207, 0.3098, 0.3044, 0.2991, 0.2937, 0.2883, 0.2830, 0.2776, 0.2723, 0.2669, 0.2615, 0.2562, 0.2508, 0.2455, 0.2401, 0.2375, 0.2349, 0.2323, 0.2297, 0.2272, 0.2246, 0.2220, 0.2194, 0.2168, 0.2143, 0.2117, 0.2091, 0.2065, 0.2039, 0.2014, 0.1993, 0.1977, 0.1962, 0.1947, 0.1932, 0.1916, 0.1901, 0.1900, 0.1900, 0.1900, 0.1900, 0.1900, 0.1907, 0.1937, 0.1968, 0.1999, 0.2029, 0.2060, 0.2091, 0.2121, 0.2152, 0.2183, 0.2218, 0.2258, 0.2299, 0.2340, 0.2381, 0.2422, 0.2462, 0.2503, 0.2544, 0.2585, 0.2626, 0.2667, 0.2707, 0.2748, 0.2789, 0.2830, 0.2871, 0.2912, 0.2953, 0.2993, 0.3034, 0.3075, 0.3117, 0.3162, 0.3206, 0.3251, 0.3296, 0.3340, 0.3385, 0.3429, 0.3474, 0.3519, 0.3565, 0.3611, 0.3657, 0.3703, 0.3749, 0.3794, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3800, 0.3805, 0.3814, 0.3823, 0.3833, 0.3842, 0.3852, 0.3861, 0.3871, 0.3880, 0.3889, 0.3899, 0.3925, 0.3953, 0.3981, 0.4010, 0.4038, 0.4066, 0.4095, 0.4123, 0.4151, 0.4179, 0.4205, 0.4224, 0.4243, 0.4262, 0.4280, 0.4299, 0.4318, 0.4337, 0.4356, 0.4375, 0.4393, 0.4408, 0.4421, 0.4434, 0.4447, 0.4460, 0.4473, 0.4486, 0.4499, 0.4512, 0.4524, 0.4537, 0.4550, 0.4563, 0.4576, 0.4589, 0.4601, 0.4610, 0.4618, 0.4627, 0.4635, 0.4643, 0.4652, 0.4660, 0.4669, 0.4677, 0.4686, 0.4694, 0.4703, 0.4711, 0.4719, 0.4728, 0.4736, 0.4745, 0.4753, 0.4762, 0.4770, 0.4779, 0.4787, 0.4796, 0.4804, 0.4812, 0.4820, 0.4828, 0.4836, 0.4845, 0.4853, 0.4861, 0.4869, 0.4877, 0.4885, 0.4894, 0.4900, 0.4900, 0.4900, 0.4900, 0.4900, 0.4900, 0.4900, 0.4900, 0.4900, 0.4893, 0.4862, 0.4831, 0.4801, 0.4770, 0.4739, 0.4709, 0.4678, 0.4647, 0.4617, 0.4589, 0.4564, 0.4540, 0.4515, 0.4491, 0.4467, 0.4442, 0.4418, 0.4393, 0.4369, 0.4344, 0.4320, 0.4295, 0.4271, 0.4246, 0.4222, 0.4197, 0.4170, 0.4144, 0.4117, 0.4090, 0.4064, 0.4037, 0.4010, 0.3984, 0.3957, 0.3930, 0.3904, 0.3877, 0.3851, 0.3824, 0.3797, 0.3771, 0.3744, 0.3717, 0.3692, 0.3669, 0.3645, 0.3622, 0.3599, 0.3575, 0.3552, 0.3529, 0.3505, 0.3482, 0.3459, 0.3435, 0.3412, 0.3389, 0.3365, 0.3342, 0.3319, 0.3294, 0.3263, 0.3233, 0.3202, 0.3171, 0.3141, 0.3110, 0.3079, 0.3049, 0.3018, 0.2987, 0.2957, 0.2926, 0.2896, 0.2865, 0.2834, 0.2804, 0.2773, 0.2742, 0.2712, 0.2681, 0.2651, 0.2620, 0.2589, 0.2559, 0.2528, 0.2497, 0.2467, 0.2436, 0.2406, 0.2383, 0.2363, 0.2342, 0.2322, 0.2302, 0.2281, 0.2261, 0.2240, 0.2220, 0.2200, 0.2179, 0.2159, 0.2138, 0.2118, 0.2098, 0.2081, 0.2065, 0.2048, 0.2031, 0.2014, 0.1998, 0.1981, 0.1964, 0.1948, 0.1931, 0.1914, 0.1898, 0.1881, 0.1864, 0.1847, 0.1831, 0.1814, 0.1796, 0.1770, 0.1744, 0.1719, 0.1693, 0.1667, 0.1641, 0.1615, 0.1590, 0.1564, 0.1538, 0.1512, 0.1486, 0.1461, 0.1435, 0.1409, 0.1386, 0.1363, 0.1341, 0.1319, 0.1296, 0.1274, 0.1252, 0.1230, 0.1207, 0.1190, 0.1175, 0.1161, 0.1146, 0.1131, 0.1117, 0.1102, 0.1087, 0.1073, 0.1058, 0.1043, 0.1028, 0.1014, 0.0999, 0.0984, 0.0970, 0.0955, 0.0940, 0.0925, 0.0911, 0.0900, 0.0900, 0.0900, 0.0900 ])
32
+ }
33
+ self.ssc_wavelengths = range(400, 1001, 1)
34
+
35
+ def correct_ssc(self, spectrum, wavelengths, on_red=False):
36
+ if spectrum is None or wavelengths is None:
37
+ log.error("can't correct without spectrum and wavelengths")
38
+ return spectrum
39
+
40
+ if len(spectrum) != len(wavelengths):
41
+ log.error("spectrum and wavelengths have different lengths")
42
+ return spectrum
43
+
44
+ corrected = []
45
+ for i in range(len(spectrum)):
46
+ reported_intensity = spectrum[i]
47
+ wavelength = wavelengths[i]
48
+
49
+ green_factor = np.interp(wavelength, self.ssc_wavelengths, self.ssc_factors["green"])
50
+ color_factor = np.interp(wavelength, self.ssc_wavelengths, self.ssc_factors["red" if on_red else "blue"])
51
+
52
+ true_intensity = reported_intensity / (green_factor + color_factor)
53
+ corrected.append(true_intensity)
54
+ on_red = not on_red
55
+
56
+ return corrected
57
+
58
+ def bin_2x2(self, spectrum):
59
+ if spectrum is None or len(spectrum) == 0:
60
+ return spectrum
61
+ binned = []
62
+ for i in range(len(spectrum)-1):
63
+ binned.append((spectrum[i] + spectrum[i+1]) / 2.0)
64
+ binned.append(spectrum[-1])
65
+ return binned
66
+
67
+ def bin_4x2(self, spectrum, x=None):
68
+ """
69
+ - for physical pixels 0, 1, 2, 3...1951,
70
+ - Which are logically grouped 2x2 into 976 pixel groups A, B, C...
71
+ - Where A has physical pixels 0-1, B has 2-3, etc
72
+ - That would be binning (A+B)/2, and (B+C)/2, etc
73
+ - For 974 binned pixels, each averaging four physical columns
74
+ - Adjacent “bin+step” pixels would each overlap by two physical columns
75
+
76
+ @par Regarding Wavecal
77
+
78
+ Aassume the unit was wavecal'd using bin_2x2. That means the wavelength
79
+ assigned to pixel 0 is based on the bin_2x2'd average of pixel 0 and 1,
80
+ and may be thought of as the true wavelength of pixel 0.5 (halfway
81
+ between pixels 0 and 1).
82
+
83
+ Now that we're expanding to 4 pixels, the "true wavelength" of any
84
+ 4-pixel span will be the bin_2x2'd wavelength of 'start+1' pixel, which
85
+ will contain the averaged "true pixels" start+1, and start+2.
86
+ """
87
+ binned, new_x = [], []
88
+ for start in range(0, len(spectrum) - 4, 2):
89
+ binned.append(sum(spectrum[start:start+4])/4.0)
90
+ if x is not None:
91
+ new_x.append(x[start+1])
92
+ if x is None:
93
+ return binned
94
+ else:
95
+ return binned, new_x
96
+
97
+ def bin_4x2_interp(self, spectrum, wavelengths):
98
+ # first do the normal bin_4x2, yielding 974 intensities and wavelengths
99
+ y, x = self.bin_4x2(spectrum, wavelengths)
100
+
101
+ # now interpolate that BACK to the original pixel count
102
+ y_interp = np.interp(wavelengths, x, y)
103
+
104
+ # we don't need to return a modified wavecal, because the original
105
+ # (presumably bin_2x2'd) wavecal should still be correct
106
+ return y_interp
107
+
108
+ def bin_4x2_avg(self, spectrum):
109
+ binned = []
110
+ for i in range(0, len(spectrum) - 1, 2):
111
+ # 0, 1, 2, 3, 4, 5
112
+ # \__/ \__/
113
+ # A1 B1 where i == 0
114
+ # \___AB1__/
115
+ #
116
+ # \__/ \__/ where i == 2
117
+ # A2 B2
118
+ # \___AB2__/
119
+ A = (spectrum[i] + spectrum[i+1]) / 2.0
120
+ binned.append(A)
121
+ if i + 3 < len(spectrum):
122
+ B = (spectrum[i+2] + spectrum[i+3]) / 2.0
123
+ AB = (A + B) / 2.0 # adds no information, maintains number of data points
124
+ binned.append(AB)
125
+
126
+ # Why is this necessary? Because due to the above logic, when advancing
127
+ # to the next pair of pixels (to create the new 'A'), we don't yet know
128
+ # if we'll output ONE averaged pixel (the new A) or THREE (the new A, AB
129
+ # and B). Depending on the number of pixels in the input spectrum, if
130
+ # the total spectrum size is divisible by 2 but not by 4, then there may
131
+ # be a trailing A which is not followed by either an AB or B, leaving this
132
+ # to round-out the spectrum length. In even 'odder' cases, in which the
133
+ # source spectrum is not divisible by 2, this would again be needed as the
134
+ # final A would still leave a gap.
135
+ #
136
+ # As to why we're filling-out the missing pixels with copies of the last
137
+ # AVERAGED value, rather than "unaveraged raw data", it's a matter of
138
+ # aesthetics and data cleanliness: it is not unreasonable or uncommon to
139
+ # have "flat" regions at the ends of a spectrum, but appending unaveraged
140
+ # data to an otherwise averaged spectrum can create "jaggies" or a sudden
141
+ # (if brief) cliff which can confuse other post-processing algorithms
142
+ # (performing baseline correction and the like).
143
+ #
144
+ # Long story short, if you are calling this algorithm, then presumably
145
+ # smoothness is desired, and that is what this ensures.
146
+ while len(binned) < len(spectrum):
147
+ log.debug("bin_4x2_avg: filling-out")
148
+ binned.append(binned[-1])
149
+ log.debug(f"bin_4x2_avg: given spectrum len {len(spectrum)}, returning len {len(binned)}")
150
+
151
+ return binned
152
+
153
+ # Unit Test
154
+ if __name__ == "__main__":
155
+ # unbinned (but 3-scan averaged) cyclohexane from 633XS,
156
+ # ALREADY CROPPED TO HORIZONTAL ROI (1093 wavelength/pixel pairs)
157
+ data = [ 646.86, 248.00, 646.97, 1916.33, 647.09, 290.67, 647.21, 2012.00, 647.32, 209.00, 647.44, 1998.00, 647.55, 236.67, 647.67, 2089.00, 647.78, 317.00, 647.90, 2169.67, 648.01, 339.67, 648.13, 2400.33, 648.24, 356.33, 648.36, 2562.00, 648.47, 301.33, 648.59, 2680.00, 648.70, 398.00, 648.82, 2735.33, 648.93, 370.67, 649.05, 2772.00, 649.16, 333.00, 649.28, 2729.00, 649.39, 374.33, 649.51, 2670.67, 649.62, 442.33, 649.74, 2661.00, 649.85, 293.67, 649.97, 2541.33, 650.08, 354.33, 650.19, 2800.33, 650.31, 403.33, 650.42, 3043.67, 650.54, 424.67, 650.65, 3120.00, 650.77, 433.00, 650.88, 3107.33, 650.99, 443.67, 651.11, 2881.67, 651.22, 437.00, 651.34, 2691.33, 651.45, 505.33, 651.56, 2557.33, 651.68, 366.00, 651.79, 2606.33, 651.90, 370.00, 652.02, 2498.00, 652.13, 388.00, 652.25, 2473.67, 652.36, 267.67, 652.47, 2470.33, 652.59, 315.00, 652.70, 2433.67, 652.81, 321.33, 652.93, 2475.33, 653.04, 297.00, 653.15, 2376.33, 653.27, 327.00, 653.38, 2403.33, 653.49, 316.00, 653.61, 2459.67, 653.72, 419.67, 653.83, 2450.33, 653.94, 279.67, 654.06, 2225.00, 654.17, 333.00, 654.28, 2199.67, 654.40, 387.67, 654.51, 2337.67, 654.62, 377.67, 654.73, 2276.00, 654.85, 259.67, 654.96, 2274.67, 655.07, 220.00, 655.18, 2219.67, 655.30, 292.67, 655.41, 2299.00, 655.52, 322.33, 655.63, 2224.00, 655.75, 293.33, 655.86, 2220.00, 655.97, 296.67, 656.08, 2334.00, 656.19, 246.67, 656.31, 2239.00, 656.42, 227.67, 656.53, 2288.67, 656.64, 348.00, 656.75, 2246.33, 656.87, 373.33, 656.98, 2175.33, 657.09, 250.00, 657.20, 2133.00, 657.31, 232.33, 657.42, 2191.00, 657.54, 258.33, 657.65, 2248.33, 657.76, 284.00, 657.87, 2223.00, 657.98, 225.00, 658.09, 2252.33, 658.20, 323.00, 658.32, 2212.33, 658.43, 284.67, 658.54, 2280.67, 658.65, 312.67, 658.76, 2240.33, 658.87, 284.00, 658.98, 2200.00, 659.09, 330.67, 659.20, 2134.33, 659.32, 295.33, 659.43, 2173.33, 659.54, 293.33, 659.65, 2122.33, 659.76, 292.33, 659.87, 2233.33, 659.98, 249.00, 660.09, 2160.00, 660.20, 268.67, 660.31, 2180.67, 660.42, 244.33, 660.53, 2167.67, 660.64, 289.00, 660.75, 2108.00, 660.86, 306.00, 660.97, 2117.00, 661.08, 310.00, 661.19, 2245.33, 661.30, 337.33, 661.42, 2197.33, 661.53, 353.67, 661.64, 2241.33, 661.75, 287.00, 661.86, 2164.00, 661.97, 321.33, 662.08, 2244.00, 662.18, 275.67, 662.29, 2371.33, 662.40, 399.00, 662.51, 2344.33, 662.62, 232.00, 662.73, 2359.00, 662.84, 377.67, 662.95, 2326.00, 663.06, 232.67, 663.17, 2262.67, 663.28, 356.67, 663.39, 2302.00, 663.50, 377.67, 663.61, 2229.00, 663.72, 276.33, 663.83, 2255.00, 663.94, 248.33, 664.05, 2295.67, 664.16, 347.67, 664.26, 2310.33, 664.37, 275.00, 664.48, 2384.00, 664.59, 350.67, 664.70, 2383.67, 664.81, 289.33, 664.92, 2378.67, 665.03, 377.00, 665.14, 2508.00, 665.25, 357.33, 665.35, 2537.33, 665.46, 438.00, 665.57, 2736.00, 665.68, 441.33, 665.79, 2939.67, 665.90, 469.33, 666.01, 3284.00, 666.11, 613.00, 666.22, 4927.00, 666.33, 1581.00, 666.44, 11753.67, 666.55, 3125.33, 666.66, 16871.67, 666.76, 3997.33, 666.87, 16807.00, 666.98, 3465.67, 667.09, 14626.00, 667.20, 2749.67, 667.30, 11620.67, 667.41, 2076.33, 667.52, 8486.00, 667.63, 1324.67, 667.74, 5059.33, 667.84, 744.67, 667.95, 2913.67, 668.06, 423.00, 668.17, 2609.00, 668.28, 469.33, 668.38, 2598.33, 668.49, 417.33, 668.60, 2431.00, 668.71, 418.67, 668.81, 2550.33, 668.92, 431.67, 669.03, 2441.33, 669.14, 304.67, 669.24, 2436.67, 669.35, 424.33, 669.46, 2431.67, 669.56, 441.33, 669.67, 2415.67, 669.78, 355.00, 669.89, 2333.33, 669.99, 382.00, 670.10, 2352.67, 670.21, 446.33, 670.31, 2316.33, 670.42, 394.33, 670.53, 2375.33, 670.63, 359.00, 670.74, 2362.67, 670.85, 385.33, 670.96, 2479.00, 671.06, 322.33, 671.17, 2474.33, 671.28, 417.67, 671.38, 2369.33, 671.49, 416.00, 671.60, 2348.33, 671.70, 318.00, 671.81, 2442.67, 671.91, 370.00, 672.02, 2452.00, 672.13, 368.33, 672.23, 2390.33, 672.34, 378.00, 672.45, 2439.33, 672.55, 423.00, 672.66, 2396.33, 672.76, 484.00, 672.87, 2334.00, 672.98, 449.33, 673.08, 2338.33, 673.19, 423.67, 673.29, 2358.67, 673.40, 490.00, 673.51, 2382.00, 673.61, 431.00, 673.72, 2316.00, 673.82, 409.00, 673.93, 2344.67, 674.04, 419.33, 674.14, 2393.00, 674.25, 460.33, 674.35, 2389.00, 674.46, 442.00, 674.56, 2451.33, 674.67, 478.67, 674.77, 2558.33, 674.88, 491.33, 674.99, 2579.67, 675.09, 429.33, 675.20, 2607.00, 675.30, 501.33, 675.41, 2736.00, 675.51, 562.33, 675.62, 2969.00, 675.72, 598.00, 675.83, 3204.00, 675.93, 698.00, 676.04, 3649.00, 676.14, 804.67, 676.25, 4322.67, 676.35, 1066.00, 676.46, 5566.33, 676.56, 1649.00, 676.67, 7730.67, 676.77, 2118.33, 676.88, 9089.33, 676.98, 2467.67, 677.09, 9611.00, 677.19, 2358.67, 677.29, 9096.00, 677.40, 2123.00, 677.50, 8134.67, 677.61, 1841.67, 677.71, 6711.00, 677.82, 1416.00, 677.92, 5464.33, 678.03, 1151.33, 678.13, 4296.33, 678.23, 881.33, 678.34, 3629.33, 678.44, 756.67, 678.55, 3268.67, 678.65, 694.33, 678.76, 3010.67, 678.86, 631.00, 678.96, 2923.33, 679.07, 616.33, 679.17, 2732.33, 679.28, 575.67, 679.38, 2527.00, 679.48, 598.33, 679.59, 2711.33, 679.69, 539.00, 679.80, 2617.00, 679.90, 522.00, 680.00, 2609.67, 680.11, 594.67, 680.21, 2625.67, 680.31, 511.33, 680.42, 2525.00, 680.52, 545.33, 680.62, 2577.67, 680.73, 540.33, 680.83, 2600.33, 680.93, 573.33, 681.04, 2610.67, 681.14, 577.00, 681.24, 2647.33, 681.35, 534.33, 681.45, 2617.33, 681.55, 584.33, 681.66, 2582.00, 681.76, 544.00, 681.86, 2645.67, 681.97, 547.33, 682.07, 2570.33, 682.17, 582.67, 682.28, 2771.00, 682.38, 664.67, 682.48, 3075.67, 682.59, 866.33, 682.69, 3553.00, 682.79, 906.67, 682.89, 4156.33, 683.00, 1050.33, 683.10, 4114.00, 683.20, 1098.00, 683.30, 3691.33, 683.41, 902.00, 683.51, 3439.00, 683.61, 852.00, 683.71, 3218.33, 683.82, 818.00, 683.92, 2937.67, 684.02, 693.33, 684.12, 2767.67, 684.23, 711.67, 684.33, 2689.33, 684.43, 623.00, 684.53, 2717.33, 684.64, 666.67, 684.74, 2540.00, 684.84, 712.00, 684.94, 2522.67, 685.04, 659.33, 685.15, 2601.67, 685.25, 605.67, 685.35, 2620.67, 685.45, 630.67, 685.55, 2644.00, 685.66, 663.67, 685.76, 2741.33, 685.86, 771.67, 685.96, 2757.33, 686.06, 748.00, 686.17, 2859.67, 686.27, 798.00, 686.37, 3054.00, 686.47, 807.33, 686.57, 3127.00, 686.67, 837.00, 686.78, 3271.33, 686.88, 921.33, 686.98, 3637.33, 687.08, 1013.67, 687.18, 3984.33, 687.28, 1197.67, 687.38, 4854.33, 687.49, 1668.00, 687.59, 6293.33, 687.69, 2161.67, 687.79, 8230.67, 687.89, 2894.67, 687.99, 10100.33, 688.09, 3375.33, 688.19, 10305.67, 688.30, 3248.33, 688.40, 9796.33, 688.50, 2892.00, 688.60, 8670.00, 688.70, 2588.33, 688.80, 7349.00, 688.90, 2137.67, 689.00, 6271.33, 689.10, 1717.33, 689.20, 5216.00, 689.30, 1477.67, 689.41, 4484.33, 689.51, 1247.33, 689.61, 4228.00, 689.71, 1118.33, 689.81, 3882.00, 689.91, 1057.00, 690.01, 3678.33, 690.11, 972.67, 690.21, 3544.67, 690.31, 1082.00, 690.41, 3455.33, 690.51, 1067.00, 690.61, 3370.00, 690.71, 1017.00, 690.81, 3351.00, 690.91, 967.67, 691.01, 3447.33, 691.11, 1011.33, 691.21, 3563.67, 691.31, 1198.33, 691.41, 3763.67, 691.51, 1189.33, 691.61, 4174.67, 691.71, 1227.33, 691.81, 4434.67, 691.91, 1287.00, 692.01, 4419.67, 692.11, 1408.00, 692.21, 4315.33, 692.31, 1233.67, 692.41, 4054.67, 692.51, 1186.33, 692.61, 3835.67, 692.71, 1108.00, 692.81, 3505.00, 692.91, 1142.00, 693.01, 3347.33, 693.11, 1013.67, 693.21, 3182.00, 693.31, 946.33, 693.41, 3065.67, 693.51, 990.67, 693.61, 3109.67, 693.71, 1015.67, 693.81, 3301.67, 693.91, 976.67, 694.01, 3227.33, 694.11, 1061.00, 694.21, 3428.67, 694.31, 1149.67, 694.40, 3445.67, 694.50, 1195.00, 694.60, 3669.33, 694.70, 1150.33, 694.80, 3859.67, 694.90, 1192.67, 695.00, 4012.00, 695.10, 1222.67, 695.20, 4197.00, 695.30, 1474.00, 695.40, 4681.00, 695.49, 1612.33, 695.59, 5113.00, 695.69, 1933.67, 695.79, 5877.67, 695.89, 2215.33, 695.99, 7021.33, 696.09, 2619.33, 696.19, 8630.33, 696.28, 3430.33, 696.38, 10581.33, 696.48, 4018.33, 696.58, 11778.33, 696.68, 4252.00, 696.78, 11737.00, 696.88, 4076.33, 696.97, 10570.33, 697.07, 3542.33, 697.17, 9343.67, 697.27, 3050.33, 697.37, 7930.00, 697.47, 2616.00, 697.57, 6855.00, 697.66, 2119.33, 697.76, 5764.33, 697.86, 1755.33, 697.96, 4995.33, 698.06, 1643.33, 698.15, 4436.67, 698.25, 1325.67, 698.35, 4104.67, 698.45, 1255.00, 698.55, 3773.67, 698.64, 1188.67, 698.74, 3612.00, 698.84, 1177.00, 698.94, 3340.67, 699.04, 1158.67, 699.13, 3332.00, 699.23, 1106.67, 699.33, 3183.00, 699.43, 977.33, 699.53, 3084.33, 699.62, 955.33, 699.72, 2858.67, 699.82, 991.00, 699.92, 2944.00, 700.01, 851.00, 700.11, 2900.67, 700.21, 906.67, 700.31, 2852.67, 700.40, 907.00, 700.50, 2813.00, 700.60, 939.67, 700.70, 2801.67, 700.79, 872.00, 700.89, 2761.67, 700.99, 951.33, 701.09, 2970.00, 701.18, 930.67, 701.28, 2824.67, 701.38, 890.00, 701.48, 2815.67, 701.57, 869.00, 701.67, 2766.00, 701.77, 893.67, 701.86, 2828.00, 701.96, 890.67, 702.06, 2753.33, 702.15, 880.00, 702.25, 2767.33, 702.35, 846.33, 702.45, 2708.00, 702.54, 803.00, 702.64, 2594.33, 702.74, 846.67, 702.83, 2620.67, 702.93, 837.00, 703.03, 2605.33, 703.12, 834.67, 703.22, 2555.33, 703.32, 868.33, 703.41, 2661.00, 703.51, 868.67, 703.61, 2726.33, 703.70, 856.67, 703.80, 2869.33, 703.90, 1030.00, 703.99, 2788.00, 704.09, 989.67, 704.19, 2942.33, 704.28, 937.33, 704.38, 2888.67, 704.48, 923.67, 704.57, 2783.00, 704.67, 921.33, 704.76, 2730.00, 704.86, 928.67, 704.96, 2665.00, 705.05, 801.33, 705.15, 2552.00, 705.25, 792.00, 705.34, 2595.67, 705.44, 810.00, 705.53, 2611.00, 705.63, 811.67, 705.73, 2441.33, 705.82, 806.67, 705.92, 2347.33, 706.01, 766.33, 706.11, 2381.67, 706.21, 781.67, 706.30, 2382.33, 706.40, 807.33, 706.49, 2432.67, 706.59, 758.33, 706.68, 2454.00, 706.78, 826.00, 706.88, 2412.67, 706.97, 811.67, 707.07, 2395.67, 707.16, 847.33, 707.26, 2295.67, 707.35, 751.67, 707.45, 2358.33, 707.54, 762.67, 707.64, 2429.00, 707.74, 766.00, 707.83, 2494.67, 707.93, 855.67, 708.02, 2492.00, 708.12, 796.67, 708.21, 2461.33, 708.31, 781.00, 708.40, 2389.00, 708.50, 738.33, 708.59, 2456.00, 708.69, 768.67, 708.78, 2456.67, 708.88, 769.33, 708.97, 2526.33, 709.07, 787.67, 709.16, 2407.67, 709.26, 793.33, 709.36, 2456.33, 709.45, 769.00, 709.55, 2333.00, 709.64, 804.00, 709.73, 2418.33, 709.83, 805.33, 709.92, 2448.00, 710.02, 766.67, 710.11, 2383.67, 710.21, 732.00, 710.30, 2383.00, 710.40, 759.33, 710.49, 2381.67, 710.59, 749.67, 710.68, 2376.00, 710.78, 833.00, 710.87, 2402.00, 710.97, 726.67, 711.06, 2267.00, 711.16, 740.67, 711.25, 2369.67, 711.35, 736.00, 711.44, 2266.33, 711.53, 775.67, 711.63, 2283.67, 711.72, 717.67, 711.82, 2309.00, 711.91, 663.33, 712.01, 2267.67, 712.10, 736.67, 712.19, 2376.67, 712.29, 696.67, 712.38, 2313.67, 712.48, 668.33, 712.57, 2228.67, 712.67, 705.33, 712.76, 2360.67, 712.85, 704.67, 712.95, 2137.33, 713.04, 709.33, 713.14, 2196.33, 713.23, 715.67, 713.32, 2274.67, 713.42, 707.33, 713.51, 2183.67, 713.61, 680.33, 713.70, 2184.00, 713.79, 719.33, 713.89, 2182.67, 713.98, 617.67, 714.08, 2179.33, 714.17, 713.00, 714.26, 2241.00, 714.36, 638.00, 714.45, 2234.67, 714.54, 708.00, 714.64, 2262.00, 714.73, 629.00, 714.82, 2262.33, 714.92, 613.67, 715.01, 2218.00, 715.11, 718.00, 715.20, 2196.67, 715.29, 654.67, 715.39, 2208.67, 715.48, 654.67, 715.57, 2156.33, 715.67, 644.00, 715.76, 2137.33, 715.85, 695.33, 715.95, 2178.00, 716.04, 671.00, 716.13, 2278.67, 716.23, 663.00, 716.32, 2108.33, 716.41, 735.33, 716.51, 2161.33, 716.60, 713.00, 716.69, 2139.00, 716.78, 637.00, 716.88, 2218.67, 716.97, 707.33, 717.06, 2194.33, 717.16, 667.00, 717.25, 2270.67, 717.34, 630.33, 717.44, 2143.00, 717.53, 641.00, 717.62, 2263.00, 717.71, 571.67, 717.81, 2125.00, 717.90, 703.67, 717.99, 2178.00, 718.09, 660.00, 718.18, 2136.00, 718.27, 614.00, 718.36, 2143.00, 718.46, 653.67, 718.55, 2206.67, 718.64, 680.33, 718.73, 2065.33, 718.83, 673.33, 718.92, 2090.33, 719.01, 680.33, 719.10, 1964.33, 719.20, 674.33, 719.29, 2057.33, 719.38, 583.00, 719.47, 2198.67, 719.57, 661.67, 719.66, 2070.33, 719.75, 685.33, 719.84, 2100.67, 719.94, 629.67, 720.03, 2121.67, 720.12, 538.33, 720.21, 2063.33, 720.30, 602.67, 720.40, 2078.00, 720.49, 549.33, 720.58, 2012.00, 720.67, 623.00, 720.77, 2030.00, 720.86, 632.33, 720.95, 2066.00, 721.04, 629.00, 721.13, 2040.33, 721.23, 635.33, 721.32, 2063.00, 721.41, 627.33, 721.50, 2025.67, 721.59, 598.00, 721.69, 2017.67, 721.78, 557.00, 721.87, 2117.67, 721.96, 611.33, 722.05, 2033.67, 722.14, 657.00, 722.24, 2051.00, 722.33, 680.33, 722.42, 2020.33, 722.51, 598.33, 722.60, 2109.00, 722.69, 489.00, 722.79, 2057.00, 722.88, 581.67, 722.97, 2140.67, 723.06, 606.00, 723.15, 2002.67, 723.24, 584.33, 723.33, 2140.67, 723.43, 639.33, 723.52, 2024.00, 723.61, 626.33, 723.70, 1937.67, 723.79, 551.33, 723.88, 2097.67, 723.97, 544.00, 724.07, 2072.00, 724.16, 586.33, 724.25, 1921.67, 724.34, 504.33, 724.43, 1981.67, 724.52, 548.67, 724.61, 1921.00, 724.70, 536.67, 724.80, 1987.33, 724.89, 511.00, 724.98, 1994.33, 725.07, 580.67, 725.16, 1880.00, 725.25, 551.67, 725.34, 1964.33, 725.43, 549.00, 725.52, 1839.00, 725.61, 490.67, 725.71, 1976.67, 725.80, 595.67, 725.89, 1906.33, 725.98, 532.67, 726.07, 1871.67, 726.16, 482.67, 726.25, 1905.33, 726.34, 478.67, 726.43, 1932.67, 726.52, 564.00, 726.61, 1929.67, 726.70, 500.33, 726.79, 1761.67, 726.88, 499.00, 726.98, 1892.67, 727.07, 488.33, 727.16, 1981.33, 727.25, 609.33, 727.34, 1941.33, 727.43, 567.00, 727.52, 2006.00, 727.61, 529.67, 727.70, 1964.33, 727.79, 502.67, 727.88, 1956.67, 727.97, 502.33, 728.06, 1900.00, 728.15, 535.00, 728.24, 1959.67, 728.33, 531.00, 728.42, 1928.33, 728.51, 488.00, 728.60, 1878.67, 728.69, 558.33, 728.78, 1957.33, 728.87, 510.00, 728.96, 1924.67, 729.05, 527.33, 729.14, 1845.33, 729.23, 530.33, 729.32, 1857.33, 729.41, 435.33, 729.50, 1867.00, 729.59, 529.00, 729.68, 1878.33, 729.77, 506.00, 729.86, 1822.67, 729.95, 533.00, 730.04, 1844.00, 730.13, 544.00, 730.22, 1846.33, 730.31, 494.00, 730.40, 1949.33, 730.49, 500.00, 730.58, 1899.67, 730.67, 579.00, 730.76, 1855.00, 730.85, 548.67, 730.94, 1993.67, 731.03, 488.00, 731.12, 1889.67, 731.21, 509.33, 731.30, 1828.00, 731.39, 460.67, 731.48, 1839.33, 731.57, 416.67, 731.66, 1941.00, 731.74, 485.33, 731.83, 1870.33, 731.92, 541.00, 732.01, 1907.33, 732.10, 472.33, 732.19, 1834.67, 732.28, 494.00, 732.37, 1863.00, 732.46, 460.67, 732.55, 1849.33, 732.64, 514.67, 732.73, 1941.00, 732.82, 540.33, 732.91, 1871.67, 732.99, 516.33, 733.08, 1865.67, 733.17, 487.00, 733.26, 1944.00, 733.35, 584.00, 733.44, 1896.33, 733.53, 463.33, 733.62, 1831.33, 733.71, 491.00, 733.80, 1873.00, 733.88, 545.00, 733.97, 1903.00, 734.06, 462.67, 734.15, 1888.00, 734.24, 511.67, 734.33, 1959.00, 734.42, 471.00, 734.51, 1809.67, 734.60, 461.00, 734.68, 1765.33, 734.77, 515.00, 734.86, 1885.00, 734.95, 485.67, 735.04, 1743.00, 735.13, 480.67, 735.22, 1887.00, 735.30, 485.67, 735.39, 1923.33, 735.48, 503.33, 735.57, 1880.33, 735.66, 443.67, 735.75, 1949.67, 735.84, 467.33, 735.92, 1970.33, 736.01, 489.67, 736.10, 1961.67, 736.19, 597.67, 736.28, 2049.67, 736.37, 507.67, 736.46, 2056.33, 736.54, 541.67, 736.63, 2082.33, 736.72, 521.33, 736.81, 1968.00, 736.90, 540.33, 736.98, 1950.33, 737.07, 504.00, 737.16, 1968.00, 737.25, 454.67, 737.34, 1863.33, 737.43, 490.33, 737.51, 1934.00, 737.60, 482.67, 737.69, 1937.33, 737.78, 455.67, 737.87, 1909.00, 737.95, 517.33, 738.04, 1831.00, 738.13, 563.33, 738.22, 1877.33, 738.31, 473.67, 738.39, 1839.00, 738.48, 583.33, 738.57, 1875.33, 738.66, 517.00, 738.74, 1842.33, 738.83, 377.33, 738.92, 1868.00, 739.01, 521.67, 739.10, 1741.00, 739.18, 501.33, 739.27, 1792.67, 739.36, 499.67, 739.45, 1832.67, 739.53, 470.00, 739.62, 1824.67, 739.71, 526.67, 739.80, 1799.67, 739.88, 501.67, 739.97, 1819.33, 740.06, 511.33, 740.15, 1753.67, 740.23, 470.67, 740.32, 1761.33, 740.41, 467.33, 740.50, 1708.33, 740.58, 464.33, 740.67, 1781.33, 740.76, 463.33, 740.85, 1773.00, 740.93, 434.67, 741.02, 1743.00, 741.11, 434.00, 741.20, 1853.67, 741.28, 455.33, 741.37, 1824.00, 741.46, 523.33, 741.54, 1776.33, 741.63, 495.00, 741.72, 1822.67, 741.81, 516.67, 741.89, 1878.67, 741.98, 516.67, 742.07, 1803.00, 742.15, 494.67, 742.24, 1886.67, 742.33, 493.33, 742.42, 1820.67, 742.50, 521.00, 742.59, 1846.33, 742.68, 493.33, 742.76, 1929.67, 742.85, 557.33, 742.94, 1915.00, 743.02, 487.33, 743.11, 1942.00, 743.20, 550.67, 743.28, 1992.67, 743.37, 636.00, 743.46, 1955.33, 743.54, 546.00, 743.63, 1941.33, 743.72, 521.67, 743.80, 1948.00, 743.89, 574.00, 743.98, 1956.67, 744.06, 543.33, 744.15, 1969.00, 744.24, 571.67, 744.32, 1856.67, 744.41, 577.00, 744.50, 1834.67, 744.58, 533.67, 744.67, 1826.67, 744.76, 509.67, 744.84, 1769.33, 744.93, 575.00, 745.02, 1679.67, 745.10, 495.33, 745.19, 1832.67, 745.27, 470.33, 745.36, 1704.67, 745.45, 522.67, 745.53, 1717.00, 745.62, 526.67, 745.71, 1658.33, 745.79, 520.00, 745.88, 1608.00, 745.96, 498.33, 746.05, 1587.67, 746.14, 521.33, 746.22, 1685.67, 746.31, 463.33, 746.40, 1654.33, 746.48, 472.67, 746.57, 1649.33, 746.65, 481.00, 746.74, 1641.33, 746.83, 513.00, 746.91, 1624.00, 747.00, 512.67, 747.08, 1725.00, 747.17, 517.33, 747.26, 1650.33, 747.34, 520.67, 747.43, 1623.33, 747.51, 489.67, 747.60, 1739.33, 747.68, 519.33, 747.77, 1745.33, 747.86, 465.67, 747.94, 1629.00, 748.03, 493.67, 748.11, 1751.33, 748.20, 521.00, 748.28, 1811.33, 748.37, 502.67, 748.46, 1704.67, 748.54, 491.00, 748.63, 1791.33, 748.71, 597.33, 748.80, 1787.33, 748.88, 612.33, 748.97, 1945.00, 749.05, 555.33, 749.14, 1967.00, 749.23, 611.67, 749.31, 1951.33, 749.40, 668.67, 749.48, 1968.33, 749.57, 590.00, 749.65, 1834.67, 749.74, 555.33, 749.82, 1852.33, 749.91, 535.33, 749.99, 1816.00, 750.08, 551.67, 750.16, 1818.33, 750.25, 596.00, 750.33, 1695.33, 750.42, 484.67, 750.51, 1850.67, 750.59, 569.00, 750.68, 1706.33, 750.76, 571.33, 750.85, 1764.00, 750.93, 521.67, 751.02, 1732.67, 751.10, 463.33, 751.19, 1717.00, 751.27, 524.33, 751.36, 1738.00, 751.44, 513.67, 751.53, 1636.67, 751.61, 481.33, 751.70, 1691.67, 751.78, 503.67, 751.87, 1715.00, 751.95, 488.33, 752.04, 1676.00, 752.12, 462.33, 752.20, 1743.00, 752.29, 550.33, 752.37, 1757.67, 752.46, 576.67, 752.54, 1706.67, 752.63, 536.00, 752.71, 1708.33, 752.80, 531.33, 752.88, 1811.67, 752.97, 564.00, 753.05, 1746.67, 753.14, 549.33, 753.22, 1729.33, 753.31, 596.67, 753.39, 1843.00, 753.47, 499.33 ]
158
+ x, y = [], []
159
+ for i in range(0, len(data) - 1, 2):
160
+ x.append(data[i])
161
+ y.append(data[i+1])
162
+
163
+ imx385 = IMX385()
164
+ binned = imx385.bin_2x2(y)
165
+ corrected = imx385.correct_ssc(y, x)
166
+ both = imx385.bin_2x2(corrected)
167
+
168
+ mode_3, mode_3_x = imx385.bin_4x2(y, x)
169
+ mode_4 = imx385.bin_4x2_interp(y, x)
170
+ mode_5 = imx385.bin_4x2_avg(y)
171
+
172
+ print(f"lengths, x {len(x)}, y {len(y)}, binned {len(binned)}, corrected {len(corrected)}, both {len(both)}, mode_3 {len(mode_3)}, mode_4 {len(mode_4)}, mode_5 {len(mode_5)}")
173
+
174
+ print("pixel, wavelength, raw, binned, ssc_corrected, both, mode_4, mode_5, mode_3_x, mode_3")
175
+ for i in range(len(x)):
176
+ print(f"{i}, {x[i]:8.2f}, {y[i]:8.2f}, {binned[i]:8.2f}, {corrected[i]:8.2f}, {both[i]:8.2f}, {mode_4[i]:8.2f}, {mode_5[i]:8.2f}", end='')
177
+ if i < len(mode_3):
178
+ print(f", {mode_3_x[i]:8.2f}, {mode_3[i]:8.2f}", end='')
179
+ print()
@@ -0,0 +1,40 @@
1
+ import logging
2
+
3
+ from .SpectrometerResponse import SpectrometerResponse
4
+ from .SpectrometerResponse import ErrorLevel
5
+
6
+ log = logging.getLogger(__name__)
7
+
8
+ class InterfaceDevice:
9
+ def __init__(self):
10
+ """
11
+ Any class that communicates to a spectrometer should inherit this class.
12
+ It provides the common functions that avoid repeated code.
13
+
14
+ @todo move scan averaging, TakeOneRequest logic upstream from
15
+ WasatchDevice, AndorDevice etc to here (simplify children of this
16
+ class while while increasing code-reuse and consistency across all
17
+ InterfaceDevice variants)
18
+ """
19
+ self.process_f = {}
20
+ self.remaining_throwaways = 0
21
+
22
+ def handle_requests(self, requests):
23
+ responses = []
24
+ for request in requests:
25
+ try:
26
+ cmd = request.cmd
27
+ proc_func = self.process_f.get(cmd, None)
28
+ if proc_func is None:
29
+ log.error(f"handle_requests: unsupported command {request.cmd}")
30
+ responses.append(SpectrometerResponse(error_msg=f"unsupported cmd {request.cmd}", error_lvl=ErrorLevel.low))
31
+ elif request.args == [] and request.kwargs == {}:
32
+ # log.debug(f"handle_requests: relaying {request.cmd} w/o args to {proc_func}")
33
+ responses.append(proc_func())
34
+ else:
35
+ # log.debug(f"handle_requests: relaying {request.cmd} with args {request.args}, kwargs {request.kwargs} to proc_func {proc_func}")
36
+ responses.append(proc_func(*request.args, **request.kwargs))
37
+ except Exception as e:
38
+ log.error(f"error in handling request {request} of {e}", exc_info=1)
39
+ responses.append(SpectrometerResponse(error_msg="error processing cmd", error_lvl=ErrorLevel.medium))
40
+ return responses