pyglaze 0.1.2__tar.gz → 0.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.
- {pyglaze-0.1.2/src/pyglaze.egg-info → pyglaze-0.2.0}/PKG-INFO +1 -1
- {pyglaze-0.1.2 → pyglaze-0.2.0}/pyproject.toml +2 -2
- pyglaze-0.2.0/src/pyglaze/__init__.py +1 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/datamodels/pulse.py +18 -0
- pyglaze-0.2.0/src/pyglaze/device/__init__.py +7 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/device/ampcom.py +54 -59
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/device/configuration.py +1 -15
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/devtools/mock_device.py +15 -3
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/scanning/scanner.py +1 -5
- {pyglaze-0.1.2 → pyglaze-0.2.0/src/pyglaze.egg-info}/PKG-INFO +1 -1
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze.egg-info/SOURCES.txt +0 -7
- pyglaze-0.1.2/src/pyglaze/__init__.py +0 -1
- pyglaze-0.1.2/src/pyglaze/device/__init__.py +0 -15
- pyglaze-0.1.2/src/pyglaze/device/_delayunit_data/carmen-nonuniform-2023-10-20.pickle +0 -0
- pyglaze-0.1.2/src/pyglaze/device/_delayunit_data/g1-linearized-2023-04-04.pickle +0 -0
- pyglaze-0.1.2/src/pyglaze/device/_delayunit_data/g2-linearized-2023-04-04.pickle +0 -0
- pyglaze-0.1.2/src/pyglaze/device/_delayunit_data/g2-nonuniform-2023-04-04.pickle +0 -0
- pyglaze-0.1.2/src/pyglaze/device/_delayunit_data/mock_delay.pickle +0 -0
- pyglaze-0.1.2/src/pyglaze/device/delayunit.py +0 -151
- pyglaze-0.1.2/src/pyglaze/device/identifiers.py +0 -41
- {pyglaze-0.1.2 → pyglaze-0.2.0}/LICENSE +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/MANIFEST.in +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/README.md +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/setup.cfg +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/datamodels/__init__.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/datamodels/waveform.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/devtools/__init__.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/devtools/thz_pulse.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/helpers/__init__.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/helpers/types.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/helpers/utilities.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/interpolation/__init__.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/interpolation/interpolation.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/py.typed +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/scanning/__init__.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/scanning/_asyncscanner.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze/scanning/client.py +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze.egg-info/dependency_links.txt +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze.egg-info/requires.txt +0 -0
- {pyglaze-0.1.2 → pyglaze-0.2.0}/src/pyglaze.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyglaze"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Pyglaze is a library used to operate the devices of Glaze Technologies"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -74,7 +74,7 @@ convention = "google"
|
|
|
74
74
|
]
|
|
75
75
|
|
|
76
76
|
[tool.bumpver]
|
|
77
|
-
current_version = "0.
|
|
77
|
+
current_version = "0.2.0"
|
|
78
78
|
version_pattern = "MAJOR.MINOR.PATCH[-TAG]"
|
|
79
79
|
commit_message = "BUMP VERSION {old_version} -> {new_version}"
|
|
80
80
|
tag_message = "v{new_version}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -457,6 +457,24 @@ class Pulse:
|
|
|
457
457
|
|
|
458
458
|
return cast(float, np.max(max_estimate) - np.min(min_estimate))
|
|
459
459
|
|
|
460
|
+
def estimate_zero_crossing(self: Pulse) -> float:
|
|
461
|
+
"""Estimates the zero crossing of the pulse between the maximum and minimum value.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
float: Estimated zero crossing.
|
|
465
|
+
"""
|
|
466
|
+
argmax = np.argmax(self.signal)
|
|
467
|
+
argmin = np.argmin(self.signal)
|
|
468
|
+
if argmax < argmin:
|
|
469
|
+
idx = np.searchsorted(-self.signal[argmax:argmin], 0) + argmax - 1
|
|
470
|
+
else:
|
|
471
|
+
idx = np.searchsorted(self.signal[argmin:argmax], 0) + argmin - 1
|
|
472
|
+
|
|
473
|
+
# To find the zero crossing, solve 0 = s1 + a * (t - t1) for t: t = t1 - s1 / a
|
|
474
|
+
t1, s1 = self.time[idx], self.signal[idx]
|
|
475
|
+
a = (self.signal[idx + 1] - self.signal[idx]) / self.dt
|
|
476
|
+
return cast(float, t1 - s1 / a)
|
|
477
|
+
|
|
460
478
|
def to_native_dict(self: Pulse) -> dict[str, list[float] | None]:
|
|
461
479
|
"""Converts the Pulse object to a native dictionary.
|
|
462
480
|
|
|
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from functools import cached_property
|
|
9
9
|
from math import modf
|
|
10
|
-
from typing import TYPE_CHECKING, ClassVar, overload
|
|
10
|
+
from typing import TYPE_CHECKING, Callable, ClassVar, overload
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import serial
|
|
@@ -20,7 +20,6 @@ from pyglaze.device.configuration import (
|
|
|
20
20
|
Interval,
|
|
21
21
|
LeDeviceConfiguration,
|
|
22
22
|
)
|
|
23
|
-
from pyglaze.device.delayunit import Delay, load_delayunit
|
|
24
23
|
from pyglaze.devtools.mock_device import _mock_device_factory
|
|
25
24
|
from pyglaze.helpers.utilities import LOGGER_NAME, _BackoffRetry
|
|
26
25
|
|
|
@@ -67,7 +66,7 @@ class _ForceAmpCom:
|
|
|
67
66
|
@cached_property
|
|
68
67
|
def times(self: _ForceAmpCom) -> FloatArray:
|
|
69
68
|
return _delay_from_intervals(
|
|
70
|
-
delayunit=
|
|
69
|
+
delayunit=lambda x: x,
|
|
71
70
|
intervals=self.config.scan_intervals,
|
|
72
71
|
points_per_interval=_points_per_interval(
|
|
73
72
|
self.scanning_points, self._squished_intervals
|
|
@@ -216,7 +215,6 @@ class _LeAmpCom:
|
|
|
216
215
|
__ser: serial.Serial | LeMockDevice = field(init=False)
|
|
217
216
|
|
|
218
217
|
ENCODING: ClassVar[str] = "utf-8"
|
|
219
|
-
DAC_BITWIDTH: ClassVar[int] = 4096 # 12-bit DAC
|
|
220
218
|
|
|
221
219
|
OK_RESPONSE: ClassVar[str] = "ACK"
|
|
222
220
|
START_COMMAND: ClassVar[str] = "G"
|
|
@@ -230,30 +228,30 @@ class _LeAmpCom:
|
|
|
230
228
|
return self.config.n_points
|
|
231
229
|
|
|
232
230
|
@cached_property
|
|
233
|
-
def
|
|
234
|
-
|
|
235
|
-
delayunit=load_delayunit(self.config.delayunit),
|
|
236
|
-
intervals=self.config.scan_intervals,
|
|
237
|
-
points_per_interval=_points_per_interval(
|
|
238
|
-
self.scanning_points, self._squished_intervals
|
|
239
|
-
),
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
@cached_property
|
|
243
|
-
def scanning_list(self: _LeAmpCom) -> list[int]:
|
|
244
|
-
scanning_list: list[int] = []
|
|
231
|
+
def scanning_list(self: _LeAmpCom) -> list[float]:
|
|
232
|
+
scanning_list: list[float] = []
|
|
245
233
|
for interval, n_points in zip(
|
|
246
|
-
self.
|
|
247
|
-
_points_per_interval(self.scanning_points, self.
|
|
234
|
+
self._intervals,
|
|
235
|
+
_points_per_interval(self.scanning_points, self._intervals),
|
|
248
236
|
):
|
|
249
|
-
denormalized = self._denormalize_interval(interval)
|
|
250
237
|
scanning_list.extend(
|
|
251
238
|
np.linspace(
|
|
252
|
-
|
|
253
|
-
|
|
239
|
+
interval.lower,
|
|
240
|
+
interval.upper,
|
|
241
|
+
n_points,
|
|
242
|
+
endpoint=len(self._intervals) == 1,
|
|
243
|
+
),
|
|
254
244
|
)
|
|
255
245
|
return scanning_list
|
|
256
246
|
|
|
247
|
+
@cached_property
|
|
248
|
+
def bytes_to_receive(self: _LeAmpCom) -> int:
|
|
249
|
+
"""Number of bytes to receive for a single scan.
|
|
250
|
+
|
|
251
|
+
We expect to receive 3 arrays of floats (delays, X and Y), each with self.scanning_points elements.
|
|
252
|
+
"""
|
|
253
|
+
return self.scanning_points * 12
|
|
254
|
+
|
|
257
255
|
def __post_init__(self: _LeAmpCom) -> None:
|
|
258
256
|
self.__ser = _serial_factory(self.config)
|
|
259
257
|
|
|
@@ -271,39 +269,28 @@ class _LeAmpCom:
|
|
|
271
269
|
|
|
272
270
|
def write_list_length_and_integration_periods_and_use_ema(self: _LeAmpCom) -> str:
|
|
273
271
|
self._encode_send_response(self.SEND_SETTINGS_COMMAND)
|
|
274
|
-
self.
|
|
272
|
+
self._raw_byte_send_ints(
|
|
275
273
|
[self.scanning_points, self.config.integration_periods, self.config.use_ema]
|
|
276
274
|
)
|
|
277
275
|
return self._get_response()
|
|
278
276
|
|
|
279
277
|
def write_list(self: _LeAmpCom) -> str:
|
|
280
278
|
self._encode_send_response(self.SEND_LIST_COMMAND)
|
|
281
|
-
self.
|
|
279
|
+
self._raw_byte_send_floats(self.scanning_list)
|
|
282
280
|
return self._get_response()
|
|
283
281
|
|
|
284
|
-
def start_scan(self: _LeAmpCom) -> tuple[str, np.ndarray]:
|
|
282
|
+
def start_scan(self: _LeAmpCom) -> tuple[str, np.ndarray, np.ndarray, np.ndarray]:
|
|
285
283
|
self._encode_send_response(self.START_COMMAND)
|
|
286
284
|
self._await_scan_finished()
|
|
287
|
-
Xs, Ys = self._read_scan()
|
|
285
|
+
times, Xs, Ys = self._read_scan()
|
|
288
286
|
|
|
289
287
|
radii, angles = self._convert_to_r_angle(Xs, Ys)
|
|
290
|
-
|
|
291
|
-
output_array = np.zeros((self.scanning_points, 3))
|
|
292
|
-
output_array[:, 0] = self.times
|
|
293
|
-
output_array[:, 1] = radii
|
|
294
|
-
output_array[:, 2] = angles
|
|
295
|
-
|
|
296
|
-
return self.START_COMMAND, output_array
|
|
288
|
+
return self.START_COMMAND, np.array(times), np.array(radii), np.array(angles)
|
|
297
289
|
|
|
298
290
|
@cached_property
|
|
299
|
-
def
|
|
291
|
+
def _intervals(self: _LeAmpCom) -> list[Interval]:
|
|
300
292
|
"""Intervals squished into effective DAC range."""
|
|
301
|
-
return
|
|
302
|
-
intervals=self.config.scan_intervals or [Interval(lower=0.0, upper=1.0)],
|
|
303
|
-
lower_bound=self.config.fs_dac_lower_bound,
|
|
304
|
-
upper_bound=self.config.fs_dac_upper_bound,
|
|
305
|
-
bitwidth=self.DAC_BITWIDTH,
|
|
306
|
-
)
|
|
293
|
+
return self.config.scan_intervals or [Interval(lower=0.0, upper=1.0)]
|
|
307
294
|
|
|
308
295
|
def _convert_to_r_angle(
|
|
309
296
|
self: _LeAmpCom, Xs: list, Ys: list
|
|
@@ -312,11 +299,6 @@ class _LeAmpCom:
|
|
|
312
299
|
angle = np.arctan2(np.array(Ys), np.array(Xs))
|
|
313
300
|
return r, np.rad2deg(angle)
|
|
314
301
|
|
|
315
|
-
def _denormalize_interval(self: _LeAmpCom, interval: Interval) -> list[int]:
|
|
316
|
-
lower = int(interval.lower * self.DAC_BITWIDTH)
|
|
317
|
-
upper = int(interval.upper * self.DAC_BITWIDTH)
|
|
318
|
-
return [lower, upper]
|
|
319
|
-
|
|
320
302
|
def _encode_send_response(self: _LeAmpCom, command: str) -> str:
|
|
321
303
|
self._encode_and_send(command)
|
|
322
304
|
return self._get_response()
|
|
@@ -324,12 +306,18 @@ class _LeAmpCom:
|
|
|
324
306
|
def _encode_and_send(self: _LeAmpCom, command: str) -> None:
|
|
325
307
|
self.__ser.write(command.encode(self.ENCODING))
|
|
326
308
|
|
|
327
|
-
def
|
|
309
|
+
def _raw_byte_send_ints(self: _LeAmpCom, values: list[int]) -> None:
|
|
328
310
|
c = BitArray()
|
|
329
311
|
for value in values:
|
|
330
312
|
c.append(BitArray(uintle=value, length=16))
|
|
331
313
|
self.__ser.write(c.tobytes())
|
|
332
314
|
|
|
315
|
+
def _raw_byte_send_floats(self: _LeAmpCom, values: list[float]) -> None:
|
|
316
|
+
c = BitArray()
|
|
317
|
+
for value in values:
|
|
318
|
+
c.append(BitArray(floatle=value, length=32))
|
|
319
|
+
self.__ser.write(c.tobytes())
|
|
320
|
+
|
|
333
321
|
def _await_scan_finished(self: _LeAmpCom) -> None:
|
|
334
322
|
time.sleep(self.config._sweep_length_ms * 1.0e-3) # noqa: SLF001, access to private attribute for backwards compatibility
|
|
335
323
|
status = self._get_status()
|
|
@@ -345,26 +333,31 @@ class _LeAmpCom:
|
|
|
345
333
|
@_BackoffRetry(
|
|
346
334
|
backoff_base=1e-2, max_tries=5, logger=logging.getLogger(LOGGER_NAME)
|
|
347
335
|
)
|
|
348
|
-
def _read_scan(self: _LeAmpCom) -> tuple[list[float], list[float]]:
|
|
336
|
+
def _read_scan(self: _LeAmpCom) -> tuple[list[float], list[float], list[float]]:
|
|
349
337
|
self._encode_and_send(self.FETCH_COMMAND)
|
|
338
|
+
scan_bytes = self.__ser.read(self.bytes_to_receive)
|
|
350
339
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if len(scan_bytes) != bytes_to_receive:
|
|
354
|
-
msg = f"received {len(scan_bytes)} bytes, expected {bytes_to_receive}"
|
|
340
|
+
if len(scan_bytes) != self.bytes_to_receive:
|
|
341
|
+
msg = f"received {len(scan_bytes)} bytes, expected {self.bytes_to_receive}"
|
|
355
342
|
raise serialutil.SerialException(msg)
|
|
356
343
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
Ys =
|
|
344
|
+
times = self._bytes_to_floats(scan_bytes, 0, self.scanning_points * 4)
|
|
345
|
+
Xs = self._bytes_to_floats(
|
|
346
|
+
scan_bytes, self.scanning_points * 4, self.scanning_points * 8
|
|
347
|
+
)
|
|
348
|
+
Ys = self._bytes_to_floats(
|
|
349
|
+
scan_bytes, self.scanning_points * 8, self.scanning_points * 12
|
|
350
|
+
)
|
|
351
|
+
return times, Xs, Ys
|
|
352
|
+
|
|
353
|
+
def _bytes_to_floats(
|
|
354
|
+
self: _LeAmpCom, scan_bytes: bytes, from_idx: int, to_idx: int
|
|
355
|
+
) -> list[float]:
|
|
356
|
+
return [
|
|
362
357
|
BitArray(bytes=scan_bytes[d : d + 4]).floatle
|
|
363
|
-
for d in range(
|
|
358
|
+
for d in range(from_idx, to_idx, 4)
|
|
364
359
|
]
|
|
365
360
|
|
|
366
|
-
return Xs, Ys
|
|
367
|
-
|
|
368
361
|
def _get_status(self: _LeAmpCom) -> _LeStatus:
|
|
369
362
|
msg = self._encode_send_response(self.STATUS_COMMAND)
|
|
370
363
|
if msg == _LeStatus.SCANNING.value:
|
|
@@ -434,7 +427,9 @@ def _squish_intervals(
|
|
|
434
427
|
|
|
435
428
|
|
|
436
429
|
def _delay_from_intervals(
|
|
437
|
-
delayunit:
|
|
430
|
+
delayunit: Callable[[FloatArray], FloatArray],
|
|
431
|
+
intervals: list[Interval],
|
|
432
|
+
points_per_interval: list[int],
|
|
438
433
|
) -> FloatArray:
|
|
439
434
|
"""Convert a list of intervals to a list of delay times."""
|
|
440
435
|
times: list[float] = []
|
|
@@ -5,8 +5,6 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from dataclasses import asdict, dataclass, field
|
|
6
6
|
from typing import TYPE_CHECKING, ClassVar, TypeVar
|
|
7
7
|
|
|
8
|
-
from .delayunit import validate_delayunit
|
|
9
|
-
|
|
10
8
|
if TYPE_CHECKING:
|
|
11
9
|
from pathlib import Path
|
|
12
10
|
|
|
@@ -83,7 +81,6 @@ class ForceDeviceConfiguration(DeviceConfiguration):
|
|
|
83
81
|
Args:
|
|
84
82
|
amp_port: The name of the serial port the amp is connected to.
|
|
85
83
|
sweep_length_ms: The length of the sweep in milliseconds.
|
|
86
|
-
delayunit: Name of the delay calculator.
|
|
87
84
|
scan_intervals: The intervals to scan.
|
|
88
85
|
integration_periods: The number of integration periods to use.
|
|
89
86
|
modulation_frequency: The frequency of the modulation in Hz.
|
|
@@ -97,7 +94,6 @@ class ForceDeviceConfiguration(DeviceConfiguration):
|
|
|
97
94
|
|
|
98
95
|
amp_port: str
|
|
99
96
|
sweep_length_ms: float
|
|
100
|
-
delayunit: str
|
|
101
97
|
scan_intervals: list[Interval] = field(default_factory=lambda: [Interval(0.0, 1.0)])
|
|
102
98
|
integration_periods: int = 100
|
|
103
99
|
modulation_frequency: int = 10000 # Hz
|
|
@@ -114,9 +110,6 @@ class ForceDeviceConfiguration(DeviceConfiguration):
|
|
|
114
110
|
def _sweep_length_ms(self: ForceDeviceConfiguration) -> float:
|
|
115
111
|
return self.sweep_length_ms
|
|
116
112
|
|
|
117
|
-
def __post_init__(self: ForceDeviceConfiguration) -> None: # noqa: D105
|
|
118
|
-
validate_delayunit(self.delayunit)
|
|
119
|
-
|
|
120
113
|
def save(self: ForceDeviceConfiguration, path: Path) -> str:
|
|
121
114
|
"""Save a DeviceConfiguration to a file.
|
|
122
115
|
|
|
@@ -172,7 +165,6 @@ class LeDeviceConfiguration(DeviceConfiguration):
|
|
|
172
165
|
|
|
173
166
|
Args:
|
|
174
167
|
amp_port: The name of the serial port the amp is connected to.
|
|
175
|
-
delayunit: Name of the delay calculator.
|
|
176
168
|
use_ema: Whether to use en exponentially moving average filter during lockin detection.
|
|
177
169
|
n_points: The number of points to scan.
|
|
178
170
|
scan_intervals: The intervals to scan.
|
|
@@ -181,21 +173,15 @@ class LeDeviceConfiguration(DeviceConfiguration):
|
|
|
181
173
|
"""
|
|
182
174
|
|
|
183
175
|
amp_port: str
|
|
184
|
-
delayunit: str
|
|
185
176
|
use_ema: bool = True
|
|
186
177
|
n_points: int = 1000
|
|
187
178
|
scan_intervals: list[Interval] = field(default_factory=lambda: [Interval(0.0, 1.0)])
|
|
188
179
|
integration_periods: int = 10
|
|
189
180
|
amp_timeout_seconds: float = 0.2
|
|
181
|
+
modulation_frequency: int = 10000 # Hz
|
|
190
182
|
|
|
191
|
-
modulation_frequency: ClassVar[int] = 10000 # Hz
|
|
192
|
-
fs_dac_lower_bound: ClassVar[int] = 300
|
|
193
|
-
fs_dac_upper_bound: ClassVar[int] = 3700
|
|
194
183
|
amp_baudrate: ClassVar[int] = 1000000 # bit/s
|
|
195
184
|
|
|
196
|
-
def __post_init__(self: LeDeviceConfiguration) -> None: # noqa: D105
|
|
197
|
-
validate_delayunit(self.delayunit)
|
|
198
|
-
|
|
199
185
|
@property
|
|
200
186
|
def _sweep_length_ms(self: LeDeviceConfiguration) -> float:
|
|
201
187
|
return self.n_points * self._time_constant_ms
|
|
@@ -195,7 +195,7 @@ class LeMockDevice(MockDevice):
|
|
|
195
195
|
self.n_scanning_points: int | None = None
|
|
196
196
|
self.integration_periods: int | None = None
|
|
197
197
|
self.use_ema: bool | None = None
|
|
198
|
-
self.scanning_list: list[
|
|
198
|
+
self.scanning_list: list[float] | None = None
|
|
199
199
|
self._scan_start_time: float | None = None
|
|
200
200
|
|
|
201
201
|
def write(self: LeMockDevice, input_bytes: bytes) -> None:
|
|
@@ -297,7 +297,7 @@ class LeMockDevice(MockDevice):
|
|
|
297
297
|
self.state = _LeMockState.RECEIVED_SETTINGS
|
|
298
298
|
|
|
299
299
|
def _handle_waiting_for_list(self: LeMockDevice, input_bytes: bytes) -> None:
|
|
300
|
-
self.scanning_list = self.
|
|
300
|
+
self.scanning_list = self._decode_floats(input_bytes)
|
|
301
301
|
self.state = _LeMockState.RECEIVED_LIST
|
|
302
302
|
|
|
303
303
|
def _decode_ints(self: LeMockDevice, input_bytes: bytes) -> list[int]:
|
|
@@ -307,6 +307,13 @@ class LeMockDevice(MockDevice):
|
|
|
307
307
|
for i in range(0, len(input_bytes), 2)
|
|
308
308
|
]
|
|
309
309
|
|
|
310
|
+
def _decode_floats(self: LeMockDevice, input_bytes: bytes) -> list[float]:
|
|
311
|
+
# Convert every four bytes to a 32-bit float (assuming little-endian format)
|
|
312
|
+
return [
|
|
313
|
+
struct.unpack("<f", input_bytes[i : i + 4])[0]
|
|
314
|
+
for i in range(0, len(input_bytes), 4)
|
|
315
|
+
]
|
|
316
|
+
|
|
310
317
|
def _scan_has_finished(self: LeMockDevice) -> bool:
|
|
311
318
|
if not self.is_scanning:
|
|
312
319
|
return True
|
|
@@ -329,7 +336,12 @@ class LeMockDevice(MockDevice):
|
|
|
329
336
|
self.n_failures += 1
|
|
330
337
|
numbers = np.array([])
|
|
331
338
|
else:
|
|
332
|
-
numbers =
|
|
339
|
+
numbers = np.concatenate(
|
|
340
|
+
(
|
|
341
|
+
np.array(self.scanning_list) * 100e-12, # mock time values
|
|
342
|
+
self.rng.random(2 * len(self.scanning_list)),
|
|
343
|
+
)
|
|
344
|
+
)
|
|
333
345
|
|
|
334
346
|
# Each scanning point will generate an X and a Y value (lockin detection)
|
|
335
347
|
return struct.pack("<" + "f" * len(numbers), *numbers)
|
|
@@ -198,11 +198,7 @@ class LeScanner(_ScannerImplementation[LeDeviceConfiguration]):
|
|
|
198
198
|
Returns:
|
|
199
199
|
Unprocessed scan.
|
|
200
200
|
"""
|
|
201
|
-
_,
|
|
202
|
-
|
|
203
|
-
time = responses[:, 0]
|
|
204
|
-
radius = responses[:, 1]
|
|
205
|
-
theta = responses[:, 2]
|
|
201
|
+
_, time, radius, theta = self._ampcom.start_scan()
|
|
206
202
|
self._phase_estimator.update_estimate(radius=radius, theta=theta)
|
|
207
203
|
|
|
208
204
|
return UnprocessedWaveform.from_polar_coords(
|
|
@@ -15,13 +15,6 @@ src/pyglaze/datamodels/waveform.py
|
|
|
15
15
|
src/pyglaze/device/__init__.py
|
|
16
16
|
src/pyglaze/device/ampcom.py
|
|
17
17
|
src/pyglaze/device/configuration.py
|
|
18
|
-
src/pyglaze/device/delayunit.py
|
|
19
|
-
src/pyglaze/device/identifiers.py
|
|
20
|
-
src/pyglaze/device/_delayunit_data/carmen-nonuniform-2023-10-20.pickle
|
|
21
|
-
src/pyglaze/device/_delayunit_data/g1-linearized-2023-04-04.pickle
|
|
22
|
-
src/pyglaze/device/_delayunit_data/g2-linearized-2023-04-04.pickle
|
|
23
|
-
src/pyglaze/device/_delayunit_data/g2-nonuniform-2023-04-04.pickle
|
|
24
|
-
src/pyglaze/device/_delayunit_data/mock_delay.pickle
|
|
25
18
|
src/pyglaze/devtools/__init__.py
|
|
26
19
|
src/pyglaze/devtools/mock_device.py
|
|
27
20
|
src/pyglaze/devtools/thz_pulse.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.2"
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from .configuration import ForceDeviceConfiguration, Interval, LeDeviceConfiguration
|
|
2
|
-
from .delayunit import NonuniformDelay, UniformDelay, list_delayunits, load_delayunit
|
|
3
|
-
from .identifiers import get_device_id, list_devices
|
|
4
|
-
|
|
5
|
-
__all__ = [
|
|
6
|
-
"LeDeviceConfiguration",
|
|
7
|
-
"ForceDeviceConfiguration",
|
|
8
|
-
"Interval",
|
|
9
|
-
"NonuniformDelay",
|
|
10
|
-
"UniformDelay",
|
|
11
|
-
"list_delayunits",
|
|
12
|
-
"load_delayunit",
|
|
13
|
-
"get_device_id",
|
|
14
|
-
"list_devices",
|
|
15
|
-
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import pickle
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
from dataclasses import asdict, dataclass
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING, Callable, cast
|
|
9
|
-
from uuid import UUID, uuid4
|
|
10
|
-
|
|
11
|
-
import numpy as np
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from pyglaze.helpers.types import FloatArray
|
|
15
|
-
|
|
16
|
-
__all__ = ["UniformDelay", "NonuniformDelay", "list_delayunits", "load_delayunit"]
|
|
17
|
-
|
|
18
|
-
_DELAYUNITS_PATH = Path(__file__).parent / "_delayunit_data"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def validate_delayunit(name: str) -> None:
|
|
22
|
-
delayunits = list_delayunits()
|
|
23
|
-
if name not in delayunits:
|
|
24
|
-
msg = f"Unknown delayunit '{name}'. Valid options are: {', '.join(delayunits)}."
|
|
25
|
-
raise ValueError(msg)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def list_delayunits() -> list[str]:
|
|
29
|
-
"""List all available delayunits.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
A list of all available delayunits.
|
|
33
|
-
|
|
34
|
-
"""
|
|
35
|
-
return [delayunit.stem for delayunit in _DELAYUNITS_PATH.iterdir()]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def load_delayunit(name: str) -> Delay:
|
|
39
|
-
"""Load a delayunit by name.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
name: The name of the delayunit to load.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
The loaded delayunit.
|
|
46
|
-
"""
|
|
47
|
-
try:
|
|
48
|
-
return _load_delayunit_from_path(_DELAYUNITS_PATH / f"{name}.pickle")
|
|
49
|
-
except FileNotFoundError as e:
|
|
50
|
-
msg = f"Unknown delayunit requested ('{name}'). Known units are: {list_delayunits()}"
|
|
51
|
-
raise NameError(msg) from e
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _load_delayunit_from_path(path: Path) -> Delay:
|
|
55
|
-
with Path(path).open("rb") as f:
|
|
56
|
-
_dict: dict = pickle.load(f)
|
|
57
|
-
delay_type = _dict.pop("type")
|
|
58
|
-
return cast(Delay, globals()[delay_type](**_dict))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@dataclass(frozen=True)
|
|
62
|
-
class Delay(ABC):
|
|
63
|
-
friendly_name: str
|
|
64
|
-
unique_id: UUID
|
|
65
|
-
creation_time: datetime
|
|
66
|
-
|
|
67
|
-
def __call__(self: Delay, x: FloatArray) -> FloatArray:
|
|
68
|
-
if np.max(x) > 1.0 or np.min(x) < 0.0:
|
|
69
|
-
msg = "All values of 'x' must be between 0 and 1."
|
|
70
|
-
raise ValueError(msg)
|
|
71
|
-
|
|
72
|
-
return self._call(x)
|
|
73
|
-
|
|
74
|
-
@property
|
|
75
|
-
def filename(self: Delay) -> str:
|
|
76
|
-
return f"{self.friendly_name}-{self.creation_time.strftime('%Y-%m-%d')}.pickle"
|
|
77
|
-
|
|
78
|
-
@abstractmethod
|
|
79
|
-
def _call(self: Delay, x: FloatArray) -> FloatArray: ...
|
|
80
|
-
|
|
81
|
-
def save(self: Delay, path: Path) -> None:
|
|
82
|
-
with Path(path).open("wb") as f:
|
|
83
|
-
pickle.dump({"type": self.__class__.__name__, **asdict(self)}, f)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@dataclass(frozen=True)
|
|
87
|
-
class UniformDelay(Delay):
|
|
88
|
-
"""A delay calculator that calculates equidisant delays."""
|
|
89
|
-
|
|
90
|
-
time_window: float
|
|
91
|
-
|
|
92
|
-
def _call(self: UniformDelay, x: FloatArray) -> FloatArray:
|
|
93
|
-
return x * self.time_window
|
|
94
|
-
|
|
95
|
-
@classmethod
|
|
96
|
-
def new(cls: type[UniformDelay], time_window: float, friendly_name: str) -> Delay:
|
|
97
|
-
"""Create a new Delay object.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
time_window: The time window for the delay.
|
|
101
|
-
friendly_name: The friendly name of the delay.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
Delay: The newly created Delay object.
|
|
105
|
-
"""
|
|
106
|
-
return cls(
|
|
107
|
-
friendly_name=friendly_name,
|
|
108
|
-
unique_id=uuid4(),
|
|
109
|
-
creation_time=datetime.now(), # noqa: DTZ005
|
|
110
|
-
time_window=time_window,
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@dataclass(frozen=True)
|
|
115
|
-
class NonuniformDelay(Delay):
|
|
116
|
-
"""A delay calculator that calculates non-equidistant delays."""
|
|
117
|
-
|
|
118
|
-
time_window: float
|
|
119
|
-
residual_interpolator: Callable[[FloatArray], FloatArray]
|
|
120
|
-
|
|
121
|
-
def _call(self: NonuniformDelay, x: FloatArray) -> FloatArray:
|
|
122
|
-
return (
|
|
123
|
-
x * self.time_window
|
|
124
|
-
+ self.residual_interpolator(x)
|
|
125
|
-
- self.residual_interpolator(np.asarray(0.0))
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
@classmethod
|
|
129
|
-
def new(
|
|
130
|
-
cls: type[NonuniformDelay],
|
|
131
|
-
friendly_name: str,
|
|
132
|
-
time_window: float,
|
|
133
|
-
residual_interpolator: Callable[[FloatArray], FloatArray],
|
|
134
|
-
) -> Delay:
|
|
135
|
-
"""Create a new NonuniformDelay object.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
friendly_name: The friendly name of the NonuniformDelay object.
|
|
139
|
-
time_window: The time window of the pulse.
|
|
140
|
-
residual_interpolator: a residual interpolator for calculating the nonuniform part of the delay.
|
|
141
|
-
|
|
142
|
-
Returns:
|
|
143
|
-
Delay: The newly created Delay object.
|
|
144
|
-
"""
|
|
145
|
-
return cls(
|
|
146
|
-
friendly_name=friendly_name,
|
|
147
|
-
unique_id=uuid4(),
|
|
148
|
-
creation_time=datetime.now(), # noqa: DTZ005
|
|
149
|
-
time_window=time_window,
|
|
150
|
-
residual_interpolator=residual_interpolator,
|
|
151
|
-
)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from typing import Literal, get_args
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from typing_extensions import TypeAlias
|
|
5
|
-
|
|
6
|
-
DeviceName: TypeAlias = Literal["glaze1", "glaze2", "carmen"]
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _device_ids() -> dict[DeviceName, UUID]:
|
|
10
|
-
return {
|
|
11
|
-
"glaze1": UUID("5042dbda-e9bc-4216-a614-ac56d0a32023"),
|
|
12
|
-
"glaze2": UUID("66fa482a-1ef4-4076-a883-72d7bf43e151"),
|
|
13
|
-
"carmen": UUID("6a54db26-fa88-4146-b04f-b84b945bfea8"),
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def list_devices() -> list[DeviceName]:
|
|
18
|
-
"""List all available devices.
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
A list of all available devices.
|
|
22
|
-
"""
|
|
23
|
-
return list(_device_ids().keys())
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def get_device_id(name: DeviceName) -> UUID:
|
|
27
|
-
"""Get the UUID of a device by its name.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
name: The name of the device.
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Unique identifier of a device.
|
|
34
|
-
"""
|
|
35
|
-
try:
|
|
36
|
-
return _device_ids()[name]
|
|
37
|
-
except KeyError as e:
|
|
38
|
-
msg = (
|
|
39
|
-
f"Device {name} does not exist. Possible values are {get_args(DeviceName)}"
|
|
40
|
-
)
|
|
41
|
-
raise ValueError(msg) from e
|
|
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
|