wasatch 2.1.65__tar.gz → 2.2.0__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.1.65 → wasatch-2.2.0}/CHANGELOG.md +21 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/PKG-INFO +1 -1
- {wasatch-2.1.65 → wasatch-2.2.0}/demo.py +80 -55
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/AndorDevice.py +2 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/EEPROM.py +35 -32
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/FeatureIdentificationDevice.py +14 -5
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/TakeOneRequest.py +13 -6
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/WasatchDevice.py +9 -6
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/WasatchDeviceWrapper.py +21 -14
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/WrapperWorker.py +30 -3
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/__init__.py +1 -1
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/applog.py +1 -1
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/utils.py +1 -1
- {wasatch-2.1.65 → wasatch-2.2.0}/.example_pypirc +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/.flake8 +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/.gitignore +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/.pylintrc +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/Doxyfile +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/LICENSE +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/MANIFEST.in +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/Makefile +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/README.md +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/README_PYPI.md +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/README_RPI.md +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/README_SETTINGS.md +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/README_VIRTUAL.md +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/WasatchShell/README.md +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/WasatchShell/laser-test.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/WasatchShell/load-test.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/WasatchShell/one-shot.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/WasatchShell/wasatch-shell.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/demo-simple.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/demo-virtual.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/demo-workflow.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/environments/conda-linux.yml +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/environments/conda-macos.yml +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/environments/conda-rpi.yml +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/environments/conda-win10.yml +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/pyproject.toml +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/requirements.txt +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/screenshots/multiplatform.png +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/scripts/dark-vs-integ.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/scripts/deploy +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/setup.cfg +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/testSpectrometers/SiG_785/eeprom/EEPROM-EM-9c65d19f4c.json +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/testSpectrometers/SiG_785/readings/enlighten-20210329-094722-799520-WP-00686.csv +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/testSpectrometers/SiG_785/readings/enlighten-20210329-094755-288958-WP-00686.csv +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/testSpectrometers/WP-00887/eeprom/WP-00887-mock.json +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/testSpectrometers/WP-00904/eeprom/WP-00904-mock.json +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/testSpectrometers/WP-00913/eeprom/WP-00913-mock.json +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/tests/existing_implementations.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/tests/noise-by-setpoint.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/tests/queues.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/tests/unit.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/udev/10-wasatch.rules +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/AbstractUSBDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/BLEDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/BalanceAcquisition.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/CSVLoader.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/CommandSettings.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/ControlObject.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/DetectorROI.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/DetectorRegions.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/DeviceFinderUSB.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/DeviceID.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/EEPROM_multical.sav +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/FPGAOptions.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/FirmwareRequirements.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/HardwareInfo.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/InterfaceDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/MockUSBDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/OceanDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/PollStatus.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/ProcessedReading.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/ROI.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/Reading.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/RealUSBDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/SPIDevice.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/SpectrometerRequest.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/SpectrometerResponse.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/SpectrometerSettings.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/SpectrometerState.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/StatusMessage.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/WasatchBus.py +0 -0
- {wasatch-2.1.65 → wasatch-2.2.0}/wasatch/simulation_protocol.py +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
- 2024-08-22 2.2.0
|
|
4
|
+
- cutting release for ENLIGHTEN 4.1.0
|
|
5
|
+
- 2024-06-27 2.1.73
|
|
6
|
+
- update demo.py to support multiple spectrometers
|
|
7
|
+
- add optional `callback` parameter to WrapperWorker for Inversion of Control (IoC)
|
|
8
|
+
- 2024-06-27 2.1.72
|
|
9
|
+
- temporarily disable GET_LASER_ENABLED on XS
|
|
10
|
+
- 2024-06-19 2.1.71
|
|
11
|
+
- disable applog to stdout if stdout missing
|
|
12
|
+
- 2024-06-12 2.1.70
|
|
13
|
+
- force laser to disable on perc/PWM zero
|
|
14
|
+
- 2024-05-30 2.1.69
|
|
15
|
+
- added avg_resolution to edittable EEPROM fields
|
|
16
|
+
- 2024-05-24 2.1.68
|
|
17
|
+
- added horizontal ROI to XL virtual EEPROM
|
|
18
|
+
- Raspberry Pi testing (no change)
|
|
19
|
+
- 2024-05-22 2.1.67
|
|
20
|
+
- relax thread logging 10sec after successful connection
|
|
21
|
+
- display connected spectrometer properties in demo.py
|
|
22
|
+
- 2024-05-07 2.1.66
|
|
23
|
+
- gave TakeOneRequest a copy-ctor
|
|
3
24
|
- 2024-05-02 2.1.65
|
|
4
25
|
- updated README links to work on pypi servers
|
|
5
26
|
- 2024-05-02 2.1.64
|
|
@@ -31,7 +31,6 @@ import wasatch
|
|
|
31
31
|
from wasatch import utils
|
|
32
32
|
from wasatch import applog
|
|
33
33
|
from wasatch.WasatchBus import WasatchBus
|
|
34
|
-
from wasatch.OceanDevice import OceanDevice
|
|
35
34
|
from wasatch.WasatchDevice import WasatchDevice
|
|
36
35
|
from wasatch.WasatchDeviceWrapper import WasatchDeviceWrapper
|
|
37
36
|
from wasatch.RealUSBDevice import RealUSBDevice
|
|
@@ -48,14 +47,16 @@ class WasatchDemo:
|
|
|
48
47
|
|
|
49
48
|
def __init__(self, argv=None):
|
|
50
49
|
self.bus = None
|
|
51
|
-
self.
|
|
50
|
+
self.devices = {}
|
|
52
51
|
self.logger = None
|
|
53
52
|
self.outfile = None
|
|
54
53
|
self.exiting = False
|
|
54
|
+
self.reading_count = 0
|
|
55
55
|
|
|
56
56
|
self.args = self.parse_args(argv)
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
if self.args.log_level != "NEVER":
|
|
59
|
+
self.logger = applog.MainLogger(self.args.log_level)
|
|
59
60
|
log.info("Wasatch.PY version %s", wasatch.__version__)
|
|
60
61
|
|
|
61
62
|
############################################################################
|
|
@@ -66,7 +67,7 @@ class WasatchDemo:
|
|
|
66
67
|
|
|
67
68
|
def parse_args(self, argv):
|
|
68
69
|
parser = argparse.ArgumentParser(description="Simple demo to acquire spectra from command-line interface")
|
|
69
|
-
parser.add_argument("--log-level", type=str, default="INFO", help="logging level [DEBUG,INFO,WARNING,ERROR,CRITICAL]
|
|
70
|
+
parser.add_argument("--log-level", type=str, default="INFO", help="logging level", choices=["DEBUG","INFO","WARNING","ERROR","CRITICAL","NEVER"])
|
|
70
71
|
parser.add_argument("--integration-time-ms", type=int, default=10, help="integration time (ms, default 10)")
|
|
71
72
|
parser.add_argument("--scans-to-average", type=int, default=1, help="scans to average (default 1)")
|
|
72
73
|
parser.add_argument("--boxcar-half-width", type=int, default=0, help="boxcar half-width (default 0)")
|
|
@@ -83,11 +84,7 @@ class WasatchDemo:
|
|
|
83
84
|
print("Wasatch.PY %s" % wasatch.__version__)
|
|
84
85
|
sys.exit(0)
|
|
85
86
|
|
|
86
|
-
# normalize log level
|
|
87
87
|
args.log_level = args.log_level.upper()
|
|
88
|
-
if not re.match("^(DEBUG|INFO|ERROR|WARNING|CRITICAL)$", args.log_level):
|
|
89
|
-
print("Invalid log level: %s (defaulting to INFO)" % args.log_level)
|
|
90
|
-
args.log_level = "INFO"
|
|
91
88
|
|
|
92
89
|
return args
|
|
93
90
|
|
|
@@ -98,12 +95,7 @@ class WasatchDemo:
|
|
|
98
95
|
############################################################################
|
|
99
96
|
|
|
100
97
|
def connect(self):
|
|
101
|
-
"""
|
|
102
|
-
attempt to connect to it. """
|
|
103
|
-
|
|
104
|
-
# if we're already connected, nevermind
|
|
105
|
-
if self.device is not None:
|
|
106
|
-
return
|
|
98
|
+
""" Connect to all discoverable Wasatch spectrometers. """
|
|
107
99
|
|
|
108
100
|
# lazy-load a USB bus
|
|
109
101
|
if self.bus is None:
|
|
@@ -114,35 +106,43 @@ class WasatchDemo:
|
|
|
114
106
|
print("No Wasatch USB spectrometers found.")
|
|
115
107
|
return
|
|
116
108
|
|
|
117
|
-
device_id
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
log.debug("instantiating WasatchDevice (blocking)")
|
|
130
|
-
if device_id.vid == 0x24aa:
|
|
131
|
-
device = WasatchDevice(device_id)
|
|
109
|
+
for device_id in self.bus.device_ids:
|
|
110
|
+
log.debug("connect: trying to connect to %s", device_id)
|
|
111
|
+
|
|
112
|
+
if self.args.non_blocking:
|
|
113
|
+
# Non-blocking means we instantiate a WasatchDeviceWrapper (WDW)
|
|
114
|
+
# The WDW incapsulates its own threading.Thread (WrapperWorker)
|
|
115
|
+
# which continuously acquires spectra free-running mode.
|
|
116
|
+
log.debug("instantiating WasatchDeviceWrapper (non-blocking)")
|
|
117
|
+
device = WasatchDeviceWrapper(
|
|
118
|
+
device_id = device_id,
|
|
119
|
+
log_level = self.args.log_level,
|
|
120
|
+
callback = self.WrapperWorker_callback)
|
|
132
121
|
else:
|
|
133
|
-
|
|
122
|
+
# We're going to place blocking calls to spectrometers, so we can
|
|
123
|
+
# instantiate a plain WasatchDevice (no need for WDW or WrapperWorker)
|
|
124
|
+
log.debug("instantiating WasatchDevice (blocking)")
|
|
125
|
+
device = WasatchDevice(device_id)
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
ok = device.connect()
|
|
128
|
+
if not ok:
|
|
129
|
+
log.critical("connect: can't connect to %s", device_id)
|
|
130
|
+
continue
|
|
139
131
|
|
|
140
|
-
|
|
132
|
+
log.debug("connect: device connected")
|
|
133
|
+
if device.settings is None:
|
|
134
|
+
log.error("can't have device without SpectrometerSettings")
|
|
135
|
+
continue
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
print(f"connected to {device.settings.full_model()} {device.settings.eeprom.serial_number} with {device.settings.pixels()} pixels " +
|
|
138
|
+
f"from ({device.settings.wavelengths[0]:.2f}, {device.settings.wavelengths[-1]:.2f}nm)" +
|
|
139
|
+
( f" ({device.settings.wavenumbers[0]:.2f}, {device.settings.wavenumbers[-1]:.2f}cm⁻¹)" if device.settings.has_excitation() else "") +
|
|
140
|
+
f" (Microcontroller {device.settings.microcontroller_firmware_version}," +
|
|
141
|
+
f" FPGA {device.settings.fpga_firmware_version})")
|
|
144
142
|
|
|
145
|
-
|
|
143
|
+
self.devices[str(device.device_id)] = device
|
|
144
|
+
|
|
145
|
+
return len(self.devices) > 0
|
|
146
146
|
|
|
147
147
|
############################################################################
|
|
148
148
|
# #
|
|
@@ -154,15 +154,15 @@ class WasatchDemo:
|
|
|
154
154
|
log.info("Wasatch.PY %s Demo", wasatch.__version__)
|
|
155
155
|
|
|
156
156
|
# apply initial settings
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
for device_id, device in self.devices.items():
|
|
158
|
+
device.change_setting("integration_time_ms", self.args.integration_time_ms)
|
|
159
|
+
device.change_setting("scans_to_average", self.args.scans_to_average)
|
|
160
160
|
|
|
161
161
|
# initialize outfile if one was specified
|
|
162
162
|
if self.args.outfile:
|
|
163
163
|
try:
|
|
164
164
|
self.outfile = open(self.args.outfile, "w")
|
|
165
|
-
self.outfile.write("time,temp
|
|
165
|
+
self.outfile.write("time,temp,serial,spectrum\n")
|
|
166
166
|
except:
|
|
167
167
|
log.error("Error initializing %s", self.args.outfile)
|
|
168
168
|
self.outfile = None
|
|
@@ -170,7 +170,9 @@ class WasatchDemo:
|
|
|
170
170
|
# read spectra until user presses Control-Break
|
|
171
171
|
while not self.exiting:
|
|
172
172
|
start_time = datetime.datetime.now()
|
|
173
|
-
self.
|
|
173
|
+
if not self.args.non_blocking:
|
|
174
|
+
for device_id, device in self.devices.items():
|
|
175
|
+
self.attempt_reading(device)
|
|
174
176
|
end_time = datetime.datetime.now()
|
|
175
177
|
|
|
176
178
|
if self.args.max > 0 and self.reading_count >= self.args.max:
|
|
@@ -185,14 +187,13 @@ class WasatchDemo:
|
|
|
185
187
|
try:
|
|
186
188
|
time.sleep(float(sleep_ms) / 1000)
|
|
187
189
|
except:
|
|
188
|
-
# log.critical("WasatchDemo.run sleep() caught an exception", exc_info=1)
|
|
189
190
|
self.exiting = True
|
|
190
191
|
|
|
191
192
|
log.debug("WasatchDemo.run exiting")
|
|
192
193
|
|
|
193
|
-
def attempt_reading(self):
|
|
194
|
+
def attempt_reading(self, device):
|
|
194
195
|
try:
|
|
195
|
-
reading_response = self.acquire_reading()
|
|
196
|
+
reading_response = self.acquire_reading(device)
|
|
196
197
|
except:
|
|
197
198
|
log.critical("attempt_reading caught exception", exc_info=1)
|
|
198
199
|
self.exiting = True
|
|
@@ -213,23 +214,42 @@ class WasatchDemo:
|
|
|
213
214
|
|
|
214
215
|
self.process_reading(reading_response.data)
|
|
215
216
|
|
|
216
|
-
def acquire_reading(self):
|
|
217
|
+
def acquire_reading(self, device):
|
|
217
218
|
# We want the demo to effectively block on new scans, so keep
|
|
218
219
|
# polling the subprocess until a reading is ready. In other apps,
|
|
219
220
|
# we could do other things (like respond to GUI events) if
|
|
220
221
|
# device.acquire_data() was None (meaning the next spectrum wasn't
|
|
221
222
|
# yet ready).
|
|
222
223
|
while True:
|
|
223
|
-
reading =
|
|
224
|
+
reading = device.acquire_data()
|
|
224
225
|
if reading is None:
|
|
225
226
|
log.debug("waiting on next reading")
|
|
226
227
|
else:
|
|
227
228
|
return reading
|
|
228
229
|
|
|
230
|
+
def WrapperWorker_callback(self, response):
|
|
231
|
+
if response is None:
|
|
232
|
+
log.error("WrapperWorker_callback received null SpectrometerResponse")
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
reading = response.data
|
|
236
|
+
if reading is None:
|
|
237
|
+
log.error("WrapperWorker_callback received null Reading")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
self.process_reading(reading)
|
|
241
|
+
|
|
229
242
|
def process_reading(self, reading):
|
|
230
|
-
if self.
|
|
243
|
+
if (self.exiting or
|
|
244
|
+
(self.args.scans_to_average > 1 and not reading.averaged) or
|
|
245
|
+
(self.args.max > 0 and self.reading_count >= self.args.max)):
|
|
231
246
|
return
|
|
232
247
|
|
|
248
|
+
device_id = reading.device_id
|
|
249
|
+
log.debug(f"received reading from device_id {device_id}")
|
|
250
|
+
device = self.devices[str(reading.device_id)]
|
|
251
|
+
settings = device.settings
|
|
252
|
+
|
|
233
253
|
self.reading_count += 1
|
|
234
254
|
|
|
235
255
|
if self.args.boxcar_half_width > 0:
|
|
@@ -238,7 +258,7 @@ class WasatchDemo:
|
|
|
238
258
|
spectrum = reading.spectrum
|
|
239
259
|
|
|
240
260
|
if self.args.ascii_art:
|
|
241
|
-
print("\n".join(wasatch.utils.ascii_spectrum(spectrum, rows=20, cols=80, x_axis=
|
|
261
|
+
print("\n".join(wasatch.utils.ascii_spectrum(spectrum, rows=20, cols=80, x_axis=settings.wavelengths, x_unit="nm")))
|
|
242
262
|
else:
|
|
243
263
|
spectrum_min = numpy.amin(spectrum)
|
|
244
264
|
spectrum_max = numpy.amax(spectrum)
|
|
@@ -246,7 +266,9 @@ class WasatchDemo:
|
|
|
246
266
|
spectrum_std = numpy.std (spectrum)
|
|
247
267
|
size_in_bytes = psutil.Process(os.getpid()).memory_info().rss
|
|
248
268
|
|
|
249
|
-
print("
|
|
269
|
+
print("%s: %s %4d Detector: %5.2f degC Min: %8.2f Max: %8.2f Avg: %8.2f StdDev: %8.2f Memory: %11d" % (
|
|
270
|
+
reading.timestamp,
|
|
271
|
+
settings.eeprom.serial_number,
|
|
250
272
|
self.reading_count,
|
|
251
273
|
reading.detector_temperature_degC,
|
|
252
274
|
spectrum_min,
|
|
@@ -257,8 +279,9 @@ class WasatchDemo:
|
|
|
257
279
|
log.debug("%s", str(reading))
|
|
258
280
|
|
|
259
281
|
if self.outfile:
|
|
260
|
-
self.outfile.write("%s,%.2f,%s\n" % (datetime.datetime.now(),
|
|
282
|
+
self.outfile.write("%s,%.2f,%s,%s\n" % (datetime.datetime.now(),
|
|
261
283
|
reading.detector_temperature_degC,
|
|
284
|
+
settings.eeprom.serial_number,
|
|
262
285
|
",".join(format(x, ".2f") for x in spectrum)))
|
|
263
286
|
|
|
264
287
|
################################################################################
|
|
@@ -272,9 +295,11 @@ def signal_handler(signal, frame):
|
|
|
272
295
|
def clean_shutdown():
|
|
273
296
|
log.debug("Exiting")
|
|
274
297
|
if demo:
|
|
275
|
-
if demo.args
|
|
276
|
-
|
|
277
|
-
|
|
298
|
+
if demo.args.non_blocking and len(demo.devices):
|
|
299
|
+
for device_id, device in demo.devices.items():
|
|
300
|
+
log.debug(f"closing {device_id}")
|
|
301
|
+
device.disconnect()
|
|
302
|
+
time.sleep(1)
|
|
278
303
|
|
|
279
304
|
if demo.logger:
|
|
280
305
|
log.debug("closing logger")
|
|
@@ -472,6 +472,8 @@ class AndorDevice(InterfaceDevice):
|
|
|
472
472
|
'invert_x_axis',
|
|
473
473
|
'wavelength_coeffs',
|
|
474
474
|
'excitation_nm_float',
|
|
475
|
+
'roi_horizontal_end',
|
|
476
|
+
'roi_horizontal_start',
|
|
475
477
|
'raman_intensity_coeffs',
|
|
476
478
|
'raman_intensity_calibration_order',
|
|
477
479
|
'startup_temp_degC',
|
|
@@ -113,37 +113,40 @@ class EEPROM:
|
|
|
113
113
|
self.write_buffers = []
|
|
114
114
|
self.digest = None
|
|
115
115
|
|
|
116
|
-
self.editable = [
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
116
|
+
self.editable = [
|
|
117
|
+
"avg_resolution",
|
|
118
|
+
"bad_pixels",
|
|
119
|
+
"bin_2x2",
|
|
120
|
+
"calibrated_by",
|
|
121
|
+
"calibration_date",
|
|
122
|
+
"cutoff_filter_installed",
|
|
123
|
+
"detector_gain",
|
|
124
|
+
"detector_gain_odd",
|
|
125
|
+
"detector_offset",
|
|
126
|
+
"detector_offset_odd",
|
|
127
|
+
"excitation_nm",
|
|
128
|
+
"excitation_nm_float",
|
|
129
|
+
"gen15",
|
|
130
|
+
"has_shutter",
|
|
131
|
+
"laser_power_coeffs",
|
|
132
|
+
"laser_warmup_sec",
|
|
133
|
+
"laser_watchdog_sec",
|
|
134
|
+
"linearity_coeffs",
|
|
135
|
+
"max_laser_power_mW",
|
|
136
|
+
"min_laser_power_mW",
|
|
137
|
+
"raman_intensity_calibration_order",
|
|
138
|
+
"raman_intensity_coeffs",
|
|
139
|
+
"roi_horizontal_end",
|
|
140
|
+
"roi_horizontal_start",
|
|
141
|
+
"roi_vertical_region_1_end",
|
|
142
|
+
"roi_vertical_region_1_start",
|
|
143
|
+
"roi_vertical_region_2_end",
|
|
144
|
+
"roi_vertical_region_2_start",
|
|
145
|
+
"roi_vertical_region_3_end",
|
|
146
|
+
"roi_vertical_region_3_start",
|
|
147
|
+
"user_text",
|
|
148
|
+
"wavelength_coeffs",
|
|
149
|
+
]
|
|
147
150
|
|
|
148
151
|
self.init_raman_intensity_calibration()
|
|
149
152
|
self.init_spline()
|
|
@@ -738,7 +741,7 @@ class EEPROM:
|
|
|
738
741
|
else:
|
|
739
742
|
struct.pack_into(data_type, buf, start_byte, value)
|
|
740
743
|
|
|
741
|
-
if
|
|
744
|
+
if False:
|
|
742
745
|
extra = "" if label is None else (" (%s)" % label)
|
|
743
746
|
log.debug("Packed (%d, %2d, %2d) '%s' value %s -> %s%s",
|
|
744
747
|
page, start_byte, length, data_type, value, buf[start_byte:end_byte], extra)
|
|
@@ -123,7 +123,6 @@ class FeatureIdentificationDevice(InterfaceDevice):
|
|
|
123
123
|
self.shutdown_requested = False
|
|
124
124
|
|
|
125
125
|
self.last_spectrum = None
|
|
126
|
-
self.spectrum_count = 0
|
|
127
126
|
self.prev_pixels = None
|
|
128
127
|
|
|
129
128
|
# in case of I2C collisions within the spectrometer, e.g. due to battery-LED status
|
|
@@ -201,6 +200,8 @@ class FeatureIdentificationDevice(InterfaceDevice):
|
|
|
201
200
|
log.debug("on Windows, so NOT setting configuration and claiming interface")
|
|
202
201
|
elif "macOS" in platform.platform():
|
|
203
202
|
log.debug("on MacOS, so NOT setting configuration and claiming interface")
|
|
203
|
+
# elif "raspberrypi" in str(os.uname()):
|
|
204
|
+
# log.debug("on Raspberry Pi, so NOT setting configuration and claiming interface")
|
|
204
205
|
else:
|
|
205
206
|
log.debug("on posix, so setting configuration and claiming interface")
|
|
206
207
|
|
|
@@ -1961,13 +1962,13 @@ class FeatureIdentificationDevice(InterfaceDevice):
|
|
|
1961
1962
|
return SpectrometerResponse(data=self.settings.state.selected_laser)
|
|
1962
1963
|
|
|
1963
1964
|
def get_laser_enabled(self):
|
|
1964
|
-
|
|
1965
|
-
flag = 0 != res.data
|
|
1966
|
-
|
|
1967
|
-
if self.device_id.pid == 0x4000:
|
|
1965
|
+
if self.settings.is_xs():
|
|
1968
1966
|
# MZ: get_laser_enabled not currently robust on SiG :-(
|
|
1969
1967
|
return SpectrometerResponse(data=self.settings.state.laser_enabled)
|
|
1970
1968
|
|
|
1969
|
+
res = self._get_code(0xe2, label="GET_LASER_ENABLED", msb_len=1)
|
|
1970
|
+
flag = 0 != res.data
|
|
1971
|
+
|
|
1971
1972
|
log.debug(f"get_laser_enabled: {flag} (storing to {self.settings.state})")
|
|
1972
1973
|
self.settings.state.laser_enabled = flag
|
|
1973
1974
|
return SpectrometerResponse(data=flag)
|
|
@@ -2198,6 +2199,8 @@ class FeatureIdentificationDevice(InterfaceDevice):
|
|
|
2198
2199
|
|
|
2199
2200
|
# don't want anything weird when passing over USB
|
|
2200
2201
|
value = float(max(0, min(100, value)))
|
|
2202
|
+
if value <= 0:
|
|
2203
|
+
return set_laser_enable(False)
|
|
2201
2204
|
|
|
2202
2205
|
# If full power (and allowed), disable modulation and exit
|
|
2203
2206
|
if value >= 100:
|
|
@@ -2212,6 +2215,9 @@ class FeatureIdentificationDevice(InterfaceDevice):
|
|
|
2212
2215
|
period_us = 1000 if self.settings.state.laser_power_high_resolution else 100
|
|
2213
2216
|
width_us = int(round(1.0 * value * period_us / 100.0, 0)) # note that value is in range (0, 100) not (0, 1)
|
|
2214
2217
|
|
|
2218
|
+
if width_us <= 0:
|
|
2219
|
+
return set_laser_enable(False)
|
|
2220
|
+
|
|
2215
2221
|
# pulse width can't be longer than period, or shorter than 1us
|
|
2216
2222
|
width_us = max(1, min(width_us, period_us))
|
|
2217
2223
|
|
|
@@ -2812,6 +2818,9 @@ class FeatureIdentificationDevice(InterfaceDevice):
|
|
|
2812
2818
|
log.debug(msg)
|
|
2813
2819
|
return
|
|
2814
2820
|
|
|
2821
|
+
# MZ: just disable for now
|
|
2822
|
+
# return
|
|
2823
|
+
|
|
2815
2824
|
if self.settings.microcontroller_firmware_version == "1.0.2.9":
|
|
2816
2825
|
msg = "ambient temperature ARM requires newer firmware"
|
|
2817
2826
|
log.debug(msg)
|
|
@@ -5,12 +5,19 @@ import threading
|
|
|
5
5
|
|
|
6
6
|
class TakeOneRequest:
|
|
7
7
|
|
|
8
|
-
def __init__(self, take_dark=False, enable_laser_before=False, disable_laser_after=False, laser_warmup_ms=0, scans_to_average=1):
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
def __init__(self, take_dark=False, enable_laser_before=False, disable_laser_after=False, laser_warmup_ms=0, scans_to_average=1, template=None):
|
|
9
|
+
if template is not None:
|
|
10
|
+
self.take_dark = template.take_dark
|
|
11
|
+
self.enable_laser_before = template.enable_laser_before
|
|
12
|
+
self.disable_laser_after = template.disable_laser_after
|
|
13
|
+
self.laser_warmup_ms = template.laser_warmup_ms
|
|
14
|
+
self.scans_to_average = template.scans_to_average
|
|
15
|
+
else:
|
|
16
|
+
self.take_dark = take_dark
|
|
17
|
+
self.enable_laser_before = enable_laser_before
|
|
18
|
+
self.disable_laser_after = disable_laser_after
|
|
19
|
+
self.laser_warmup_ms = laser_warmup_ms
|
|
20
|
+
self.scans_to_average = scans_to_average
|
|
14
21
|
|
|
15
22
|
self.request_id = f"{os.getpid():05d}-{threading.get_native_id():05d}-{time.time_ns()}-{random.randrange(65536):05d}"
|
|
16
23
|
|
|
@@ -293,7 +293,7 @@ class WasatchDevice(InterfaceDevice):
|
|
|
293
293
|
# (given sleep()'s precision) for each acquisition. For true precision
|
|
294
294
|
# this should all go into the firmware anyway.
|
|
295
295
|
|
|
296
|
-
auto_enable_laser = tor and tor.enable_laser_before
|
|
296
|
+
auto_enable_laser = tor is not None and tor.enable_laser_before
|
|
297
297
|
log.debug("acquire_spectrum: auto_enable_laser = %s", auto_enable_laser)
|
|
298
298
|
|
|
299
299
|
dark_reading = SpectrometerResponse()
|
|
@@ -841,7 +841,7 @@ class WasatchDevice(InterfaceDevice):
|
|
|
841
841
|
self.session_reading_count += 1
|
|
842
842
|
reading.session_count = self.session_reading_count
|
|
843
843
|
reading.sum_count = self.sum_count
|
|
844
|
-
log.debug(f"take_one_averaged_reading: reading.sum_count now {reading.sum_count}")
|
|
844
|
+
log.debug(f"take_one_averaged_reading: reading.sum_count now {reading.sum_count}, session_count {reading.session_count}")
|
|
845
845
|
|
|
846
846
|
# have we completed the averaged reading?
|
|
847
847
|
if sum_locally:
|
|
@@ -878,17 +878,17 @@ class WasatchDevice(InterfaceDevice):
|
|
|
878
878
|
# to the device.
|
|
879
879
|
#
|
|
880
880
|
# Essentially this iterates through all the (setting, value) pairs we've
|
|
881
|
-
# received through change_setting() which have not yet been processed, and
|
|
881
|
+
# received through change_setting() which have not yet been processed, and <-- MZ: incomplete sentence
|
|
882
882
|
#
|
|
883
883
|
#
|
|
884
|
-
# Note that
|
|
884
|
+
# Note that WrapperWorker.run "de-dupes" commands on
|
|
885
885
|
# receipt from ENLIGHTEN, so the command stream arising from that source
|
|
886
886
|
# should already be optimized and minimal. Commands injected manually by
|
|
887
887
|
# calling WasatchDevice.change_setting() do not receive this treatment.
|
|
888
888
|
#
|
|
889
|
-
# In the normal
|
|
889
|
+
# In the normal multithreaded (ENLIGHTEN) workflow, this function is called
|
|
890
890
|
# at the beginning of acquire_data, itself ticked regularly by
|
|
891
|
-
#
|
|
891
|
+
# WrapperWorker.run.
|
|
892
892
|
def process_commands(self):
|
|
893
893
|
control_object = "throwaway"
|
|
894
894
|
retval = False
|
|
@@ -900,6 +900,9 @@ class WasatchDevice(InterfaceDevice):
|
|
|
900
900
|
# is this a command used by WasatchDevice itself, and not
|
|
901
901
|
# passed down to FeatureIdentificationDevice?
|
|
902
902
|
if control_object.setting.lower() == "acquire":
|
|
903
|
+
# MZ: does this ever happen? It looks like the current
|
|
904
|
+
# request sent down from WrapperWorker is "acquire_data"
|
|
905
|
+
# not "acquire"...
|
|
903
906
|
log.debug("process_commands: acquire found")
|
|
904
907
|
retval = True
|
|
905
908
|
else:
|
|
@@ -17,6 +17,9 @@ log = logging.getLogger(__name__)
|
|
|
17
17
|
# Readings and StatusMessages) for multiprocessing-safe device communications
|
|
18
18
|
# and acquisition under Windows and Qt.
|
|
19
19
|
#
|
|
20
|
+
# @todo This document is full of references to continuous_poll(). That method
|
|
21
|
+
# no longer exists, and has been replaced by WrapperWorker.run()
|
|
22
|
+
#
|
|
20
23
|
# @par Lifecycle
|
|
21
24
|
#
|
|
22
25
|
# From ENLIGHTEN's standpoint (the original Wasatch.PY caller), here's what's going on:
|
|
@@ -134,9 +137,10 @@ class WasatchDeviceWrapper:
|
|
|
134
137
|
# could include "FILE:/path/to/dir", etc. However, device_id is just
|
|
135
138
|
# a string scalar to this class, and actually parsing / using it should be
|
|
136
139
|
# entirely encapsulated within WasatchDevice and lower using DeviceID.
|
|
137
|
-
def __init__(self, device_id, log_level):
|
|
140
|
+
def __init__(self, device_id, log_level, callback=None):
|
|
138
141
|
self.device_id = device_id
|
|
139
142
|
self.log_level = log_level
|
|
143
|
+
self.callback = callback
|
|
140
144
|
|
|
141
145
|
self.settings_queue = False
|
|
142
146
|
self.response_queue = False
|
|
@@ -223,7 +227,9 @@ class WasatchDeviceWrapper:
|
|
|
223
227
|
is_ocean = self.is_ocean,
|
|
224
228
|
is_andor = self.is_andor,
|
|
225
229
|
is_spi = self.is_spi,
|
|
226
|
-
is_ble = self.is_ble
|
|
230
|
+
is_ble = self.is_ble,
|
|
231
|
+
log_level = self.log_level,
|
|
232
|
+
callback = self.callback)
|
|
227
233
|
log.debug("device wrapper: Instance created for worker")
|
|
228
234
|
|
|
229
235
|
self.wrapper_worker.daemon = True
|
|
@@ -236,14 +242,25 @@ class WasatchDeviceWrapper:
|
|
|
236
242
|
self.settings = None
|
|
237
243
|
log.debug("connect: setup connection, returning to controller for settings polling")
|
|
238
244
|
|
|
245
|
+
if self.callback:
|
|
246
|
+
self.wait_for_settings()
|
|
247
|
+
|
|
239
248
|
return True
|
|
240
249
|
|
|
241
|
-
def
|
|
250
|
+
def wait_for_settings(self):
|
|
251
|
+
while True:
|
|
252
|
+
if self.poll_settings():
|
|
253
|
+
break
|
|
254
|
+
if (datetime.datetime.now() - self.connect_start_time).total_seconds() > 2:
|
|
255
|
+
raise Exception("Failed to read SpectrometerSettings")
|
|
256
|
+
time.sleep(0.1)
|
|
257
|
+
|
|
258
|
+
def poll_settings(self):
|
|
242
259
|
""" @returns SpectrometerResponse(True) on success, (False) otherwise """
|
|
243
260
|
log.debug("polling device settings")
|
|
244
261
|
if not self.settings_queue.empty():
|
|
245
262
|
result = self.settings_queue.get_nowait()
|
|
246
|
-
if result is None:
|
|
263
|
+
if result is None:
|
|
247
264
|
log.critical("poll_settings: failed to retrieve device settings (got None, shouldn't happen)")
|
|
248
265
|
return SpectrometerResponse(False, error_msg="Failed to retrieve device settings")
|
|
249
266
|
|
|
@@ -280,12 +297,6 @@ class WasatchDeviceWrapper:
|
|
|
280
297
|
|
|
281
298
|
self.connected = False
|
|
282
299
|
|
|
283
|
-
# MZ: why do we recreate these?
|
|
284
|
-
self.settings_queue = Queue()
|
|
285
|
-
self.response_queue = Queue()
|
|
286
|
-
self.message_queue = Queue()
|
|
287
|
-
self.command_queue = Queue()
|
|
288
|
-
|
|
289
300
|
return True
|
|
290
301
|
|
|
291
302
|
##
|
|
@@ -413,10 +424,6 @@ class WasatchDeviceWrapper:
|
|
|
413
424
|
# none of those. Yet apparently we did read some normal readings.
|
|
414
425
|
# Return the latest of those.
|
|
415
426
|
|
|
416
|
-
# MZ: this would be a possible place to revert logging from DEBUG to
|
|
417
|
-
# whatever was passed (self.log_level), especially if this is the
|
|
418
|
-
# first reading returned by this thread.
|
|
419
|
-
|
|
420
427
|
if WasatchDeviceWrapper.DISABLE_RESPONSE_QUEUE:
|
|
421
428
|
self.previous_reading = last_reading
|
|
422
429
|
return last_reading
|
|
@@ -2,6 +2,7 @@ import threading
|
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
4
|
from queue import Queue
|
|
5
|
+
from datetime import datetime
|
|
5
6
|
|
|
6
7
|
from .SpectrometerResponse import SpectrometerResponse
|
|
7
8
|
from .SpectrometerRequest import SpectrometerRequest
|
|
@@ -34,6 +35,8 @@ class WrapperWorker(threading.Thread):
|
|
|
34
35
|
# TODO: Create ABC of hardware device that keeps common functions like handle_requests
|
|
35
36
|
POLLER_WAIT_SEC = 0.05 # .05sec = 50ms = update from hardware device at 20Hz
|
|
36
37
|
|
|
38
|
+
DEBUG_SEC = 10 # enforce debug logging for the 1st 10sec after connecting a new spectrometer
|
|
39
|
+
|
|
37
40
|
def __init__(
|
|
38
41
|
self,
|
|
39
42
|
device_id,
|
|
@@ -41,11 +44,12 @@ class WrapperWorker(threading.Thread):
|
|
|
41
44
|
response_queue,
|
|
42
45
|
settings_queue,
|
|
43
46
|
message_queue,
|
|
44
|
-
is_ocean,
|
|
47
|
+
is_ocean, # MZ: all these bools should have just been a string "type"
|
|
45
48
|
is_andor,
|
|
46
49
|
is_spi,
|
|
47
50
|
is_ble,
|
|
48
|
-
|
|
51
|
+
log_level,
|
|
52
|
+
callback=None):
|
|
49
53
|
|
|
50
54
|
threading.Thread.__init__(self)
|
|
51
55
|
|
|
@@ -58,8 +62,18 @@ class WrapperWorker(threading.Thread):
|
|
|
58
62
|
self.response_queue = response_queue
|
|
59
63
|
self.settings_queue = settings_queue
|
|
60
64
|
self.message_queue = message_queue
|
|
65
|
+
self.log_level = log_level
|
|
66
|
+
self.callback = callback
|
|
67
|
+
|
|
61
68
|
self.connected_device = None
|
|
62
69
|
|
|
70
|
+
self.thread_start = datetime.now()
|
|
71
|
+
self.initial_connection_logging = True
|
|
72
|
+
|
|
73
|
+
# enforce debug logging around ENLIGHTEN connections
|
|
74
|
+
if not self.callback:
|
|
75
|
+
logging.getLogger().setLevel("DEBUG")
|
|
76
|
+
|
|
63
77
|
##
|
|
64
78
|
# This is essentially the main() loop in a thread.
|
|
65
79
|
# All communications with the parent thread are routed through
|
|
@@ -176,7 +190,11 @@ class WrapperWorker(threading.Thread):
|
|
|
176
190
|
continue
|
|
177
191
|
log.debug(f"response {reading_response} data is {reading_response.data}")
|
|
178
192
|
|
|
179
|
-
if
|
|
193
|
+
if self.callback:
|
|
194
|
+
log.debug("worker returning response via callback")
|
|
195
|
+
self.callback(reading_response)
|
|
196
|
+
|
|
197
|
+
elif reading_response.keep_alive:
|
|
180
198
|
log.debug("worker is flowing up keep_alive")
|
|
181
199
|
self.response_queue.put(reading_response)
|
|
182
200
|
|
|
@@ -209,6 +227,15 @@ class WrapperWorker(threading.Thread):
|
|
|
209
227
|
except:
|
|
210
228
|
log.error("unable to push Reading %d to GUI", reading_response.data.session_count, exc_info=1)
|
|
211
229
|
|
|
230
|
+
# We just successfully sent a reading back to the main thread.
|
|
231
|
+
# After sending 10sec of such readings, we can disable the default
|
|
232
|
+
# DEBUG logging and revert to whatever the caller requested.
|
|
233
|
+
if (self.initial_connection_logging and
|
|
234
|
+
(datetime.now() - self.thread_start).total_seconds() > self.DEBUG_SEC):
|
|
235
|
+
log.info(f"relaxing log_level to {self.log_level} after {self.DEBUG_SEC}sec")
|
|
236
|
+
logging.getLogger().setLevel(self.log_level)
|
|
237
|
+
self.initial_connection_logging = False
|
|
238
|
+
|
|
212
239
|
else:
|
|
213
240
|
log.error("received non-failure Reading without spectrum...ignoring?")
|
|
214
241
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"""This package is a driver for Wasatch Photonics spectrometers"""
|
|
2
2
|
|
|
3
|
-
__version__ = "2.
|
|
3
|
+
__version__ = "2.2.0" # This is used by flit and other pypi things
|
|
4
4
|
version = __version__ # This is to avoid breaking other files that originally used .version instead of __version__
|
|
@@ -159,7 +159,7 @@ class MainLogger:
|
|
|
159
159
|
fh.setFormatter(formatter)
|
|
160
160
|
root_logger.addHandler(fh)
|
|
161
161
|
|
|
162
|
-
if self.enable_stdout:
|
|
162
|
+
if self.enable_stdout and sys.stdout is not None:
|
|
163
163
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
164
164
|
stream_handler = logging.StreamHandler(sys.stdout)
|
|
165
165
|
stream_handler.setFormatter(formatter)
|
|
@@ -468,7 +468,7 @@ def coeffs_look_valid(coeffs, count=None):
|
|
|
468
468
|
|
|
469
469
|
# check for constants (all coefficients the same value)
|
|
470
470
|
all_const = True
|
|
471
|
-
log.
|
|
471
|
+
# log.debug(coeffs)
|
|
472
472
|
for i in range(1, len(coeffs)):
|
|
473
473
|
if coeffs[0] != coeffs[i]:
|
|
474
474
|
all_const = False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|