pytesdaqx 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. pytesdaqx/__init__.py +10 -0
  2. pytesdaqx/acquisition/__init__.py +21 -0
  3. pytesdaqx/acquisition/fake.py +101 -0
  4. pytesdaqx/acquisition/nidaq_ai.py +723 -0
  5. pytesdaqx/acquisition/segment.py +39 -0
  6. pytesdaqx/acquisition/session.py +117 -0
  7. pytesdaqx/acquisition/streaming.py +167 -0
  8. pytesdaqx/analysis/__init__.py +3 -0
  9. pytesdaqx/analysis/continuity.py +244 -0
  10. pytesdaqx/cli/__init__.py +1 -0
  11. pytesdaqx/cli/build_manifest.py +149 -0
  12. pytesdaqx/cli/display_metadata.py +381 -0
  13. pytesdaqx/cli/laser.py +148 -0
  14. pytesdaqx/cli/run.py +184 -0
  15. pytesdaqx/cli/signal_generator.py +217 -0
  16. pytesdaqx/cli/squid.py +150 -0
  17. pytesdaqx/cli/temperature.py +127 -0
  18. pytesdaqx/cli/tes_bias.py +324 -0
  19. pytesdaqx/cli/transducer.py +73 -0
  20. pytesdaqx/cli/validate_config.py +48 -0
  21. pytesdaqx/cli/view_traces.py +964 -0
  22. pytesdaqx/config/__init__.py +30 -0
  23. pytesdaqx/config/durations.py +32 -0
  24. pytesdaqx/config/models.py +158 -0
  25. pytesdaqx/config/resolver.py +302 -0
  26. pytesdaqx/config/scan_values.py +141 -0
  27. pytesdaqx/config/schema.py +1052 -0
  28. pytesdaqx/config/temperature.py +419 -0
  29. pytesdaqx/config/validators.py +55 -0
  30. pytesdaqx/control/__init__.py +3 -0
  31. pytesdaqx/control/coordinator.py +1742 -0
  32. pytesdaqx/hardware/__init__.py +18 -0
  33. pytesdaqx/hardware/base.py +163 -0
  34. pytesdaqx/hardware/factory.py +48 -0
  35. pytesdaqx/hardware/legacy.py +157 -0
  36. pytesdaqx/hardware/registry.py +109 -0
  37. pytesdaqx/instruments/__init__.py +19 -0
  38. pytesdaqx/instruments/base.py +35 -0
  39. pytesdaqx/instruments/builtins.py +1632 -0
  40. pytesdaqx/instruments/communication.py +269 -0
  41. pytesdaqx/instruments/external.py +49 -0
  42. pytesdaqx/instruments/remote.py +443 -0
  43. pytesdaqx/instruments/signal_generator/__init__.py +0 -0
  44. pytesdaqx/instruments/signal_generator/agilent33500B.py +274 -0
  45. pytesdaqx/instruments/signal_generator/keithley2400.py +122 -0
  46. pytesdaqx/instruments/signal_generator/keysightDSOX1200.py +140 -0
  47. pytesdaqx/instruments/signal_generator/rigoldg800.py +253 -0
  48. pytesdaqx/instruments/temperature_controller/__init__.py +25 -0
  49. pytesdaqx/instruments/temperature_controller/base.py +40 -0
  50. pytesdaqx/instruments/temperature_controller/lakeshore.py +641 -0
  51. pytesdaqx/instruments/temperature_controller/macrt.py +195 -0
  52. pytesdaqx/instruments/temperature_controller/macrt_module.py +406 -0
  53. pytesdaqx/instruments/tes_squid/__init__.py +0 -0
  54. pytesdaqx/instruments/tes_squid/feb.py +657 -0
  55. pytesdaqx/instruments/tes_squid/magnicon.py +1340 -0
  56. pytesdaqx/io/__init__.py +69 -0
  57. pytesdaqx/io/acquisition_catalog.py +2205 -0
  58. pytesdaqx/io/catalog.py +17 -0
  59. pytesdaqx/io/hdf5_layout.py +93 -0
  60. pytesdaqx/io/hdf5_reader.py +405 -0
  61. pytesdaqx/io/metadata_compat.py +668 -0
  62. pytesdaqx/io/raw_data_reader.py +193 -0
  63. pytesdaqx/io/raw_data_writer.py +640 -0
  64. pytesdaqx/io/reader_utils.py +8 -0
  65. pytesdaqx/io/stream_reader.py +1372 -0
  66. pytesdaqx/io/zarr_reader.py +650 -0
  67. pytesdaqx/io/zarr_utils.py +550 -0
  68. pytesdaqx/naming.py +296 -0
  69. pytesdaqx/processing/__init__.py +3 -0
  70. pytesdaqx/processing/downsample.py +119 -0
  71. pytesdaqx/recorder/__init__.py +38 -0
  72. pytesdaqx/recorder/async_writer.py +84 -0
  73. pytesdaqx/recorder/base.py +31 -0
  74. pytesdaqx/recorder/hdf5.py +356 -0
  75. pytesdaqx/recorder/zarr.py +837 -0
  76. pytesdaqx/run/__init__.py +4 -0
  77. pytesdaqx/run/runner.py +107 -0
  78. pytesdaqx/run/yaml_run.py +2834 -0
  79. pytesdaqx/sequencer/__init__.py +10 -0
  80. pytesdaqx/sequencer/scan.py +98 -0
  81. pytesdaqx/sequencer/sequencer.py +117 -0
  82. pytesdaqx/services/__init__.py +24 -0
  83. pytesdaqx/services/laser.py +374 -0
  84. pytesdaqx/services/signal_generator_load.py +161 -0
  85. pytesdaqx/services/squid.py +489 -0
  86. pytesdaqx/services/temperature.py +472 -0
  87. pytesdaqx/services/tes_bias.py +1191 -0
  88. pytesdaqx/services/transducer.py +185 -0
  89. pytesdaqx/testing/__init__.py +0 -0
  90. pytesdaqx/testing/analyze_continuity.py +67 -0
  91. pytesdaqx/testing/lab_check.py +66 -0
  92. pytesdaqx/testing/ni_cli_common.py +82 -0
  93. pytesdaqx/testing/real_ni_benchmark.py +270 -0
  94. pytesdaqx/testing/real_ni_continuity_test.py +132 -0
  95. pytesdaqx/testing/real_ni_run_continuous.py +147 -0
  96. pytesdaqx/testing/real_ni_smoke.py +115 -0
  97. pytesdaqx/testing/real_ni_trigger_test.py +108 -0
  98. pytesdaqx-0.1.0.dist-info/METADATA +93 -0
  99. pytesdaqx-0.1.0.dist-info/RECORD +102 -0
  100. pytesdaqx-0.1.0.dist-info/WHEEL +5 -0
  101. pytesdaqx-0.1.0.dist-info/entry_points.txt +19 -0
  102. pytesdaqx-0.1.0.dist-info/top_level.txt +1 -0
pytesdaqx/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Python acquisition and instrument-control framework for TES detector readout."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("pytesdaqx")
7
+ except PackageNotFoundError: # Source tree imported before installation.
8
+ __version__ = "0+unknown"
9
+
10
+ __all__ = ["__version__"]
@@ -0,0 +1,21 @@
1
+ from .fake import FakeAcquisitionBackend
2
+ from .nidaq_ai import NIDAQAIBackend, NIDAQAIConfig, NIDAQAIError
3
+ from .segment import Segment
4
+ from .session import DAQSession
5
+ from .streaming import (
6
+ SegmentRingBuffer,
7
+ StreamingAcquisitionLoop,
8
+ StreamingResult,
9
+ )
10
+
11
+ __all__ = [
12
+ "Segment",
13
+ "FakeAcquisitionBackend",
14
+ "DAQSession",
15
+ "NIDAQAIBackend",
16
+ "NIDAQAIConfig",
17
+ "NIDAQAIError",
18
+ "SegmentRingBuffer",
19
+ "StreamingResult",
20
+ "StreamingAcquisitionLoop",
21
+ ]
@@ -0,0 +1,101 @@
1
+ """Deterministic, hardware-free acquisition backend for development and tests.
2
+
3
+ ``FakeAcquisitionBackend`` generates synthetic segments (noise around
4
+ zero, seeded for reproducibility) with the same shape/timing contract as
5
+ ``NIDAQAIBackend``, so configuration resolution and acquisition
6
+ orchestration can be exercised end to end without contacting hardware.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import itertools
12
+ import time
13
+ from typing import Any
14
+
15
+ import numpy as np
16
+
17
+ from pytesdaqx.config.durations import parse_duration
18
+
19
+ from .segment import Segment
20
+
21
+
22
+ class FakeAcquisitionBackend:
23
+ """Synthetic acquisition backend producing seeded noise segments.
24
+
25
+ Drop-in stand-in for ``NIDAQAIBackend`` behind ``DAQSession``: same
26
+ ``configure``/acquire contract, no hardware access.
27
+ """
28
+
29
+ def __init__(self, *, dtype=np.float32, noise_rms=0.01, seed=12345):
30
+ self.dtype = np.dtype(dtype)
31
+ self.noise_rms = noise_rms
32
+ self.rng = np.random.default_rng(seed)
33
+ self._counter = itertools.count(1)
34
+
35
+ def configure(
36
+ self,
37
+ *,
38
+ measurement_name: str,
39
+ measurement_config: dict[str, Any],
40
+ detector_channels: list[str],
41
+ ) -> None:
42
+ self.measurement_name = measurement_name
43
+ self.measurement_config = measurement_config
44
+ self.detector_channels = list(detector_channels)
45
+
46
+ adc = measurement_config.get("adc", {}) or {}
47
+ self.sample_rate_hz = float(adc.get("sample_rate", adc.get("sample_rate_hz", 1000.0)))
48
+ self.n_samples = int(
49
+ adc.get(
50
+ "n_samples",
51
+ adc.get(
52
+ "samples_per_read_block",
53
+ adc.get("samples_per_trace", adc.get("samples_per_segment", 0)),
54
+ ),
55
+ )
56
+ )
57
+ if self.n_samples <= 0:
58
+ duration = adc.get(
59
+ "read_block_duration",
60
+ adc.get("trace_duration", adc.get("segment_duration", "1s")),
61
+ )
62
+ self.n_samples = self._samples_from_duration(duration, self.sample_rate_hz)
63
+
64
+ def read_segment(self) -> Segment:
65
+ segment_id = next(self._counter)
66
+ n_channels = len(self.detector_channels)
67
+ times = np.arange(self.n_samples) / self.sample_rate_hz
68
+ data = np.zeros((n_channels, self.n_samples), dtype=float)
69
+
70
+ for index in range(n_channels):
71
+ data[index] = (
72
+ 0.1 * index
73
+ + 0.02 * np.sin(2 * np.pi * (3 + index) * times)
74
+ + self.rng.normal(0, self.noise_rms, self.n_samples)
75
+ )
76
+
77
+ return Segment(
78
+ data=data.astype(self.dtype),
79
+ detector_channels=self.detector_channels,
80
+ measurement=self.measurement_name,
81
+ label=self.measurement_config.get("label", self.measurement_name),
82
+ sample_rate_hz=self.sample_rate_hz,
83
+ timestamp=time.time(),
84
+ segment_id=segment_id,
85
+ metadata={
86
+ "backend": "fake",
87
+ "adc_mode": self.measurement_config.get("adc", {}).get("mode"),
88
+ },
89
+ )
90
+
91
+ def close(self) -> None:
92
+ return None
93
+
94
+ @staticmethod
95
+ def _samples_from_duration(duration: Any, sample_rate: float) -> int:
96
+ parsed = parse_duration(duration)
97
+ if parsed == "auto_from_cycles":
98
+ parsed = 1.0
99
+ if parsed is None or isinstance(parsed, str) or parsed <= 0:
100
+ raise ValueError(f"Invalid duration {duration!r}")
101
+ return int(round(float(parsed) * sample_rate))