pytesdaqx 0.2.2__tar.gz → 0.2.3__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.
Files changed (220) hide show
  1. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/PKG-INFO +1 -1
  2. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/data_io.md +29 -9
  3. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/cli/data_inspection.md +2 -0
  4. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/acquisition_config.md +50 -4
  5. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/run_setup_yaml.md +27 -0
  6. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/scans.md +2 -0
  7. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/README.md +15 -0
  8. pytesdaqx-0.2.3/examples/configuration/blinded/acquisition_blinded_75_25.yaml +117 -0
  9. pytesdaqx-0.2.3/examples/configuration/blinded/generate_blinded_sequence.py +165 -0
  10. pytesdaqx-0.2.3/examples/configuration/vibration/hardware_fake.yaml +15 -0
  11. pytesdaqx-0.2.3/examples/configuration/vibration/run_setup.yaml +45 -0
  12. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pyproject.toml +1 -1
  13. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/view_traces.py +2 -1
  14. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/resolver.py +37 -0
  15. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/schema.py +32 -0
  16. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/hdf5_reader.py +10 -9
  17. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/metadata_compat.py +70 -11
  18. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/raw_data_writer.py +30 -2
  19. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/zarr_reader.py +10 -11
  20. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/naming.py +44 -1
  21. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/recorder/hdf5.py +4 -1
  22. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/recorder/zarr.py +38 -2
  23. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/run/yaml_run.py +210 -16
  24. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/PKG-INFO +1 -1
  25. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/SOURCES.txt +8 -0
  26. pytesdaqx-0.2.3/tests/test_accelerometer_readout.py +175 -0
  27. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_raw_data_writer.py +30 -0
  28. pytesdaqx-0.2.3/tests/test_read_block_defaults.py +123 -0
  29. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_repository_layout.py +1 -0
  30. pytesdaqx-0.2.3/tests/test_stream_id_uniqueness.py +167 -0
  31. pytesdaqx-0.2.3/tests/test_unit_conversion.py +80 -0
  32. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_chunking_and_sharding.py +48 -0
  33. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/MANIFEST.in +0 -0
  34. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/README.md +0 -0
  35. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/acquisition_pipeline.md +0 -0
  36. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/adding_hardware.md +0 -0
  37. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/architecture.md +0 -0
  38. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/configuration_pipeline.md +0 -0
  39. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/hardware_support.md +0 -0
  40. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/index.md +0 -0
  41. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/package_structure.md +0 -0
  42. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/release_process.md +0 -0
  43. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/developer/testing.md +0 -0
  44. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/index.md +0 -0
  45. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/installation/index.md +0 -0
  46. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/installation/rocky_linux_9.md +0 -0
  47. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/installation/troubleshooting.md +0 -0
  48. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/installation/ubuntu.md +0 -0
  49. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/installation/validation.md +0 -0
  50. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/installation/visa.md +0 -0
  51. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/cli/acquisition.md +0 -0
  52. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/cli/index.md +0 -0
  53. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/cli/instrument_control.md +0 -0
  54. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/cli/lab_diagnostics.md +0 -0
  55. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/hardware_yaml.md +0 -0
  56. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/index.md +0 -0
  57. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/resolution.md +0 -0
  58. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/configuration/temperature_controllers.md +0 -0
  59. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/data_format.md +0 -0
  60. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/quickstart.md +0 -0
  61. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/terminology.md +0 -0
  62. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/troubleshooting.md +0 -0
  63. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/docs/user/workflows.md +0 -0
  64. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/basic/acquisition_background.yaml +0 -0
  65. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/basic/acquisition_didv_background.yaml +0 -0
  66. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/basic/acquisition_notebook_demo.yaml +0 -0
  67. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/basic/hardware_fake.yaml +0 -0
  68. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/basic/hardware_feb_signal_generator.yaml +0 -0
  69. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/basic/run_setup.yaml +0 -0
  70. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/calibration/acquisition_laser.yaml +0 -0
  71. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/pxi_loopback/README.md +0 -0
  72. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/pxi_loopback/acquisition_didv.yaml +0 -0
  73. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/pxi_loopback/acquisition_iv_restore.yaml +0 -0
  74. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_didv_ac.yaml +0 -0
  75. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_didv_frequency_focus.yaml +0 -0
  76. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_temperature_fake.yaml +0 -0
  77. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_tes_bias_iv_didv.yaml +0 -0
  78. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/scans/acquisition_transducer_vibration_scan.yaml +0 -0
  79. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/temperature/hardware_lakeshore372.yaml +0 -0
  80. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/temperature/hardware_macrt.yaml +0 -0
  81. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/temperature/run_setup_lakeshore372.yaml +0 -0
  82. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/configuration/temperature/run_setup_macrt.yaml +0 -0
  83. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/simulation/README.md +0 -0
  84. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/examples/simulation/write_simulated_zarr.py +0 -0
  85. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/mkdocs.yml +0 -0
  86. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/__init__.py +0 -0
  87. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/__init__.py +0 -0
  88. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/fake.py +0 -0
  89. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/nidaq_ai.py +0 -0
  90. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/segment.py +0 -0
  91. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/session.py +0 -0
  92. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/acquisition/streaming.py +0 -0
  93. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/analysis/__init__.py +0 -0
  94. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/analysis/continuity.py +0 -0
  95. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/__init__.py +0 -0
  96. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/build_manifest.py +0 -0
  97. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/display_metadata.py +0 -0
  98. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/laser.py +0 -0
  99. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/run.py +0 -0
  100. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/signal_generator.py +0 -0
  101. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/squid.py +0 -0
  102. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/temperature.py +0 -0
  103. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/tes_bias.py +0 -0
  104. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/transducer.py +0 -0
  105. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/cli/validate_config.py +0 -0
  106. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/__init__.py +0 -0
  107. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/durations.py +0 -0
  108. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/models.py +0 -0
  109. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/scan_values.py +0 -0
  110. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/temperature.py +0 -0
  111. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/config/validators.py +0 -0
  112. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/control/__init__.py +0 -0
  113. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/control/coordinator.py +0 -0
  114. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/hardware/__init__.py +0 -0
  115. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/hardware/base.py +0 -0
  116. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/hardware/factory.py +0 -0
  117. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/hardware/legacy.py +0 -0
  118. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/hardware/registry.py +0 -0
  119. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/__init__.py +0 -0
  120. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/base.py +0 -0
  121. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/builtins.py +0 -0
  122. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/communication.py +0 -0
  123. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/external.py +0 -0
  124. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/remote.py +0 -0
  125. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/__init__.py +0 -0
  126. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/agilent33500B.py +0 -0
  127. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/keithley2400.py +0 -0
  128. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/keysightDSOX1200.py +0 -0
  129. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/signal_generator/rigoldg800.py +0 -0
  130. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/__init__.py +0 -0
  131. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/base.py +0 -0
  132. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/lakeshore.py +0 -0
  133. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/macrt.py +0 -0
  134. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/temperature_controller/macrt_module.py +0 -0
  135. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/tes_squid/__init__.py +0 -0
  136. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/tes_squid/feb.py +0 -0
  137. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/instruments/tes_squid/magnicon.py +0 -0
  138. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/__init__.py +0 -0
  139. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/acquisition_catalog.py +0 -0
  140. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/catalog.py +0 -0
  141. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/hdf5_layout.py +0 -0
  142. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/raw_data_reader.py +0 -0
  143. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/reader_utils.py +0 -0
  144. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/stream_reader.py +0 -0
  145. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/io/zarr_utils.py +0 -0
  146. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/processing/__init__.py +0 -0
  147. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/processing/downsample.py +0 -0
  148. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/recorder/__init__.py +0 -0
  149. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/recorder/async_writer.py +0 -0
  150. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/recorder/base.py +0 -0
  151. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/run/__init__.py +0 -0
  152. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/run/runner.py +0 -0
  153. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/sequencer/__init__.py +0 -0
  154. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/sequencer/scan.py +0 -0
  155. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/sequencer/sequencer.py +0 -0
  156. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/__init__.py +0 -0
  157. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/laser.py +0 -0
  158. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/signal_generator_load.py +0 -0
  159. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/squid.py +0 -0
  160. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/temperature.py +0 -0
  161. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/tes_bias.py +0 -0
  162. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/services/transducer.py +0 -0
  163. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/__init__.py +0 -0
  164. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/analyze_continuity.py +0 -0
  165. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/lab_check.py +0 -0
  166. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/ni_cli_common.py +0 -0
  167. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_benchmark.py +0 -0
  168. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_continuity_test.py +0 -0
  169. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_run_continuous.py +0 -0
  170. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_smoke.py +0 -0
  171. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx/testing/real_ni_trigger_test.py +0 -0
  172. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/dependency_links.txt +0 -0
  173. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/entry_points.txt +0 -0
  174. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/requires.txt +0 -0
  175. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/pytesdaqx.egg-info/top_level.txt +0 -0
  176. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/setup.cfg +0 -0
  177. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_acquisition_catalog_api_cleanup.py +0 -0
  178. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_acquisition_catalog_naming.py +0 -0
  179. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_api_cleanup.py +0 -0
  180. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_cli_contract.py +0 -0
  181. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_code_cleanup_regressions.py +0 -0
  182. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_detector_settings_normalization.py +0 -0
  183. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_developer_documentation.py +0 -0
  184. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_distribution_readiness.py +0 -0
  185. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_facility_catalog_metadata.py +0 -0
  186. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_feb_driver.py +0 -0
  187. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_feb_signal_generator_didv.py +0 -0
  188. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_finite_record_metadata.py +0 -0
  189. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_io_coordinate_cleanup.py +0 -0
  190. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_lakeshore372_driver.py +0 -0
  191. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_laser_configuration_ownership.py +0 -0
  192. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_macrt_driver.py +0 -0
  193. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_magnicon_driver.py +0 -0
  194. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_manifest_and_zarr_metadata.py +0 -0
  195. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_metadata_canonical_schema.py +0 -0
  196. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_optional_hdf5_import.py +0 -0
  197. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_partition_planning.py +0 -0
  198. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_release_cleanup.py +0 -0
  199. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_run_cli_cleanup.py +0 -0
  200. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_scan_documentation.py +0 -0
  201. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_scan_values.py +0 -0
  202. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_signal_generator_cli.py +0 -0
  203. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_signal_generator_drivers.py +0 -0
  204. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_signal_generator_load_policy.py +0 -0
  205. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_stream_reader_api_cleanup.py +0 -0
  206. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_temperature_acquisition_integration.py +0 -0
  207. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_temperature_cli.py +0 -0
  208. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_temperature_configuration.py +0 -0
  209. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_temperature_safety_cleanup.py +0 -0
  210. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_temperature_service.py +0 -0
  211. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_transducer_configuration_ownership.py +0 -0
  212. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_user_documentation.py +0 -0
  213. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_view_trace_partitions.py +0 -0
  214. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_compression.py +0 -0
  215. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_default_and_naming.py +0 -0
  216. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_manifest_sidecars.py +0 -0
  217. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_metadata_layout.py +0 -0
  218. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_native_backend.py +0 -0
  219. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_read_block_terminology.py +0 -0
  220. {pytesdaqx-0.2.2 → pytesdaqx-0.2.3}/tests/test_zarr_trace_labels.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytesdaqx
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: TES detector acquisition, instrument control, and raw-data recording
5
5
  Author: TESSERACT DAQ developers
6
6
  Classifier: Development Status :: 3 - Alpha
@@ -52,21 +52,41 @@ centered on its trigger.
52
52
  `ZarrRecorder` defaults to zstd compression (level 9) and to sharded arrays for
53
53
  both shapes:
54
54
 
55
- - **Continuous** (`channel_sample`): 1s chunks, sharded into 10s shards
56
- (`continuous_sharding=True`, `continuous_shard_target_duration_s=10.0`).
57
- Small chunks keep partial reads cheap (decompression cost is paid per
58
- chunk touched, not per array), while sharding keeps the on-disk object
59
- count low for filesystems like NERSC/CC-IN2P3 that penalize very large
60
- numbers of small files.
55
+ - **Continuous** (`channel_sample`): small chunks keep partial reads cheap
56
+ (decompression cost is paid per chunk touched, not per array), while
57
+ sharding keeps the on-disk object count low for filesystems like
58
+ NERSC/CC-IN2P3 that penalize very large numbers of small files.
61
59
  - **Finite/trace** (`trace_channel_sample`): one trace per chunk, sharded
62
60
  toward a 10s-equivalent target (`trace_sharding=True`,
63
61
  `trace_shard_target_duration_s=10.0`).
64
62
 
63
+ The continuous geometry has two layers, because the useful sizes depend on
64
+ the acquisition and only the run layer knows it:
65
+
66
+ | | `ZarrRecorder` defaults | resolved by `pytesdaqx-run` from the YAML |
67
+ |---|---|---|
68
+ | chunk | `1s` | `0.25s`, grown towards 1 MiB at low data rates (capped 10s), then shortened to divide one read block |
69
+ | shard | `10s` | one read block: the longest block up to 60s that divides the step duration |
70
+ | shard bytes | 128 MiB | 8 GiB, so a whole block is never clamped |
71
+ | chunks per shard | `max_chunks_per_shard=100` | `continuous_max_chunks_per_shard` (high): one block fills a shard in a single write |
72
+
73
+ Constructing `ZarrRecorder` directly (including through `RawDataWriter`
74
+ without recorder options) therefore keeps the older 1s/10s geometry; pass
75
+ the options explicitly to match a run. See
76
+ [acquisition configuration](../user/configuration/acquisition_config.md)
77
+ for the user-facing rules.
78
+
79
+ A shard is rewritten whenever part of it is written, so a read block shorter
80
+ than the shard multiplies disk writes (measured: 5.5x for 1s blocks in 10s
81
+ shards, 30x for 1s blocks in 60s shards, at 3 channels and 1.25 MHz). Keeping
82
+ shard == read block writes each shard exactly once.
83
+
65
84
  Both shard-sizing paths (`_continuous_chunking_metadata` and
66
85
  `_trace_shard_traces` in `pytesdaqx/recorder/zarr.py`) apply a third
67
- constraint, `max_chunks_per_shard` (default 100), in addition to the
68
- duration and byte-size targets: `shard_chunk_count = min(by_duration,
69
- by_bytes, max_chunks_per_shard)`.
86
+ constraint, `max_chunks_per_shard`, in addition to the duration and
87
+ byte-size targets: `shard_chunk_count = min(by_duration, by_bytes,
88
+ max_chunks_per_shard)`. A chunk or shard reduced by any of these limits
89
+ emits a warning naming the binding one.
70
90
 
71
91
  This cap exists because of a measured defect in zarr-python 3.1.5: the
72
92
  per-write cost of an incremental `resize()` + append into a sharded array
@@ -110,6 +110,8 @@ pytesdaqx-view-traces acquisition_dir \
110
110
 
111
111
  The default display units are detector amps when the required readout-circuit metadata are available. Other supported choices include ADC volts or raw ADC values, depending on stored read mode and CLI options.
112
112
 
113
+ Accelerometer channels can be displayed in g with `--units g`; this requires `readout_circuit.amplifier_gain` (and optionally `sensitivity_v_per_g`) in the run setup. See [Accelerometer readout](../configuration/run_setup_yaml.md#accelerometer-readout).
114
+
113
115
  If amp conversion fails, inspect the channel `readout_circuit` values in stored metadata, especially output gain and resistance/turn-ratio fields.
114
116
 
115
117
  ### Plot decimation
@@ -53,9 +53,36 @@ sequence:
53
53
  duration: 15m
54
54
  ```
55
55
 
56
+ ### A step records whole read blocks
57
+
58
+ Acquisition always records complete read blocks, so `sequence[].duration` is
59
+ rounded **up** to a whole number of them. With a 60-second read block a
60
+ 20-second step records 60 seconds of data, not 20. Keep the step duration a
61
+ multiple of the read-block duration; pytesdaqx warns when it is not, naming
62
+ the amount of data that will actually be taken.
63
+
56
64
  ### Automatic read-block duration
57
65
 
58
- `read_block_duration` is optional. When omitted, pytesdaqx derives it from the effective Zarr chunk duration after applying the configured chunk-duration target and chunk-size cap. This preserves aligned writes without requiring normal users to tune acquisition buffering.
66
+ `read_block_duration` is optional. When omitted, pytesdaqx uses the longest
67
+ block up to 60 seconds that divides the step duration exactly, so that a step
68
+ never takes more data than requested:
69
+
70
+ | Step duration | Automatic read block | Blocks recorded |
71
+ |---|---|---|
72
+ | 3 min | 60 s | 3 |
73
+ | 100 s | 50 s | 2 |
74
+ | 20 s | 20 s | 1 |
75
+ | 1 s (scan point) | 1 s | 1 |
76
+
77
+ One read block is written as one shard, so this also sets the file count: a
78
+ three-minute stream becomes three data objects instead of eighteen at the old
79
+ ten-second default. The cost is buffering: one block is held in memory before
80
+ it is written, which is `channels x sample_rate x 2 bytes x block` (1.2 GB for
81
+ 8 channels at 1.25 MHz with a 60-second block), and an interrupted acquisition
82
+ loses the block in flight.
83
+
84
+ Steps driven by `n_read_blocks` rather than `duration` keep the historical
85
+ chunk-aligned block.
59
86
 
60
87
  For example:
61
88
 
@@ -88,10 +115,29 @@ A **chunk** is a physical Zarr storage-layout unit. It is independent of a read
88
115
 
89
116
  | Key | Default | Meaning |
90
117
  |---|---:|---|
91
- | `chunk_duration` | `10s` | Continuous chunk-duration target |
118
+ | `chunk_duration` | `0.25s`, grown at low data rates | Continuous chunk-duration target |
92
119
  | `chunk_target_mb` | `128` | Maximum target chunk size |
93
-
94
- The resolved chunk may be shorter than `chunk_duration` when the byte-size cap is reached.
120
+ | `shard_duration` | one read block | Continuous shard-duration target |
121
+ | `shard_target_mb` | `8192` (continuous), `128` (traces) | Maximum target shard size |
122
+ | `max_chunks_per_shard` | `100` (traces) | Chunks per trace shard |
123
+
124
+ A chunk is the unit a reader must decompress, so the default keeps it short
125
+ (about 5-8 MB at 20-32 MB/s) to make short random reads such as feature
126
+ windows cheap; at low data rates it grows towards 1 MiB so it stays worth
127
+ compressing, up to 10 seconds. A shard is one file and defaults to one read
128
+ block, which writes each shard exactly once.
129
+
130
+ The resolved chunk may be shorter than `chunk_duration`, and the resolved
131
+ shard shorter than `shard_duration`, when a byte cap or `max_chunks_per_shard`
132
+ is reached. Both cases emit a warning naming the binding limit.
133
+
134
+ ### Shards, blocks and write cost
135
+
136
+ A shard is rewritten every time part of it is written, so a read block shorter
137
+ than the shard multiplies disk writes: at 3 channels and 1.25 MHz, one-second
138
+ blocks with 10-second shards write 4.2 GB for 764 MB of data (5.5x), and with
139
+ 60-second shards 23 GB (30x). Leaving both at their defaults, or setting
140
+ `read_block_duration` equal to `shard_duration`, writes each shard once.
95
141
 
96
142
  ## Finite acquisition: traces
97
143
 
@@ -108,6 +108,33 @@ readout_circuit:
108
108
 
109
109
  The correct constants depend on the installed readout circuit.
110
110
 
111
+ ### Accelerometer readout
112
+
113
+ Auxiliary sensors such as accelerometers use the same `readout_circuit`
114
+ mapping. The conversion from ADC voltage to acceleration depends on the
115
+ sensor specification and its signal-conditioner gain:
116
+
117
+ ```yaml
118
+ channels:
119
+ AccelerometerX:
120
+ adc:
121
+ device: adc1
122
+ channel: 0
123
+ readout_circuit:
124
+ sensor_type: accelerometer
125
+ amplifier_gain: 100 # signal conditioner gain (typically 1, 10 or 100)
126
+ sensitivity_v_per_g: 1.0 # sensor sensitivity in V/g (default 1)
127
+ ```
128
+
129
+ Acceleration in g is `volts / (amplifier_gain * sensitivity_v_per_g)`.
130
+ `amplifier_gain` is required when `sensor_type: accelerometer`; both values
131
+ must be positive. The constants are saved with the stream metadata, exposed
132
+ by `get_detector_settings()` as `amplifier_gain`, `sensitivity_v_per_g`, and
133
+ `acceleration_norm_v_per_g`, and used when reading data with `units="g"`.
134
+
135
+ A complete example with three accelerometers and a vibration transducer is
136
+ in `examples/configuration/vibration/`.
137
+
111
138
  ## Other run-level mappings
112
139
 
113
140
  A run setup may also define:
@@ -430,6 +430,8 @@ Cleanup is not implicit. Without `scan.cleanup.temperature`, the final scan setp
430
430
 
431
431
  A vibration transducer is driven by a signal generator; `channels` names logical transducers from `run_setup.transducers`, not detector channels.
432
432
 
433
+ Accelerometer channels recorded during the scan should define their volts-to-g conversion in `run_setup.yaml` (see [Accelerometer readout](run_setup_yaml.md#accelerometer-readout)); `examples/configuration/vibration/run_setup.yaml` pairs with the vibration scan example below.
434
+
433
435
  | Target | Values and units | Behavior |
434
436
  |---|---|---|
435
437
  | `transducer.frequency_hz` | Numeric drive frequency in Hz | Sets the transducer signal generator's frequency |
@@ -42,6 +42,10 @@ configuration/
42
42
  README.md
43
43
  acquisition_didv.yaml
44
44
  acquisition_iv_restore.yaml
45
+
46
+ vibration/
47
+ hardware_fake.yaml
48
+ run_setup.yaml
45
49
  ```
46
50
 
47
51
  The files under `basic/` form the common configuration foundation:
@@ -57,6 +61,17 @@ The files under `basic/` form the common configuration foundation:
57
61
 
58
62
  The basic hardware and run-setup examples also include a fake temperature controller using separate `sensors` and `heaters` inventory mappings. The logical `mc` path is suitable for testing `pytesdaqx-temperature`, canonical `temperature.setpoint` scans, stability waiting, and opt-in stream snapshots with `acquisition_temperature_fake.yaml`.
59
63
 
64
+ The `vibration/` directory maps three accelerometers (`AccelerometerX`, `AccelerometerY`, `AccelerometerZ`) to `adc1` channels 0-2, with their volts-to-g conversion (`readout_circuit.amplifier_gain` and `sensitivity_v_per_g`), plus a vibration transducer. Use it with `scans/acquisition_transducer_vibration_scan.yaml`:
65
+
66
+ ```bash
67
+ pytesdaqx-run \
68
+ --hardware examples/configuration/vibration/hardware_fake.yaml \
69
+ --run-setup examples/configuration/vibration/run_setup.yaml \
70
+ --acquisition-config examples/configuration/scans/acquisition_transducer_vibration_scan.yaml \
71
+ --backend fake \
72
+ --no-save-data
73
+ ```
74
+
60
75
  The `temperature/` directory contains the native MACRT inventory and logical temperature mappings used for real-controller CLI tests.
61
76
 
62
77
  The scan examples cover the currently maintained scan patterns. Less common
@@ -0,0 +1,117 @@
1
+ # Blinded physics running: 75% restricted / 25% open, with a grouped
2
+ # IV+dIdV calibration block at the start and end of every cycle.
3
+ #
4
+ # One cycle (repeated by run.n_sequence_repeats):
5
+ #
6
+ # [ IV -> dIdV ] -> 4 data slots -> [ dIdV ]
7
+ # opening calib 3 restricted closing calib
8
+ # + 1 open
9
+ #
10
+ # Why the data block is split into slots instead of one restricted block
11
+ # followed by one open block: with a fixed "restricted then open" order the
12
+ # open sample always sits further from the calibration than the restricted
13
+ # sample does, so bias-point drift biases exactly the 25% used to develop
14
+ # cuts and efficiencies. Splitting into slots and moving the open slot
15
+ # around makes the time-since-dIdV distribution the same for both samples.
16
+ #
17
+ # The open slot's position is rotated by hand here (see the four cycle
18
+ # variants below). For real running, generate this file with a seeded RNG
19
+ # so the position is randomized rather than a fixed rotation, and record the
20
+ # seed -- a fixed rotation can alias with periodic systematics (diurnal
21
+ # temperature, cryocooler, fridge fills).
22
+ #
23
+ # Slot length (5m here) must be short compared to the drift timescale. The
24
+ # two bracketing dIdVs measure that drift directly: if the opening and
25
+ # closing dIdV disagree beyond tolerance, shorten the slots.
26
+
27
+ run:
28
+ file_format: zarr
29
+ save_data: true
30
+ channels: [TES_A, TES_B]
31
+ output_dir: /data/pytesdaqx
32
+ acquisition_label: blinded_physics
33
+ stop_policy: finish_current_step
34
+ # One cycle = 1 IV + 2 dIdV + 4 x 5m data = ~20m of physics data.
35
+ # 72 repeats ~= 24h of running.
36
+ n_sequence_repeats: 72
37
+
38
+ sequence:
39
+ # ---- opening calibration block (IV and dIdV grouped together) ----
40
+ - measurement: iv
41
+ label: iv
42
+ duration: 2m
43
+
44
+ - measurement: didv
45
+ label: didv_open
46
+ duration: 1m
47
+
48
+ # ---- settling window, discarded, applied equally to both samples ----
49
+ # The dIdV square wave injects heat. Taking this as its own labeled step
50
+ # means the transient lands in a stream both samples see identically,
51
+ # instead of being absorbed by whichever slot happens to run first.
52
+ - measurement: background
53
+ label: settle
54
+ duration: 30s
55
+ restricted: 0
56
+
57
+ # ---- data block: 4 slots, 3 restricted + 1 open ----
58
+ # This cycle places the open slot 2nd. Rotate/randomize per cycle.
59
+ - measurement: background
60
+ label: physics
61
+ duration: 5m
62
+ restricted: 1
63
+
64
+ - measurement: background
65
+ label: physics
66
+ duration: 5m
67
+ restricted: 0
68
+
69
+ - measurement: background
70
+ label: physics
71
+ duration: 5m
72
+ restricted: 1
73
+
74
+ - measurement: background
75
+ label: physics
76
+ duration: 5m
77
+ restricted: 1
78
+
79
+ # ---- closing calibration: brackets the block for interpolation ----
80
+ - measurement: didv
81
+ label: didv_close
82
+ duration: 1m
83
+
84
+ measurements:
85
+ iv:
86
+ type: iv
87
+ adc:
88
+ mode: continuous
89
+ sample_rate: 1250000
90
+ read_block_duration: 2s
91
+ adc_input_range_v: [-10, 10]
92
+ read_mode: unscaled_int16
93
+
94
+ didv:
95
+ type: didv
96
+ waveform:
97
+ shape: square
98
+ frequency_hz: 50
99
+ amplitude_vpp: 0.01
100
+ adc:
101
+ mode: continuous
102
+ sample_rate: 1250000
103
+ trace_duration: 2s
104
+ adc_input_range_v: [-10, 10]
105
+ read_mode: unscaled_int16
106
+
107
+ background:
108
+ type: background
109
+ adc:
110
+ mode: continuous
111
+ sample_rate: 1250000
112
+ read_block_duration: 1s
113
+ adc_input_range_v: [-10, 10]
114
+ read_mode: unscaled_int16
115
+ recorder:
116
+ chunk_duration: 1s
117
+ shard_duration: 10s
@@ -0,0 +1,165 @@
1
+ """Generate a blinded physics acquisition YAML with a randomized open slot.
2
+
3
+ Each cycle is:
4
+
5
+ IV -> dIdV -> settle -> [4 data slots: 3 restricted + 1 open] -> dIdV
6
+
7
+ The open slot's position inside the 4-slot unit is drawn with *stratified*
8
+ randomization (permuted blocks): every group of 4 consecutive cycles uses
9
+ each of the 4 positions exactly once, in random order. That gives exactly
10
+ 25% open data AND an open/restricted "time since dIdV" distribution that is
11
+ balanced by construction, not just on average -- while staying random, so
12
+ it cannot alias with a periodic systematic the way a fixed rotation can.
13
+
14
+ The seed is recorded in the generated file so the layout is reproducible
15
+ and auditable after unblinding.
16
+
17
+ Usage:
18
+ python generate_blinded_sequence.py --cycles 72 --seed 20260918 \
19
+ --output acquisition_blinded_generated.yaml
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import random
26
+ from pathlib import Path
27
+
28
+ SLOTS_PER_CYCLE = 4 # 3 restricted + 1 open -> 25% open
29
+
30
+
31
+ def open_slot_positions(n_cycles: int, seed: int, slots: int = SLOTS_PER_CYCLE) -> list[int]:
32
+ """Open-slot position per cycle, stratified over every block of `slots`."""
33
+ rng = random.Random(seed)
34
+ positions: list[int] = []
35
+ while len(positions) < n_cycles:
36
+ block = list(range(slots))
37
+ rng.shuffle(block)
38
+ positions.extend(block)
39
+ return positions[:n_cycles]
40
+
41
+
42
+ def render_cycle(cycle: int, open_slot: int, slot_duration: str) -> str:
43
+ lines = [
44
+ f" # ---- cycle {cycle}: open slot in position {open_slot + 1} of {SLOTS_PER_CYCLE} ----",
45
+ " - measurement: iv",
46
+ " label: iv",
47
+ " duration: 2m",
48
+ "",
49
+ " - measurement: didv",
50
+ " label: didv_open",
51
+ " duration: 1m",
52
+ "",
53
+ " - measurement: background",
54
+ " label: settle",
55
+ " duration: 30s",
56
+ " restricted: 0",
57
+ "",
58
+ ]
59
+ for slot in range(SLOTS_PER_CYCLE):
60
+ restricted = 0 if slot == open_slot else 1
61
+ lines += [
62
+ " - measurement: background",
63
+ " label: physics",
64
+ f" duration: {slot_duration}",
65
+ f" restricted: {restricted}",
66
+ "",
67
+ ]
68
+ lines += [
69
+ " - measurement: didv",
70
+ " label: didv_close",
71
+ " duration: 1m",
72
+ "",
73
+ ]
74
+ return "\n".join(lines)
75
+
76
+
77
+ HEADER = """# GENERATED FILE -- do not edit by hand.
78
+ # Regenerate with:
79
+ # python generate_blinded_sequence.py --cycles {cycles} --seed {seed}
80
+ #
81
+ # Blinded physics running, {open_pct}% open / {restricted_pct}% restricted.
82
+ # Open-slot positions (1-based, one per cycle), stratified random with the
83
+ # seed below:
84
+ # {positions}
85
+ #
86
+ # blinding_seed: {seed}
87
+
88
+ run:
89
+ file_format: zarr
90
+ save_data: true
91
+ channels: [TES_A, TES_B]
92
+ output_dir: /data/pytesdaqx
93
+ acquisition_label: blinded_physics
94
+ stop_policy: finish_current_step
95
+ # Cycles are unrolled below, so the sequence runs once.
96
+ n_sequence_repeats: 1
97
+
98
+ sequence:
99
+ """
100
+
101
+ FOOTER = """measurements:
102
+ iv:
103
+ type: iv
104
+ adc:
105
+ mode: continuous
106
+ sample_rate: 1250000
107
+ read_block_duration: 2s
108
+ adc_input_range_v: [-10, 10]
109
+ read_mode: unscaled_int16
110
+
111
+ didv:
112
+ type: didv
113
+ waveform:
114
+ shape: square
115
+ frequency_hz: 50
116
+ amplitude_vpp: 0.01
117
+ adc:
118
+ mode: continuous
119
+ sample_rate: 1250000
120
+ trace_duration: 2s
121
+ adc_input_range_v: [-10, 10]
122
+ read_mode: unscaled_int16
123
+
124
+ background:
125
+ type: background
126
+ adc:
127
+ mode: continuous
128
+ sample_rate: 1250000
129
+ read_block_duration: 1s
130
+ adc_input_range_v: [-10, 10]
131
+ read_mode: unscaled_int16
132
+ recorder:
133
+ chunk_duration: 1s
134
+ shard_duration: 10s
135
+ """
136
+
137
+
138
+ def main() -> None:
139
+ parser = argparse.ArgumentParser(description=__doc__)
140
+ parser.add_argument("--cycles", type=int, default=72)
141
+ parser.add_argument("--seed", type=int, required=True)
142
+ parser.add_argument("--slot-duration", default="5m")
143
+ parser.add_argument("--output", type=Path, default=Path("acquisition_blinded_generated.yaml"))
144
+ args = parser.parse_args()
145
+
146
+ positions = open_slot_positions(args.cycles, args.seed)
147
+ body = "\n".join(
148
+ render_cycle(cycle, slot, args.slot_duration) for cycle, slot in enumerate(positions)
149
+ )
150
+ header = HEADER.format(
151
+ cycles=args.cycles,
152
+ seed=args.seed,
153
+ open_pct=int(100 / SLOTS_PER_CYCLE),
154
+ restricted_pct=int(100 - 100 / SLOTS_PER_CYCLE),
155
+ positions=", ".join(str(p + 1) for p in positions),
156
+ )
157
+ args.output.write_text(header + body + FOOTER, encoding="utf-8")
158
+
159
+ counts = {slot + 1: positions.count(slot) for slot in range(SLOTS_PER_CYCLE)}
160
+ print(f"wrote {args.output}: {args.cycles} cycles, seed {args.seed}")
161
+ print(f"open-slot position counts: {counts}")
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -0,0 +1,15 @@
1
+ # Fake hardware inventory for vibration studies: one ADC reading three
2
+ # accelerometers and one signal generator driving a vibration transducer.
3
+
4
+ instruments:
5
+ adc1:
6
+ driver: fake
7
+ type: pci_adc
8
+ device_alias: Dev1
9
+ model: fake_adc
10
+ channels: [0, 1, 2, 3, 4, 5, 6, 7]
11
+
12
+ agilent1:
13
+ driver: fake
14
+ type: signal_generator
15
+ channels: [1, 2]
@@ -0,0 +1,45 @@
1
+ facility: 2
2
+ fridge_run: 123
3
+ fridge_run_start: "2026-04-12 19:00:00"
4
+
5
+ # Example run setup for vibration studies: three accelerometer axes on adc1
6
+ # channels 0-2 and one vibration transducer.
7
+ #
8
+ # Acceleration in g = volts / (amplifier_gain * sensitivity_v_per_g).
9
+ # amplifier_gain is the signal-conditioner gain (typically 1, 10 or 100);
10
+ # sensitivity_v_per_g is the accelerometer sensitivity (default 1 V/g).
11
+
12
+ channels:
13
+ AccelerometerX:
14
+ adc:
15
+ device: adc1
16
+ channel: 0
17
+ readout_circuit:
18
+ sensor_type: accelerometer
19
+ amplifier_gain: 100
20
+ sensitivity_v_per_g: 1.0
21
+
22
+ AccelerometerY:
23
+ adc:
24
+ device: adc1
25
+ channel: 1
26
+ readout_circuit:
27
+ sensor_type: accelerometer
28
+ amplifier_gain: 100
29
+ sensitivity_v_per_g: 1.0
30
+
31
+ AccelerometerZ:
32
+ adc:
33
+ device: adc1
34
+ channel: 2
35
+ readout_circuit:
36
+ sensor_type: accelerometer
37
+ amplifier_gain: 100
38
+ sensitivity_v_per_g: 1.0
39
+
40
+ # Topology only; drive frequency/amplitude are scan.parameters targets
41
+ # (see examples/configuration/scans/acquisition_transducer_vibration_scan.yaml).
42
+ transducers:
43
+ transducer1:
44
+ device: agilent1
45
+ channel: 2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytesdaqx"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "TES detector acquisition, instrument control, and raw-data recording"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.13"
@@ -168,7 +168,7 @@ def parse_args(argv: list[str] | None = None):
168
168
  parser.add_argument(
169
169
  "--units",
170
170
  default="amps",
171
- choices=["adc", "volts", "amps"],
171
+ choices=["adc", "volts", "amps", "g"],
172
172
  help="Vertical-axis units; default: amps",
173
173
  )
174
174
  parser.add_argument(
@@ -736,6 +736,7 @@ def _units_label(units: str) -> str:
736
736
  "amps": "Current [A]",
737
737
  "volts": "Voltage [V]",
738
738
  "adc": "ADC counts",
739
+ "g": "Acceleration [g]",
739
740
  }.get(key, str(units))
740
741
 
741
742
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import warnings as _warnings
3
4
  from pathlib import Path
4
5
  from typing import Any
5
6
 
@@ -68,6 +69,13 @@ class ConfigResolver:
68
69
  )
69
70
  resolved_temperatures = resolve_temperature_paths(run_setup, hardware)
70
71
 
72
+ # A channel selected for specific measurements is easy to leave out of
73
+ # run.channels by accident, and then it is silently never recorded.
74
+ excluded_warnings = self._record_for_exclusion_warnings(run_setup, selected_channels)
75
+ for message in excluded_warnings:
76
+ _warnings.warn(message, stacklevel=2)
77
+ validation_warnings = list(validation_warnings) + excluded_warnings
78
+
71
79
  resolved_channels = self._resolve_channels(run_setup=run_setup, hardware=hardware, selected_channels=selected_channels)
72
80
  measurements = require_mapping(acquisition_config.get("measurements", {}), name="measurements")
73
81
  sequence = self._resolve_sequence(
@@ -125,6 +133,35 @@ class ConfigResolver:
125
133
  if isinstance(cfg, dict)
126
134
  }
127
135
 
136
+ @staticmethod
137
+ def _record_for_exclusion_warnings(
138
+ run_setup: dict[str, Any], selected_channels: list[str] | None
139
+ ) -> list[str]:
140
+ """Warn about channels reserved for a measurement but left out of the run.
141
+
142
+ ``record_for`` only narrows the channels of ``run.channels`` (or
143
+ ``--channels``) to the measurements that should record them; it cannot
144
+ add a channel that the run never selected.
145
+ """
146
+
147
+ if not selected_channels:
148
+ return []
149
+ selected = {str(name) for name in selected_channels}
150
+ messages = []
151
+ for name, cfg in (run_setup.get("channels", {}) or {}).items():
152
+ if str(name) in selected or not isinstance(cfg, dict):
153
+ continue
154
+ allowed = cfg.get("record_for", cfg.get("measurements", cfg.get("record_measurements")))
155
+ if allowed is None:
156
+ continue
157
+ allowed_list = [allowed] if isinstance(allowed, str) else list(allowed)
158
+ messages.append(
159
+ f"run_setup.channels.{name} has record_for {allowed_list} but is not "
160
+ f"in run.channels, so it is never recorded. Add {name} to "
161
+ f"run.channels: record_for then limits it to those measurements."
162
+ )
163
+ return messages
164
+
128
165
  @staticmethod
129
166
  def _resolve_record_channels(acquisition_config: dict[str, Any], cli_channels: list[str] | None) -> list[str] | None:
130
167
  if cli_channels is not None: