wasatch 2.2.15__tar.gz → 2.3.2__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.
- {wasatch-2.2.15 → wasatch-2.3.2}/CHANGELOG.md +33 -0
- wasatch-2.3.2/Makefile +34 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/PKG-INFO +19 -5
- {wasatch-2.2.15 → wasatch-2.3.2}/demo.py +0 -2
- wasatch-2.3.2/pyproject.toml +36 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/AndorDevice.py +201 -115
- wasatch-2.3.2/wasatch/AreaScanImage.py +19 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/AutoRaman.py +6 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/AutoRamanRequest.py +2 -2
- wasatch-2.3.2/wasatch/BLEDevice.py +1032 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/CSVLoader.py +7 -3
- wasatch-2.3.2/wasatch/DeviceFinderBLE.py +107 -0
- wasatch-2.3.2/wasatch/DeviceFinderTCP.py +20 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/DeviceFinderUSB.py +33 -29
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/DeviceID.py +116 -54
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/EEPROM.py +151 -27
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/FeatureIdentificationDevice.py +510 -144
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/FirmwareRequirements.py +24 -7
- wasatch-2.3.2/wasatch/IDSCamera.py +916 -0
- wasatch-2.3.2/wasatch/IDSDevice.py +329 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/InterfaceDevice.py +11 -4
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/Reading.py +10 -11
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/SpectrometerResponse.py +1 -1
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/SpectrometerSettings.py +4 -1
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/SpectrometerState.py +6 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/StatusMessage.py +3 -0
- wasatch-2.3.2/wasatch/TCPDevice.py +409 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/TakeOneRequest.py +17 -11
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/WasatchBus.py +40 -15
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/WasatchDevice.py +172 -174
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/WasatchDeviceWrapper.py +34 -35
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/WrapperWorker.py +59 -37
- wasatch-2.3.2/wasatch/__init__.py +4 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/utils.py +32 -4
- wasatch-2.2.15/Makefile +0 -37
- wasatch-2.2.15/pyproject.toml +0 -13
- wasatch-2.2.15/requirements.txt +0 -11
- wasatch-2.2.15/setup.cfg +0 -24
- wasatch-2.2.15/wasatch/BLEDevice.py +0 -457
- wasatch-2.2.15/wasatch/__init__.py +0 -4
- {wasatch-2.2.15 → wasatch-2.3.2}/.example_pypirc +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/.flake8 +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/.gitignore +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/.pylintrc +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/Doxyfile +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/LICENSE +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/MANIFEST.in +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/README.md +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/README_PYPI.md +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/README_RPI.md +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/README_SETTINGS.md +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/README_VIRTUAL.md +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/WasatchShell/README.md +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/WasatchShell/laser-test.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/WasatchShell/load-test.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/WasatchShell/one-shot.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/WasatchShell/wasatch-shell.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/demo-auto-raman.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/demo-simple.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/demo-virtual.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/demo-workflow.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/environments/conda-linux.yml +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/environments/conda-macos.yml +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/environments/conda-rpi.yml +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/environments/conda-win10.yml +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/screenshots/multiplatform.png +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/scripts/dark-vs-integ.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/scripts/deploy +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/testSpectrometers/SiG_785/eeprom/EEPROM-EM-9c65d19f4c.json +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/testSpectrometers/SiG_785/readings/enlighten-20210329-094722-799520-WP-00686.csv +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/testSpectrometers/SiG_785/readings/enlighten-20210329-094755-288958-WP-00686.csv +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/testSpectrometers/WP-00887/eeprom/WP-00887-mock.json +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/testSpectrometers/WP-00904/eeprom/WP-00904-mock.json +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/testSpectrometers/WP-00913/eeprom/WP-00913-mock.json +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/tests/existing_implementations.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/tests/noise-by-setpoint.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/tests/queues.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/tests/unit.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/udev/10-wasatch.rules +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/AbstractUSBDevice.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/BalanceAcquisition.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/CommandSettings.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/ControlObject.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/DetectorROI.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/DetectorRegions.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/EEPROM_multical.sav +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/FPGAOptions.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/HardwareInfo.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/IMX385.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/MockUSBDevice.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/OceanDevice.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/PollStatus.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/ProcessedReading.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/ROI.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/RealUSBDevice.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/SPIDevice.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/SpectrometerRequest.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/applog.py +0 -0
- {wasatch-2.2.15 → wasatch-2.3.2}/wasatch/simulation_protocol.py +0 -0
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
- 2025-06-18 2.3.2
|
|
4
|
+
- XS area scan
|
|
5
|
+
- fixed off-by-one stop line
|
|
6
|
+
- fixed xs_area_scan_offset_kludge on newer FPGAs
|
|
7
|
+
- added "max" to utils.vercmp
|
|
8
|
+
- 2025-05-30 2.3.1
|
|
9
|
+
- enforce signed and unsigned integers in MultiWavecalCalibration (resolve XL
|
|
10
|
+
config files with floating-point horizontal ROI)
|
|
11
|
+
- update BatchCollection and Scan Averaging for AndorDevice
|
|
12
|
+
- added EEPROM.laser_interlock_excluded
|
|
13
|
+
- area scan mostly works for XS
|
|
14
|
+
- packaging
|
|
15
|
+
- remove legacy package install files
|
|
16
|
+
- add all dependencies to pyproject.toml
|
|
17
|
+
- 2025-04-08 2.3.0
|
|
18
|
+
- IDS cameras
|
|
19
|
+
- add IDSDevice, IDSCamera
|
|
20
|
+
- area scan
|
|
21
|
+
- add AreaScanImage
|
|
22
|
+
- expect start-of-frame marker at spectrum[0]
|
|
23
|
+
- move row index to spectrum[1]
|
|
24
|
+
- redesigned for Hamamatsu
|
|
25
|
+
- vertical ROI
|
|
26
|
+
- support vertical ROI in Hamamatsu
|
|
27
|
+
- firmware logging
|
|
28
|
+
- allow ENLIGHTEN to display firmware log
|
|
29
|
+
- 2025-02-26 2.2.18
|
|
30
|
+
- add laser state to Reading from AutoRaman
|
|
31
|
+
- 2025-02-11 2.2.17
|
|
32
|
+
- [#133] merged in Fast BatchCollection fixes, part of ENLIGHTEN [#516]
|
|
33
|
+
- 2025-01-20 2.2.16
|
|
34
|
+
- trace previous integration time, use in computing timeouts
|
|
35
|
+
- disable automatic laser shutoff at connection
|
|
3
36
|
- 2024-12-06 2.2.15
|
|
4
37
|
- XL Fixes
|
|
5
38
|
- fixed scan averaging
|
wasatch-2.3.2/Makefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
help:
|
|
2
|
+
@echo "Supported targets:"
|
|
3
|
+
@echo " doc (render Doxygen)"
|
|
4
|
+
@echo " clean (delete artifacts)"
|
|
5
|
+
@echo " cloc (count SLOC)"
|
|
6
|
+
@echo " publish (flit --> pypi)"
|
|
7
|
+
@echo " publish-test (flit --> testpypi)"
|
|
8
|
+
@echo " pip-install-local"
|
|
9
|
+
|
|
10
|
+
.PHONY: doc docs clean cloc publish publish-test pip-install-local
|
|
11
|
+
|
|
12
|
+
cloc:
|
|
13
|
+
@cloc --include-lang=Python .
|
|
14
|
+
|
|
15
|
+
doc docs:
|
|
16
|
+
@echo "Rendering Doxygen..."
|
|
17
|
+
@mkdir -p doxygen
|
|
18
|
+
@doxygen 1>doxygen.out 2>doxygen.err
|
|
19
|
+
@cat doxygen.err
|
|
20
|
+
|
|
21
|
+
clean:
|
|
22
|
+
@rm -rf doxygen \
|
|
23
|
+
doxygen.out \
|
|
24
|
+
doxygen.err
|
|
25
|
+
@find . -name \*.pyc -exec rm {} \;
|
|
26
|
+
|
|
27
|
+
pip-install-local:
|
|
28
|
+
pip install $$PWD
|
|
29
|
+
|
|
30
|
+
publish-test:
|
|
31
|
+
flit publish --repository testpypi
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
flit publish
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wasatch
|
|
3
|
-
Version: 2.2
|
|
4
|
-
Summary:
|
|
5
|
-
|
|
3
|
+
Version: 2.3.2
|
|
4
|
+
Summary: Application-level driver for Wasatch Photonics spectrometers
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Requires-Dist: numpy
|
|
8
|
+
Requires-Dist: six
|
|
9
|
+
Requires-Dist: psutil
|
|
10
|
+
Requires-Dist: future
|
|
11
|
+
Requires-Dist: pygtail
|
|
12
|
+
Requires-Dist: pyusb
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: pexpect
|
|
15
|
+
Requires-Dist: seabreeze
|
|
16
|
+
Requires-Dist: pyudev
|
|
17
|
+
Requires-Dist: pywin32; platform_system=='Windows'
|
|
18
|
+
Requires-Dist: pyftdi
|
|
19
|
+
Requires-Dist: crcmod
|
|
20
|
+
Requires-Dist: bleak
|
|
21
|
+
Requires-Dist: ids_peak; platform_system=='Windows'
|
|
22
|
+
Project-URL: Homepage, https://github.com/WasatchPhotonics/wasatch.py
|
|
9
23
|
|
|
10
24
|

|
|
11
25
|
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
# DESCRIPTION: Simple cmd-line demo to confirm that Wasatch.PY is working #
|
|
7
7
|
# and can connect to and control a spectrometer. #
|
|
8
8
|
# #
|
|
9
|
-
# INVOCATION: $ python -u demo.py #
|
|
10
|
-
# #
|
|
11
9
|
################################################################################
|
|
12
10
|
|
|
13
11
|
import os
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wasatch"
|
|
3
|
+
version = "2.3.2"
|
|
4
|
+
description= "Application-level driver for Wasatch Photonics spectrometers"
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
dependencies = [
|
|
8
|
+
#"mkl",
|
|
9
|
+
"numpy",
|
|
10
|
+
"six",
|
|
11
|
+
"psutil",
|
|
12
|
+
"future",
|
|
13
|
+
"pygtail",
|
|
14
|
+
"pyusb",
|
|
15
|
+
"requests",
|
|
16
|
+
"pexpect",
|
|
17
|
+
"seabreeze",
|
|
18
|
+
"pyudev",
|
|
19
|
+
"pywin32; platform_system=='Windows'",
|
|
20
|
+
"pyftdi",
|
|
21
|
+
"crcmod",
|
|
22
|
+
"bleak",
|
|
23
|
+
"ids_peak; platform_system=='Windows'",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/WasatchPhotonics/wasatch.py"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires=["setuptools"]
|
|
31
|
+
build-backend="setuptools.build_meta"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where=["."]
|
|
35
|
+
include=["wasatch"]
|
|
36
|
+
exclude=[]
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import usb
|
|
3
3
|
import json
|
|
4
|
+
import numpy as np
|
|
4
5
|
import struct
|
|
5
6
|
import logging
|
|
6
7
|
import datetime
|
|
8
|
+
|
|
7
9
|
from ctypes import *
|
|
8
10
|
|
|
9
11
|
from .SpectrometerSettings import SpectrometerSettings
|
|
10
12
|
from .SpectrometerResponse import SpectrometerResponse
|
|
11
13
|
from .InterfaceDevice import InterfaceDevice
|
|
14
|
+
from .StatusMessage import StatusMessage
|
|
12
15
|
from .DeviceID import DeviceID
|
|
13
16
|
from .Reading import Reading
|
|
14
17
|
from .ROI import ROI
|
|
@@ -71,11 +74,10 @@ class AndorDevice(InterfaceDevice):
|
|
|
71
74
|
# An AndorDevice has-a SpectrometerSettings, which has-a EEPROM, which
|
|
72
75
|
# has-a MultiWavelengthCalibration
|
|
73
76
|
self.settings = SpectrometerSettings(self.device_id)
|
|
74
|
-
self.summed_spectra = None
|
|
75
|
-
self.sum_count = 0
|
|
76
77
|
self.session_reading_count = 0
|
|
77
|
-
self.
|
|
78
|
-
self.
|
|
78
|
+
self.sum_count = 0
|
|
79
|
+
self.take_one_request = None
|
|
80
|
+
|
|
79
81
|
self.dll_fail = True
|
|
80
82
|
self.tec_enabled = True
|
|
81
83
|
self.driver = None
|
|
@@ -147,6 +149,27 @@ class AndorDevice(InterfaceDevice):
|
|
|
147
149
|
# Private Methods
|
|
148
150
|
###############################################################
|
|
149
151
|
|
|
152
|
+
def _queue_message(self, setting, value):
|
|
153
|
+
"""
|
|
154
|
+
If an upstream queue is defined, send the name-value pair. Does nothing
|
|
155
|
+
if the caller hasn't provided a queue.
|
|
156
|
+
|
|
157
|
+
"setting" is application (caller) dependent, but ENLIGHTEN currently uses
|
|
158
|
+
"marquee_info" and "marquee_error".
|
|
159
|
+
"""
|
|
160
|
+
if self.message_queue is None:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
msg = StatusMessage(setting, value)
|
|
164
|
+
try:
|
|
165
|
+
log.debug(f"queue_message: msg {msg}")
|
|
166
|
+
self.message_queue.put(msg)
|
|
167
|
+
except:
|
|
168
|
+
log.error(f"failed to enqueue StatusMessage {msg}", exc_info=1)
|
|
169
|
+
|
|
170
|
+
def not_implemented(self):
|
|
171
|
+
pass
|
|
172
|
+
|
|
150
173
|
def _init_process_funcs(self):
|
|
151
174
|
process_f = {}
|
|
152
175
|
|
|
@@ -158,10 +181,14 @@ class AndorDevice(InterfaceDevice):
|
|
|
158
181
|
process_f["init_tec_setpoint"] = self.init_tec_setpoint
|
|
159
182
|
process_f["set_tec_setpoint"] = self.set_tec_setpoint
|
|
160
183
|
process_f["init_detector_area"] = self.init_detector_area
|
|
161
|
-
process_f["scans_to_average"] = self.
|
|
184
|
+
process_f["scans_to_average"] = self.set_scans_to_average
|
|
162
185
|
process_f["high_gain_mode_enable"] = self.high_gain_mode_enable
|
|
163
186
|
process_f["save_config"] = self.save_config
|
|
164
187
|
process_f["vertical_binning"] = self.set_vertical_binning
|
|
188
|
+
process_f["take_one_request"] = self.set_take_one_request
|
|
189
|
+
|
|
190
|
+
process_f["reset_scan_averaging"] = self.not_implemented
|
|
191
|
+
process_f["heartbeat"] = self.not_implemented
|
|
165
192
|
|
|
166
193
|
##################################################################
|
|
167
194
|
# What follows is the old init-lambdas that are squashed into process_f
|
|
@@ -204,142 +231,147 @@ class AndorDevice(InterfaceDevice):
|
|
|
204
231
|
os.makedirs(self.config_dir)
|
|
205
232
|
return os.path.isfile(self.config_file)
|
|
206
233
|
|
|
207
|
-
def _get_spectrum_raw(self):
|
|
234
|
+
def _get_spectrum_raw(self):
|
|
208
235
|
"""
|
|
209
236
|
@todo missing bad-pixel correction
|
|
210
237
|
"""
|
|
211
238
|
spec_arr = c_long * self.pixels
|
|
212
239
|
spec_init_vals = [0] * self.pixels
|
|
213
|
-
|
|
240
|
+
spectrum = spec_arr(*spec_init_vals)
|
|
214
241
|
|
|
215
242
|
# ask for spectrum then collect, NOT multithreaded (though we should look into that!), blocks
|
|
216
243
|
self.driver.StartAcquisition()
|
|
217
244
|
self.driver.WaitForAcquisition()
|
|
218
|
-
|
|
245
|
+
result = self.driver.GetAcquiredData(spectrum, c_ulong(self.pixels))
|
|
219
246
|
|
|
220
|
-
if (
|
|
221
|
-
log.
|
|
247
|
+
if (result != self.SUCCESS):
|
|
248
|
+
log.error(f"_get_spectrum_raw: GetAcquiredData failed (result {result})")
|
|
222
249
|
return
|
|
223
250
|
|
|
224
251
|
# convert from wasatch.AndorDevice.c_long_Array_512
|
|
225
|
-
|
|
252
|
+
spectrum = np.array(spectrum, dtype=np.float32) # [x for x in spectrum]
|
|
226
253
|
|
|
227
254
|
if (self.settings.eeprom.invert_x_axis):
|
|
228
|
-
|
|
255
|
+
# spectrum.reverse()
|
|
256
|
+
spectrum = spectrum[::-1]
|
|
257
|
+
|
|
258
|
+
# Andor cameras can return all zeros when saturated
|
|
259
|
+
if not spectrum.any():
|
|
260
|
+
self._queue_message("marquee_error", "Andor camera is saturated")
|
|
229
261
|
|
|
230
|
-
|
|
262
|
+
# log.debug(f"_get_spectrum_raw: returning spectrum {spectrum}")
|
|
263
|
+
return spectrum
|
|
231
264
|
|
|
232
265
|
def _take_one_averaged_reading(self):
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
266
|
+
"""
|
|
267
|
+
@note this may be collecting a dark spectrum requested through TakeOneRequest.take_dark
|
|
268
|
+
@returns Reading
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
########################################################################
|
|
272
|
+
# take the averaged spectrum
|
|
273
|
+
########################################################################
|
|
274
|
+
|
|
275
|
+
tor = self.take_one_request
|
|
276
|
+
if tor is not None:
|
|
277
|
+
scans_to_average = tor.scans_to_average
|
|
278
|
+
log.debug(f"take_one_averaged_reading: taking scans_to_average from tor {tor}")
|
|
245
279
|
else:
|
|
246
|
-
|
|
247
|
-
|
|
280
|
+
scans_to_average = self.settings.state.scans_to_average
|
|
281
|
+
log.debug(f"take_one_averaged_reading: taking scans_to_average from SpectrometerState")
|
|
248
282
|
|
|
249
|
-
log.debug("take_one_averaged_reading:
|
|
283
|
+
log.debug(f"take_one_averaged_reading: scans_to_average {scans_to_average}")
|
|
250
284
|
|
|
251
285
|
# either take one measurement (normal), or a bunch (blocking averaging)
|
|
252
|
-
reading =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
result = self.driver.GetTemperature(byref(c_temp))
|
|
270
|
-
|
|
271
|
-
label = self.get_error_code(result)
|
|
272
|
-
|
|
273
|
-
if label in [ "DRV_SUCCESS",
|
|
274
|
-
"DRV_TEMPERATURE_DRIFT",
|
|
275
|
-
"DRV_TEMPERATURE_STABILIZED",
|
|
276
|
-
"DRV_TEMPERATURE_NOT_REACHED",
|
|
277
|
-
"DRV_TEMPERATURE_NOT_STABILIZED" ]:
|
|
278
|
-
reading.detector_temperature_degC = c_temp.value
|
|
279
|
-
log.debug(f"Andor temperature {reading.detector_temperature_degC:.2f} ({label})")
|
|
280
|
-
else:
|
|
281
|
-
log.error(f"unable to read detector temperature, result was {label}")
|
|
282
|
-
|
|
286
|
+
reading = Reading(self.device_id) # reading.timestamp is when reading STARTED, not FINISHED!
|
|
287
|
+
self.sum_count = 0
|
|
288
|
+
failure_count = 0
|
|
289
|
+
self.summed_spectrum = None
|
|
290
|
+
MAX_FAILURES = 3
|
|
291
|
+
|
|
292
|
+
# this loop can DELIBERATELY be changed (shortened, reset or lengthened)
|
|
293
|
+
# by external changes to self.sum_count and self.settings.state.scans_to_average
|
|
294
|
+
while self.sum_count < scans_to_average:
|
|
295
|
+
|
|
296
|
+
# monitor for external changes (but NOT new TakeOneRequest...that will appear in due process)
|
|
297
|
+
if scans_to_average != self.settings.state.scans_to_average:
|
|
298
|
+
log.debug(f"detected change in scans_to_average from {scans_to_average} to {self.settings.state.scans_to_average}")
|
|
299
|
+
self.scan_count = 0
|
|
300
|
+
scans_to_average = self.settings.state.scans_to_average
|
|
301
|
+
self.summed_spectrum = None
|
|
302
|
+
|
|
283
303
|
try:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
304
|
+
log.debug(f"take_one_averaged_reading: taking spectrum {self.sum_count+1} of {scans_to_average}")
|
|
305
|
+
spectrum = self._get_spectrum_raw()
|
|
306
|
+
except usb.USBError:
|
|
307
|
+
failure_count += 1
|
|
308
|
+
log.error(f"failure_count {failure_count}, encountered USB error in reading for device {self.device}", exc_info=1)
|
|
309
|
+
if failure_count < MAX_FAILURES:
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
if spectrum is None or spectrum == []:
|
|
313
|
+
failure_count += 1
|
|
314
|
+
log.error(f"failure_count {failure_count}, received empty spectrum {reading.spectrum}")
|
|
315
|
+
if failure_count < MAX_FAILURES:
|
|
316
|
+
log.error(f"failure_count {failure_count}, trying again")
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
if failure_count >= MAX_FAILURES:
|
|
320
|
+
msg = f"exceeded MAX_FAILURES {MAX_FAILURES} while trying to read an averaged spectrum"
|
|
321
|
+
log.error(msg)
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
log.debug("successfully read one spectrum")
|
|
325
|
+
self.sum_count += 1
|
|
326
|
+
if scans_to_average > 1 and self.sum_count < scans_to_average:
|
|
327
|
+
# MZ: why aren't we following this pattern in WasatchDevice / FeatureInterfaceDevice?
|
|
328
|
+
log.debug(f"floating message upstream because sum_count {self.sum_count}, scans_to_average {scans_to_average}")
|
|
329
|
+
self._queue_message("scan_averaging", (self.device_id, self.sum_count))
|
|
330
|
+
|
|
331
|
+
if self.summed_spectrum is None:
|
|
332
|
+
self.summed_spectrum = spectrum
|
|
333
|
+
else:
|
|
334
|
+
self.summed_spectrum += spectrum
|
|
335
|
+
log.debug(f"take_one_averaged_reading: sum_count {self.sum_count}, summed_spectrum : {self.summed_spectrum[0:9]}")
|
|
336
|
+
|
|
337
|
+
# have we completed the averaged reading?
|
|
338
|
+
reading.spectrum = self.summed_spectrum / self.sum_count if self.sum_count > 1 else spectrum
|
|
339
|
+
|
|
340
|
+
log.debug(f"take_one_averaged_reading: {'averaged' if self.sum_count > 1 else 'non-averaged'} spectrum : %s ...", reading.spectrum[0:9])
|
|
341
|
+
reading.averaged = True
|
|
342
|
+
reading.sum_count = self.sum_count
|
|
343
|
+
return reading
|
|
344
|
+
|
|
345
|
+
def get_detector_temperature_degC(self):
|
|
346
|
+
if self.tec_enabled:
|
|
347
|
+
log.debug("TEC enabled, so reading temperature")
|
|
348
|
+
|
|
349
|
+
use_float = True # seems to work on 785XL (WP-01635 and WP-01491)
|
|
350
|
+
if use_float:
|
|
351
|
+
c_temp = c_float()
|
|
352
|
+
result = self.driver.GetTemperatureF(byref(c_temp))
|
|
353
|
+
else:
|
|
354
|
+
c_temp = c_int()
|
|
355
|
+
result = self.driver.GetTemperature(byref(c_temp))
|
|
289
356
|
|
|
290
357
|
# MZ: why were we trying to use GetTemperatureF(float) here, when above it was GetTemperature(int)?
|
|
291
358
|
# temperature = c_float()
|
|
292
359
|
# temp_success = self.driver.GetTemperatureF(byref(temperature))
|
|
293
360
|
# reading.detector_temperature_degC = temperature.value
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
self.summed_spectra = [float(i) for i in reading.spectrum]
|
|
306
|
-
else:
|
|
307
|
-
log.debug("device.take_one_averaged_reading: summing spectra")
|
|
308
|
-
for i in range(len(self.summed_spectra)):
|
|
309
|
-
self.summed_spectra[i] += reading.spectrum[i]
|
|
310
|
-
self.sum_count += 1
|
|
311
|
-
log.debug("device.take_one_averaged_reading: summed_spectra : %s ...", self.summed_spectra[0:9])
|
|
312
|
-
|
|
313
|
-
# count spectra
|
|
314
|
-
self.session_reading_count += 1
|
|
315
|
-
reading.session_count = self.session_reading_count
|
|
316
|
-
reading.sum_count = self.sum_count
|
|
317
|
-
|
|
318
|
-
# have we completed the averaged reading?
|
|
319
|
-
if averaging_enabled:
|
|
320
|
-
if self.sum_count >= self.settings.state.scans_to_average:
|
|
321
|
-
reading.spectrum = [ x / self.sum_count for x in self.summed_spectra ]
|
|
322
|
-
log.debug("device.take_one_averaged_reading: averaged_spectrum : %s ...", reading.spectrum[0:9])
|
|
323
|
-
reading.averaged = True
|
|
324
|
-
|
|
325
|
-
# reset for next average
|
|
326
|
-
self.summed_spectra = None
|
|
327
|
-
self.sum_count = 0
|
|
361
|
+
|
|
362
|
+
label = self.get_error_code(result)
|
|
363
|
+
|
|
364
|
+
if label in [ "DRV_SUCCESS",
|
|
365
|
+
"DRV_TEMPERATURE_DRIFT",
|
|
366
|
+
"DRV_TEMPERATURE_STABILIZED",
|
|
367
|
+
"DRV_TEMPERATURE_NOT_REACHED",
|
|
368
|
+
"DRV_TEMPERATURE_NOT_STABILIZED" ]:
|
|
369
|
+
detector_temperature_degC = c_temp.value
|
|
370
|
+
log.debug(f"Andor temperature {detector_temperature_degC:.2f} ({label})")
|
|
371
|
+
return detector_temperature_degC
|
|
328
372
|
else:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
reading.averaged = True
|
|
332
|
-
|
|
333
|
-
# were we told to only take one (potentially averaged) measurement?
|
|
334
|
-
if self.take_one and reading.averaged:
|
|
335
|
-
log.debug("completed take_one")
|
|
336
|
-
self.change_setting("cancel_take_one", True)
|
|
337
|
-
|
|
338
|
-
log.debug("device.take_one_averaged_reading: returning %s", reading)
|
|
339
|
-
if reading.spectrum is not None and reading.spectrum != []:
|
|
340
|
-
self.failure_count = 0
|
|
341
|
-
# reading.dump_area_scan()
|
|
342
|
-
return SpectrometerResponse(data=reading)
|
|
373
|
+
log.error(f"unable to read detector temperature, result was {label}")
|
|
374
|
+
|
|
343
375
|
|
|
344
376
|
def _close_ex_shutter(self):
|
|
345
377
|
self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 2), "SetShutterEx(2)")
|
|
@@ -439,6 +471,10 @@ class AndorDevice(InterfaceDevice):
|
|
|
439
471
|
self.settings.eeprom.has_cooling = True
|
|
440
472
|
return SpectrometerResponse(data=True)
|
|
441
473
|
|
|
474
|
+
def set_take_one_request(self, tor):
|
|
475
|
+
log.debug(f"set_take_one_request: storing {tor}")
|
|
476
|
+
self.take_one_request = tor
|
|
477
|
+
|
|
442
478
|
def set_vertical_binning(self, roi):
|
|
443
479
|
"""
|
|
444
480
|
Note that this follows the same (start, end) vertical ROI API as
|
|
@@ -547,8 +583,54 @@ class AndorDevice(InterfaceDevice):
|
|
|
547
583
|
self.set_tec_setpoint(self.settings.eeprom.startup_temp_degC)
|
|
548
584
|
|
|
549
585
|
def acquire_data(self):
|
|
586
|
+
# handle TakeOneRequest.take_dark
|
|
587
|
+
dark_reading = None
|
|
588
|
+
tor = self.take_one_request
|
|
589
|
+
log.debug(f"acquire_data: tor {tor}")
|
|
590
|
+
if tor and tor.take_dark:
|
|
591
|
+
self.set_shutter_enable(True)
|
|
592
|
+
dark_reading = self._take_one_averaged_reading()
|
|
593
|
+
if dark is None:
|
|
594
|
+
return SpectrometerResponse(False, error_msg="failed to collect dark")
|
|
595
|
+
self.set_shutter_enable(False)
|
|
596
|
+
|
|
597
|
+
# get spectrum (potentially averaged)
|
|
550
598
|
reading = self._take_one_averaged_reading()
|
|
551
|
-
|
|
599
|
+
if reading is None:
|
|
600
|
+
return SpectrometerResponse(False, error_msg="failed to collect reading")
|
|
601
|
+
|
|
602
|
+
# fill-out metadata
|
|
603
|
+
if dark_reading:
|
|
604
|
+
reading.dark = dark_reading.spectrum
|
|
605
|
+
reading.integration_time_ms = self.settings.state.integration_time_ms
|
|
606
|
+
reading.detector_temperature_degC = self.get_detector_temperature_degC()
|
|
607
|
+
|
|
608
|
+
# only bump session_count by one for a dark-corrected averaged reading
|
|
609
|
+
# (regardless of how many spectra were collected to generate it)
|
|
610
|
+
self.session_reading_count += 1
|
|
611
|
+
reading.session_count = self.session_reading_count
|
|
612
|
+
|
|
613
|
+
# update or close TakeOneRequest
|
|
614
|
+
if tor is not None:
|
|
615
|
+
log.debug(f"acquire_data: adding to to reading: {tor}")
|
|
616
|
+
reading.take_one_request = tor
|
|
617
|
+
|
|
618
|
+
# XL units don't provide laser control
|
|
619
|
+
if tor.laser_warmup_ms: log.error("TakeOneRequest.laser_warmup_ms not supported")
|
|
620
|
+
if tor.auto_raman_request: log.error("TakeOneRequest.auto_raman_request not supported")
|
|
621
|
+
if tor.enable_laser_before: log.error("TakeOneRequest.enable_laser_before not supported")
|
|
622
|
+
if tor.disable_laser_after: log.error("TakeOneRequest.disable_laser_after not supported")
|
|
623
|
+
|
|
624
|
+
# readings_current/target are for "fast (streaming) BatchCollection"
|
|
625
|
+
if tor.readings_target:
|
|
626
|
+
tor.readings_current += 1
|
|
627
|
+
|
|
628
|
+
if not tor.readings_target or tor.readings_current >= tor.readings_target:
|
|
629
|
+
log.debug(f"completed {tor}")
|
|
630
|
+
self.take_one_request = None
|
|
631
|
+
|
|
632
|
+
log.debug(f"acquire_data: reading {reading}")
|
|
633
|
+
return SpectrometerResponse(data=reading)
|
|
552
634
|
|
|
553
635
|
def set_shutter_enable(self, enable):
|
|
554
636
|
if enable:
|
|
@@ -679,9 +761,13 @@ class AndorDevice(InterfaceDevice):
|
|
|
679
761
|
log.debug(f"set AD channel {ADnumber} with horizontal speed {HSnumber} ({STemp})")
|
|
680
762
|
return SpectrometerResponse(True)
|
|
681
763
|
|
|
682
|
-
def
|
|
764
|
+
def set_scans_to_average(self, value):
|
|
765
|
+
value = int(value)
|
|
766
|
+
|
|
767
|
+
# reset count on changes
|
|
683
768
|
self.sum_count = 0
|
|
684
|
-
self.
|
|
769
|
+
self.summed_spectrum = None
|
|
770
|
+
self.settings.state.scans_to_average = value
|
|
685
771
|
return SpectrometerResponse(True)
|
|
686
772
|
|
|
687
773
|
def get_error_code_long(self, code):
|
|
@@ -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}>"
|
|
@@ -105,6 +105,9 @@ class AutoRaman:
|
|
|
105
105
|
reading.sum_count = 1 # todo: get_scans_to_average()
|
|
106
106
|
reading.new_integration_time_ms = None # todo: get_integration_time_ms()
|
|
107
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
|
|
108
111
|
|
|
109
112
|
return SpectrometerResponse(data=reading)
|
|
110
113
|
|
|
@@ -415,6 +418,9 @@ class AutoRaman:
|
|
|
415
418
|
reading.sum_count = num_avg
|
|
416
419
|
reading.new_integration_time_ms = int_time
|
|
417
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
|
|
418
424
|
|
|
419
425
|
log.debug("done")
|
|
420
426
|
return reading
|
|
@@ -5,7 +5,7 @@ class AutoRamanRequest:
|
|
|
5
5
|
START_GAIN_DB = 0
|
|
6
6
|
MAX_INTEG_MS = 2000
|
|
7
7
|
MIN_INTEG_MS = 10
|
|
8
|
-
MAX_GAIN_DB =
|
|
8
|
+
MAX_GAIN_DB = 30
|
|
9
9
|
MIN_GAIN_DB = 0
|
|
10
10
|
TARGET_COUNTS = 45000
|
|
11
11
|
MAX_COUNTS = 50000
|
|
@@ -82,7 +82,7 @@ if __name__ == "__main__":
|
|
|
82
82
|
start_gain_db = 0,
|
|
83
83
|
max_integ_ms = 2000,
|
|
84
84
|
min_integ_ms = 10,
|
|
85
|
-
max_gain_db =
|
|
85
|
+
max_gain_db = 30,
|
|
86
86
|
min_gain_db = 0,
|
|
87
87
|
target_counts = 45000,
|
|
88
88
|
max_counts = 50000,
|