dt5810 2.0.1__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.
- dt5810-2.0.1/LICENSE +24 -0
- dt5810-2.0.1/MANIFEST.in +6 -0
- dt5810-2.0.1/PKG-INFO +88 -0
- dt5810-2.0.1/README.md +51 -0
- dt5810-2.0.1/build_wheel.py +195 -0
- dt5810-2.0.1/examples/01_info.py +38 -0
- dt5810-2.0.1/examples/02_fixed_energy_pulser.py +31 -0
- dt5810-2.0.1/examples/03_spectrum_emulation.py +51 -0
- dt5810-2.0.1/examples/04_custom_shape.py +36 -0
- dt5810-2.0.1/examples/05_monitor.py +42 -0
- dt5810-2.0.1/examples/06_mca_input.py +51 -0
- dt5810-2.0.1/examples/07_awg.py +37 -0
- dt5810-2.0.1/pyproject.toml +54 -0
- dt5810-2.0.1/setup.cfg +4 -0
- dt5810-2.0.1/src/dt5810/__init__.py +72 -0
- dt5810-2.0.1/src/dt5810/_arrays.py +84 -0
- dt5810-2.0.1/src/dt5810/_lib.py +343 -0
- dt5810-2.0.1/src/dt5810/_version.py +3 -0
- dt5810-2.0.1/src/dt5810/device.py +670 -0
- dt5810-2.0.1/src/dt5810/enums.py +214 -0
- dt5810-2.0.1/src/dt5810/errors.py +126 -0
- dt5810-2.0.1/src/dt5810/library.py +143 -0
- dt5810-2.0.1/src/dt5810/py.typed +0 -0
- dt5810-2.0.1/src/dt5810/types.py +218 -0
- dt5810-2.0.1/src/dt5810.egg-info/PKG-INFO +88 -0
- dt5810-2.0.1/src/dt5810.egg-info/SOURCES.txt +32 -0
- dt5810-2.0.1/src/dt5810.egg-info/dependency_links.txt +1 -0
- dt5810-2.0.1/src/dt5810.egg-info/requires.txt +6 -0
- dt5810-2.0.1/src/dt5810.egg-info/top_level.txt +1 -0
- dt5810-2.0.1/tests/conftest.py +51 -0
- dt5810-2.0.1/tests/test_channel.py +280 -0
- dt5810-2.0.1/tests/test_device.py +228 -0
- dt5810-2.0.1/tests/test_examples.py +31 -0
- dt5810-2.0.1/tests/test_library.py +266 -0
dt5810-2.0.1/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nuclear Instruments SRL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The CyAPI library (sdk/cpp/third_party/cyapi) is Copyright Cypress Semiconductor
|
|
24
|
+
Corporation (Infineon Technologies) and is distributed under its own license terms.
|
dt5810-2.0.1/MANIFEST.in
ADDED
dt5810-2.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dt5810
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Python binding of the DT5810 SDK: control of the CAEN DT5810 / DT5810B Digital Detector Emulator
|
|
5
|
+
Author-email: Nuclear Instruments <support@nuclearinstruments.eu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk
|
|
8
|
+
Project-URL: Documentation, https://public-repo.pages.nuclearinstruments.eu/dt5810/dt5810-sdk/
|
|
9
|
+
Project-URL: Source, https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk
|
|
10
|
+
Project-URL: Issues, https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk/-/issues
|
|
11
|
+
Keywords: DT5810,DT5810B,CAEN,Nuclear Instruments,detector emulator,pulser,nuclear physics
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
27
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.8
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Provides-Extra: numpy
|
|
33
|
+
Requires-Dist: numpy; extra == "numpy"
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: pytest; extra == "test"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# dt5810 — Python binding of the DT5810 SDK
|
|
39
|
+
|
|
40
|
+
Control the **CAEN DT5810 / DT5810B Digital Detector Emulator** (Nuclear Instruments) from
|
|
41
|
+
Python: energy (fixed or spectrum), pulse shape, time base, noise, baseline drift,
|
|
42
|
+
correlation, digital I/O, run control, monitoring, AWG / sequences and the analog input
|
|
43
|
+
MCA. The package calls the native DT5810 library (`dt5810.dll` / `libdt5810.so`) through
|
|
44
|
+
`ctypes`; a built-in simulator (`sim://`) lets you develop and test without hardware.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
pip install dt5810
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The wheels for Windows x64 and Linux x86_64 (glibc 2.34+) include the native library.
|
|
53
|
+
Python 3.8 or later; no mandatory dependency (`pip install dt5810[numpy]` to get numpy
|
|
54
|
+
arrays from the read functions with `as_numpy=True`).
|
|
55
|
+
|
|
56
|
+
With the source distribution, or to use another build of the library, point
|
|
57
|
+
`DT5810_LIBRARY` at it (full path of `dt5810.dll` / `libdt5810.so`); otherwise the library
|
|
58
|
+
is looked up in the package, then by the system loader.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import dt5810
|
|
64
|
+
|
|
65
|
+
print(dt5810.version())
|
|
66
|
+
for d in dt5810.enumerate(dt5810.Bus.USB):
|
|
67
|
+
print(d.serial, d.address)
|
|
68
|
+
|
|
69
|
+
with dt5810.open("sim://") as dev: # or "usb://", "usb://NI050123"
|
|
70
|
+
print(dev.info.model_name, dev.info.serial)
|
|
71
|
+
ch = dev.channel(0)
|
|
72
|
+
ch.set_fixed_energy(20000) # LSB, 0..32767
|
|
73
|
+
ch.set_drc(rise_ns=20, decay_ns=50000)
|
|
74
|
+
ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=10000)
|
|
75
|
+
ch.enable()
|
|
76
|
+
print(ch.stats())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Errors raise `dt5810.Dt5810Error` subclasses (`OutOfRangeError`, `InvalidParameterError`,
|
|
80
|
+
`NoDataError`, `NotFoundError`, `DeviceTimeoutError`, ...) carrying `.status` and
|
|
81
|
+
`.message`. The `examples/` folder has runnable scripts for every feature
|
|
82
|
+
(`python examples/01_info.py usb://`).
|
|
83
|
+
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
User guide and API reference: <https://public-repo.pages.nuclearinstruments.eu/dt5810/dt5810-sdk/>
|
|
87
|
+
|
|
88
|
+
Source: <https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk> — MIT License.
|
dt5810-2.0.1/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# dt5810 — Python binding of the DT5810 SDK
|
|
2
|
+
|
|
3
|
+
Control the **CAEN DT5810 / DT5810B Digital Detector Emulator** (Nuclear Instruments) from
|
|
4
|
+
Python: energy (fixed or spectrum), pulse shape, time base, noise, baseline drift,
|
|
5
|
+
correlation, digital I/O, run control, monitoring, AWG / sequences and the analog input
|
|
6
|
+
MCA. The package calls the native DT5810 library (`dt5810.dll` / `libdt5810.so`) through
|
|
7
|
+
`ctypes`; a built-in simulator (`sim://`) lets you develop and test without hardware.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install dt5810
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The wheels for Windows x64 and Linux x86_64 (glibc 2.34+) include the native library.
|
|
16
|
+
Python 3.8 or later; no mandatory dependency (`pip install dt5810[numpy]` to get numpy
|
|
17
|
+
arrays from the read functions with `as_numpy=True`).
|
|
18
|
+
|
|
19
|
+
With the source distribution, or to use another build of the library, point
|
|
20
|
+
`DT5810_LIBRARY` at it (full path of `dt5810.dll` / `libdt5810.so`); otherwise the library
|
|
21
|
+
is looked up in the package, then by the system loader.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import dt5810
|
|
27
|
+
|
|
28
|
+
print(dt5810.version())
|
|
29
|
+
for d in dt5810.enumerate(dt5810.Bus.USB):
|
|
30
|
+
print(d.serial, d.address)
|
|
31
|
+
|
|
32
|
+
with dt5810.open("sim://") as dev: # or "usb://", "usb://NI050123"
|
|
33
|
+
print(dev.info.model_name, dev.info.serial)
|
|
34
|
+
ch = dev.channel(0)
|
|
35
|
+
ch.set_fixed_energy(20000) # LSB, 0..32767
|
|
36
|
+
ch.set_drc(rise_ns=20, decay_ns=50000)
|
|
37
|
+
ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=10000)
|
|
38
|
+
ch.enable()
|
|
39
|
+
print(ch.stats())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Errors raise `dt5810.Dt5810Error` subclasses (`OutOfRangeError`, `InvalidParameterError`,
|
|
43
|
+
`NoDataError`, `NotFoundError`, `DeviceTimeoutError`, ...) carrying `.status` and
|
|
44
|
+
`.message`. The `examples/` folder has runnable scripts for every feature
|
|
45
|
+
(`python examples/01_info.py usb://`).
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
User guide and API reference: <https://public-repo.pages.nuclearinstruments.eu/dt5810/dt5810-sdk/>
|
|
50
|
+
|
|
51
|
+
Source: <https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk> — MIT License.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Builds a platform wheel of the dt5810 package with the native library inside.
|
|
3
|
+
|
|
4
|
+
python build_wheel.py --lib <dt5810.dll | libdt5810.so> --plat-name <tag> [--outdir dist]
|
|
5
|
+
|
|
6
|
+
Examples of `--plat-name`: ``win_amd64``, ``manylinux_2_34_x86_64``.
|
|
7
|
+
|
|
8
|
+
Steps: copy the library into ``src/dt5810/_native/``, build the (pure) wheel with
|
|
9
|
+
``python -m build --wheel`` (or ``pip wheel --no-deps`` when ``build`` is not installed),
|
|
10
|
+
retag it ``py3-none-<plat>`` (WHEEL file, RECORD and file name rewritten here: no extra
|
|
11
|
+
tool needed), check that it carries the library, and empty ``_native/`` again.
|
|
12
|
+
|
|
13
|
+
The build does not use an isolated environment when setuptools is already installed
|
|
14
|
+
(no network needed); pass ``--isolation`` to force one.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import base64
|
|
21
|
+
import glob
|
|
22
|
+
import hashlib
|
|
23
|
+
import importlib.util
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
import zipfile
|
|
30
|
+
|
|
31
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
32
|
+
NATIVE = os.path.join(HERE, "src", "dt5810", "_native")
|
|
33
|
+
NATIVE_NAMES = ("dt5810.dll", "libdt5810.so", "libdt5810.dylib")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def log(msg: str) -> None:
|
|
37
|
+
print("[build_wheel] " + msg, flush=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def native_name(lib: str, plat: str) -> str:
|
|
41
|
+
if plat.startswith("win"):
|
|
42
|
+
return "dt5810.dll"
|
|
43
|
+
if plat.startswith("macosx"):
|
|
44
|
+
return "libdt5810.dylib"
|
|
45
|
+
if lib.lower().endswith(".dll"):
|
|
46
|
+
raise SystemExit("a .dll cannot go in a %s wheel" % plat)
|
|
47
|
+
return "libdt5810.so"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def clean_native() -> None:
|
|
51
|
+
for name in NATIVE_NAMES:
|
|
52
|
+
p = os.path.join(NATIVE, name)
|
|
53
|
+
if os.path.exists(p):
|
|
54
|
+
os.remove(p)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def have(module: str) -> bool:
|
|
58
|
+
return importlib.util.find_spec(module) is not None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def setuptools_can_build_wheels() -> bool:
|
|
62
|
+
if not have("setuptools"):
|
|
63
|
+
return False
|
|
64
|
+
if have("wheel"):
|
|
65
|
+
return True
|
|
66
|
+
try: # setuptools >= 70.1 has its own bdist_wheel
|
|
67
|
+
import setuptools
|
|
68
|
+
|
|
69
|
+
major, minor = (int(x) for x in setuptools.__version__.split(".")[:2])
|
|
70
|
+
return (major, minor) >= (70, 1)
|
|
71
|
+
except Exception:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def build_pure_wheel(tmpdir: str, isolation: bool) -> str:
|
|
76
|
+
for d in ("build", os.path.join("src", "dt5810.egg-info")):
|
|
77
|
+
shutil.rmtree(os.path.join(HERE, d), ignore_errors=True)
|
|
78
|
+
no_isolation = not isolation and setuptools_can_build_wheels()
|
|
79
|
+
if have("build"):
|
|
80
|
+
cmd = [sys.executable, "-m", "build", "--wheel", "--outdir", tmpdir]
|
|
81
|
+
if no_isolation:
|
|
82
|
+
cmd.append("--no-isolation")
|
|
83
|
+
else:
|
|
84
|
+
cmd = [sys.executable, "-m", "pip", "wheel", "--no-deps", "-w", tmpdir]
|
|
85
|
+
if no_isolation:
|
|
86
|
+
cmd.append("--no-build-isolation")
|
|
87
|
+
cmd.append(HERE)
|
|
88
|
+
log(" ".join(cmd))
|
|
89
|
+
subprocess.check_call(cmd, cwd=HERE)
|
|
90
|
+
wheels = glob.glob(os.path.join(tmpdir, "dt5810-*.whl"))
|
|
91
|
+
if len(wheels) != 1:
|
|
92
|
+
raise SystemExit("expected one wheel in %s, found %r" % (tmpdir, wheels))
|
|
93
|
+
for d in ("build", os.path.join("src", "dt5810.egg-info")):
|
|
94
|
+
shutil.rmtree(os.path.join(HERE, d), ignore_errors=True)
|
|
95
|
+
return wheels[0]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def record_hash(data: bytes) -> str:
|
|
99
|
+
digest = hashlib.sha256(data).digest()
|
|
100
|
+
return "sha256=" + base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def retag(wheel: str, plat: str, outdir: str) -> str:
|
|
104
|
+
"""Rewrites `wheel` as py3-none-<plat> into `outdir`; returns the new path."""
|
|
105
|
+
base = os.path.basename(wheel)
|
|
106
|
+
parts = base[:-4].split("-")
|
|
107
|
+
if len(parts) < 5:
|
|
108
|
+
raise SystemExit("unexpected wheel name " + base)
|
|
109
|
+
name, ver = parts[0], parts[1]
|
|
110
|
+
tag = "py3-none-" + plat
|
|
111
|
+
target = os.path.join(outdir, "%s-%s-%s.whl" % (name, ver, tag))
|
|
112
|
+
dist_info = "%s-%s.dist-info" % (name, ver)
|
|
113
|
+
with zipfile.ZipFile(wheel) as zin:
|
|
114
|
+
entries = [(i, zin.read(i.filename)) for i in zin.infolist()]
|
|
115
|
+
files = {}
|
|
116
|
+
order = []
|
|
117
|
+
for info, data in entries:
|
|
118
|
+
order.append(info)
|
|
119
|
+
files[info.filename] = data
|
|
120
|
+
wheel_meta = files[dist_info + "/WHEEL"].decode("utf-8").splitlines()
|
|
121
|
+
out_lines = []
|
|
122
|
+
for line in wheel_meta:
|
|
123
|
+
if line.startswith("Tag:"):
|
|
124
|
+
continue
|
|
125
|
+
if line.startswith("Root-Is-Purelib:"):
|
|
126
|
+
line = "Root-Is-Purelib: false"
|
|
127
|
+
out_lines.append(line)
|
|
128
|
+
while out_lines and not out_lines[-1].strip():
|
|
129
|
+
out_lines.pop()
|
|
130
|
+
out_lines.append("Tag: " + tag)
|
|
131
|
+
files[dist_info + "/WHEEL"] = ("\n".join(out_lines) + "\n").encode("utf-8")
|
|
132
|
+
record_name = dist_info + "/RECORD"
|
|
133
|
+
record = []
|
|
134
|
+
for info in order:
|
|
135
|
+
if info.filename == record_name or info.filename.endswith("/"):
|
|
136
|
+
continue
|
|
137
|
+
data = files[info.filename]
|
|
138
|
+
record.append("%s,%s,%d" % (info.filename, record_hash(data), len(data)))
|
|
139
|
+
record.append(record_name + ",,")
|
|
140
|
+
files[record_name] = ("\n".join(record) + "\n").encode("utf-8")
|
|
141
|
+
if os.path.exists(target):
|
|
142
|
+
os.remove(target)
|
|
143
|
+
with zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as zout:
|
|
144
|
+
for info in order:
|
|
145
|
+
zi = zipfile.ZipInfo(info.filename, date_time=info.date_time)
|
|
146
|
+
zi.external_attr = info.external_attr
|
|
147
|
+
zi.compress_type = zipfile.ZIP_DEFLATED
|
|
148
|
+
zout.writestr(zi, files[info.filename])
|
|
149
|
+
return target
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main() -> int:
|
|
153
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
154
|
+
ap.add_argument("--lib", required=True, help="path of dt5810.dll or libdt5810.so")
|
|
155
|
+
ap.add_argument("--plat-name", required=True,
|
|
156
|
+
help="platform tag, e.g. win_amd64 or manylinux_2_34_x86_64")
|
|
157
|
+
ap.add_argument("--outdir", default=os.path.join(HERE, "dist"))
|
|
158
|
+
ap.add_argument("--isolation", action="store_true",
|
|
159
|
+
help="build in an isolated environment (downloads setuptools)")
|
|
160
|
+
ap.add_argument("--keep-native", action="store_true",
|
|
161
|
+
help="leave the library in src/dt5810/_native/ afterwards")
|
|
162
|
+
args = ap.parse_args()
|
|
163
|
+
|
|
164
|
+
lib = os.path.abspath(args.lib)
|
|
165
|
+
if not os.path.isfile(lib):
|
|
166
|
+
raise SystemExit("library not found: " + lib)
|
|
167
|
+
plat = args.plat_name.replace("-", "_").replace(".", "_")
|
|
168
|
+
outdir = os.path.abspath(args.outdir)
|
|
169
|
+
os.makedirs(outdir, exist_ok=True)
|
|
170
|
+
|
|
171
|
+
os.makedirs(NATIVE, exist_ok=True)
|
|
172
|
+
clean_native()
|
|
173
|
+
dest = os.path.join(NATIVE, native_name(lib, plat))
|
|
174
|
+
shutil.copy2(lib, dest)
|
|
175
|
+
log("copied %s -> %s" % (lib, dest))
|
|
176
|
+
try:
|
|
177
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
178
|
+
pure = build_pure_wheel(tmp, args.isolation)
|
|
179
|
+
target = retag(pure, plat, outdir)
|
|
180
|
+
finally:
|
|
181
|
+
if not args.keep_native:
|
|
182
|
+
clean_native()
|
|
183
|
+
|
|
184
|
+
with zipfile.ZipFile(target) as z:
|
|
185
|
+
names = z.namelist()
|
|
186
|
+
inside = "dt5810/_native/" + os.path.basename(dest)
|
|
187
|
+
if inside not in names:
|
|
188
|
+
raise SystemExit("the wheel does not contain " + inside)
|
|
189
|
+
log("wheel: " + target)
|
|
190
|
+
print(target)
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
sys.exit(main())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Lists the instruments and prints the identity of one of them.
|
|
2
|
+
|
|
3
|
+
python 01_info.py [address] (default "sim://", e.g. "usb://" or "usb://NI050123")
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import dt5810
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(address: str) -> None:
|
|
12
|
+
print("dt5810 package %s, native library %s" % (dt5810.__version__, dt5810.version()))
|
|
13
|
+
print("buses compiled in: %s" % ", ".join(b.name for b in dt5810.supported_buses()))
|
|
14
|
+
|
|
15
|
+
buses = dt5810.Bus.SIMULATOR if address.startswith("sim://") else dt5810.Bus.USB
|
|
16
|
+
for d in dt5810.enumerate(buses):
|
|
17
|
+
print("found %-10s %-20s %s" % (d.serial or "?", d.address, d.description))
|
|
18
|
+
|
|
19
|
+
with dt5810.open(address) as dev:
|
|
20
|
+
info = dev.info
|
|
21
|
+
print("model %s (code 0x%08X)" % (info.model_name, info.model_code))
|
|
22
|
+
print("serial %s" % info.serial)
|
|
23
|
+
print("firmware 0x%08X" % info.firmware_release)
|
|
24
|
+
print("outputs/inputs %d / %d" % (info.output_channels, info.input_channels))
|
|
25
|
+
print("clock %.0f MHz, DAC %.0f MS/s"
|
|
26
|
+
% (info.clock_hz / 1e6, info.sample_rate_hz / 1e6))
|
|
27
|
+
print("running %s" % dt5810.RunningMode(dev.running_mode()).name)
|
|
28
|
+
act = dev.get_activation()
|
|
29
|
+
print("activation active=%s trial_counter=%d expired=%s" % tuple(act))
|
|
30
|
+
clk = dev.get_clock_config()
|
|
31
|
+
print("clock config external_in=%s clock_out=%s" % tuple(clk))
|
|
32
|
+
for i in range(4):
|
|
33
|
+
cal = dev.read_calibration(i)
|
|
34
|
+
print("calibration %d offset=%g gain=%g lsb_to_volt=%g" % ((i,) + tuple(cal)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""A periodic pulser: fixed amplitude, exponential shape, constant rate.
|
|
2
|
+
|
|
3
|
+
python 02_fixed_energy_pulser.py [address] (default "sim://")
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
import dt5810
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(address: str) -> None:
|
|
13
|
+
with dt5810.open(address) as dev:
|
|
14
|
+
ch = dev.channel(0)
|
|
15
|
+
ch.set_output(gain=1.0, offset=0, invert=False, filter=True,
|
|
16
|
+
range=dt5810.OutputRange.HIGH_PERFORMANCE)
|
|
17
|
+
ch.set_fixed_energy(16000) # LSB (0..32767)
|
|
18
|
+
ch.set_drc(rise_ns=20, decay_ns=50000) # exponential pulse
|
|
19
|
+
ch.set_timebase(dt5810.TimebaseMode.CONSTANT, rate_hz=1000)
|
|
20
|
+
ch.set_noise() # no noise
|
|
21
|
+
ch.enable()
|
|
22
|
+
print("amplitude: 16000 LSB = %.3f V" % ch.lsb_to_volt(16000))
|
|
23
|
+
|
|
24
|
+
time.sleep(1.0)
|
|
25
|
+
st = ch.stats()
|
|
26
|
+
print("rate %d cps, %d events" % (st.count_rate, st.event_counter))
|
|
27
|
+
ch.disable()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Emulates a gamma spectrum: three Gaussian peaks on an exponential background,
|
|
2
|
+
Poisson arrivals and some Gaussian noise.
|
|
3
|
+
|
|
4
|
+
python 03_spectrum_emulation.py [address] (default "sim://")
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
import dt5810
|
|
12
|
+
|
|
13
|
+
PEAKS = [ # (bin, sigma in bins, relative intensity)
|
|
14
|
+
(2000, 25, 1.0),
|
|
15
|
+
(6000, 40, 0.6),
|
|
16
|
+
(11000, 60, 0.3),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def make_spectrum():
|
|
21
|
+
s = []
|
|
22
|
+
for i in range(dt5810.SPECTRUM_BINS):
|
|
23
|
+
v = 0.02 * math.exp(-i / 3000.0) # background
|
|
24
|
+
for centre, sigma, height in PEAKS:
|
|
25
|
+
v += height * math.exp(-0.5 * ((i - centre) / sigma) ** 2)
|
|
26
|
+
s.append(v)
|
|
27
|
+
return s
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main(address: str) -> None:
|
|
31
|
+
with dt5810.open(address) as dev:
|
|
32
|
+
ch = dev.channel(0)
|
|
33
|
+
ch.set_spectrum(make_spectrum()) # program + select the spectrum
|
|
34
|
+
ch.set_drc(rise_ns=30, decay_ns=40000)
|
|
35
|
+
ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=20000, dead_time_ns=200)
|
|
36
|
+
ch.set_noise(gauss=100)
|
|
37
|
+
ch.reset_spectrum_loopback()
|
|
38
|
+
ch.enable()
|
|
39
|
+
|
|
40
|
+
time.sleep(2.0)
|
|
41
|
+
spectrum = ch.spectrum_loopback() # 4096 bins
|
|
42
|
+
total = sum(spectrum)
|
|
43
|
+
print("emitted spectrum: %d counts in %d bins" % (total, len(spectrum)))
|
|
44
|
+
if total:
|
|
45
|
+
top = max(range(len(spectrum)), key=spectrum.__getitem__)
|
|
46
|
+
print("highest bin: %d (%d counts)" % (top, spectrum[top]))
|
|
47
|
+
ch.disable()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Custom pulse shapes: one shape from samples, then two shapes with probabilities.
|
|
2
|
+
|
|
3
|
+
python 04_custom_shape.py [address] (default "sim://")
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import dt5810
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(address: str) -> None:
|
|
13
|
+
with dt5810.open(address) as dev:
|
|
14
|
+
ch = dev.channel(0)
|
|
15
|
+
ts = 1.0 / dev.info.sample_rate_hz # sample period in s
|
|
16
|
+
|
|
17
|
+
# the "fast" exponential of the Control Software: 50 ns rise, 5 us decay
|
|
18
|
+
fast = dt5810.make_fast_exponential(rise_us=0.05, decay_us=5.0, sample_period_s=ts,
|
|
19
|
+
length=dt5810.CUSTOM_SHAPE_MAX_POINTS)
|
|
20
|
+
ch.set_custom_shape(fast, pileup_limit=16)
|
|
21
|
+
print("custom shape: %d samples, peak %.0f" % (len(fast), max(fast)))
|
|
22
|
+
|
|
23
|
+
# a slower, Gaussian-like shape mixed with the first one: 70 % / 30 %
|
|
24
|
+
slow = [32575 * math.exp(-0.5 * ((i - 300) / 80.0) ** 2) for i in range(1200)]
|
|
25
|
+
ch.set_multi_shapes([fast, slow], [70, 30], pileup_limit=8)
|
|
26
|
+
print("multishape: 70 % fast exponential, 30 % gaussian")
|
|
27
|
+
|
|
28
|
+
ch.set_fixed_energy(20000)
|
|
29
|
+
ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=5000)
|
|
30
|
+
ch.enable()
|
|
31
|
+
ch.disable()
|
|
32
|
+
ch.disable_custom_shape() # back to the digital RC shaper
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Monitoring: statistics, run-control live data and the signal / spectrum loopback.
|
|
2
|
+
|
|
3
|
+
python 05_monitor.py [address] [seconds] (default "sim://", 3 s)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
import dt5810
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(address: str, seconds: float) -> None:
|
|
13
|
+
with dt5810.open(address) as dev:
|
|
14
|
+
ch = dev.channel(0)
|
|
15
|
+
ch.set_fixed_energy(12000)
|
|
16
|
+
ch.set_drc(rise_ns=20, decay_ns=20000)
|
|
17
|
+
ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=50000)
|
|
18
|
+
ch.set_run_control(dt5810.RunMode.FREE_RUNNING)
|
|
19
|
+
ch.run_control_reset_stats()
|
|
20
|
+
ch.reset_counter()
|
|
21
|
+
ch.enable()
|
|
22
|
+
|
|
23
|
+
end = time.time() + seconds
|
|
24
|
+
while time.time() < end:
|
|
25
|
+
time.sleep(0.5)
|
|
26
|
+
st = ch.stats()
|
|
27
|
+
ld = ch.live_data()
|
|
28
|
+
print("rate %6d cps events %9d real %.2f s run %.2f s busy=%s sat=%s"
|
|
29
|
+
% (st.count_rate, st.event_counter, ld.real_time_s, ld.run_time_s,
|
|
30
|
+
ld.busy, ld.saturated))
|
|
31
|
+
|
|
32
|
+
signal = ch.signal_loopback() # 8192 samples
|
|
33
|
+
spectrum = ch.spectrum_loopback() # 4096 bins
|
|
34
|
+
print("signal loopback: %d samples, min %d, max %d"
|
|
35
|
+
% (len(signal), min(signal), max(signal)))
|
|
36
|
+
print("spectrum loopback: %d bins, %d counts" % (len(spectrum), sum(spectrum)))
|
|
37
|
+
ch.disable()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://",
|
|
42
|
+
float(sys.argv[2]) if len(sys.argv) > 2 else 3.0)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""The analog input MCA: trapezoidal filter, spectrum acquisition and preview.
|
|
2
|
+
|
|
3
|
+
python 06_mca_input.py [address] [seconds] (default "sim://", 2 s)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
import dt5810
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(address: str, seconds: float) -> None:
|
|
13
|
+
with dt5810.open(address) as dev:
|
|
14
|
+
dev.mca_set_input(impedance=0, divider=0, mode=0, scale=0, gain=0, offset=0)
|
|
15
|
+
|
|
16
|
+
cfg = dt5810.mca_default_config()
|
|
17
|
+
cfg.peaking_us = 2.0
|
|
18
|
+
cfg.flat_top_us = 0.5
|
|
19
|
+
cfg.decay_tau_us = 50.0
|
|
20
|
+
dev.mca_configure(cfg)
|
|
21
|
+
|
|
22
|
+
dev.mca_reset()
|
|
23
|
+
dev.mca_clear_spectrum()
|
|
24
|
+
dev.mca_run(True)
|
|
25
|
+
time.sleep(seconds)
|
|
26
|
+
spectrum = dev.mca_get_spectrum() # 16384 bins
|
|
27
|
+
dev.mca_stop()
|
|
28
|
+
total = sum(spectrum)
|
|
29
|
+
print("MCA spectrum: %d counts in %d bins" % (total, len(spectrum)))
|
|
30
|
+
if total:
|
|
31
|
+
peak = max(range(len(spectrum)), key=spectrum.__getitem__)
|
|
32
|
+
print("peak at bin %d" % peak)
|
|
33
|
+
|
|
34
|
+
dev.mca_preview_configure(analog1=0, analog2=1, digital1=0, digital2=1,
|
|
35
|
+
trigger_source=0, trigger_level=500, post_trigger=1024)
|
|
36
|
+
for _ in range(20):
|
|
37
|
+
try:
|
|
38
|
+
p = dev.mca_preview_read()
|
|
39
|
+
break
|
|
40
|
+
except dt5810.NoDataError: # not triggered yet
|
|
41
|
+
time.sleep(0.1)
|
|
42
|
+
else:
|
|
43
|
+
print("the preview did not trigger")
|
|
44
|
+
return
|
|
45
|
+
print("preview: %d samples, analog1 max %d, analog2 max %d"
|
|
46
|
+
% (len(p.analog1), max(p.analog1), max(p.analog2)))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://",
|
|
51
|
+
float(sys.argv[2]) if len(sys.argv) > 2 else 2.0)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Arbitrary waveform generator: a sine wave on channel 0, a pulse sequence on channel 1.
|
|
2
|
+
|
|
3
|
+
python 07_awg.py [address] (default "sim://")
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import dt5810
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(address: str) -> None:
|
|
13
|
+
with dt5810.open(address) as dev:
|
|
14
|
+
awg = dev.channel(0)
|
|
15
|
+
samples = [int(15000 * math.sin(2 * math.pi * i / 256)) for i in range(4096)]
|
|
16
|
+
awg.set_awg_waveform(samples) # padded to a multiple of 16
|
|
17
|
+
awg.set_awg_scaler(0) # one sample per clock period
|
|
18
|
+
awg.set_mode(dt5810.ChannelMode.AWG)
|
|
19
|
+
awg.enable()
|
|
20
|
+
print("channel 0: AWG, %d samples" % len(samples))
|
|
21
|
+
|
|
22
|
+
seq = dev.channel(1)
|
|
23
|
+
energies = [4000, 8000, 12000, 16000]
|
|
24
|
+
times = [2500, 2500, 2500, 25000] # clock periods after the previous pulse
|
|
25
|
+
seq.set_sequence(energy=energies, time=times)
|
|
26
|
+
seq.set_drc(rise_ns=20, decay_ns=10000)
|
|
27
|
+
seq.enable()
|
|
28
|
+
print("channel 1: sequence of %d pulses" % len(energies))
|
|
29
|
+
|
|
30
|
+
seq.disable()
|
|
31
|
+
seq.clear_sequence()
|
|
32
|
+
awg.disable()
|
|
33
|
+
awg.set_mode(dt5810.ChannelMode.PULSER)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
|